From e0622ea744bdcabc15fa0544e7d1bff8ced0bb24 Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Thu, 5 Aug 2021 18:23:05 +0100 Subject: [PATCH] Handle type alias in capturing wildcards We extend capturing wildcards so that it operates on the type after dealiasing. However, in line with, for example, derivedRefinedType, we want to return the original type if nothing was captured, because it's on that basis that adapt decides whether it needs to cast and re-adapt to the new type with the type boxes. Co-authored-by: Seth Tisue --- .../dotty/tools/dotc/typer/Inferencing.scala | 32 ++++++++++--------- .../src/dotty/tools/dotc/typer/Typer.scala | 2 +- tests/pos/i12739-fallout.scala | 8 +++++ tests/pos/i12739.scala | 26 +++++++++++++++ 4 files changed, 52 insertions(+), 16 deletions(-) create mode 100644 tests/pos/i12739-fallout.scala create mode 100644 tests/pos/i12739.scala diff --git a/compiler/src/dotty/tools/dotc/typer/Inferencing.scala b/compiler/src/dotty/tools/dotc/typer/Inferencing.scala index 6f65d81c1635..7654b98995ff 100644 --- a/compiler/src/dotty/tools/dotc/typer/Inferencing.scala +++ b/compiler/src/dotty/tools/dotc/typer/Inferencing.scala @@ -499,28 +499,30 @@ object Inferencing { propagate(accu(SimpleIdentityMap.empty, tp)) } + /** Run the transformation after dealiasing but return the original type if it was a no-op. */ + private def derivedOnDealias(tp: Type)(transform: Type => Type)(using Context) = { + val dealiased = tp.dealias + val transformed = transform(dealiased) + if transformed eq dealiased then tp // return the original type, not the result of dealiasing + else transformed + } + /** Replace every top-level occurrence of a wildcard type argument by * a fresh skolem type. The skolem types are of the form $i.CAP, where * $i is a skolem of type `scala.internal.TypeBox`, and `CAP` is its * type member. See the documentation of `TypeBox` for a rationale why we do this. */ - def captureWildcards(tp: Type)(using Context): Type = tp match { + def captureWildcards(tp: Type)(using Context): Type = derivedOnDealias(tp) { case tp @ AppliedType(tycon, args) if tp.hasWildcardArg => - tycon.typeParams match { - case tparams @ ((_: Symbol) :: _) => - val boundss = tparams.map(_.paramInfo.substApprox(tparams.asInstanceOf[List[TypeSymbol]], args)) - val args1 = args.zipWithConserve(boundss) { (arg, bounds) => - arg match { - case TypeBounds(lo, hi) => - val skolem = SkolemType(defn.TypeBoxClass.typeRef.appliedTo(lo | bounds.loBound, hi & bounds.hiBound)) - TypeRef(skolem, defn.TypeBox_CAP) - case arg => arg - } - } - tp.derivedAppliedType(tycon, args1) - case _ => - tp + val tparams = tycon.typeParamSymbols + val args1 = args.zipWithConserve(tparams.map(_.paramInfo.substApprox(tparams, args))) { + case (TypeBounds(lo, hi), bounds) => + val skolem = SkolemType(defn.TypeBoxClass.typeRef.appliedTo(lo | bounds.loBound, hi & bounds.hiBound)) + TypeRef(skolem, defn.TypeBox_CAP) + case (arg, _) => + arg } + if tparams.isEmpty then tp else tp.derivedAppliedType(tycon, args1) case tp: AndOrType => tp.derivedAndOrType(captureWildcards(tp.tp1), captureWildcards(tp.tp2)) case tp: RefinedType => tp.derivedRefinedType(captureWildcards(tp.parent), tp.refinedName, tp.refinedInfo) case tp: RecType => tp.derivedRecType(captureWildcards(tp.parent)) diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 6728cab6f3d0..556287da5221 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -3160,7 +3160,7 @@ class Typer extends Namer */ def adapt(tree: Tree, pt: Type, locked: TypeVars, tryGadtHealing: Boolean = true)(using Context): Tree = try - trace(i"adapting $tree to $pt ${if (tryGadtHealing) "" else "(tryGadtHealing=false)" }\n", typr, show = true) { + trace(i"adapting $tree to $pt ${if (tryGadtHealing) "" else "(tryGadtHealing=false)" }", typr, show = true) { record("adapt") adapt1(tree, pt, locked, tryGadtHealing) } diff --git a/tests/pos/i12739-fallout.scala b/tests/pos/i12739-fallout.scala new file mode 100644 index 000000000000..7f685b3c1a0f --- /dev/null +++ b/tests/pos/i12739-fallout.scala @@ -0,0 +1,8 @@ +// This is a minimisation of the fallout that the original fix caused on Shapeless 3. + +type Foo = { type Bar } + +extension (foo: Foo) + def toBar(): foo.Bar = ??? + +def test(foo: Foo): foo.Bar = foo.toBar() diff --git a/tests/pos/i12739.scala b/tests/pos/i12739.scala new file mode 100644 index 000000000000..baf7a7ae2698 --- /dev/null +++ b/tests/pos/i12739.scala @@ -0,0 +1,26 @@ +object X { + + class CA[A] + type C = CA[_] + val c: C = ??? + def f[A](r: CA[A]) = () + def g(): CA[_] = CA() + def h(): C = ??? + + // works + f(c) + + // works + val x = c.asInstanceOf[C] + f(x) + + // was: error + f(c.asInstanceOf[C]) + + // works, error in Scala 2 + f(c.asInstanceOf[c.type]) + + f(c.asInstanceOf[CA[_]]) + f(g()) + f(h()) +}