@@ -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 */
0 commit comments