From f6a16cb0d16f423a7458d639df7a8b28f1ffad8f Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Wed, 11 Jan 2023 09:19:32 +0000 Subject: [PATCH] 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. --- .../src/dotty/tools/dotc/typer/Typer.scala | 40 ++++++++++++------- tests/pos/i16603.scala | 20 ++++++++++ 2 files changed, 46 insertions(+), 14 deletions(-) create mode 100644 tests/pos/i16603.scala diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index e65eba4d5dd8..c9ebd826de49 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -622,11 +622,15 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer val superAccess = qual.isInstanceOf[Super] val rawType = selectionType(tree, qual) val checkedType = accessibleType(rawType, superAccess) - if checkedType.exists then + + 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 @@ -638,6 +642,26 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer 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 + 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 @@ -3986,19 +4010,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer pt match case pt: SelectionProto => - 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. - gadts.println(i"Trying to heal member selection by GADT-approximating $wtp") - val gadtApprox = Inferencing.approximateGADT(wtp) - gadts.println(i"GADT-approximated $wtp ~~ $gadtApprox") - if pt.isMatchedBy(gadtApprox) then - gadts.println(i"Member selection healed by GADT approximation") - tree.cast(gadtApprox) - else tree - else if tree.tpe.derivesFrom(defn.PairClass) && !defn.isTupleNType(tree.tpe.widenDealias) then + if tree.tpe.derivesFrom(defn.PairClass) && !defn.isTupleNType(tree.tpe.widenDealias) then // If this is a generic tuple we need to cast it to make the TupleN/ members accessible. // This works only for generic tuples of known size up to 22. defn.tupleTypes(tree.tpe.widenTermRefExpr) match diff --git a/tests/pos/i16603.scala b/tests/pos/i16603.scala new file mode 100644 index 000000000000..5d5e6c9c9398 --- /dev/null +++ b/tests/pos/i16603.scala @@ -0,0 +1,20 @@ +trait MyData + +object MyData: + extension (m: MyData) + def printIt() = println("hey from my data") + +class MyClass: + def sel(s: String): Int = s.hashCode() + +enum MyTag[A]: + case MyDataTag extends MyTag[MyData] + case MyClassTag extends MyTag[MyClass] + +def callExtension[A](tag: MyTag[A], a:A): Unit = + tag match + case MyTag.MyDataTag => a.printIt() + case MyTag.MyClassTag => a.sel("hi") + +def callExtensionDirectly(m: MyData): Unit = + m.printIt()