From e2b18ffccd185b4ada1d96932428af833732fc6f Mon Sep 17 00:00:00 2001 From: Wojciech Mazur Date: Mon, 8 Jul 2024 20:35:59 +0200 Subject: [PATCH 1/3] Rename to typedSelectWithAdapt [Cherry-picked 6230405edb0d356469bd3d36560431c9e3d8f72a][modified] --- compiler/src/dotty/tools/dotc/typer/Typer.scala | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index fdcac975d71c..f299ba1e7ab6 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -681,7 +681,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer then report.error(StableIdentPattern(tree, pt), tree.srcPos) - def typedSelect(tree0: untpd.Select, pt: Type, qual: Tree)(using Context): Tree = + def typedSelectWithAdapt(tree0: untpd.Select, pt: Type, qual: Tree)(using Context): Tree = val selName = tree0.name val tree = cpy.Select(tree0)(qual, selName) val superAccess = qual.isInstanceOf[Super] @@ -703,10 +703,10 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer // 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) + typedSelectWithAdapt(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))) + typedSelectWithAdapt(tree, pt, qual.cast(defn.tupleType(elems))) else val tree1 = tryExtensionOrConversion( tree, pt, IgnoredProto(pt), qual, ctx.typerState.ownedVars, this, inSelect = true) @@ -728,7 +728,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer finish(tree1, qual1, checkedType1) else if qual1.tpe.isSmallGenericTuple then gadts.println(i"Tuple member selection healed by GADT approximation") - typedSelect(tree, pt, qual1) + typedSelectWithAdapt(tree, pt, qual1) else tryExtensionOrConversion(tree1, pt, IgnoredProto(pt), qual1, ctx.typerState.ownedVars, this, inSelect = true) else EmptyTree @@ -736,7 +736,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer if !tree1.isEmpty then tree1 else if canDefineFurther(qual.tpe.widen) then - typedSelect(tree, pt, qual) + typedSelectWithAdapt(tree, pt, qual) else if qual.tpe.derivesFrom(defn.DynamicClass) && selName.isTermName && !isDynamicExpansion(tree) then @@ -752,14 +752,14 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer inaccessibleErrorType(rawType, superAccess, tree.srcPos) case _ => notAMemberErrorType(tree, qual, pt)) - end typedSelect + end typedSelectWithAdapt def typedSelect(tree: untpd.Select, pt: Type)(using Context): Tree = { record("typedSelect") def typeSelectOnTerm(using Context): Tree = val qual = typedExpr(tree.qualifier, shallowSelectionProto(tree.name, pt, this, tree.nameSpan)) - typedSelect(tree, pt, qual).withSpan(tree.span).computeNullable() + typedSelectWithAdapt(tree, pt, qual).withSpan(tree.span).computeNullable() def javaSelectOnType(qual: Tree)(using Context) = // semantic name conversion for `O$` in java code @@ -3563,7 +3563,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer if isExtension then return found else checkImplicitConversionUseOK(found, selProto) - return withoutMode(Mode.ImplicitsEnabled)(typedSelect(tree, pt, found)) + return withoutMode(Mode.ImplicitsEnabled)(typedSelectWithAdapt(tree, pt, found)) case failure: SearchFailure => if failure.isAmbiguous then return From 3dc65c6f6b6951b1a993852f5af843c97b13e93d Mon Sep 17 00:00:00 2001 From: Wojciech Mazur Date: Mon, 8 Jul 2024 21:13:09 +0200 Subject: [PATCH 2/3] Reorganise typedSelectWithAdapt Prior to the next commit, I broke up the logic into internal methods, so some can be reused, consuming then in a big Tree#orElse chain. I also took the opportunity to rename the method, to more easily distinguish it from the other typedSelect. [Cherry-picked c477ceab6b79c9021fa9f5bad4d9910db51c0cc1][modified] --- .../src/dotty/tools/dotc/typer/Typer.scala | 144 ++++++++++-------- 1 file changed, 84 insertions(+), 60 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index f299ba1e7ab6..732f9a944292 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -686,72 +686,96 @@ 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. - typedSelectWithAdapt(tree, pt, qual) - else if qual.tpe.isSmallGenericTuple then - val elems = qual.tpe.widenTermRefExpr.tupleElementTypes.getOrElse(Nil) - typedSelectWithAdapt(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") - typedSelectWithAdapt(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 + + def tryType(tree: untpd.Select, qual: Tree, rawType: Type) = + val checkedType = accessibleType(rawType, superAccess) + // If regular selection is typeable, we are done + 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 trySimplifyApply() = + if selName == nme.apply && qual.tpe.widen.isInstanceOf[MethodType] then + // Simplify `m.apply(...)` to `m(...)` + qual + else EmptyTree + + 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 + // 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. typedSelectWithAdapt(tree, pt, qual) - else if qual.tpe.derivesFrom(defn.DynamicClass) - && selName.isTermName && !isDynamicExpansion(tree) - then + else EmptyTree + + def trySmallGenericTuple(qual: Tree, withCast: Boolean) = + if qual.tpe.isSmallGenericTuple then + if withCast then + val elems = qual.tpe.widenTermRefExpr.tupleElementTypes.getOrElse(Nil) + typedSelectWithAdapt(tree, pt, qual.cast(defn.tupleType(elems))) + else + typedSelectWithAdapt(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() = + 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(qual1, withCast = false)) + .orElse(tryExt(tree1, qual1)) + else EmptyTree + + def tryDefineFurther() = + if canDefineFurther(qual.tpe.widen) then + typedSelectWithAdapt(tree, pt, qual) + else EmptyTree + + def dynamicSelect(pt: Type) = val tree2 = cpy.Select(tree0)(untpd.TypedSplice(qual), selName) if pt.isInstanceOf[FunOrPolyProto] || pt == AssignProto 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)) + + def tryDynamic() = + if qual.tpe.derivesFrom(defn.DynamicClass) && selName.isTermName && !isDynamicExpansion(tree) then + dynamicSelect(pt) + else EmptyTree + + def reportAnError() = + assignType(tree, + rawType match + case rawType: NamedType => + inaccessibleErrorType(rawType, superAccess, tree.srcPos) + case _ => + notAMemberErrorType(tree, qual, pt)) + + tryType(tree, qual, rawType) + .orElse(trySimplifyApply()) + .orElse(tryInstantiateTypeVar()) + .orElse(trySmallGenericTuple(qual, withCast = true)) + .orElse(tryExt(tree, qual)) + .orElse(tryGadt()) + .orElse(tryDefineFurther()) + .orElse(tryDynamic()) + .orElse(reportAnError()) end typedSelectWithAdapt def typedSelect(tree: untpd.Select, pt: Type)(using Context): Tree = { From 8d7892ae69f59de966a5f4e3aa9e5ab3ecad9558 Mon Sep 17 00:00:00 2001 From: Wojciech Mazur Date: Mon, 8 Jul 2024 21:31:33 +0200 Subject: [PATCH 3/3] 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 +}