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 fdcac975d71c..d8f427ff9206 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -681,85 +681,133 @@ 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] 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 + + 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 + + // Otherwise, simplify `m.apply(...)` to `m(...)` + def trySimplifyApply() = + if selName == nme.apply && qual.tpe.widen.isInstanceOf[MethodType] then + 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 + // 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 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 + val elems = qual.tpe.widenTermRefExpr.tupleElementTypes.getOrElse(Nil) + typedSelectWithAdapt(tree, pt, qual.cast(defn.tupleType(elems))) + else + 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 + // 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 + + // 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) + 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)) - end typedSelect + + // 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) + 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(tryLiftToThis()) + .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 = { 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 +3611,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 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 +}