Skip to content

Commit

Permalink
Heal member-select on opaque reference
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
dwijnand committed Feb 19, 2024
1 parent fe77e3f commit c89a399
Show file tree
Hide file tree
Showing 3 changed files with 110 additions and 66 deletions.
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/core/TypeComparer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1575,7 +1575,7 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling
* Note: It would be legal to do the lifting also if M does not contain opaque types,
* but in this case the retries in tryLiftedToThis would be redundant.
*/
private def liftToThis(tp: Type): Type = {
def liftToThis(tp: Type): Type = {

def findEnclosingThis(moduleClass: Symbol, from: Symbol): Type =
if ((from.owner eq moduleClass) && from.isPackageObject && from.is(Opaque)) from.thisType
Expand Down
150 changes: 85 additions & 65 deletions compiler/src/dotty/tools/dotc/typer/Typer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -688,72 +688,92 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
val tree = cpy.Select(tree0)(qual, selName)
val superAccess = qual.isInstanceOf[Super]
val rawType = selectionType(tree, qual)
val checkedType = accessibleType(rawType, superAccess)

def finish(tree: untpd.Select, qual: Tree, checkedType: Type): Tree =
val select = toNotNullTermRef(assignType(tree, checkedType), pt)
if selName.isTypeName then checkStable(qual.tpe, qual.srcPos, "type prefix")
checkLegalValue(select, pt)
ConstFold(select)

if checkedType.exists then
finish(tree, qual, checkedType)
else if selName == nme.apply && qual.tpe.widen.isInstanceOf[MethodType] then
// Simplify `m.apply(...)` to `m(...)`
qual
else if couldInstantiateTypeVar(qual.tpe.widen) then
// there's a simply visible type variable in the result; try again with a more defined qualifier type
// There's a second trial where we try to instantiate all type variables in `qual.tpe.widen`,
// but that is done only after we search for extension methods or conversions.
typedSelect(tree, pt, qual)
else if qual.tpe.isSmallGenericTuple then
val elems = qual.tpe.widenTermRefExpr.tupleElementTypes.getOrElse(Nil)
typedSelect(tree, pt, qual.cast(defn.tupleType(elems)))
else
val tree1 = tryExtensionOrConversion(
tree, pt, IgnoredProto(pt), qual, ctx.typerState.ownedVars, this, inSelect = true)
.orElse {
if ctx.gadt.isNarrowing then
// try GADT approximation if we're trying to select a member
// Member lookup cannot take GADTs into account b/c of cache, so we
// approximate types based on GADT constraints instead. For an example,
// see MemberHealing in gadt-approximation-interaction.scala.
val wtp = qual.tpe.widen
gadts.println(i"Trying to heal member selection by GADT-approximating $wtp")
val gadtApprox = Inferencing.approximateGADT(wtp)
gadts.println(i"GADT-approximated $wtp ~~ $gadtApprox")
val qual1 = qual.cast(gadtApprox)
val tree1 = cpy.Select(tree0)(qual1, selName)
val checkedType1 = accessibleType(selectionType(tree1, qual1), superAccess = false)
if checkedType1.exists then
gadts.println(i"Member selection healed by GADT approximation")
finish(tree1, qual1, checkedType1)
else if qual1.tpe.isSmallGenericTuple then
gadts.println(i"Tuple member selection healed by GADT approximation")
typedSelect(tree, pt, qual1)
else
tryExtensionOrConversion(tree1, pt, IgnoredProto(pt), qual1, ctx.typerState.ownedVars, this, inSelect = true)
else EmptyTree
}
if !tree1.isEmpty then
tree1
else if canDefineFurther(qual.tpe.widen) then
typedSelect(tree, pt, qual)
else if qual.tpe.derivesFrom(defn.DynamicClass)
&& selName.isTermName && !isDynamicExpansion(tree)
then
val tree2 = cpy.Select(tree0)(untpd.TypedSplice(qual), selName)
if pt.isInstanceOf[FunOrPolyProto] || pt == LhsProto then
assignType(tree2, TryDynamicCallType)

def tryType(tree: untpd.Select, qual: Tree, rawType: Type) =
val checkedType = accessibleType(rawType, superAccess)
if checkedType.exists then
val select = toNotNullTermRef(assignType(tree, checkedType), pt)
if selName.isTypeName then checkStable(qual.tpe, qual.srcPos, "type prefix")
checkLegalValue(select, pt)
ConstFold(select)
else EmptyTree

def trySmallGenericTuple(tree: untpd.Select, qual: Tree, withCast: Boolean) =
if qual.tpe.isSmallGenericTuple then
if withCast then
val elems = qual.tpe.widenTermRefExpr.tupleElementTypes.getOrElse(Nil)
typedSelect(tree, pt, qual.cast(defn.tupleType(elems)))
else
typedDynamicSelect(tree2, Nil, pt)
else
assignType(tree,
rawType match
case rawType: NamedType =>
inaccessibleErrorType(rawType, superAccess, tree.srcPos)
case _ =>
notAMemberErrorType(tree, qual, pt))
typedSelect(tree, pt, qual)
else EmptyTree

def tryExt(tree: untpd.Select, qual: Tree) =
tryExtensionOrConversion(
tree, pt, IgnoredProto(pt), qual, ctx.typerState.ownedVars, this, inSelect = true)

def tryGadt(tree: untpd.Select) =
if ctx.gadt.isNarrowing then
// try GADT approximation if we're trying to select a member
// Member lookup cannot take GADTs into account b/c of cache, so we
// approximate types based on GADT constraints instead. For an example,
// see MemberHealing in gadt-approximation-interaction.scala.
val wtp = qual.tpe.widen
gadts.println(i"Trying to heal member selection by GADT-approximating $wtp")
val gadtApprox = Inferencing.approximateGADT(wtp)
gadts.println(i"GADT-approximated $wtp ~~ $gadtApprox")
val qual1 = qual.cast(gadtApprox)
val tree1 = cpy.Select(tree0)(qual1, selName)
tryType(tree1, qual1, selectionType(tree1, qual1))
.orElse(trySmallGenericTuple(tree, qual1, withCast = false))
.orElse(tryExt(tree1, qual1))
else EmptyTree

tryType(tree, qual, rawType)
.orElse {
if selName == nme.apply && qual.tpe.widen.isInstanceOf[MethodType] then
// Simplify `m.apply(...)` to `m(...)`
qual
else EmptyTree
}
.orElse {
if couldInstantiateTypeVar(qual.tpe.widen) then
// there's a simply visible type variable in the result; try again with a more defined qualifier type
// There's a second trial where we try to instantiate all type variables in `qual.tpe.widen`,
// but that is done only after we search for extension methods or conversions.
typedSelect(tree, pt, qual)
else EmptyTree
}
.orElse {
val wtp = qual.tpe.widen
val liftedTp = comparing(_.liftToThis(wtp))
if liftedTp ne wtp then
val qual1 = qual.cast(liftedTp)
val tree1 = cpy.Select(tree0)(qual1, selName)
val rawType1 = selectionType(tree1, qual1)
tryType(tree1, qual1, rawType1)
else EmptyTree
}
.orElse(trySmallGenericTuple(tree, qual, withCast = true))
.orElse(tryExt(tree, qual))
.orElse(tryGadt(tree))
.orElse:
if canDefineFurther(qual.tpe.widen) then
typedSelect(tree, pt, qual)
else if qual.tpe.derivesFrom(defn.DynamicClass)
&& selName.isTermName && !isDynamicExpansion(tree)
then
val tree2 = cpy.Select(tree0)(untpd.TypedSplice(qual), selName)
if pt.isInstanceOf[FunOrPolyProto] || pt == LhsProto then
assignType(tree2, TryDynamicCallType)
else
typedDynamicSelect(tree2, Nil, pt)
else
assignType(tree,
rawType match
case rawType: NamedType =>
inaccessibleErrorType(rawType, superAccess, tree.srcPos)
case _ =>
notAMemberErrorType(tree, qual, pt))
end typedSelect

def typedSelect(tree: untpd.Select, pt: Type)(using Context): Tree = {
Expand Down
24 changes: 24 additions & 0 deletions tests/pos/i19609.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
object o { u =>
opaque type T = String

def st = summon[String =:= T]
def su = summon[String =:= u.T]
def so = summon[String =:= o.T]

def ts = summon[T =:= String]
def tu = summon[T =:= u.T]
def to = summon[T =:= o.T]

def us = summon[u.T =:= String]
def ut = summon[u.T =:= T]
def uo = summon[u.T =:= o.T]

def os = summon[o.T =:= String]
def ot = summon[o.T =:= T]
def ou = summon[o.T =:= u.T]

def ms(x: String): Int = x.length // ok
def mt(x: T): Int = x.length // ok
def mu(x: u.T): Int = x.length // ok
def mo(x: o.T): Int = x.length // was: error: value length is not a member of o.T
}

0 comments on commit c89a399

Please sign in to comment.