From bc5dd764f9a6ff85d302bb67d8d1e2c3680b1284 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 8 Dec 2021 22:25:56 +0100 Subject: [PATCH 1/2] Harden erasure of PolyFunction apply's Fix in case qualifier is an intersection with a PolyFunction type. Fixes #13950 --- .../src/dotty/tools/dotc/transform/Erasure.scala | 16 +++++++++++++--- tests/pos/i13950.scala | 5 +++++ 2 files changed, 18 insertions(+), 3 deletions(-) create mode 100644 tests/pos/i13950.scala diff --git a/compiler/src/dotty/tools/dotc/transform/Erasure.scala b/compiler/src/dotty/tools/dotc/transform/Erasure.scala index ab6f00dfc575..81fee107892f 100644 --- a/compiler/src/dotty/tools/dotc/transform/Erasure.scala +++ b/compiler/src/dotty/tools/dotc/transform/Erasure.scala @@ -667,8 +667,18 @@ object Erasure { def mapOwner(sym: Symbol): Symbol = if !sym.exists && tree.name == nme.apply then // PolyFunction apply Selects will not have a symbol, so deduce the owner - // from the typed the erasure of the original qualifier. - val owner = erasure(tree.qualifier.typeOpt).typeSymbol + // from the typed tree of the erasure of the original qualifier's PolyFunction type. + // Need to sidestep information loss in the erasure of AndTypes, see pos/i13950.scala. + val polyFunctionQualType = inContext(preErasureCtx) { + def narrowToPolyFun(tp: Type)(using Context): Type = + if tp.derivesFrom(defn.PolyFunctionClass) then + tp match + case AndType(tp1, tp2) => narrowToPolyFun(tp1).orElse(narrowToPolyFun(tp2)) + case _ => tp + else NoType + narrowToPolyFun(tree.qualifier.typeOpt.widen) + } + val owner = erasure(polyFunctionQualType).typeSymbol if defn.isFunctionClass(owner) then owner else NoSymbol else val owner = sym.maybeOwner @@ -687,7 +697,7 @@ object Erasure { val owner = mapOwner(origSym) val sym = if (owner eq origSym.maybeOwner) origSym else owner.info.decl(tree.name).symbol - assert(sym.exists, origSym.showLocated) + assert(sym.exists, i"no owner from $owner/${origSym.showLocated} in $tree") if owner == defn.ObjectClass then checkValue(qual1) diff --git a/tests/pos/i13950.scala b/tests/pos/i13950.scala new file mode 100644 index 000000000000..768786169cd0 --- /dev/null +++ b/tests/pos/i13950.scala @@ -0,0 +1,5 @@ +def example = + (1,2).map[[_] =>> Int]([C] => (x1: C) => x1 match { + case x2: ([V] => () => Int) => + x2[Int]() + }) \ No newline at end of file From e2b122f82ca02f706c84b0cce856805f035d4eb7 Mon Sep 17 00:00:00 2001 From: Guillaume Martres Date: Tue, 4 Jan 2022 15:28:54 +0100 Subject: [PATCH 2/2] Alternative approach to PolyFunction apply erasure The previous approach did not handle situations where the qualifier was not directly an AndType, but a type whose underlying type is an AndType like in example2 and example3 (this commit also minimizes the original example further). --- .../dotty/tools/dotc/core/TypeErasure.scala | 15 +++++++++----- .../dotty/tools/dotc/transform/Erasure.scala | 20 +++++++++---------- tests/pos/i13950.scala | 5 ----- tests/run/i13950.scala | 11 ++++++++++ 4 files changed, 30 insertions(+), 21 deletions(-) delete mode 100644 tests/pos/i13950.scala create mode 100644 tests/run/i13950.scala diff --git a/compiler/src/dotty/tools/dotc/core/TypeErasure.scala b/compiler/src/dotty/tools/dotc/core/TypeErasure.scala index 6435d0622ebd..1473bcc559e2 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeErasure.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeErasure.scala @@ -524,6 +524,15 @@ object TypeErasure { case tp: OrType => hasStableErasure(tp.tp1) && hasStableErasure(tp.tp2) case _ => false } + + /** The erasure of `PolyFunction { def apply: $applyInfo }` */ + def erasePolyFunctionApply(applyInfo: Type)(using Context): Type = + assert(applyInfo.isInstanceOf[PolyType]) + val res = applyInfo.resultType + val paramss = res.paramNamess + assert(paramss.length == 1) + erasure(defn.FunctionType(paramss.head.length, + isContextual = res.isImplicitMethod, isErased = res.isErasedMethod)) } import TypeErasure._ @@ -597,11 +606,7 @@ class TypeErasure(sourceLanguage: SourceLanguage, semiEraseVCs: Boolean, isConst case ExprType(rt) => defn.FunctionType(0) case RefinedType(parent, nme.apply, refinedInfo) if parent.typeSymbol eq defn.PolyFunctionClass => - assert(refinedInfo.isInstanceOf[PolyType]) - val res = refinedInfo.resultType - val paramss = res.paramNamess - assert(paramss.length == 1) - this(defn.FunctionType(paramss.head.length, isContextual = res.isImplicitMethod, isErased = res.isErasedMethod)) + erasePolyFunctionApply(refinedInfo) case tp: TypeProxy => this(tp.underlying) case tp @ AndType(tp1, tp2) => diff --git a/compiler/src/dotty/tools/dotc/transform/Erasure.scala b/compiler/src/dotty/tools/dotc/transform/Erasure.scala index 81fee107892f..357ff1e6e3e6 100644 --- a/compiler/src/dotty/tools/dotc/transform/Erasure.scala +++ b/compiler/src/dotty/tools/dotc/transform/Erasure.scala @@ -668,18 +668,16 @@ object Erasure { if !sym.exists && tree.name == nme.apply then // PolyFunction apply Selects will not have a symbol, so deduce the owner // from the typed tree of the erasure of the original qualifier's PolyFunction type. - // Need to sidestep information loss in the erasure of AndTypes, see pos/i13950.scala. - val polyFunctionQualType = inContext(preErasureCtx) { - def narrowToPolyFun(tp: Type)(using Context): Type = - if tp.derivesFrom(defn.PolyFunctionClass) then - tp match - case AndType(tp1, tp2) => narrowToPolyFun(tp1).orElse(narrowToPolyFun(tp2)) - case _ => tp - else NoType - narrowToPolyFun(tree.qualifier.typeOpt.widen) + // We cannot simply call `erasure` on the qualifier because its erasure might be + // `Object` due to how we erase intersections (see pos/i13950.scala). + // Instead, we manually lookup the type of `apply` in the qualifier. + inContext(preErasureCtx) { + val qualTp = tree.qualifier.typeOpt.widen + if qualTp.derivesFrom(defn.PolyFunctionClass) then + erasePolyFunctionApply(qualTp.select(nme.apply).widen).classSymbol + else + NoSymbol } - val owner = erasure(polyFunctionQualType).typeSymbol - if defn.isFunctionClass(owner) then owner else NoSymbol else val owner = sym.maybeOwner if defn.specialErasure.contains(owner) then diff --git a/tests/pos/i13950.scala b/tests/pos/i13950.scala deleted file mode 100644 index 768786169cd0..000000000000 --- a/tests/pos/i13950.scala +++ /dev/null @@ -1,5 +0,0 @@ -def example = - (1,2).map[[_] =>> Int]([C] => (x1: C) => x1 match { - case x2: ([V] => () => Int) => - x2[Int]() - }) \ No newline at end of file diff --git a/tests/run/i13950.scala b/tests/run/i13950.scala new file mode 100644 index 000000000000..b8f93129beb8 --- /dev/null +++ b/tests/run/i13950.scala @@ -0,0 +1,11 @@ +def example(x: Any & ([V] => V => Int)) = + x[Int](1) +def example2(x: (Any & ([V] => V => Int)) @unchecked) = + x[Int](1) +def example3[S <: Any & ([V] => V => Int)](x: S) = + x[Int](1) + +@main def Test = + example([A] => (x: A) => 1) + example2([A] => (x: A) => 1) + example3([A] => (x: A) => 1)