Skip to content

Commit 5fc747a

Browse files
committed
Support lazy vals under separation checking
Initializers are be treated as read-only methods and thus are forbidden from invoking update methods.
1 parent 954fe07 commit 5fc747a

File tree

2 files changed

+109
-0
lines changed

2 files changed

+109
-0
lines changed

compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -746,6 +746,15 @@ class CheckCaptures extends Recheck, SymTransformer:
746746
checkUpdate(qualType, tree.srcPos):
747747
i"Cannot call update ${tree.symbol} of ${qualType.showRef}"
748748

749+
// Additionally, lazy val initializers should not call update methods
750+
if curEnv.owner.is(Lazy) then
751+
qualType.captureSet.elems.foreach: elem =>
752+
if elem.isExclusive then
753+
report.error(
754+
em"""Lazy val initializer calls update method on exclusive capability $elem;
755+
|lazy val initializers should only access capabilities in a read-only fashion.""",
756+
tree.srcPos)
757+
749758
// If selecting a lazy val member, charge the qualifier since accessing
750759
// the lazy val can trigger initialization that uses the qualifier's capabilities
751760
if tree.symbol.is(Lazy) then
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
import language.experimental.captureChecking
2+
import language.experimental.separationChecking
3+
import caps.*
4+
5+
class Ref(x: Int) extends Mutable:
6+
private var value: Int = x
7+
def get(): Int = value
8+
update def set(newValue: Int): Unit = value = newValue
9+
10+
// For testing types other than functions
11+
class Wrapper(val ref: Ref^) extends Mutable:
12+
def compute(): Int = ref.get()
13+
update def mutate(x: Int): Unit = ref.set(x)
14+
15+
class WrapperRd(val ref: Ref^{cap.rd}):
16+
def compute(): Int = ref.get()
17+
18+
@main def run =
19+
val r: Ref^ = Ref(0)
20+
val r2: Ref^ = Ref(42)
21+
22+
// Test case 1: Read-only access in initializer - should be OK
23+
lazy val lazyVal: () ->{r.rd} Int =
24+
val current = r2.get()
25+
() => r.get() + current
26+
27+
// Test case 2: Exclusive access in initializer - should error
28+
lazy val lazyVal2: () ->{r.rd} Int =
29+
val current = r2.get()
30+
r.set(current * 100) // error - exclusive access in initializer
31+
() => r.get()
32+
33+
// Test case 3: Exclusive access in returned closure - should be OK
34+
lazy val lazyVal3: () ->{r} Int =
35+
val current = r2.get()
36+
() => r.set(current); r.get() // exclusive access in closure, not initializer
37+
38+
// Test case 4: Multiple nested blocks with exclusive access - should error
39+
lazy val lazyVal4: () ->{r.rd} Int =
40+
val x = {
41+
val y = {
42+
r.set(100) // error
43+
r.get()
44+
}
45+
y + 1
46+
}
47+
() => r.get() + x
48+
49+
// Test case 5: Multiple nested blocks with only read access - should be OK
50+
lazy val lazyVal5: () ->{r.rd} Int =
51+
val x = {
52+
val y = {
53+
r.get() // only read access in nested blocks
54+
}
55+
y + 1
56+
}
57+
() => r.get() + x
58+
59+
// Test case 6: WrapperRd type - exclusive access in initializer - should error
60+
lazy val lazyVal6: WrapperRd^{r} =
61+
r.set(200) // error
62+
WrapperRd(r)
63+
64+
// Test case 7: WrapperRd type - non-exclusive access in initializer - should be ok
65+
lazy val lazyVal7: WrapperRd^{r} =
66+
r.get()
67+
WrapperRd(r)
68+
69+
// Test case 8: Wrapper type - exclusive access in initializer - should error
70+
lazy val lazyVal8: Wrapper^{cap, r} =
71+
r.set(200) // error
72+
Wrapper(r)
73+
74+
// Test case 9: Wrapper type - non-exclusive access in initializer - should be ok
75+
lazy val lazyVal9: Wrapper^{cap, r} =
76+
r.get()
77+
Wrapper(r)
78+
79+
// Test case 10: Conditional with exclusive access - should error
80+
lazy val lazyVal10: WrapperRd^{r} =
81+
val x = if r.get() > 0 then
82+
r.set(0) // error exclusive access in conditional
83+
1
84+
else 2
85+
WrapperRd(r)
86+
87+
// Test case 11: Try-catch with exclusive access - should error
88+
lazy val lazyVal11: () ->{r.rd} Int =
89+
val x = try
90+
r.set(42) // error
91+
r.get()
92+
catch
93+
case _: Exception => 0
94+
() => r.get() + x
95+
96+
// // Test case 12: Local exclusive access - should be OK
97+
// lazy val lazyVal12: () => Int =
98+
// val r3: Ref^ = Ref(10)
99+
// r3.set(100) FIXME should work
100+
// () => r3.get()

0 commit comments

Comments
 (0)