Skip to content

Commit b71f89d

Browse files
Backport "Heal member-select on opaque reference" to LTS (#21131)
Backports #19730 to the LTS branch. PR submitted by the release tooling. [skip ci]
2 parents 52b87c2 + 8d7892a commit b71f89d

File tree

4 files changed

+151
-67
lines changed

4 files changed

+151
-67
lines changed

compiler/src/dotty/tools/dotc/core/TypeComparer.scala

+2-2
Original file line numberDiff line numberDiff line change
@@ -1494,7 +1494,7 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling
14941494
* Note: It would be legal to do the lifting also if M does not contain opaque types,
14951495
* but in this case the retries in tryLiftedToThis would be redundant.
14961496
*/
1497-
private def liftToThis(tp: Type): Type = {
1497+
def liftToThis(tp: Type): Type = {
14981498

14991499
def findEnclosingThis(moduleClass: Symbol, from: Symbol): Type =
15001500
if ((from.owner eq moduleClass) && from.isPackageObject && from.is(Opaque)) from.thisType
@@ -1515,7 +1515,7 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling
15151515
val tycon1 = liftToThis(tp.tycon)
15161516
if (tycon1 ne tp.tycon) tp.derivedAppliedType(tycon1, tp.args) else tp
15171517
case tp: TypeVar if tp.isInstantiated =>
1518-
liftToThis(tp.inst)
1518+
liftToThis(tp.instanceOpt)
15191519
case tp: AnnotatedType =>
15201520
val parent1 = liftToThis(tp.parent)
15211521
if (parent1 ne tp.parent) tp.derivedAnnotatedType(parent1, tp.annot) else tp

compiler/src/dotty/tools/dotc/typer/Typer.scala

+113-65
Original file line numberDiff line numberDiff line change
@@ -681,85 +681,133 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
681681
then
682682
report.error(StableIdentPattern(tree, pt), tree.srcPos)
683683

684-
def typedSelect(tree0: untpd.Select, pt: Type, qual: Tree)(using Context): Tree =
684+
def typedSelectWithAdapt(tree0: untpd.Select, pt: Type, qual: Tree)(using Context): Tree =
685685
val selName = tree0.name
686686
val tree = cpy.Select(tree0)(qual, selName)
687687
val superAccess = qual.isInstanceOf[Super]
688688
val rawType = selectionType(tree, qual)
689-
val checkedType = accessibleType(rawType, superAccess)
690-
691-
def finish(tree: untpd.Select, qual: Tree, checkedType: Type): Tree =
692-
val select = toNotNullTermRef(assignType(tree, checkedType), pt)
693-
if selName.isTypeName then checkStable(qual.tpe, qual.srcPos, "type prefix")
694-
checkLegalValue(select, pt)
695-
ConstFold(select)
696-
697-
if checkedType.exists then
698-
finish(tree, qual, checkedType)
699-
else if selName == nme.apply && qual.tpe.widen.isInstanceOf[MethodType] then
700-
// Simplify `m.apply(...)` to `m(...)`
701-
qual
702-
else if couldInstantiateTypeVar(qual.tpe.widen) then
703-
// there's a simply visible type variable in the result; try again with a more defined qualifier type
704-
// There's a second trial where we try to instantiate all type variables in `qual.tpe.widen`,
705-
// but that is done only after we search for extension methods or conversions.
706-
typedSelect(tree, pt, qual)
707-
else if qual.tpe.isSmallGenericTuple then
708-
val elems = qual.tpe.widenTermRefExpr.tupleElementTypes.getOrElse(Nil)
709-
typedSelect(tree, pt, qual.cast(defn.tupleType(elems)))
710-
else
711-
val tree1 = tryExtensionOrConversion(
712-
tree, pt, IgnoredProto(pt), qual, ctx.typerState.ownedVars, this, inSelect = true)
713-
.orElse {
714-
if ctx.gadt.isNarrowing then
715-
// try GADT approximation if we're trying to select a member
716-
// Member lookup cannot take GADTs into account b/c of cache, so we
717-
// approximate types based on GADT constraints instead. For an example,
718-
// see MemberHealing in gadt-approximation-interaction.scala.
719-
val wtp = qual.tpe.widen
720-
gadts.println(i"Trying to heal member selection by GADT-approximating $wtp")
721-
val gadtApprox = Inferencing.approximateGADT(wtp)
722-
gadts.println(i"GADT-approximated $wtp ~~ $gadtApprox")
723-
val qual1 = qual.cast(gadtApprox)
724-
val tree1 = cpy.Select(tree0)(qual1, selName)
725-
val checkedType1 = accessibleType(selectionType(tree1, qual1), superAccess = false)
726-
if checkedType1.exists then
727-
gadts.println(i"Member selection healed by GADT approximation")
728-
finish(tree1, qual1, checkedType1)
729-
else if qual1.tpe.isSmallGenericTuple then
730-
gadts.println(i"Tuple member selection healed by GADT approximation")
731-
typedSelect(tree, pt, qual1)
732-
else
733-
tryExtensionOrConversion(tree1, pt, IgnoredProto(pt), qual1, ctx.typerState.ownedVars, this, inSelect = true)
734-
else EmptyTree
735-
}
736-
if !tree1.isEmpty then
737-
tree1
738-
else if canDefineFurther(qual.tpe.widen) then
739-
typedSelect(tree, pt, qual)
740-
else if qual.tpe.derivesFrom(defn.DynamicClass)
741-
&& selName.isTermName && !isDynamicExpansion(tree)
742-
then
689+
690+
def tryType(tree: untpd.Select, qual: Tree, rawType: Type) =
691+
val checkedType = accessibleType(rawType, superAccess)
692+
// If regular selection is typeable, we are done
693+
if checkedType.exists then
694+
val select = toNotNullTermRef(assignType(tree, checkedType), pt)
695+
if selName.isTypeName then checkStable(qual.tpe, qual.srcPos, "type prefix")
696+
checkLegalValue(select, pt)
697+
ConstFold(select)
698+
else EmptyTree
699+
700+
// Otherwise, simplify `m.apply(...)` to `m(...)`
701+
def trySimplifyApply() =
702+
if selName == nme.apply && qual.tpe.widen.isInstanceOf[MethodType] then
703+
qual
704+
else EmptyTree
705+
706+
// Otherwise, if there's a simply visible type variable in the result, try again
707+
// with a more defined qualifier type. There's a second trial where we try to instantiate
708+
// all type variables in `qual.tpe.widen`, but that is done only after we search for
709+
// extension methods or conversions.
710+
def tryInstantiateTypeVar() =
711+
if couldInstantiateTypeVar(qual.tpe.widen) then
712+
// there's a simply visible type variable in the result; try again with a more defined qualifier type
713+
// There's a second trial where we try to instantiate all type variables in `qual.tpe.widen`,
714+
// but that is done only after we search for extension methods or conversions.
715+
typedSelectWithAdapt(tree, pt, qual)
716+
else EmptyTree
717+
718+
// Otherwise, heal member selection on an opaque reference,
719+
// reusing the logic in TypeComparer.
720+
def tryLiftToThis() =
721+
val wtp = qual.tpe.widen
722+
val liftedTp = comparing(_.liftToThis(wtp))
723+
if liftedTp ne wtp then
724+
val qual1 = qual.cast(liftedTp)
725+
val tree1 = cpy.Select(tree0)(qual1, selName)
726+
val rawType1 = selectionType(tree1, qual1)
727+
tryType(tree1, qual1, rawType1)
728+
else EmptyTree
729+
730+
// Otherwise, map combinations of A *: B *: .... EmptyTuple with nesting levels <= 22
731+
// to the Tuple class of the right arity and select from that one
732+
def trySmallGenericTuple(qual: Tree, withCast: Boolean) =
733+
if qual.tpe.isSmallGenericTuple then
734+
if withCast then
735+
val elems = qual.tpe.widenTermRefExpr.tupleElementTypes.getOrElse(Nil)
736+
typedSelectWithAdapt(tree, pt, qual.cast(defn.tupleType(elems)))
737+
else
738+
typedSelectWithAdapt(tree, pt, qual)
739+
else EmptyTree
740+
741+
// Otherwise try an extension or conversion
742+
def tryExt(tree: untpd.Select, qual: Tree) =
743+
tryExtensionOrConversion(
744+
tree, pt, IgnoredProto(pt), qual, ctx.typerState.ownedVars, this, inSelect = true
745+
)
746+
747+
// Otherwise, try a GADT approximation if we're trying to select a member
748+
def tryGadt() =
749+
if ctx.gadt.isNarrowing then
750+
// Member lookup cannot take GADTs into account b/c of cache, so we
751+
// approximate types based on GADT constraints instead. For an example,
752+
// see MemberHealing in gadt-approximation-interaction.scala.
753+
val wtp = qual.tpe.widen
754+
gadts.println(i"Trying to heal member selection by GADT-approximating $wtp")
755+
val gadtApprox = Inferencing.approximateGADT(wtp)
756+
gadts.println(i"GADT-approximated $wtp ~~ $gadtApprox")
757+
val qual1 = qual.cast(gadtApprox)
758+
val tree1 = cpy.Select(tree0)(qual1, selName)
759+
tryType(tree1, qual1, selectionType(tree1, qual1))
760+
.orElse(trySmallGenericTuple(qual1, withCast = false))
761+
.orElse(tryExt(tree1, qual1))
762+
else EmptyTree
763+
764+
// Otherwise, if there are uninstantiated type variables in the qualifier type,
765+
// instantiate them and try again
766+
def tryDefineFurther() =
767+
if canDefineFurther(qual.tpe.widen) then
768+
typedSelectWithAdapt(tree, pt, qual)
769+
else EmptyTree
770+
771+
def dynamicSelect(pt: Type) =
743772
val tree2 = cpy.Select(tree0)(untpd.TypedSplice(qual), selName)
744773
if pt.isInstanceOf[FunOrPolyProto] || pt == AssignProto then
745774
assignType(tree2, TryDynamicCallType)
746775
else
747776
typedDynamicSelect(tree2, Nil, pt)
748-
else
749-
assignType(tree,
750-
rawType match
751-
case rawType: NamedType =>
752-
inaccessibleErrorType(rawType, superAccess, tree.srcPos)
753-
case _ =>
754-
notAMemberErrorType(tree, qual, pt))
755-
end typedSelect
777+
778+
// Otherwise, if the qualifier derives from class Dynamic, expand to a
779+
// dynamic dispatch using selectDynamic or applyDynamic
780+
def tryDynamic() =
781+
if qual.tpe.derivesFrom(defn.DynamicClass) && selName.isTermName && !isDynamicExpansion(tree) then
782+
dynamicSelect(pt)
783+
else EmptyTree
784+
785+
def reportAnError() =
786+
assignType(tree,
787+
rawType match
788+
case rawType: NamedType =>
789+
inaccessibleErrorType(rawType, superAccess, tree.srcPos)
790+
case _ =>
791+
notAMemberErrorType(tree, qual, pt))
792+
793+
tryType(tree, qual, rawType)
794+
.orElse(trySimplifyApply())
795+
.orElse(tryInstantiateTypeVar())
796+
.orElse(tryLiftToThis())
797+
.orElse(trySmallGenericTuple(qual, withCast = true))
798+
.orElse(tryExt(tree, qual))
799+
.orElse(tryGadt())
800+
.orElse(tryDefineFurther())
801+
.orElse(tryDynamic())
802+
.orElse(reportAnError())
803+
end typedSelectWithAdapt
756804

757805
def typedSelect(tree: untpd.Select, pt: Type)(using Context): Tree = {
758806
record("typedSelect")
759807

760808
def typeSelectOnTerm(using Context): Tree =
761809
val qual = typedExpr(tree.qualifier, shallowSelectionProto(tree.name, pt, this, tree.nameSpan))
762-
typedSelect(tree, pt, qual).withSpan(tree.span).computeNullable()
810+
typedSelectWithAdapt(tree, pt, qual).withSpan(tree.span).computeNullable()
763811

764812
def javaSelectOnType(qual: Tree)(using Context) =
765813
// semantic name conversion for `O$` in java code
@@ -3563,7 +3611,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
35633611
if isExtension then return found
35643612
else
35653613
checkImplicitConversionUseOK(found, selProto)
3566-
return withoutMode(Mode.ImplicitsEnabled)(typedSelect(tree, pt, found))
3614+
return withoutMode(Mode.ImplicitsEnabled)(typedSelectWithAdapt(tree, pt, found))
35673615
case failure: SearchFailure =>
35683616
if failure.isAmbiguous then
35693617
return

tests/pos/i19609.orig.scala

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
object o {
2+
opaque type T = String
3+
4+
summon[o.T =:= T] // OK
5+
summon[o.T =:= String] // OK
6+
7+
def test1(t: T): Int =
8+
t.length // OK
9+
10+
def test2(t: o.T): Int =
11+
t.length // Error: value length is not a member of Playground.o.T
12+
}

tests/pos/i19609.scala

+24
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
object o { u =>
2+
opaque type T = String
3+
4+
def st = summon[String =:= T]
5+
def su = summon[String =:= u.T]
6+
def so = summon[String =:= o.T]
7+
8+
def ts = summon[T =:= String]
9+
def tu = summon[T =:= u.T]
10+
def to = summon[T =:= o.T]
11+
12+
def us = summon[u.T =:= String]
13+
def ut = summon[u.T =:= T]
14+
def uo = summon[u.T =:= o.T]
15+
16+
def os = summon[o.T =:= String]
17+
def ot = summon[o.T =:= T]
18+
def ou = summon[o.T =:= u.T]
19+
20+
def ms(x: String): Int = x.length // ok
21+
def mt(x: T): Int = x.length // ok
22+
def mu(x: u.T): Int = x.length // ok
23+
def mo(x: o.T): Int = x.length // was: error: value length is not a member of o.T
24+
}

0 commit comments

Comments
 (0)