Skip to content

Commit 8d7892a

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. [Cherry-picked 4443395][modified]
1 parent 3dc65c6 commit 8d7892a

File tree

4 files changed

+64
-4
lines changed

4 files changed

+64
-4
lines changed

compiler/src/dotty/tools/dotc/core/TypeComparer.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1494,7 +1494,7 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling
14941494
* Note: It would be legal to do the lifting also if M does not contain opaque types,
14951495
* but in this case the retries in tryLiftedToThis would be redundant.
14961496
*/
1497-
private def liftToThis(tp: Type): Type = {
1497+
def liftToThis(tp: Type): Type = {
14981498

14991499
def findEnclosingThis(moduleClass: Symbol, from: Symbol): Type =
15001500
if ((from.owner eq moduleClass) && from.isPackageObject && from.is(Opaque)) from.thisType
@@ -1515,7 +1515,7 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling
15151515
val tycon1 = liftToThis(tp.tycon)
15161516
if (tycon1 ne tp.tycon) tp.derivedAppliedType(tycon1, tp.args) else tp
15171517
case tp: TypeVar if tp.isInstantiated =>
1518-
liftToThis(tp.inst)
1518+
liftToThis(tp.instanceOpt)
15191519
case tp: AnnotatedType =>
15201520
val parent1 = liftToThis(tp.parent)
15211521
if (parent1 ne tp.parent) tp.derivedAnnotatedType(parent1, tp.annot) else tp

compiler/src/dotty/tools/dotc/typer/Typer.scala

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -697,12 +697,16 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
697697
ConstFold(select)
698698
else EmptyTree
699699

700+
// Otherwise, simplify `m.apply(...)` to `m(...)`
700701
def trySimplifyApply() =
701702
if selName == nme.apply && qual.tpe.widen.isInstanceOf[MethodType] then
702-
// Simplify `m.apply(...)` to `m(...)`
703703
qual
704704
else EmptyTree
705705

706+
// Otherwise, if there's a simply visible type variable in the result, try again
707+
// with a more defined qualifier type. There's a second trial where we try to instantiate
708+
// all type variables in `qual.tpe.widen`, but that is done only after we search for
709+
// extension methods or conversions.
706710
def tryInstantiateTypeVar() =
707711
if couldInstantiateTypeVar(qual.tpe.widen) then
708712
// there's a simply visible type variable in the result; try again with a more defined qualifier type
@@ -711,6 +715,20 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
711715
typedSelectWithAdapt(tree, pt, qual)
712716
else EmptyTree
713717

718+
// Otherwise, heal member selection on an opaque reference,
719+
// reusing the logic in TypeComparer.
720+
def tryLiftToThis() =
721+
val wtp = qual.tpe.widen
722+
val liftedTp = comparing(_.liftToThis(wtp))
723+
if liftedTp ne wtp then
724+
val qual1 = qual.cast(liftedTp)
725+
val tree1 = cpy.Select(tree0)(qual1, selName)
726+
val rawType1 = selectionType(tree1, qual1)
727+
tryType(tree1, qual1, rawType1)
728+
else EmptyTree
729+
730+
// Otherwise, map combinations of A *: B *: .... EmptyTuple with nesting levels <= 22
731+
// to the Tuple class of the right arity and select from that one
714732
def trySmallGenericTuple(qual: Tree, withCast: Boolean) =
715733
if qual.tpe.isSmallGenericTuple then
716734
if withCast then
@@ -720,14 +738,15 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
720738
typedSelectWithAdapt(tree, pt, qual)
721739
else EmptyTree
722740

741+
// Otherwise try an extension or conversion
723742
def tryExt(tree: untpd.Select, qual: Tree) =
724743
tryExtensionOrConversion(
725744
tree, pt, IgnoredProto(pt), qual, ctx.typerState.ownedVars, this, inSelect = true
726745
)
727746

747+
// Otherwise, try a GADT approximation if we're trying to select a member
728748
def tryGadt() =
729749
if ctx.gadt.isNarrowing then
730-
// try GADT approximation if we're trying to select a member
731750
// Member lookup cannot take GADTs into account b/c of cache, so we
732751
// approximate types based on GADT constraints instead. For an example,
733752
// see MemberHealing in gadt-approximation-interaction.scala.
@@ -742,6 +761,8 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
742761
.orElse(tryExt(tree1, qual1))
743762
else EmptyTree
744763

764+
// Otherwise, if there are uninstantiated type variables in the qualifier type,
765+
// instantiate them and try again
745766
def tryDefineFurther() =
746767
if canDefineFurther(qual.tpe.widen) then
747768
typedSelectWithAdapt(tree, pt, qual)
@@ -754,6 +775,8 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
754775
else
755776
typedDynamicSelect(tree2, Nil, pt)
756777

778+
// Otherwise, if the qualifier derives from class Dynamic, expand to a
779+
// dynamic dispatch using selectDynamic or applyDynamic
757780
def tryDynamic() =
758781
if qual.tpe.derivesFrom(defn.DynamicClass) && selName.isTermName && !isDynamicExpansion(tree) then
759782
dynamicSelect(pt)
@@ -770,6 +793,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
770793
tryType(tree, qual, rawType)
771794
.orElse(trySimplifyApply())
772795
.orElse(tryInstantiateTypeVar())
796+
.orElse(tryLiftToThis())
773797
.orElse(trySmallGenericTuple(qual, withCast = true))
774798
.orElse(tryExt(tree, qual))
775799
.orElse(tryGadt())

tests/pos/i19609.orig.scala

Lines changed: 12 additions & 0 deletions
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+
}

tests/pos/i19609.scala

Lines changed: 24 additions & 0 deletions
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)