From eb9bf1acffb8faff71c420baabe76fd8323608a2 Mon Sep 17 00:00:00 2001 From: odersky Date: Tue, 12 Jul 2022 17:10:36 +0200 Subject: [PATCH 1/2] Harden erasure of TermRefs If a getter TermRef has a MethodType, erase it to the erasure of the method result type. Fixes #15649 Since the fault in #15649 is highly non-deterministic, I can only guess what the reason was. I think what happened was that the TermRef was created very late, when the TermRef was already a getter. I.e. after phase `Getters`, which is itself after `Erasure`. The whole thing was launched from the code that generates static forwarders in the backend. Type erasure is run at erasure phase, but if there is no previous denotation of the `TermRef`, it will keep the first one it read, which would have the `MethodType` as underlying type. The end result was that the `TermRef` was erased to a method type, where it should have been erased to the result type. Consequently, we ended up with a MethodType as the parameter type of another method, which is unexpected. But all that is just a guess. I am not even sure this PR will fix the problem since it comes up so rarely. --- compiler/src/dotty/tools/dotc/core/TypeErasure.scala | 10 ++++++++-- tests/pos/i15649.scala | 11 +++++++++++ 2 files changed, 19 insertions(+), 2 deletions(-) create mode 100644 tests/pos/i15649.scala diff --git a/compiler/src/dotty/tools/dotc/core/TypeErasure.scala b/compiler/src/dotty/tools/dotc/core/TypeErasure.scala index 41993d9fb578..2efe1b779e22 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeErasure.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeErasure.scala @@ -602,7 +602,9 @@ class TypeErasure(sourceLanguage: SourceLanguage, semiEraseVCs: Boolean, isConst else if (tp.isRepeatedParam) apply(tp.translateFromRepeated(toArray = sourceLanguage.isJava)) else if (semiEraseVCs && isDerivedValueClass(tycon.classSymbol)) eraseDerivedValueClass(tp) else apply(tp.translucentSuperType) - case _: TermRef | _: ThisType => + case tp: TermRef => + this(underlyingOfTermRef(tp)) + case _: ThisType => this(tp.widen) case SuperType(thistpe, supertpe) => SuperType(this(thistpe), this(supertpe)) @@ -687,6 +689,10 @@ class TypeErasure(sourceLanguage: SourceLanguage, semiEraseVCs: Boolean, isConst tp } + private def underlyingOfTermRef(tp: TermRef)(using Context) = tp.widen match + case tpw @ MethodType(Nil) if tp.symbol.isGetter => tpw.resultType + case tpw => tpw + private def eraseArray(tp: Type)(using Context) = { val defn.ArrayOf(elemtp) = tp: @unchecked if (isGenericArrayElement(elemtp, isScala2 = sourceLanguage.isScala2)) defn.ObjectType @@ -832,7 +838,7 @@ class TypeErasure(sourceLanguage: SourceLanguage, semiEraseVCs: Boolean, isConst case JavaArrayType(elem) => sigName(elem) ++ "[]" case tp: TermRef => - sigName(tp.widen) + sigName(underlyingOfTermRef(tp)) case ExprType(rt) => sigName(defn.FunctionOf(Nil, rt)) case tp: TypeVar => diff --git a/tests/pos/i15649.scala b/tests/pos/i15649.scala new file mode 100644 index 000000000000..8c82c8db39a8 --- /dev/null +++ b/tests/pos/i15649.scala @@ -0,0 +1,11 @@ +trait ConfigSourceModule: + object ConfigSource: + class R + +object M extends ConfigSourceModule + +object Foo: + implicit class FromConfigSource(c: M.ConfigSource.type) + +object FooBar: // problem disappears if we rename as `Bar` + def foo: M.ConfigSource.R = new M.ConfigSource.R // problem disappears if we use `???` as rhs \ No newline at end of file From 9b7ba2406feaa39967fe1ffeae4e6f55b213b0c3 Mon Sep 17 00:00:00 2001 From: odersky Date: Wed, 13 Jul 2022 18:18:00 +0200 Subject: [PATCH 2/2] Add comment --- compiler/src/dotty/tools/dotc/core/TypeErasure.scala | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/compiler/src/dotty/tools/dotc/core/TypeErasure.scala b/compiler/src/dotty/tools/dotc/core/TypeErasure.scala index 2efe1b779e22..1fc7ee5d22a8 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeErasure.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeErasure.scala @@ -689,6 +689,15 @@ class TypeErasure(sourceLanguage: SourceLanguage, semiEraseVCs: Boolean, isConst tp } + /** Widen term ref, skipping any `()` parameter of an eventual getter. Used to erase a TermRef. + * Since getters are introduced after erasure, one would think that erasing a TermRef + * could just use `widen`. However, it's possible that the TermRef got read from a class + * file after Getters (i.e. in the backend). In that case, the reference will not get + * an earlier denotation even when time travelling forward to erasure. Hence, we + * need to take the extra precaution of going from nullary method types to their resuls. + * A test case where this is needed is pos/i15649.scala, which fails non-deterministically + * if `underlyingOfTermRef` is replaced by `widen`. + */ private def underlyingOfTermRef(tp: TermRef)(using Context) = tp.widen match case tpw @ MethodType(Nil) if tp.symbol.isGetter => tpw.resultType case tpw => tpw