Skip to content

Commit 44a305f

Browse files
committed
Fix scala#1895: Use proper validity period for extension methods
When we create a symbol in a DenotationTransformer, we cannot safely create it at phase `thisPhase.next` since this can lead to cycles. This is why we created extension methods at phase `thisPhase`, but they're not really valid at that point since they're only entered in their owner once the transformer has been run. This lead to stale symbol errors which were worked around in 51a458e but the fix is incomplete and does not work with scala#4604 because that PR moves the ExtensionMethods phase around. This commit removes all the workarounds and uses a simpler solution: create the symbols at phase `thisPhase` but set their validity period to match the validity of the owner transformed denotation.
1 parent 7aa1ee2 commit 44a305f

File tree

2 files changed

+32
-32
lines changed

2 files changed

+32
-32
lines changed

compiler/src/dotty/tools/dotc/core/SymDenotations.scala

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -51,13 +51,9 @@ trait SymDenotations { this: Context =>
5151
else {
5252
val initial = denot.initial
5353
val firstPhaseId = initial.validFor.firstPhaseId.max(ctx.typerPhase.id)
54-
if ((initial ne denot) || ctx.phaseId != firstPhaseId) {
55-
ctx.withPhase(firstPhaseId).stillValidInOwner(initial) ||
56-
// Workaround #1895: A symbol might not be entered into an owner
57-
// until the second phase where it exists
58-
(denot.validFor.containsPhaseId(firstPhaseId + 1)) &&
59-
ctx.withPhase(firstPhaseId + 1).stillValidInOwner(initial)
60-
} else
54+
if ((initial ne denot) || ctx.phaseId != firstPhaseId)
55+
ctx.withPhase(firstPhaseId).stillValidInOwner(initial)
56+
else
6157
stillValidInOwner(denot)
6258
}
6359

compiler/src/dotty/tools/dotc/transform/ExtensionMethods.scala

Lines changed: 29 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -62,33 +62,37 @@ class ExtensionMethods extends MiniPhase with DenotTransformer with FullParamete
6262
val decls1 = cinfo.decls.cloneScope
6363
val moduleSym = moduleClassSym.symbol.asClass
6464

65-
ctx.atPhase(thisPhase.next) { implicit ctx =>
66-
// In Scala 2, extension methods are added before pickling so we should
67-
// not generate them again.
68-
if (!(valueClass is Scala2x)) ctx.atPhase(thisPhase) { implicit ctx =>
69-
for (decl <- valueClass.classInfo.decls) {
70-
if (isMethodWithExtension(decl)) {
71-
val meth = createExtensionMethod(decl, moduleClassSym.symbol)
72-
decls1.enter(meth)
73-
// Workaround #1895: force denotation of `meth` to be
74-
// at phase where `meth` is entered into the decls of a class
75-
meth.denot(ctx.withPhase(thisPhase.next))
76-
}
77-
}
78-
}
79-
80-
val underlying = valueErasure(underlyingOfValueClass(valueClass))
81-
val evt = ErasedValueType(valueClass.typeRef, underlying)
82-
val u2evtSym = ctx.newSymbol(moduleSym, nme.U2EVT, Synthetic | Method,
83-
MethodType(List(nme.x_0), List(underlying), evt))
84-
val evt2uSym = ctx.newSymbol(moduleSym, nme.EVT2U, Synthetic | Method,
85-
MethodType(List(nme.x_0), List(evt), underlying))
86-
87-
decls1.enter(u2evtSym)
88-
decls1.enter(evt2uSym)
65+
def enterInModuleClass(sym: Symbol): Unit = {
66+
decls1.enter(sym)
67+
// This is tricky: in this denotation transformer, we transform
68+
// companion modules of value classes by adding methods to them.
69+
// Running the transformer will create these methods, but they're
70+
// only valid once it has finished running. This means we cannot use
71+
// `ctx.withPhase(thisPhase.next)` here without potentially running
72+
// into cycles. Instead, we manually set their validity after having
73+
// created them to match the validity of the owner transformed
74+
// denotation.
75+
sym.validFor = thisPhase.validFor
8976
}
9077

91-
// Add the extension methods and the cast methods u2evt$ and evt2u$
78+
// Create extension methods, except if the class comes from Scala 2
79+
// because it adds extension methods before pickling.
80+
if (!(valueClass.is(Scala2x)))
81+
for (decl <- valueClass.classInfo.decls)
82+
if (isMethodWithExtension(decl))
83+
enterInModuleClass(createExtensionMethod(decl, moduleClassSym.symbol))
84+
85+
// Create synthetic methods to cast values between the underlying type
86+
// and the ErasedValueType. These methods are removed in ElimErasedValueType.
87+
val underlying = valueErasure(underlyingOfValueClass(valueClass))
88+
val evt = ErasedValueType(valueClass.typeRef, underlying)
89+
val u2evtSym = ctx.newSymbol(moduleSym, nme.U2EVT, Synthetic | Method,
90+
MethodType(List(nme.x_0), List(underlying), evt))
91+
val evt2uSym = ctx.newSymbol(moduleSym, nme.EVT2U, Synthetic | Method,
92+
MethodType(List(nme.x_0), List(evt), underlying))
93+
enterInModuleClass(u2evtSym)
94+
enterInModuleClass(evt2uSym)
95+
9296
moduleClassSym.copySymDenotation(info = cinfo.derivedClassInfo(decls = decls1))
9397
case _ =>
9498
moduleClassSym

0 commit comments

Comments
 (0)