Skip to content

Commit a9f23c1

Browse files
committed
Heal member-select on opaque reference
When the prefix of an opaque isn't the .this reference of the module class, then its RHS isn't visible. TypeComparer uses ctx.owner to "heal" or "lift" this type such that it is. We reuse that logic for member selection.
1 parent 3f7a3fa commit a9f23c1

File tree

4 files changed

+50
-1
lines changed

4 files changed

+50
-1
lines changed

Diff for: compiler/src/dotty/tools/dotc/core/TypeComparer.scala

+1-1
Original file line numberDiff line numberDiff line change
@@ -1596,7 +1596,7 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling
15961596
* Note: It would be legal to do the lifting also if M does not contain opaque types,
15971597
* but in this case the retries in tryLiftedToThis would be redundant.
15981598
*/
1599-
private def liftToThis(tp: Type): Type = {
1599+
def liftToThis(tp: Type): Type = {
16001600

16011601
def findEnclosingThis(moduleClass: Symbol, from: Symbol): Type =
16021602
if ((from.owner eq moduleClass) && from.isPackageObject && from.is(Opaque)) from.thisType

Diff for: compiler/src/dotty/tools/dotc/typer/Typer.scala

+13
Original file line numberDiff line numberDiff line change
@@ -758,6 +758,18 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
758758
typedSelectWithAdapt(tree, pt, qual)
759759
else EmptyTree
760760

761+
// Otherwise, heal member selection on an opaque reference,
762+
// reusing the logic in TypeComparer.
763+
def tryLiftToThis() =
764+
val wtp = qual.tpe.widen
765+
val liftedTp = comparing(_.liftToThis(wtp))
766+
if liftedTp ne wtp then
767+
val qual1 = qual.cast(liftedTp)
768+
val tree1 = cpy.Select(tree0)(qual1, selName)
769+
val rawType1 = selectionType(tree1, qual1)
770+
tryType(tree1, qual1, rawType1)
771+
else EmptyTree
772+
761773
// Otherwise, try to expand a named tuple selection
762774
def tryNamedTupleSelection() =
763775
val namedTupleElems = qual.tpe.widenDealias.namedTupleElementTypes
@@ -867,6 +879,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
867879
tryType(tree, qual, rawType)
868880
.orElse(trySimplifyApply())
869881
.orElse(tryInstantiateTypeVar())
882+
.orElse(tryLiftToThis())
870883
.orElse(tryNamedTupleSelection())
871884
.orElse(trySmallGenericTuple(qual, withCast = true))
872885
.orElse(tryExt(tree, qual))

Diff for: tests/pos/i19609.orig.scala

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
object o {
2+
opaque type T = String
3+
4+
summon[o.T =:= T] // OK
5+
summon[o.T =:= String] // OK
6+
7+
def test1(t: T): Int =
8+
t.length // OK
9+
10+
def test2(t: o.T): Int =
11+
t.length // Error: value length is not a member of Playground.o.T
12+
}

Diff for: tests/pos/i19609.scala

+24
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
object o { u =>
2+
opaque type T = String
3+
4+
def st = summon[String =:= T]
5+
def su = summon[String =:= u.T]
6+
def so = summon[String =:= o.T]
7+
8+
def ts = summon[T =:= String]
9+
def tu = summon[T =:= u.T]
10+
def to = summon[T =:= o.T]
11+
12+
def us = summon[u.T =:= String]
13+
def ut = summon[u.T =:= T]
14+
def uo = summon[u.T =:= o.T]
15+
16+
def os = summon[o.T =:= String]
17+
def ot = summon[o.T =:= T]
18+
def ou = summon[o.T =:= u.T]
19+
20+
def ms(x: String): Int = x.length // ok
21+
def mt(x: T): Int = x.length // ok
22+
def mu(x: u.T): Int = x.length // ok
23+
def mo(x: o.T): Int = x.length // was: error: value length is not a member of o.T
24+
}

0 commit comments

Comments
 (0)