Skip to content

Commit 0a67c39

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 fe77e3f commit 0a67c39

File tree

4 files changed

+122
-66
lines changed

4 files changed

+122
-66
lines changed

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

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

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

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

+85-65
Original file line numberDiff line numberDiff line change
@@ -688,72 +688,92 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
688688
val tree = cpy.Select(tree0)(qual, selName)
689689
val superAccess = qual.isInstanceOf[Super]
690690
val rawType = selectionType(tree, qual)
691-
val checkedType = accessibleType(rawType, superAccess)
692-
693-
def finish(tree: untpd.Select, qual: Tree, checkedType: Type): Tree =
694-
val select = toNotNullTermRef(assignType(tree, checkedType), pt)
695-
if selName.isTypeName then checkStable(qual.tpe, qual.srcPos, "type prefix")
696-
checkLegalValue(select, pt)
697-
ConstFold(select)
698-
699-
if checkedType.exists then
700-
finish(tree, qual, checkedType)
701-
else if selName == nme.apply && qual.tpe.widen.isInstanceOf[MethodType] then
702-
// Simplify `m.apply(...)` to `m(...)`
703-
qual
704-
else if couldInstantiateTypeVar(qual.tpe.widen) then
705-
// there's a simply visible type variable in the result; try again with a more defined qualifier type
706-
// There's a second trial where we try to instantiate all type variables in `qual.tpe.widen`,
707-
// but that is done only after we search for extension methods or conversions.
708-
typedSelect(tree, pt, qual)
709-
else if qual.tpe.isSmallGenericTuple then
710-
val elems = qual.tpe.widenTermRefExpr.tupleElementTypes.getOrElse(Nil)
711-
typedSelect(tree, pt, qual.cast(defn.tupleType(elems)))
712-
else
713-
val tree1 = tryExtensionOrConversion(
714-
tree, pt, IgnoredProto(pt), qual, ctx.typerState.ownedVars, this, inSelect = true)
715-
.orElse {
716-
if ctx.gadt.isNarrowing then
717-
// try GADT approximation if we're trying to select a member
718-
// Member lookup cannot take GADTs into account b/c of cache, so we
719-
// approximate types based on GADT constraints instead. For an example,
720-
// see MemberHealing in gadt-approximation-interaction.scala.
721-
val wtp = qual.tpe.widen
722-
gadts.println(i"Trying to heal member selection by GADT-approximating $wtp")
723-
val gadtApprox = Inferencing.approximateGADT(wtp)
724-
gadts.println(i"GADT-approximated $wtp ~~ $gadtApprox")
725-
val qual1 = qual.cast(gadtApprox)
726-
val tree1 = cpy.Select(tree0)(qual1, selName)
727-
val checkedType1 = accessibleType(selectionType(tree1, qual1), superAccess = false)
728-
if checkedType1.exists then
729-
gadts.println(i"Member selection healed by GADT approximation")
730-
finish(tree1, qual1, checkedType1)
731-
else if qual1.tpe.isSmallGenericTuple then
732-
gadts.println(i"Tuple member selection healed by GADT approximation")
733-
typedSelect(tree, pt, qual1)
734-
else
735-
tryExtensionOrConversion(tree1, pt, IgnoredProto(pt), qual1, ctx.typerState.ownedVars, this, inSelect = true)
736-
else EmptyTree
737-
}
738-
if !tree1.isEmpty then
739-
tree1
740-
else if canDefineFurther(qual.tpe.widen) then
741-
typedSelect(tree, pt, qual)
742-
else if qual.tpe.derivesFrom(defn.DynamicClass)
743-
&& selName.isTermName && !isDynamicExpansion(tree)
744-
then
745-
val tree2 = cpy.Select(tree0)(untpd.TypedSplice(qual), selName)
746-
if pt.isInstanceOf[FunOrPolyProto] || pt == LhsProto then
747-
assignType(tree2, TryDynamicCallType)
691+
692+
def tryType(tree: untpd.Select, qual: Tree, rawType: Type) =
693+
val checkedType = accessibleType(rawType, superAccess)
694+
if checkedType.exists then
695+
val select = toNotNullTermRef(assignType(tree, checkedType), pt)
696+
if selName.isTypeName then checkStable(qual.tpe, qual.srcPos, "type prefix")
697+
checkLegalValue(select, pt)
698+
ConstFold(select)
699+
else EmptyTree
700+
701+
def trySmallGenericTuple(tree: untpd.Select, qual: Tree, withCast: Boolean) =
702+
if qual.tpe.isSmallGenericTuple then
703+
if withCast then
704+
val elems = qual.tpe.widenTermRefExpr.tupleElementTypes.getOrElse(Nil)
705+
typedSelect(tree, pt, qual.cast(defn.tupleType(elems)))
748706
else
749-
typedDynamicSelect(tree2, Nil, pt)
750-
else
751-
assignType(tree,
752-
rawType match
753-
case rawType: NamedType =>
754-
inaccessibleErrorType(rawType, superAccess, tree.srcPos)
755-
case _ =>
756-
notAMemberErrorType(tree, qual, pt))
707+
typedSelect(tree, pt, qual)
708+
else EmptyTree
709+
710+
def tryExt(tree: untpd.Select, qual: Tree) =
711+
tryExtensionOrConversion(
712+
tree, pt, IgnoredProto(pt), qual, ctx.typerState.ownedVars, this, inSelect = true)
713+
714+
def tryGadt(tree: untpd.Select) =
715+
if ctx.gadt.isNarrowing then
716+
// try GADT approximation if we're trying to select a member
717+
// Member lookup cannot take GADTs into account b/c of cache, so we
718+
// approximate types based on GADT constraints instead. For an example,
719+
// see MemberHealing in gadt-approximation-interaction.scala.
720+
val wtp = qual.tpe.widen
721+
gadts.println(i"Trying to heal member selection by GADT-approximating $wtp")
722+
val gadtApprox = Inferencing.approximateGADT(wtp)
723+
gadts.println(i"GADT-approximated $wtp ~~ $gadtApprox")
724+
val qual1 = qual.cast(gadtApprox)
725+
val tree1 = cpy.Select(tree0)(qual1, selName)
726+
tryType(tree1, qual1, selectionType(tree1, qual1))
727+
.orElse(trySmallGenericTuple(tree, qual1, withCast = false))
728+
.orElse(tryExt(tree1, qual1))
729+
else EmptyTree
730+
731+
tryType(tree, qual, rawType)
732+
.orElse {
733+
if selName == nme.apply && qual.tpe.widen.isInstanceOf[MethodType] then
734+
// Simplify `m.apply(...)` to `m(...)`
735+
qual
736+
else EmptyTree
737+
}
738+
.orElse {
739+
if couldInstantiateTypeVar(qual.tpe.widen) then
740+
// there's a simply visible type variable in the result; try again with a more defined qualifier type
741+
// There's a second trial where we try to instantiate all type variables in `qual.tpe.widen`,
742+
// but that is done only after we search for extension methods or conversions.
743+
typedSelect(tree, pt, qual)
744+
else EmptyTree
745+
}
746+
.orElse {
747+
val wtp = qual.tpe.widen
748+
val liftedTp = comparing(_.liftToThis(wtp))
749+
if liftedTp ne wtp then
750+
val qual1 = qual.cast(liftedTp)
751+
val tree1 = cpy.Select(tree0)(qual1, selName)
752+
val rawType1 = selectionType(tree1, qual1)
753+
tryType(tree1, qual1, rawType1)
754+
else EmptyTree
755+
}
756+
.orElse(trySmallGenericTuple(tree, qual, withCast = true))
757+
.orElse(tryExt(tree, qual))
758+
.orElse(tryGadt(tree))
759+
.orElse:
760+
if canDefineFurther(qual.tpe.widen) then
761+
typedSelect(tree, pt, qual)
762+
else if qual.tpe.derivesFrom(defn.DynamicClass)
763+
&& selName.isTermName && !isDynamicExpansion(tree)
764+
then
765+
val tree2 = cpy.Select(tree0)(untpd.TypedSplice(qual), selName)
766+
if pt.isInstanceOf[FunOrPolyProto] || pt == LhsProto then
767+
assignType(tree2, TryDynamicCallType)
768+
else
769+
typedDynamicSelect(tree2, Nil, pt)
770+
else
771+
assignType(tree,
772+
rawType match
773+
case rawType: NamedType =>
774+
inaccessibleErrorType(rawType, superAccess, tree.srcPos)
775+
case _ =>
776+
notAMemberErrorType(tree, qual, pt))
757777
end typedSelect
758778

759779
def typedSelect(tree: untpd.Select, pt: Type)(using Context): Tree = {

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)