Skip to content

Commit 9e0dc21

Browse files
authored
Merge pull request #11533 from natsukagami/promotion-rule-2
Init check: Early Promotion of fields
2 parents 9faabb6 + 1d2688e commit 9e0dc21

File tree

5 files changed

+150
-42
lines changed

5 files changed

+150
-42
lines changed

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

Lines changed: 108 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -264,55 +264,125 @@ object Checking {
264264
val Summary(pots, effs) = expand(pot)
265265
val effs2 = pots.map(FieldAccess(_, field)(eff.source))
266266
(effs2 ++ effs).toList.flatMap(check(_))
267+
}
268+
269+
/**
270+
* Check if we can just directly promote a potential.
271+
* A potential can be (currently) directly promoted if and only if:
272+
* - `pot == this` and all fields of this are initialized, or
273+
* - `pot == Warm(C, outer)` where `outer` can be directly promoted.
274+
*/
275+
private def canDirectlyPromote(pot: Potential, visited: Set[Potential] = Set.empty)(using state: State): Boolean = trace("checking direct promotion of " + pot.show, init) {
276+
if (state.safePromoted.contains(pot)) true
277+
// If this potential's promotion depends on itself, we cannot directly promote it.
278+
else if (visited.contains(pot)) false
279+
else pot match {
280+
case pot: ThisRef =>
281+
// If we have all fields initialized, then we can promote This to hot.
282+
val classRef = state.thisClass.info.asInstanceOf[ClassInfo].appliedRef
283+
classRef.fields.forall { denot =>
284+
val sym = denot.symbol
285+
sym.isOneOf(Flags.Lazy | Flags.Deferred) || state.fieldsInited.contains(sym)
286+
}
287+
case Warm(cls, outer) =>
288+
canDirectlyPromote(outer)
289+
case _ =>
290+
val summary = expand(pot)
291+
if (!summary.effs.isEmpty)
292+
false // max depth of expansion reached
293+
else summary.pots.forall(canDirectlyPromote(_, visited + pot))
294+
}
295+
}
296+
297+
/**
298+
* Check the Promotion of a Warm object, according to "Rule 2":
299+
*
300+
* Rule 2: Promote(pot)
301+
*
302+
* for all concrete methods `m` of D
303+
* pot.m!, Promote(pot.m)
304+
*
305+
* for all concrete fields `f` of D
306+
* Promote(pot.f)
307+
*
308+
* for all inner classes `F` of D
309+
* Warm[F, pot].init!, Promote(Warm[F, pot])
310+
*/
311+
private def checkPromoteWarm(warm: Warm, eff: Effect)(using state: State): Errors =
312+
val Warm(cls, outer) = warm
313+
val source = eff.source
314+
// Errors.empty
315+
val classRef = cls.info.asInstanceOf[ClassInfo].appliedRef
316+
// All members of class must be promotable.
317+
val buffer = new mutable.ArrayBuffer[Effect]
318+
val excludedFlags = Flags.Deferred | Flags.Private | Flags.Protected
319+
320+
classRef.fields.foreach { denot =>
321+
val f = denot.symbol
322+
if !f.isOneOf(excludedFlags) && f.hasSource then
323+
buffer += Promote(FieldReturn(warm, f)(source))(source)
324+
buffer += FieldAccess(warm, f)(source)
325+
}
326+
327+
classRef.membersBasedOnFlags(Flags.Method, Flags.Deferred).foreach { denot =>
328+
val m = denot.symbol
329+
if !m.isConstructor && m.hasSource && !theEnv.canIgnoreMethod(m) then
330+
buffer += MethodCall(warm, m)(source)
331+
buffer += Promote(MethodReturn(warm, m)(source))(source)
332+
}
267333

334+
classRef.memberClasses.foreach { denot =>
335+
val cls = denot.symbol.asClass
336+
if cls.hasSource then
337+
val potInner = Potentials.asSeenFrom(Warm(cls, ThisRef()(source))(source), warm)
338+
buffer += MethodCall(potInner, cls.primaryConstructor)(source)
339+
buffer += Promote(potInner)(source)
268340
}
269341

342+
for (eff <- buffer.toList) {
343+
val errs = check(eff)
344+
if !errs.isEmpty then
345+
return UnsafePromotion(warm, eff.source, state.path, errs.toList).toErrors
346+
}
347+
Errors.empty
270348

271349
private def checkPromote(eff: Promote)(using state: State): Errors =
272350
if (state.safePromoted.contains(eff.potential)) Errors.empty
273351
else {
274352
val pot = eff.potential
275-
val errs = pot match {
276-
case pot: ThisRef =>
277-
// If we have all fields initialized, then we can promote This to hot.
278-
val classRef = state.thisClass.info.asInstanceOf[ClassInfo].appliedRef
279-
val allFieldsInited = classRef.fields.forall { denot =>
280-
val sym = denot.symbol
281-
sym.isOneOf(Flags.Lazy | Flags.Deferred) || state.fieldsInited.contains(sym)
282-
}
283-
if (allFieldsInited)
284-
Errors.empty
285-
else
286-
PromoteThis(pot, eff.source, state.path).toErrors
287-
288-
case _: Cold =>
289-
PromoteCold(eff.source, state.path).toErrors
290-
291-
case pot @ Warm(cls, outer) =>
292-
val errors = state.test { checkPromote(Promote(outer)(eff.source)) }
293-
if (errors.isEmpty) Errors.empty
294-
else PromoteWarm(pot, eff.source, state.path).toErrors
295-
296-
case Fun(pots, effs) =>
297-
val errs1 = state.test {
298-
effs.toList.flatMap(check(_))
299-
}
300-
val errs2 = state.test {
301-
pots.toList.flatMap { pot =>
302-
checkPromote(Promote(pot)(eff.source))
353+
val errs =
354+
if canDirectlyPromote(pot) then
355+
Errors.empty
356+
else pot match {
357+
case pot: ThisRef =>
358+
PromoteThis(pot, eff.source, state.path).toErrors
359+
360+
case _: Cold =>
361+
PromoteCold(eff.source, state.path).toErrors
362+
363+
case pot @ Warm(cls, outer) =>
364+
checkPromoteWarm(pot, eff)
365+
366+
case Fun(pots, effs) =>
367+
val errs1 = state.test {
368+
effs.toList.flatMap(check(_))
369+
}
370+
val errs2 = state.test {
371+
pots.toList.flatMap { pot =>
372+
checkPromote(Promote(pot)(eff.source))
373+
}
303374
}
304-
}
305375

306-
if (errs1.nonEmpty || errs2.nonEmpty)
307-
UnsafePromotion(pot, eff.source, state.path, errs1 ++ errs2).toErrors
308-
else
309-
Errors.empty
376+
if (errs1.nonEmpty || errs2.nonEmpty)
377+
UnsafePromotion(pot, eff.source, state.path, errs1 ++ errs2).toErrors
378+
else
379+
Errors.empty
310380

311-
case pot =>
312-
val Summary(pots, effs) = expand(pot)
313-
val effs2 = pots.map(Promote(_)(eff.source))
314-
(effs2 ++ effs).toList.flatMap(check(_))
315-
}
381+
case pot =>
382+
val Summary(pots, effs) = expand(pot)
383+
val effs2 = pots.map(Promote(_)(eff.source))
384+
(effs2 ++ effs).toList.flatMap(check(_))
385+
}
316386
// If we can safely promote, then we don't need to check again
317387
if (errs.isEmpty)
318388
state.safePromoted += pot

tests/init/neg/inner1.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ class Foo {
44
val list = List(1, 2, 3) // error, as Inner access `this.list`
55

66
val inner: Inner = new this.Inner // ok, `list` is instantiated
7-
lib.escape(inner) // error
7+
lib.escape(inner) // ok, can promote inner early
88

99
val name = "good"
1010

tests/init/neg/inner17.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ class A {
55
val a = f
66
}
77

8-
println(new B) // error
8+
println(new B) // OK, can promote B early
99
}
1010

1111
class C extends A {

tests/init/neg/inner19.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,6 @@ class A {
1414

1515
class B extends A {
1616
println((new O.B).f)
17-
O.C(4) // error
18-
override val n = 50 // error
17+
O.C(4)
18+
override val n = 50 // error because line 16
1919
}

tests/init/pos/early-promote.scala

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
class Y {
2+
class X {
3+
class B {
4+
def g = f
5+
def g2 = n
6+
}
7+
val f = 42
8+
val b = new B // warm(B, X.this)
9+
}
10+
11+
val n = 10
12+
val x = new X
13+
List(x.b) // unsafe promotion
14+
15+
}
16+
17+
class A { // checking A
18+
class B {
19+
def bf = 42
20+
class C {
21+
def x = bf // uses outer[C], but never outer[B]
22+
}
23+
List((new C).x)
24+
def c = new C
25+
}
26+
val b = new B()
27+
List(b) // Direct promotion works here
28+
val af = 42
29+
}
30+
31+
class RecursiveF {
32+
val a = f
33+
def f: RecursiveF = f
34+
class B(x: Int)
35+
36+
println(new a.B(5))
37+
val n = 10
38+
}

0 commit comments

Comments
 (0)