Skip to content

Commit f6a16cb

Browse files
committed
Add extension/conversion to GADT selection healing
The GADT member selection "healing" logic only accounted for members that belong to the resulting GADT-approximated type, meaning extension methods weren't considered. I moved that logic to typedSelect, so that the resulting tree from running the extension or conversion attempt can be returned. In adaptToSubType it was adapting the qualifier only. For example, we try to extend/convert `a.$asInstanceOf[MyData].printIt` and get back `MyData.printIt(a.$asInstanceOf[MyData])` which we can return (instead of throwing it away and having typedSelect redo the work.) That also puts the new use of `tryExtensionOrConversion` next to the previous use, for the original qualifier type.
1 parent b65b0f2 commit f6a16cb

File tree

2 files changed

+46
-14
lines changed

2 files changed

+46
-14
lines changed

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

Lines changed: 26 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -622,11 +622,15 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
622622
val superAccess = qual.isInstanceOf[Super]
623623
val rawType = selectionType(tree, qual)
624624
val checkedType = accessibleType(rawType, superAccess)
625-
if checkedType.exists then
625+
626+
def finish(tree: untpd.Select, qual: Tree, checkedType: Type): Tree =
626627
val select = toNotNullTermRef(assignType(tree, checkedType), pt)
627628
if selName.isTypeName then checkStable(qual.tpe, qual.srcPos, "type prefix")
628629
checkLegalValue(select, pt)
629630
ConstFold(select)
631+
632+
if checkedType.exists then
633+
finish(tree, qual, checkedType)
630634
else if selName == nme.apply && qual.tpe.widen.isInstanceOf[MethodType] then
631635
// Simplify `m.apply(...)` to `m(...)`
632636
qual
@@ -638,6 +642,26 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
638642
else
639643
val tree1 = tryExtensionOrConversion(
640644
tree, pt, IgnoredProto(pt), qual, ctx.typerState.ownedVars, this, inSelect = true)
645+
.orElse {
646+
if ctx.gadt.isNarrowing then
647+
// try GADT approximation if we're trying to select a member
648+
// Member lookup cannot take GADTs into account b/c of cache, so we
649+
// approximate types based on GADT constraints instead. For an example,
650+
// see MemberHealing in gadt-approximation-interaction.scala.
651+
val wtp = qual.tpe.widen
652+
gadts.println(i"Trying to heal member selection by GADT-approximating $wtp")
653+
val gadtApprox = Inferencing.approximateGADT(wtp)
654+
gadts.println(i"GADT-approximated $wtp ~~ $gadtApprox")
655+
val qual1 = qual.cast(gadtApprox)
656+
val tree1 = cpy.Select(tree0)(qual1, selName)
657+
val checkedType1 = accessibleType(selectionType(tree1, qual1), superAccess = false)
658+
if checkedType1.exists then
659+
gadts.println(i"Member selection healed by GADT approximation")
660+
finish(tree1, qual1, checkedType1)
661+
else
662+
tryExtensionOrConversion(tree1, pt, IgnoredProto(pt), qual1, ctx.typerState.ownedVars, this, inSelect = true)
663+
else EmptyTree
664+
}
641665
if !tree1.isEmpty then
642666
tree1
643667
else if canDefineFurther(qual.tpe.widen) then
@@ -3986,19 +4010,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
39864010

39874011
pt match
39884012
case pt: SelectionProto =>
3989-
if ctx.gadt.isNarrowing then
3990-
// try GADT approximation if we're trying to select a member
3991-
// Member lookup cannot take GADTs into account b/c of cache, so we
3992-
// approximate types based on GADT constraints instead. For an example,
3993-
// see MemberHealing in gadt-approximation-interaction.scala.
3994-
gadts.println(i"Trying to heal member selection by GADT-approximating $wtp")
3995-
val gadtApprox = Inferencing.approximateGADT(wtp)
3996-
gadts.println(i"GADT-approximated $wtp ~~ $gadtApprox")
3997-
if pt.isMatchedBy(gadtApprox) then
3998-
gadts.println(i"Member selection healed by GADT approximation")
3999-
tree.cast(gadtApprox)
4000-
else tree
4001-
else if tree.tpe.derivesFrom(defn.PairClass) && !defn.isTupleNType(tree.tpe.widenDealias) then
4013+
if tree.tpe.derivesFrom(defn.PairClass) && !defn.isTupleNType(tree.tpe.widenDealias) then
40024014
// If this is a generic tuple we need to cast it to make the TupleN/ members accessible.
40034015
// This works only for generic tuples of known size up to 22.
40044016
defn.tupleTypes(tree.tpe.widenTermRefExpr) match

tests/pos/i16603.scala

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
trait MyData
2+
3+
object MyData:
4+
extension (m: MyData)
5+
def printIt() = println("hey from my data")
6+
7+
class MyClass:
8+
def sel(s: String): Int = s.hashCode()
9+
10+
enum MyTag[A]:
11+
case MyDataTag extends MyTag[MyData]
12+
case MyClassTag extends MyTag[MyClass]
13+
14+
def callExtension[A](tag: MyTag[A], a:A): Unit =
15+
tag match
16+
case MyTag.MyDataTag => a.printIt()
17+
case MyTag.MyClassTag => a.sel("hi")
18+
19+
def callExtensionDirectly(m: MyData): Unit =
20+
m.printIt()

0 commit comments

Comments
 (0)