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.

[Cherry-picked 4443395][modified]
  • Loading branch information
WojciechMazur committed Jul 8, 2024
1 parent 3dc65c6 commit 8d7892a
Show file tree
Hide file tree
Showing 4 changed files with 64 additions and 4 deletions.
4 changes: 2 additions & 2 deletions compiler/src/dotty/tools/dotc/core/TypeComparer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1494,7 +1494,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 All @@ -1515,7 +1515,7 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling
val tycon1 = liftToThis(tp.tycon)
if (tycon1 ne tp.tycon) tp.derivedAppliedType(tycon1, tp.args) else tp
case tp: TypeVar if tp.isInstantiated =>
liftToThis(tp.inst)
liftToThis(tp.instanceOpt)
case tp: AnnotatedType =>
val parent1 = liftToThis(tp.parent)
if (parent1 ne tp.parent) tp.derivedAnnotatedType(parent1, tp.annot) else tp
Expand Down
28 changes: 26 additions & 2 deletions compiler/src/dotty/tools/dotc/typer/Typer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -697,12 +697,16 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
ConstFold(select)
else EmptyTree

// Otherwise, simplify `m.apply(...)` to `m(...)`
def trySimplifyApply() =
if selName == nme.apply && qual.tpe.widen.isInstanceOf[MethodType] then
// Simplify `m.apply(...)` to `m(...)`
qual
else EmptyTree

// Otherwise, if 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.
def tryInstantiateTypeVar() =
if couldInstantiateTypeVar(qual.tpe.widen) then
// there's a simply visible type variable in the result; try again with a more defined qualifier type
Expand All @@ -711,6 +715,20 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
typedSelectWithAdapt(tree, pt, qual)
else EmptyTree

// Otherwise, heal member selection on an opaque reference,
// reusing the logic in TypeComparer.
def tryLiftToThis() =
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

// Otherwise, map combinations of A *: B *: .... EmptyTuple with nesting levels <= 22
// to the Tuple class of the right arity and select from that one
def trySmallGenericTuple(qual: Tree, withCast: Boolean) =
if qual.tpe.isSmallGenericTuple then
if withCast then
Expand All @@ -720,14 +738,15 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
typedSelectWithAdapt(tree, pt, qual)
else EmptyTree

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

// Otherwise, try a GADT approximation if we're trying to select a member
def tryGadt() =
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.
Expand All @@ -742,6 +761,8 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
.orElse(tryExt(tree1, qual1))
else EmptyTree

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

// Otherwise, if the qualifier derives from class Dynamic, expand to a
// dynamic dispatch using selectDynamic or applyDynamic
def tryDynamic() =
if qual.tpe.derivesFrom(defn.DynamicClass) && selName.isTermName && !isDynamicExpansion(tree) then
dynamicSelect(pt)
Expand All @@ -770,6 +793,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
tryType(tree, qual, rawType)
.orElse(trySimplifyApply())
.orElse(tryInstantiateTypeVar())
.orElse(tryLiftToThis())
.orElse(trySmallGenericTuple(qual, withCast = true))
.orElse(tryExt(tree, qual))
.orElse(tryGadt())
Expand Down
12 changes: 12 additions & 0 deletions tests/pos/i19609.orig.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
object o {
opaque type T = String

summon[o.T =:= T] // OK
summon[o.T =:= String] // OK

def test1(t: T): Int =
t.length // OK

def test2(t: o.T): Int =
t.length // Error: value length is not a member of Playground.o.T
}
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 8d7892a

Please sign in to comment.