From 8d7892ae69f59de966a5f4e3aa9e5ab3ecad9558 Mon Sep 17 00:00:00 2001 From: Wojciech Mazur Date: Mon, 8 Jul 2024 21:31:33 +0200 Subject: [PATCH] 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 4443395a1de7317e5b3b0349f96d5c0102d5941f][modified] --- .../dotty/tools/dotc/core/TypeComparer.scala | 4 +-- .../src/dotty/tools/dotc/typer/Typer.scala | 28 +++++++++++++++++-- tests/pos/i19609.orig.scala | 12 ++++++++ tests/pos/i19609.scala | 24 ++++++++++++++++ 4 files changed, 64 insertions(+), 4 deletions(-) create mode 100644 tests/pos/i19609.orig.scala create mode 100644 tests/pos/i19609.scala diff --git a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala index bf385c2d7fb5..7e65b877f90e 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala @@ -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 @@ -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 diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 732f9a944292..d8f427ff9206 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -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 @@ -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 @@ -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. @@ -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) @@ -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) @@ -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()) diff --git a/tests/pos/i19609.orig.scala b/tests/pos/i19609.orig.scala new file mode 100644 index 000000000000..62622075dbed --- /dev/null +++ b/tests/pos/i19609.orig.scala @@ -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 +} diff --git a/tests/pos/i19609.scala b/tests/pos/i19609.scala new file mode 100644 index 000000000000..0879fa16c7cf --- /dev/null +++ b/tests/pos/i19609.scala @@ -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 +}