11A capture checking variant
22==========================
33
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+ ```
1114 The aim is to have a system where we detect all leaks on formation
1215 and not some just on access. In the TOPLAS version, we cannot box {cap}
1316 but we can box with some capability {x} and then widen under the box
1417 into {cap}. This was not a problem because we cannot unbox cap so the
1518 data could not be accessed. But now we want to be stricter and already
1619 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.
1821
1922 - 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
2124 after rechecking. This has to be done selectively, so that the following are still guaranteed:
2225
2326 - No widening of function results.
@@ -32,8 +35,9 @@ A capture checking variant
3235 expected types are created by
3336
3437 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,
3639 3 . declared result types of defs for their right hand sides,
40+ 2 . declared types of vars for their initializers,
3741 4 . declared types of vars for the right hand sides of assignments to them,
3842 5 . declared types of seq literals for the elements in that seq literal,
3943
@@ -45,28 +49,25 @@ A capture checking variant
4549
4650 - Technicalities for type comparers and type maps:
4751
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 ` .
5855
5956 2 . Lubs of capture sets can now contain at the same time ` cap ` and other
6057 references.
6158
6259 3 . Avoidance maps can have undefined results. We need to tweak the part
6360 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.
6867
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).
7071
7172 - For any immutable variable ` x ` , introduce a capability ` x* ` which stands for
7273 "all capabilities reachable through ` x ` ". We have ` {x} <: {x*} <: {cap} ` .
@@ -93,13 +94,14 @@ class Ref[T](init: T):
9394 def get : T = x
9495 def set (y : T ) = { x = y }
9596```
97+ Note that type parameters no longer need (or can) be annotated with ` sealed ` .
9698
9799The following example does not work.
98100``` scala
99101def 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]
101103 while cur.nonEmpty do
102- val next : () => Unit = cur.head // error on unbox
104+ val next : () => Unit = cur.head
103105 next()
104106 cur = cur.tail
105107
@@ -109,15 +111,15 @@ def runAll(xs: List[Proc]): Unit =
109111Same with refs:
110112``` scala
111113def 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}
113115 while cur.get.nonEmpty do
114- val next : () => Unit = cur.get.head // error on unbox
116+ val next : () => Unit = cur.get.head
115117 next()
116118 cur.set(cur.get.tail: List [Proc ])
117119
118120 usingFile : f =>
119121 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}
121123```
122124
123125The following variant makes the loop typecheck, but
@@ -150,7 +152,7 @@ def runAll(xs: List[Proc]): Unit =
150152The following variant of the while loop is again invalid:
151153``` scala
152154def 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]
154156 while cur.nonEmpty do
155157 val next : () -> {cur* } Unit = cur.head: // error: cur* not wf since cur is not stable
156158 next()
@@ -166,7 +168,7 @@ But this doesn't:
166168def addOneProc (xs : List [Proc ]) =
167169 def x : Proc = () => write(" hello" )
168170 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
170172```
171173And this doesn't either, since ` Set ` is invariant:
172174``` scala
@@ -184,12 +186,12 @@ This also works:
184186def compose1 [A , B , C ](f : A => B , g : B => C ): A -> {f, g} C =
185187 z => g(f(z))
186188```
187- We can also use a widened result type for compose :
189+ But this does not :
188190``` scala
189191def 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
191193```
192- Even this should workL
194+ Even this should work:
193195``` scala
194196def mapCompose [A ](ps : List [(A => A , A => A )]): List [A -> {ps* } A ] =
195197 ps.map(compose1)
@@ -206,20 +208,26 @@ def mapCompose[A](ps: List[(A => A, A => A)]): List[A ->{ps*} A] =
206208 // (f: A ->{ps*} A, g: A ->{ps*} A) -> A ->{ps*} A
207209```
208210
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.
210218
211219Work items:
212220===========
213221
214222 - Implement x* references.
215223 - 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.
217226 - subcapturing: ` x <:< x* ` .
218227 - Narrowing code: in ` adaptBoxed ` where ` x.type ` gets widened to ` T^{x} ` , also
219228 do the covariant ` cap ` to ` x* ` replacement.
220229 - 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.
224233
225- def compose(f: A => B, g: B => C): A ->{f, g} C
0 commit comments