1
1
A capture checking variant
2
2
==========================
3
3
4
- - We use a stricter form of the TOPLAS model where
5
- - we can't unbox and box from and into S^{cap} (as in TOPLAS), and
6
- - we don't allow subtyping to cap under box. I.e.
7
- ```
8
- Box S1 ^ C1 <: Box S2 ^ C2
9
- if S1 <: S2, C1 <: C2, C2 != {cap}
10
- ```
4
+ - We separate encapsulation from boxing. Instead, similar to reachability types,
5
+ we don't allow widening to ` cap ` in subcapturing. The subcapturing rule (sc-var)
6
+ is revised as follows:
7
+
8
+ ```
9
+ x: S^C in E E |- C <: C' cap notin C
10
+ -------------------------------------------
11
+ E |- {x} <: C'
12
+
13
+ ```
11
14
The aim is to have a system where we detect all leaks on formation
12
15
and not some just on access. In the TOPLAS version, we cannot box {cap}
13
16
but we can box with some capability {x} and then widen under the box
14
17
into {cap}. This was not a problem because we cannot unbox cap so the
15
18
data could not be accessed. But now we want to be stricter and already
16
19
prevent the formation. This strictness is necessary to ensure soundness
17
- of the `cap -> x*` conversion described below.
20
+ of the modified (Var) rule described below.
18
21
19
22
- To compensate, and allow capture polymorphism in e.g. function arguments, we allow
20
- some subcapture slack to cap under box when comparing the types of actual and expected types
23
+ some subcapture slack to covariant cap when comparing the types of actual and expected types
21
24
after rechecking. This has to be done selectively, so that the following are still guaranteed:
22
25
23
26
- No widening of function results.
@@ -32,8 +35,9 @@ A capture checking variant
32
35
expected types are created by
33
36
34
37
1 . function parameter types for their arguments,
35
- 2. declared types of vals or vars for their right hand sides,
38
+ 2 . declared types of vals for their right hand sides,
36
39
3 . declared result types of defs for their right hand sides,
40
+ 2 . declared types of vars for their initializers,
37
41
4 . declared types of vars for the right hand sides of assignments to them,
38
42
5 . declared types of seq literals for the elements in that seq literal,
39
43
@@ -45,28 +49,25 @@ A capture checking variant
45
49
46
50
- Technicalities for type comparers and type maps:
47
51
48
- 1. Subcapturing: We need to thread through the subset propagation logic whether
49
- the elements to add to a capture set come from a boxed set. Maybe it's best
50
- for this if the isBoxed flag was moved from CapturingTypes to CaptureSets?
51
- Or, alternativelty, pass box status to subCaptures as an additional parameter,
52
- but then we also have to deal with uses of subCapturing in, say,
53
- set union or intersection. The knowledge that an element comes from a
54
- boxed set has to be propagated through maps. I.e. if r comes from a boxed
55
- set, we also assume f(r) comes from a boxed set. Then, the `x.subsumes(y)`
56
- test needs to know whether `y` comes from a boxed set. All this looks
57
- rather complicated.
52
+ 1 . Subcapturing: When applying rule (sc-var), we need to make sure that the
53
+ capture set of the info of a ref does not contain ` cap ` . In the case where
54
+ this capture set is a variable, we can use ` disallowRootCapability ` .
58
55
59
56
2 . Lubs of capture sets can now contain at the same time ` cap ` and other
60
57
references.
61
58
62
59
3 . Avoidance maps can have undefined results. We need to tweak the part
63
60
of AvoidMap that widens from a TermRef ` ref ` to ` ref.info ` , so that
64
- this is forbidden if the widening appears in a boxed capture set.
65
- This could be achieved by disallowing the root capability in a capture
66
- set that arises from mapping a boxed capture set through avoidance, using
67
- a handler that issues an appropriate error message.
61
+ this is forbidden if the ` ref.info ` contains ` cap ` . This is similar
62
+ to the restriction of subcapturing. It could be achieved by disallowing
63
+ the root capability in a capeture set that arises from mapping a capture
64
+ set through avoidance, using a handler that issues an appropriate error message.
65
+
66
+ - There is no longer a need for mutable variables and results of try's to be boxed.
68
67
69
- - As in the TOPLAS paper, mutable variables and results of try are implicitly boxed.
68
+ - The resulting system is significantly less expressive than the TOPLAS version since
69
+ we no longer support return types or variables capturing ` cap ` . But we make
70
+ up for it through the introduction of reach capabilities (see following items).
70
71
71
72
- For any immutable variable ` x ` , introduce a capability ` x* ` which stands for
72
73
"all capabilities reachable through ` x ` ". We have ` {x} <: {x*} <: {cap} ` .
@@ -93,13 +94,14 @@ class Ref[T](init: T):
93
94
def get : T = x
94
95
def set (y : T ) = { x = y }
95
96
```
97
+ Note that type parameters no longer need (or can) be annotated with ` sealed ` .
96
98
97
99
The following example does not work.
98
100
``` scala
99
101
def runAll (xs : List [Proc ]): Unit =
100
- var cur : List [() => Unit ] = xs
102
+ var cur : List [Proc ] = xs // error: xs: List[ () ->{xs} Unit], can't be widened to List[Proc]
101
103
while cur.nonEmpty do
102
- val next : () => Unit = cur.head // error on unbox
104
+ val next : () => Unit = cur.head
103
105
next()
104
106
cur = cur.tail
105
107
@@ -109,15 +111,15 @@ def runAll(xs: List[Proc]): Unit =
109
111
Same with refs:
110
112
``` scala
111
113
def runAll (xs : List [Proc ]): Unit =
112
- val cur = Ref [List [Proc ]](xs : List [() -> {xs* } Unit ]) // error on box
114
+ val cur = Ref [List [Proc ]](xs : List [() -> {xs* } Unit ]) // error, since we cannot widen {xs*} to {cap}
113
115
while cur.get.nonEmpty do
114
- val next : () => Unit = cur.get.head // error on unbox
116
+ val next : () => Unit = cur.get.head
115
117
next()
116
118
cur.set(cur.get.tail: List [Proc ])
117
119
118
120
usingFile : f =>
119
121
cur.set:
120
- (() => f.write(): () -> {f* } Unit ) :: Nil // error since we cannot widen {f*} to {cap} under box
122
+ (() => f.write(): () -> {f* } Unit ) :: Nil // error since we cannot widen {f*} to {cap}
121
123
```
122
124
123
125
The following variant makes the loop typecheck, but
@@ -150,7 +152,7 @@ def runAll(xs: List[Proc]): Unit =
150
152
The following variant of the while loop is again invalid:
151
153
``` scala
152
154
def runAll (xs : List [Proc ]): Unit =
153
- var cur : List [Proc ] = xs // error, can't widen under box , xs: List[() ->{xs*} Unit]
155
+ var cur : List [Proc ] = xs // error, can't widen, xs: List[() ->{xs*} Unit]
154
156
while cur.nonEmpty do
155
157
val next : () -> {cur* } Unit = cur.head: // error: cur* not wf since cur is not stable
156
158
next()
@@ -166,7 +168,7 @@ But this doesn't:
166
168
def addOneProc (xs : List [Proc ]) =
167
169
def x : Proc = () => write(" hello" )
168
170
val result : List [() -> {x, xs* } Unit ] = x :: xs
169
- result // error: can't widen to cap under box in function result
171
+ result // error: need to avoid `x` or `result` but cannot widen to cap in function result
170
172
```
171
173
And this doesn't either, since ` Set ` is invariant:
172
174
``` scala
@@ -184,12 +186,12 @@ This also works:
184
186
def compose1 [A , B , C ](f : A => B , g : B => C ): A -> {f, g} C =
185
187
z => g(f(z))
186
188
```
187
- We can also use a widened result type for compose :
189
+ But this does not :
188
190
``` scala
189
191
def compose2 [A , B , C ](f : A => B , g : B => C ): A => C =
190
- z => g(f(z))
192
+ z => g(f(z)) // can't widen {f, g} to `cap` in function result
191
193
```
192
- Even this should workL
194
+ Even this should work:
193
195
``` scala
194
196
def mapCompose [A ](ps : List [(A => A , A => A )]): List [A -> {ps* } A ] =
195
197
ps.map(compose1)
@@ -206,20 +208,26 @@ def mapCompose[A](ps: List[(A => A, A => A)]): List[A ->{ps*} A] =
206
208
// (f: A ->{ps*} A, g: A ->{ps*} A) -> A ->{ps*} A
207
209
```
208
210
209
- But this would not work with ` compose2 ` instead of ` compose1 ` .
211
+ Syntax Considerations:
212
+
213
+ - ` x* ` is short and has the right connotations. For the spread operator, ` xs* ` means
214
+ _ everything contained in x_ . Likewise ` x* ` in capture sets would mean all capabilities
215
+ reachable through ` x ` .
216
+ - But then we have capabilities that are not values, undermining the OCap model a bit.
217
+ On the other hand, even if we make ` x* ` values then these would have to be erased in any case.
210
218
211
219
Work items:
212
220
===========
213
221
214
222
- Implement x* references.
215
223
- internal representation: maybe have a synthetic private member ` reach ` of
216
- ` Any ` to which ` x* ` maps.
224
+ ` Any ` to which ` x* ` maps, i.e. ` x* ` is ` x.reach ` . Advantage: maps like substitutions
225
+ and asSeenFrom work out of the box.
217
226
- subcapturing: ` x <:< x* ` .
218
227
- Narrowing code: in ` adaptBoxed ` where ` x.type ` gets widened to ` T^{x} ` , also
219
228
do the covariant ` cap ` to ` x* ` replacement.
220
229
- Drop local roots
221
- - Implement restricted subtyping
222
- - Implement adaptation that widens under box
223
- - Drop sealed scheme
230
+ - Implement adaptation that maps covariant cap-sets in expected type to fluid sets.
231
+ - Implement restricted subtyping.
232
+ - Drop sealed scheme.
224
233
225
- def compose(f: A => B, g: B => C): A ->{f, g} C
0 commit comments