Skip to content

Commit f67b3cd

Browse files
committed
Address review
1 parent 3c25533 commit f67b3cd

File tree

3 files changed

+95
-29
lines changed

3 files changed

+95
-29
lines changed

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,8 @@ object Errors {
9898

9999
case class CallUnknown(meth: Symbol, source: Tree, trace: Vector[Tree]) extends Error {
100100
def show(using Context): String =
101-
"Calling the external method " + meth.show + " may cause initialization errors" + "."
101+
val prefix = if meth.is(Flags.Method) then "Calling the external method " else "Accessing the external field"
102+
prefix + meth.show + " may cause initialization errors" + "."
102103
}
103104

104105
/** Promote a value under initialization to fully-initialized */

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

Lines changed: 92 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,24 @@ class Semantic {
2424

2525
/** Abstract values
2626
*
27-
* Value = Hot | Cold | Warm | ThisRef | Fun | RefSet
27+
* Value = Hot | Cold | Warm | ThisRef | Fun | RefSet
28+
*
29+
* Cold
30+
* ┌──────► ▲ ◄──┐ ◄────┐
31+
* │ │ │ │
32+
* │ │ │ │
33+
* ThisRef(C) │ │ │
34+
* ▲ │ │ │
35+
* │ Warm(D) Fun RefSet
36+
* │ ▲ ▲ ▲
37+
* │ │ │ │
38+
* Warm(C) │ │ │
39+
* ▲ │ │ │
40+
* │ │ │ │
41+
* └─────────┴──────┴───────┘
42+
* Hot
2843
*/
29-
trait Value {
44+
sealed abstract class Value {
3045
def show: String = this.toString()
3146
}
3247

@@ -36,7 +51,7 @@ class Semantic {
3651
/** An object with unknown initialization status */
3752
case object Cold extends Value
3853

39-
/** Object referred by `this` which stores abstract values for all fields
54+
/** A reference to the object under initialization pointed by `this`
4055
*/
4156
case class ThisRef(klass: ClassSymbol) extends Value
4257

@@ -55,7 +70,11 @@ class Semantic {
5570
*/
5671
case class RefSet(refs: List[Warm | Fun | ThisRef]) extends Value
5772

73+
// end of value definition
74+
5875
/** The current object under initialization
76+
*
77+
* Note: Object is NOT a value.
5978
*/
6079
case class Objekt(klass: ClassSymbol, val fields: mutable.Map[Symbol, Value]) {
6180
val promotedValues = mutable.Set.empty[Value]
@@ -147,6 +166,9 @@ class Semantic {
147166
case (Cold, _) => Cold
148167
case (_, Cold) => Cold
149168

169+
case (a: Warm, b: ThisRef) if a.klass == b.klass => b
170+
case (a: ThisRef, b: Warm) if a.klass == b.klass => a
171+
150172
case (a: (Fun | Warm | ThisRef), b: (Fun | Warm | ThisRef)) => RefSet(a :: b :: Nil)
151173

152174
case (a: (Fun | Warm | ThisRef), RefSet(refs)) => RefSet(a :: refs)
@@ -256,7 +278,7 @@ class Semantic {
256278

257279
case Fun(body, thisV, klass) =>
258280
// meth == NoSymbol for poly functions
259-
if meth.name.toString == "tupled" then Result(value, Nil)
281+
if meth.name.toString == "tupled" then Result(value, Nil) // a call like `fun.tupled`
260282
else eval(body, thisV, klass, cacheResult = true)
261283

262284
case RefSet(refs) =>
@@ -308,14 +330,23 @@ class Semantic {
308330
// ----- Promotion ----------------------------------------------------
309331

310332
extension (value: Value)
311-
def canDirectlyPromote(using Heap, Context): Boolean =
333+
/** Can we promote the value by checking the extrinsic values?
334+
*
335+
* The extrinsic values are environment values, e.g. outers for `Warm`
336+
* and `thisV` captured in functions.
337+
*
338+
* This is a fast track for early promotion of values.
339+
*/
340+
def canPromoteExtrinsic(using Heap, Context): Boolean =
312341
value match
313342
case Hot => true
314343
case Cold => false
315344

316345
case warm: Warm =>
317-
heap.promotedValues.contains(warm)
318-
|| warm.outer.canDirectlyPromote
346+
warm.outer.canPromoteExtrinsic && {
347+
heap.promotedValues += warm
348+
true
349+
}
319350

320351
case thisRef: ThisRef =>
321352
heap.promotedValues.contains(thisRef) || {
@@ -329,12 +360,15 @@ class Semantic {
329360
}
330361

331362
case fun: Fun =>
332-
heap.promotedValues.contains(fun)
363+
fun.thisV.canPromoteExtrinsic && {
364+
heap.promotedValues += fun
365+
true
366+
}
333367

334368
case RefSet(refs) =>
335-
refs.forall(_.canDirectlyPromote)
369+
refs.forall(_.canPromoteExtrinsic)
336370

337-
end canDirectlyPromote
371+
end canPromoteExtrinsic
338372

339373
/** Promotion of values to hot */
340374
def promote(msg: String, source: Tree): Contextual[List[Error]] =
@@ -344,25 +378,30 @@ class Semantic {
344378
case Cold => PromoteCold(source, trace) :: Nil
345379

346380
case thisRef: ThisRef =>
347-
if thisRef.canDirectlyPromote then Nil
381+
if heap.promotedValues.contains(thisRef) then Nil
382+
else if thisRef.canPromoteExtrinsic then Nil
348383
else PromoteThis(source, trace) :: Nil
349384

350385
case warm: Warm =>
351-
if warm.canDirectlyPromote then Nil
386+
if heap.promotedValues.contains(warm) then Nil
387+
else if warm.canPromoteExtrinsic then Nil
352388
else {
353389
heap.promotedValues += warm
354390
val errors = warm.tryPromote(msg, source)
355391
if errors.nonEmpty then heap.promotedValues -= warm
356392
errors
357393
}
358394

359-
case Fun(body, thisV, klass) =>
360-
val res = eval(body, thisV, klass)
361-
val errors2 = res.value.promote(msg, source)
362-
if (res.errors.nonEmpty || errors2.nonEmpty)
363-
UnsafePromotion(source, trace, res.errors ++ errors2) :: Nil
395+
case fun @ Fun(body, thisV, klass) =>
396+
if heap.promotedValues.contains(fun) then Nil
364397
else
365-
Nil
398+
val res = eval(body, thisV, klass)
399+
val errors2 = res.value.promote(msg, source)
400+
if (res.errors.nonEmpty || errors2.nonEmpty)
401+
UnsafePromotion(source, trace, res.errors ++ errors2) :: Nil
402+
else
403+
heap.promotedValues += fun
404+
Nil
366405

367406
case RefSet(refs) =>
368407
refs.flatMap(_.promote(msg, source))
@@ -379,7 +418,10 @@ class Semantic {
379418
* 2. for each concrete field `f` of the warm object:
380419
* promote the field value
381420
*
382-
* TODO: we need to revisit whether this is needed once make the
421+
* If the object contains nested classes as members, the checker simply
422+
* reports a warning to avoid expensive checks.
423+
*
424+
* TODO: we need to revisit whether this is needed once we make the
383425
* system more flexible in other dimentions: e.g. leak to
384426
* methods or constructors, or use ownership for creating cold data structures.
385427
*/
@@ -430,7 +472,18 @@ class Semantic {
430472

431473
/** Evaluate an expression with the given value for `this` in a given class `klass`
432474
*
433-
* This method only handles cache logic and delegates the work to `cases`.
475+
* Note that `klass` might be a super class of the object referred by `thisV`.
476+
* The parameter `klass` is needed for `this` resolution. Consider the following code:
477+
*
478+
* class A {
479+
* A.this
480+
* class B extends A { A.this }
481+
* }
482+
*
483+
* As can be seen above, the meaning of the expression `A.this` depends on where
484+
* it is located.
485+
*
486+
* This method only handles cache logic and delegates the work to `cases`.
434487
*/
435488
def eval(expr: Tree, thisV: Value, klass: ClassSymbol, cacheResult: Boolean = false): Contextual[Result] = log("evaluating " + expr.show + ", this = " + thisV.show, printer, res => res.asInstanceOf[Result].show) {
436489
val innerMap = cache.getOrElseUpdate(thisV, new EqHashMap[Tree, Value])
@@ -551,15 +604,20 @@ class Semantic {
551604
val value = Fun(ddef.rhs, obj, klass)
552605
Result(value, Nil)
553606
case _ =>
554-
??? // impossible
607+
// The reason is that we never evaluate an expression if `thisV` is
608+
// Cold. And `thisV` can never be `Fun`.
609+
report.warning("Unexpected branch reached. this = " + thisV.show, expr.srcPos)
610+
Result(Hot, Nil)
555611

556612
case PolyFun(body) =>
557613
thisV match
558614
case obj: (ThisRef | Warm) =>
559615
val value = Fun(body, obj, klass)
560616
Result(value, Nil)
561617
case _ =>
562-
??? // impossible
618+
// See the comment for the case above
619+
report.warning("Unexpected branch reached. this = " + thisV.show, expr.srcPos)
620+
Result(Hot, Nil)
563621

564622
case Block(stats, expr) =>
565623
val ress = eval(stats, thisV, klass)
@@ -723,22 +781,22 @@ class Semantic {
723781
// ignored as they are all hot
724782

725783
// follow constructor
726-
if !cls.defTree.isEmpty then
784+
if cls.hasSource then
727785
val res2 = thisV.call(ctor, superType = NoType, source)(using heap, ctx, trace.add(source))
728786
errorBuffer ++= res2.errors
729787

730788
// parents
731789
def initParent(parent: Tree) = parent match {
732-
case tree @ Block(stats, NewExpr(tref, New(tpt), ctor, argss)) =>
790+
case tree @ Block(stats, NewExpr(tref, New(tpt), ctor, argss)) => // can happen
733791
eval(stats, thisV, klass).foreach { res => errorBuffer ++= res.errors }
734792
errorBuffer ++= evalArgs(argss.flatten, thisV, klass)
735793
superCall(tref, ctor, tree)
736794

737-
case tree @ NewExpr(tref, New(tpt), ctor, argss) =>
795+
case tree @ NewExpr(tref, New(tpt), ctor, argss) => // extends A(args)
738796
errorBuffer ++= evalArgs(argss.flatten, thisV, klass)
739797
superCall(tref, ctor, tree)
740798

741-
case _ =>
799+
case _ => // extends A or extends A[T]
742800
val tref = typeRefOf(parent.tpe)
743801
superCall(tref, tref.classSymbol.primaryConstructor, parent)
744802
}
@@ -758,6 +816,13 @@ class Semantic {
758816
parents.find(_.tpe.classSymbol == mixin) match
759817
case Some(parent) => initParent(parent)
760818
case None =>
819+
// According to the language spec, if the mixin trait requires
820+
// arguments, then the class must provide arguments to it explicitly
821+
// in the parent list. That means we will encounter it in the Some
822+
// branch.
823+
//
824+
// When a trait A extends a parameterized trait B, it cannot provide
825+
// term arguments to B. That can only be done in a concrete class.
761826
val tref = typeRefOf(klass.typeRef.baseType(mixin).typeConstructor)
762827
val ctor = tref.classSymbol.primaryConstructor
763828
if ctor.exists then superCall(tref, ctor, superParent)
@@ -780,7 +845,7 @@ class Semantic {
780845
Result(thisV, errorBuffer.toList)
781846
}
782847

783-
/** Check usage of terms inside types
848+
/** Check that path in path-dependent types are initialized
784849
*
785850
* This is intended to avoid type soundness issues in Dotty.
786851
*/

tests/init/neg/early-promote.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ class A { // checking A
2424
def c = new C
2525
}
2626
val b = new B()
27-
List(b) // error: no promotion for objects that contain inner classes
27+
List(b) // error: the checker simply issue warnings for objects that contain inner classes
2828
val af = 42
2929
}
3030

0 commit comments

Comments
 (0)