Skip to content

Commit d3357c0

Browse files
committed
Support region annotation
1 parent 116b88c commit d3357c0

File tree

5 files changed

+70
-4
lines changed

5 files changed

+70
-4
lines changed

compiler/src/dotty/tools/dotc/core/Definitions.scala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -994,6 +994,7 @@ class Definitions {
994994
@tu lazy val ImplicitNotFoundAnnot: ClassSymbol = requiredClass("scala.annotation.implicitNotFound")
995995
@tu lazy val InitWidenAnnot: ClassSymbol = requiredClass("scala.annotation.init.widen")
996996
@tu lazy val InitExposeAnnot: ClassSymbol = requiredClass("scala.annotation.init.expose")
997+
@tu lazy val InitRegionAnnot: ClassSymbol = requiredClass("scala.annotation.init.region")
997998
@tu lazy val InlineParamAnnot: ClassSymbol = requiredClass("scala.annotation.internal.InlineParam")
998999
@tu lazy val ErasedParamAnnot: ClassSymbol = requiredClass("scala.annotation.internal.ErasedParam")
9991000
@tu lazy val InvariantBetweenAnnot: ClassSymbol = requiredClass("scala.annotation.internal.InvariantBetween")

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

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,8 @@ object Objects:
8282
protected val vars: mutable.Map[Symbol, Heap.Addr] = varsMap
8383
protected val outers: mutable.Map[ClassSymbol, Value] = outersMap
8484

85+
def isObjectRef: Boolean = this.isInstanceOf[ObjectRef]
86+
8587
def klass: ClassSymbol
8688

8789
def valValue(sym: Symbol): Value = vals(sym)
@@ -479,6 +481,7 @@ object Objects:
479481
val empty: Data = Nil
480482
def extend(pos: SourcePosition)(using data: Data): Data = pos :: data
481483
def exists(pos: SourcePosition)(using data: Data): Boolean = data.indexOf(pos) >= 0
484+
def show(using data: Data, ctx: Context): String = data.map(_.show).mkString("[", ", ", "]")
482485

483486
inline def cache(using c: Cache.Data): Cache.Data = c
484487

@@ -663,11 +666,17 @@ object Objects:
663666
else
664667
errorReadOtherStaticObject(State.currentObject, addr.owner)
665668
Bottom
669+
else if ref.isObjectRef then
670+
report.warning("Access uninitialized field " + field.show + ". Call trace: " + Trace.show, Trace.position)
671+
Bottom
666672
else
667673
// initialization error, reported by the initialization checker
668674
Bottom
669675
else if ref.hasVal(target) then
670676
ref.valValue(target)
677+
else if ref.isObjectRef then
678+
report.warning("Access uninitialized field " + field.show + ". Call trace: " + Trace.show, Trace.position)
679+
Bottom
671680
else
672681
// initialization error, reported by the initialization checker
673682
Bottom
@@ -865,7 +874,7 @@ object Objects:
865874
* @param klass The enclosing class where the expression is located.
866875
* @param cacheResult It is used to reduce the size of the cache.
867876
*/
868-
def eval(expr: Tree, thisV: Value, klass: ClassSymbol, cacheResult: Boolean = false): Contextual[Value] = log("evaluating " + expr.show + ", this = " + thisV.show + " in " + klass.show, printer, (_: Value).show) {
877+
def eval(expr: Tree, thisV: Value, klass: ClassSymbol, cacheResult: Boolean = false): Contextual[Value] = log("evaluating " + expr.show + ", this = " + thisV.show + ", regions = " + Regions.show + " in " + klass.show, printer, (_: Value).show) {
869878
cache.cachedEval(thisV, expr, cacheResult) { expr => cases(expr, thisV, klass) }
870879
}
871880

@@ -956,6 +965,14 @@ object Objects:
956965
case Typed(expr, tpt) =>
957966
if tpt.tpe.hasAnnotation(defn.UncheckedAnnot) then
958967
Bottom
968+
else if tpt.tpe.hasAnnotation(defn.InitRegionAnnot) then
969+
val regions2 = Regions.extend(tpt.sourcePos)
970+
if Regions.exists(tpt.sourcePos) then
971+
report.warning("Cyclic region detected. Trace: " + Trace.show, tpt.sourcePos)
972+
Bottom
973+
else
974+
given Regions.Data = regions2
975+
eval(expr, thisV, klass)
959976
else
960977
eval(expr, thisV, klass)
961978

@@ -997,8 +1014,10 @@ object Objects:
9971014
evalExprs(cond :: thenp :: elsep :: Nil, thisV, klass).join
9981015

9991016
case Annotated(arg, annot) =>
1000-
if (expr.tpe.hasAnnotation(defn.UncheckedAnnot)) Bottom
1001-
else eval(arg, thisV, klass)
1017+
if expr.tpe.hasAnnotation(defn.UncheckedAnnot) then
1018+
Bottom
1019+
else
1020+
eval(arg, thisV, klass)
10021021

10031022
case Match(selector, cases) =>
10041023
eval(selector, thisV, klass)

library/src/scala/annotation/init.scala

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ object init:
2424
* def squre(): Int = x*x
2525
*
2626
* object B:
27-
* val a = build(new A(10): @init.widen(1)) // <-- usage
27+
* val a = build(new A(10): @init.expose) // <-- usage
2828
*
2929
* def build(o: A) = new A(o.square()) // calling methods on parameter
3030
*
@@ -36,3 +36,27 @@ object init:
3636
* It is semantically equivalent to `@init.widen(1)`.
3737
*/
3838
final class expose extends StaticAnnotation
39+
40+
/** Mark a region context.
41+
*
42+
* The same mutable field of objects in the same region have the same shape. The concept of regions is an
43+
* attempt to make context-sensitivity explainable and customizable.
44+
*
45+
* Example:
46+
*
47+
* trait B { def foo(): Int }
48+
* class C(var x: Int) extends B { def foo(): Int = 20 }
49+
* class D(var y: Int) extends B { def foo(): Int = A.m }
50+
* class Box(var value: B)
51+
*
52+
* object A:
53+
* val box1: Box = new Box(new C(5)): @init.region
54+
* val box2: Box = new Box(new D(10)): @init.region
55+
* val m: Int = box1.value.foo()
56+
*
57+
* In the above, without the two region annotation, the two objects `box1` and `box2` are of the same region.
58+
* Therefore, the field `box1.value` and `box2.value` points to both instances of `C` and `D`. Consequently,
59+
* the method call `box1.value.foo()` will be invalid, because it reaches `A.m`, which is not yet initialized.
60+
* The explicit context annotation solves the problem.
61+
*/
62+
final class region extends StaticAnnotation

tests/init/neg/global-region1.scala

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import scala.annotation.init
2+
3+
trait B { def foo(): Int }
4+
class C(var x: Int) extends B { def foo(): Int = 20 }
5+
class D(var y: Int) extends B { def foo(): Int = A.m }
6+
class Box(var value: B)
7+
8+
object A:
9+
val box1: Box = new Box(new C(5): @init.expose)
10+
val box2: Box = new Box(new D(10): @init.expose)
11+
val m: Int = box1.value.foo() // error

tests/init/pos/global-region1.scala

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import scala.annotation.init
2+
3+
trait B { def foo(): Int }
4+
class C(var x: Int) extends B { def foo(): Int = 20 }
5+
class D(var y: Int) extends B { def foo(): Int = A.m }
6+
class Box(var value: B)
7+
8+
object A:
9+
val box1: Box = new Box(new C(5): @init.expose): @init.region
10+
val box2: Box = new Box(new D(10): @init.expose): @init.region
11+
val m: Int = box1.value.foo() // ok

0 commit comments

Comments
 (0)