Skip to content

Commit 033c690

Browse files
Merge pull request #9335 from dotty-staging/init-refine
Initialization checker improvement
2 parents 4ff2ad9 + 0df4aca commit 033c690

File tree

12 files changed

+273
-263
lines changed

12 files changed

+273
-263
lines changed

compiler/src/dotty/tools/dotc/transform/init/Checker.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ class Checker extends MiniPhase {
2323
val phaseName = "initChecker"
2424

2525
// cache of class summary
26-
private val baseEnv = Env(null, mutable.Map.empty)
26+
private val baseEnv = Env(null)
2727

2828
override val runsAfter = Set(Pickler.name)
2929

@@ -52,7 +52,7 @@ class Checker extends MiniPhase {
5252
thisClass = cls,
5353
fieldsInited = mutable.Set.empty,
5454
parentsInited = mutable.Set.empty,
55-
env = baseEnv.withCtx(ctx)
55+
env = baseEnv.withCtx(ctx.withOwner(cls))
5656
)
5757

5858
Checking.checkClassBody(tree)

compiler/src/dotty/tools/dotc/transform/init/Checking.scala

Lines changed: 56 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ object Checking {
3030
*
3131
*/
3232
case class State(
33-
visited: mutable.Set[Effect], // effects that have been expanded
33+
visited: mutable.Set[Effect], // effects that have been checked
3434
path: Vector[Tree], // the path that leads to the current effect
3535
thisClass: ClassSymbol, // the concrete class of `this`
3636
fieldsInited: mutable.Set[Symbol],
@@ -41,6 +41,8 @@ object Checking {
4141
visited += eff
4242
copy(path = this.path :+ eff.source)
4343
}
44+
45+
def withOwner(sym: Symbol): State = copy(env = env.withOwner(sym))
4446
}
4547

4648
private implicit def theEnv(implicit state: State): Env = state.env
@@ -51,17 +53,19 @@ object Checking {
5153
* However, summarization can be done lazily on-demand to improve
5254
* performance.
5355
*/
54-
def checkClassBody(cdef: TypeDef)(implicit state: State): Unit = traceOp("checking " + cdef.symbol.show, init) {
56+
def checkClassBody(cdef: TypeDef)(implicit state: State): Unit = {
57+
traceIndented("\n\n>>>> checking " + cdef.symbol.show, init)
58+
5559
val cls = cdef.symbol.asClass
5660
val tpl = cdef.rhs.asInstanceOf[Template]
5761

5862
// mark current class as initialized, required for linearization
5963
state.parentsInited += cls
6064

61-
def checkClassBodyStat(tree: Tree)(using Context): Unit = traceOp("checking " + tree.show, init) {
65+
def checkClassBodyStat(tree: Tree)(implicit state: State): Unit = traceOp("checking " + tree.show, init) {
6266
tree match {
6367
case vdef : ValDef =>
64-
val (pots, effs) = Summarization.analyze(vdef.rhs)(theEnv.withOwner(vdef.symbol))
68+
val (pots, effs) = Summarization.analyze(vdef.rhs)
6569
theEnv.summaryOf(cls).cacheFor(vdef.symbol, (pots, effs))
6670
if (!vdef.symbol.is(Flags.Lazy)) {
6771
checkEffectsIn(effs, cls)
@@ -79,15 +83,31 @@ object Checking {
7983
// see spec 5.1 about "Template Evaluation".
8084
// https://www.scala-lang.org/files/archive/spec/2.13/05-classes-and-objects.html
8185

82-
def checkCtor(ctor: Symbol, tp: Type, source: Tree)(using Context): Unit = {
86+
def checkConstructor(ctor: Symbol, tp: Type, source: Tree)(implicit state: State): Unit = traceOp("checking " + ctor.show, init) {
8387
val cls = ctor.owner
8488
val classDef = cls.defTree
8589
if (!classDef.isEmpty) {
86-
if (ctor.isPrimaryConstructor) checkClassBody(classDef.asInstanceOf[TypeDef])
87-
else checkSecondaryConstructor(ctor)
90+
if (ctor.isPrimaryConstructor) checkClassBody(classDef.asInstanceOf[TypeDef])(state.withOwner(cls))
91+
else checkSecondaryConstructor(ctor)(state.withOwner(cls))
92+
}
93+
}
94+
95+
def checkSecondaryConstructor(ctor: Symbol)(implicit state: State): Unit = traceOp("checking " + ctor.show, init) {
96+
val Block(ctorCall :: stats, expr) = ctor.defTree.asInstanceOf[DefDef].rhs
97+
val cls = ctor.owner.asClass
98+
99+
traceOp("check ctor: " + ctorCall.show, init) {
100+
val ctor = ctorCall.symbol
101+
if (ctor.isPrimaryConstructor)
102+
checkClassBody(cls.defTree.asInstanceOf[TypeDef])
103+
else
104+
checkSecondaryConstructor(ctor)
105+
}
106+
107+
(stats :+ expr).foreach { stat =>
108+
val (_, effs) = Summarization.analyze(stat)(theEnv.withOwner(ctor))
109+
checkEffectsIn(effs, cls)
88110
}
89-
else if (!cls.isOneOf(Flags.EffectivelyOpenFlags))
90-
report.warning("Inheriting non-open class may cause initialization errors", source.srcPos)
91111
}
92112

93113
cls.paramAccessors.foreach { acc =>
@@ -100,61 +120,42 @@ object Checking {
100120
tpl.parents.foreach {
101121
case tree @ Block(_, parent) =>
102122
val (ctor, _, _) = decomposeCall(parent)
103-
checkCtor(ctor.symbol, parent.tpe, tree)
123+
checkConstructor(ctor.symbol, parent.tpe, tree)
104124

105125
case tree @ Apply(Block(_, parent), _) =>
106126
val (ctor, _, _) = decomposeCall(parent)
107-
checkCtor(ctor.symbol, tree.tpe, tree)
127+
checkConstructor(ctor.symbol, tree.tpe, tree)
108128

109129
case parent : Apply =>
110130
val (ctor, _, argss) = decomposeCall(parent)
111-
checkCtor(ctor.symbol, parent.tpe, parent)
131+
checkConstructor(ctor.symbol, parent.tpe, parent)
112132

113133
case ref =>
114134
val cls = ref.tpe.classSymbol.asClass
115135
if (!state.parentsInited.contains(cls) && cls.primaryConstructor.exists)
116-
checkCtor(cls.primaryConstructor, ref.tpe, ref)
136+
checkConstructor(cls.primaryConstructor, ref.tpe, ref)
117137
}
118138

119139
// check class body
120140
tpl.body.foreach { checkClassBodyStat(_) }
121141
}
122142

123-
def checkSecondaryConstructor(ctor: Symbol)(implicit state: State): Unit = traceOp("checking " + ctor.show, init) {
124-
val Block(ctorCall :: stats, expr) = ctor.defTree.asInstanceOf[DefDef].rhs
125-
val cls = ctor.owner.asClass
126-
127-
traceOp("check ctor: " + ctorCall.show, init) {
128-
val ctor = ctorCall.symbol
129-
if (ctor.isPrimaryConstructor)
130-
checkClassBody(cls.defTree.asInstanceOf[TypeDef])
131-
else
132-
checkSecondaryConstructor(ctor)
133-
}
134-
135-
(stats :+ expr).foreach { stat =>
136-
val (_, effs) = Summarization.analyze(stat)(theEnv.withOwner(ctor))
137-
checkEffectsIn(effs, cls)
138-
}
139-
}
140-
141143
private def checkEffectsIn(effs: Effects, cls: ClassSymbol)(implicit state: State): Unit = traceOp("checking effects " + Effects.show(effs), init) {
142-
val rebased = Effects.asSeenFrom(effs, ThisRef(state.thisClass)(null), cls, Potentials.empty)
143144
for {
144-
eff <- rebased
145+
eff <- effs
145146
error <- check(eff)
146147
} error.issue
147148
}
148149

149150
private def check(eff: Effect)(implicit state: State): Errors =
150-
if (state.visited.contains(eff)) Errors.empty else trace("checking effect " + eff.show, init, errs => Errors.show(errs.asInstanceOf[Errors])) {
151+
if (state.visited.contains(eff)) Errors.empty
152+
else trace("checking effect " + eff.show, init, errs => Errors.show(errs.asInstanceOf[Errors])) {
151153
implicit val state2: State = state.withVisited(eff)
152154

153155
eff match {
154156
case Promote(pot) =>
155157
pot match {
156-
case pot @ ThisRef(cls) =>
157-
assert(cls == state.thisClass, "unexpected potential " + pot.show)
158+
case pot: ThisRef =>
158159
PromoteThis(pot, eff.source, state2.path).toErrors
159160

160161
case _: Cold =>
@@ -180,18 +181,14 @@ object Checking {
180181
case FieldAccess(pot, field) =>
181182

182183
pot match {
183-
case ThisRef(cls) =>
184-
assert(cls == state.thisClass, "unexpected potential " + pot.show)
185-
186-
val target = resolve(cls, field)
184+
case _: ThisRef =>
185+
val target = resolve(state.thisClass, field)
187186
if (target.is(Flags.Lazy)) check(MethodCall(pot, target)(eff.source))
188187
else if (!state.fieldsInited.contains(target)) AccessNonInit(target, state2.path).toErrors
189188
else Errors.empty
190189

191-
case SuperRef(ThisRef(cls), supercls) =>
192-
assert(cls == state.thisClass, "unexpected potential " + pot.show)
193-
194-
val target = resolveSuper(cls, supercls, field)
190+
case SuperRef(_: ThisRef, supercls) =>
191+
val target = resolveSuper(state.thisClass, supercls, field)
195192
if (target.is(Flags.Lazy)) check(MethodCall(pot, target)(eff.source))
196193
else if (!state.fieldsInited.contains(target)) AccessNonInit(target, state2.path).toErrors
197194
else Errors.empty
@@ -217,10 +214,8 @@ object Checking {
217214

218215
case MethodCall(pot, sym) =>
219216
pot match {
220-
case thisRef @ ThisRef(cls) =>
221-
assert(cls == state.thisClass, "unexpected potential " + pot.show)
222-
223-
val target = resolve(cls, sym)
217+
case thisRef: ThisRef =>
218+
val target = resolve(state.thisClass, sym)
224219
if (!target.isOneOf(Flags.Method | Flags.Lazy))
225220
check(FieldAccess(pot, target)(eff.source))
226221
else if (target.isInternal) {
@@ -229,10 +224,8 @@ object Checking {
229224
}
230225
else CallUnknown(target, eff.source, state2.path).toErrors
231226

232-
case SuperRef(thisRef @ ThisRef(cls), supercls) =>
233-
assert(cls == state.thisClass, "unexpected potential " + pot.show)
234-
235-
val target = resolveSuper(cls, supercls, sym)
227+
case SuperRef(thisRef: ThisRef, supercls) =>
228+
val target = resolveSuper(state.thisClass, supercls, sym)
236229
if (!target.is(Flags.Method))
237230
check(FieldAccess(pot, target)(eff.source))
238231
else if (target.isInternal) {
@@ -272,17 +265,13 @@ object Checking {
272265
pot match {
273266
case MethodReturn(pot1, sym) =>
274267
pot1 match {
275-
case thisRef @ ThisRef(cls) =>
276-
assert(cls == state.thisClass, "unexpected potential " + pot.show)
277-
278-
val target = resolve(cls, sym)
268+
case thisRef: ThisRef =>
269+
val target = resolve(state.thisClass, sym)
279270
if (target.isInternal) (thisRef.potentialsOf(target), Effects.empty)
280271
else Summary.empty // warning already issued in call effect
281272

282-
case SuperRef(thisRef @ ThisRef(cls), supercls) =>
283-
assert(cls == state.thisClass, "unexpected potential " + pot.show)
284-
285-
val target = resolveSuper(cls, supercls, sym)
273+
case SuperRef(thisRef: ThisRef, supercls) =>
274+
val target = resolveSuper(state.thisClass, supercls, sym)
286275
if (target.isInternal) (thisRef.potentialsOf(target), Effects.empty)
287276
else Summary.empty // warning already issued in call effect
288277

@@ -314,17 +303,13 @@ object Checking {
314303

315304
case FieldReturn(pot1, sym) =>
316305
pot1 match {
317-
case thisRef @ ThisRef(cls) =>
318-
assert(cls == state.thisClass, "unexpected potential " + pot.show)
319-
320-
val target = resolve(cls, sym)
306+
case thisRef: ThisRef =>
307+
val target = resolve(state.thisClass, sym)
321308
if (sym.isInternal) (thisRef.potentialsOf(target), Effects.empty)
322309
else (Cold()(pot.source).toPots, Effects.empty)
323310

324-
case SuperRef(thisRef @ ThisRef(cls), supercls) =>
325-
assert(cls == state.thisClass, "unexpected potential " + pot.show)
326-
327-
val target = resolveSuper(cls, supercls, sym)
311+
case SuperRef(thisRef: ThisRef, supercls) =>
312+
val target = resolveSuper(state.thisClass, supercls, sym)
328313
if (target.isInternal) (thisRef.potentialsOf(target), Effects.empty)
329314
else (Cold()(pot.source).toPots, Effects.empty)
330315

@@ -347,19 +332,15 @@ object Checking {
347332

348333
case Outer(pot1, cls) =>
349334
pot1 match {
350-
case ThisRef(cls) =>
351-
assert(cls == state.thisClass, "unexpected potential " + pot.show)
352-
335+
case _: ThisRef =>
336+
// all outers for `this` are assumed to be hot
353337
Summary.empty
354338

355339
case _: Fun =>
356340
throw new Exception("Unexpected code reached")
357341

358342
case warm: Warm =>
359-
(warm.outerFor(cls), Effects.empty)
360-
361-
case _: Cold =>
362-
throw new Exception("Unexpected code reached")
343+
(warm.resolveOuter(cls), Effects.empty)
363344

364345
case _ =>
365346
val (pots, effs) = expand(pot1)

compiler/src/dotty/tools/dotc/transform/init/Effects.scala

Lines changed: 16 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,12 @@ object Effects {
1818
def show(effs: Effects)(using Context): String =
1919
effs.map(_.show).mkString(", ")
2020

21-
/** Effects that are related to safe initialization */
21+
/** Effects that are related to safe initialization performed on potentials */
2222
sealed trait Effect {
23-
def size: Int
23+
def potential: Potential
24+
2425
def show(using Context): String
26+
2527
def source: Tree
2628
}
2729

@@ -37,48 +39,42 @@ object Effects {
3739
* - the selection chain on a potential is too long
3840
*/
3941
case class Promote(potential: Potential)(val source: Tree) extends Effect {
40-
def size: Int = potential.size
41-
def show(using Context): String =
42-
potential.show + ""
42+
def show(using Context): String = potential.show + ""
4343
}
4444

4545
/** Field access, `a.f` */
4646
case class FieldAccess(potential: Potential, field: Symbol)(val source: Tree) extends Effect {
4747
assert(field != NoSymbol)
4848

49-
def size: Int = potential.size
50-
def show(using Context): String =
51-
potential.show + "." + field.name.show + "!"
49+
def show(using Context): String = potential.show + "." + field.name.show + "!"
5250
}
5351

5452
/** Method call, `a.m()` */
5553
case class MethodCall(potential: Potential, method: Symbol)(val source: Tree) extends Effect {
5654
assert(method != NoSymbol)
5755

58-
def size: Int = potential.size
5956
def show(using Context): String = potential.show + "." + method.name.show + "!"
6057
}
6158

6259
// ------------------ operations on effects ------------------
6360

6461
extension (eff: Effect) def toEffs: Effects = Effects.empty + eff
6562

66-
def asSeenFrom(eff: Effect, thisValue: Potential, currentClass: ClassSymbol, outer: Potentials)(implicit env: Env): Effects =
67-
trace(eff.show + " asSeenFrom " + thisValue.show + ", current = " + currentClass.show + ", outer = " + Potentials.show(outer), init, effs => show(effs.asInstanceOf[Effects])) { eff match {
63+
def asSeenFrom(eff: Effect, thisValue: Potential)(implicit env: Env): Effect =
64+
trace(eff.show + " asSeenFrom " + thisValue.show + ", current = " + currentClass.show, init, effs => show(effs.asInstanceOf[Effects])) { eff match {
6865
case Promote(pot) =>
69-
Potentials.asSeenFrom(pot, thisValue, currentClass, outer).promote(eff.source)
66+
val pot1 = Potentials.asSeenFrom(pot, thisValue)
67+
Promote(pot1)(eff.source)
7068

7169
case FieldAccess(pot, field) =>
72-
Potentials.asSeenFrom(pot, thisValue, currentClass, outer).map { pot =>
73-
FieldAccess(pot, field)(eff.source)
74-
}
70+
val pot1 = Potentials.asSeenFrom(pot, thisValue)
71+
FieldAccess(pot1, field)(eff.source)
7572

7673
case MethodCall(pot, sym) =>
77-
Potentials.asSeenFrom(pot, thisValue, currentClass, outer).map { pot =>
78-
MethodCall(pot, sym)(eff.source)
79-
}
74+
val pot1 = Potentials.asSeenFrom(pot, thisValue)
75+
MethodCall(pot1, sym)(eff.source)
8076
} }
8177

82-
def asSeenFrom(effs: Effects, thisValue: Potential, currentClass: ClassSymbol, outer: Potentials)(implicit env: Env): Effects =
83-
effs.flatMap(asSeenFrom(_, thisValue, currentClass, outer))
78+
def asSeenFrom(effs: Effects, thisValue: Potential)(implicit env: Env): Effects =
79+
effs.map(asSeenFrom(_, thisValue))
8480
}

compiler/src/dotty/tools/dotc/transform/init/Env.scala

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ import Effects._, Potentials._, Summary._
2020

2121
implicit def theCtx(implicit env: Env): Context = env.ctx
2222

23-
case class Env(ctx: Context, summaryCache: mutable.Map[ClassSymbol, ClassSummary]) {
23+
case class Env(ctx: Context) {
2424
private implicit def self: Env = this
2525

2626
// Methods that should be ignored in the checking
@@ -46,12 +46,25 @@ case class Env(ctx: Context, summaryCache: mutable.Map[ClassSymbol, ClassSummary
4646
sym.isPrimitiveValueClass || sym == defn.StringClass
4747
}
4848

49-
/** Summary of a method or field */
49+
/** Summary of a class */
50+
private val summaryCache = mutable.Map.empty[ClassSymbol, ClassSummary]
5051
def summaryOf(cls: ClassSymbol): ClassSummary =
5152
if (summaryCache.contains(cls)) summaryCache(cls)
5253
else trace("summary for " + cls.show, init, s => s.asInstanceOf[ClassSummary].show) {
5354
val summary = Summarization.classSummary(cls)
5455
summaryCache(cls) = summary
5556
summary
5657
}
58+
59+
/** Cache for outer this */
60+
private case class OuterKey(warm: Warm, cls: ClassSymbol)
61+
private val outerCache: mutable.Map[OuterKey, Potentials] = mutable.Map.empty
62+
def resolveOuter(warm: Warm, cls: ClassSymbol)(implicit env: Env): Potentials =
63+
val key = OuterKey(warm, cls)
64+
if (outerCache.contains(key)) outerCache(key)
65+
else {
66+
val pots = Potentials.resolveOuter(warm.classSymbol, warm.outer.toPots, cls)
67+
outerCache(key) = pots
68+
pots
69+
}
5770
}

0 commit comments

Comments
 (0)