@@ -24,9 +24,24 @@ class Semantic {
24
24
25
25
/** Abstract values
26
26
*
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
28
43
*/
29
- trait Value {
44
+ sealed abstract class Value {
30
45
def show : String = this .toString()
31
46
}
32
47
@@ -36,7 +51,7 @@ class Semantic {
36
51
/** An object with unknown initialization status */
37
52
case object Cold extends Value
38
53
39
- /** Object referred by `this` which stores abstract values for all fields
54
+ /** A reference to the object under initialization pointed by `this`
40
55
*/
41
56
case class ThisRef (klass : ClassSymbol ) extends Value
42
57
@@ -55,7 +70,11 @@ class Semantic {
55
70
*/
56
71
case class RefSet (refs : List [Warm | Fun | ThisRef ]) extends Value
57
72
73
+ // end of value definition
74
+
58
75
/** The current object under initialization
76
+ *
77
+ * Note: Object is NOT a value.
59
78
*/
60
79
case class Objekt (klass : ClassSymbol , val fields : mutable.Map [Symbol , Value ]) {
61
80
val promotedValues = mutable.Set .empty[Value ]
@@ -147,6 +166,9 @@ class Semantic {
147
166
case (Cold , _) => Cold
148
167
case (_, Cold ) => Cold
149
168
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
+
150
172
case (a : (Fun | Warm | ThisRef ), b : (Fun | Warm | ThisRef )) => RefSet (a :: b :: Nil )
151
173
152
174
case (a : (Fun | Warm | ThisRef ), RefSet (refs)) => RefSet (a :: refs)
@@ -256,7 +278,7 @@ class Semantic {
256
278
257
279
case Fun (body, thisV, klass) =>
258
280
// 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`
260
282
else eval(body, thisV, klass, cacheResult = true )
261
283
262
284
case RefSet (refs) =>
@@ -308,14 +330,23 @@ class Semantic {
308
330
// ----- Promotion ----------------------------------------------------
309
331
310
332
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 =
312
341
value match
313
342
case Hot => true
314
343
case Cold => false
315
344
316
345
case warm : Warm =>
317
- heap.promotedValues.contains(warm)
318
- || warm.outer.canDirectlyPromote
346
+ warm.outer.canPromoteExtrinsic && {
347
+ heap.promotedValues += warm
348
+ true
349
+ }
319
350
320
351
case thisRef : ThisRef =>
321
352
heap.promotedValues.contains(thisRef) || {
@@ -329,12 +360,15 @@ class Semantic {
329
360
}
330
361
331
362
case fun : Fun =>
332
- heap.promotedValues.contains(fun)
363
+ fun.thisV.canPromoteExtrinsic && {
364
+ heap.promotedValues += fun
365
+ true
366
+ }
333
367
334
368
case RefSet (refs) =>
335
- refs.forall(_.canDirectlyPromote )
369
+ refs.forall(_.canPromoteExtrinsic )
336
370
337
- end canDirectlyPromote
371
+ end canPromoteExtrinsic
338
372
339
373
/** Promotion of values to hot */
340
374
def promote (msg : String , source : Tree ): Contextual [List [Error ]] =
@@ -344,25 +378,30 @@ class Semantic {
344
378
case Cold => PromoteCold (source, trace) :: Nil
345
379
346
380
case thisRef : ThisRef =>
347
- if thisRef.canDirectlyPromote then Nil
381
+ if heap.promotedValues.contains(thisRef) then Nil
382
+ else if thisRef.canPromoteExtrinsic then Nil
348
383
else PromoteThis (source, trace) :: Nil
349
384
350
385
case warm : Warm =>
351
- if warm.canDirectlyPromote then Nil
386
+ if heap.promotedValues.contains(warm) then Nil
387
+ else if warm.canPromoteExtrinsic then Nil
352
388
else {
353
389
heap.promotedValues += warm
354
390
val errors = warm.tryPromote(msg, source)
355
391
if errors.nonEmpty then heap.promotedValues -= warm
356
392
errors
357
393
}
358
394
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
364
397
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
366
405
367
406
case RefSet (refs) =>
368
407
refs.flatMap(_.promote(msg, source))
@@ -379,7 +418,10 @@ class Semantic {
379
418
* 2. for each concrete field `f` of the warm object:
380
419
* promote the field value
381
420
*
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
383
425
* system more flexible in other dimentions: e.g. leak to
384
426
* methods or constructors, or use ownership for creating cold data structures.
385
427
*/
@@ -430,7 +472,18 @@ class Semantic {
430
472
431
473
/** Evaluate an expression with the given value for `this` in a given class `klass`
432
474
*
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`.
434
487
*/
435
488
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) {
436
489
val innerMap = cache.getOrElseUpdate(thisV, new EqHashMap [Tree , Value ])
@@ -551,15 +604,20 @@ class Semantic {
551
604
val value = Fun (ddef.rhs, obj, klass)
552
605
Result (value, Nil )
553
606
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 )
555
611
556
612
case PolyFun (body) =>
557
613
thisV match
558
614
case obj : (ThisRef | Warm ) =>
559
615
val value = Fun (body, obj, klass)
560
616
Result (value, Nil )
561
617
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 )
563
621
564
622
case Block (stats, expr) =>
565
623
val ress = eval(stats, thisV, klass)
@@ -723,22 +781,22 @@ class Semantic {
723
781
// ignored as they are all hot
724
782
725
783
// follow constructor
726
- if ! cls.defTree.isEmpty then
784
+ if cls.hasSource then
727
785
val res2 = thisV.call(ctor, superType = NoType , source)(using heap, ctx, trace.add(source))
728
786
errorBuffer ++= res2.errors
729
787
730
788
// parents
731
789
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
733
791
eval(stats, thisV, klass).foreach { res => errorBuffer ++= res.errors }
734
792
errorBuffer ++= evalArgs(argss.flatten, thisV, klass)
735
793
superCall(tref, ctor, tree)
736
794
737
- case tree @ NewExpr (tref, New (tpt), ctor, argss) =>
795
+ case tree @ NewExpr (tref, New (tpt), ctor, argss) => // extends A(args)
738
796
errorBuffer ++= evalArgs(argss.flatten, thisV, klass)
739
797
superCall(tref, ctor, tree)
740
798
741
- case _ =>
799
+ case _ => // extends A or extends A[T]
742
800
val tref = typeRefOf(parent.tpe)
743
801
superCall(tref, tref.classSymbol.primaryConstructor, parent)
744
802
}
@@ -758,6 +816,13 @@ class Semantic {
758
816
parents.find(_.tpe.classSymbol == mixin) match
759
817
case Some (parent) => initParent(parent)
760
818
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.
761
826
val tref = typeRefOf(klass.typeRef.baseType(mixin).typeConstructor)
762
827
val ctor = tref.classSymbol.primaryConstructor
763
828
if ctor.exists then superCall(tref, ctor, superParent)
@@ -780,7 +845,7 @@ class Semantic {
780
845
Result (thisV, errorBuffer.toList)
781
846
}
782
847
783
- /** Check usage of terms inside types
848
+ /** Check that path in path-dependent types are initialized
784
849
*
785
850
* This is intended to avoid type soundness issues in Dotty.
786
851
*/
0 commit comments