From de6764f3dc849da386cb3578b8773f438400e296 Mon Sep 17 00:00:00 2001 From: odersky Date: Thu, 5 May 2022 18:44:47 +0200 Subject: [PATCH] Invalidate some denotations in earlier phases This addresses the specific problem raised by #10044: A denotation of a NamedType goes from a UniqueRefDenotation to a SymDenotation after erasure and is then not reset to the original since the SymDenotation is valid at all phases. We remember in this case in the NamedType the first phase in which the SymDenotaton is valid and force a recompute in earlier phases. Fixes #10044 --- .../src/dotty/tools/dotc/core/Types.scala | 24 +++++++++++++++++++ compiler/test-resources/repl/i10044 | 12 ++++++++++ 2 files changed, 36 insertions(+) create mode 100644 compiler/test-resources/repl/i10044 diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index 7d57b4eb2740..5927552b8585 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -2097,6 +2097,7 @@ object Types { private var lastDenotation: Denotation | Null = null private var lastSymbol: Symbol | Null = null private var checkedPeriod: Period = Nowhere + private var firstValidPhaseId: Int = 0 private var myStableHash: Byte = 0 private var mySignature: Signature = _ private var mySignatureRunId: Int = NoRunId @@ -2212,6 +2213,8 @@ object Types { val now = ctx.period // Even if checkedPeriod == now we still need to recheck lastDenotation.validFor // as it may have been mutated by SymDenotation#installAfter + if firstValidPhaseId > now.phaseId then + revalidateDenot() if (checkedPeriod != Nowhere && lastDenotation.nn.validFor.contains(now)) { checkedPeriod = now lastDenotation.nn @@ -2340,6 +2343,18 @@ object Types { def recomputeDenot()(using Context): Unit = setDenot(memberDenot(name, allowPrivate = !symbol.exists || symbol.is(Private))) + /** Try to recompute denotation and reset `firstValidPhaseId`. + * @pre Current phase id < firstValidPhaseId + */ + def revalidateDenot()(using Context): Unit = + if (prefix ne NoPrefix) then + core.println(i"revalidate $prefix . $name, $firstValidPhaseId > ${ctx.phaseId}") + val newDenot = memberDenot(name, allowPrivate = + lastSymbol == null || !lastSymbol.nn.exists || lastSymbol.nn.is(Private)) + if newDenot.exists then + setDenot(newDenot) + firstValidPhaseId = ctx.phaseId + private def setDenot(denot: Denotation)(using Context): Unit = { if (Config.checkNoDoubleBindings) if (ctx.settings.YnoDoubleBindings.value) @@ -2570,6 +2585,15 @@ object Types { || adapted.info.eq(denot.info)) adapted else this + val lastDenot = result.lastDenotation + if denot.isInstanceOf[SymDenotation] && lastDenot != null && !lastDenot.isInstanceOf[SymDenotation] then + // In this case the new SymDenotation might be valid for all phases, which means + // we would not recompute the denotation when travelling to an earlier phase, maybe + // in the next run. We fix that problem by recording in this case in the NamedType + // the phase from which the denotation is valid. Taking the denotation at an earlier + // phase will then lead to a `revalidateDenot`. + core.println(i"overwrite ${result.toString} / ${result.lastDenotation} with $denot") + result.firstValidPhaseId = ctx.phaseId result.setDenot(denot) result.asInstanceOf[ThisType] } diff --git a/compiler/test-resources/repl/i10044 b/compiler/test-resources/repl/i10044 new file mode 100644 index 000000000000..3d0fe506ea32 --- /dev/null +++ b/compiler/test-resources/repl/i10044 @@ -0,0 +1,12 @@ +scala> object Foo { opaque type Bar = Int; object Bar { extension (b: Bar) def flip: Bar = -b; def apply(x: Int): Bar = x }} +// defined object Foo +scala> val a = Foo.Bar(42) +val a: Foo.Bar = 42 +scala> val b = a.flip +val b: Foo.Bar = -42 +scala> val c = b.flip +val c: Foo.Bar = 42 +scala> val d = c.flip +val d: Foo.Bar = -42 +scala> val e = d.flip +val e: Foo.Bar = 42