Skip to content

Commit a8fc57d

Browse files
committed
Variant: Don't allow to widen to cap anywhere
1 parent db1c784 commit a8fc57d

File tree

1 file changed

+49
-41
lines changed

1 file changed

+49
-41
lines changed

docs/_docs/internals/cc/alternatives-to-sealed.md

Lines changed: 49 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,26 @@
11
A 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

9799
The following example does not work.
98100
```scala
99101
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]
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 =
109111
Same with refs:
110112
```scala
111113
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}
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

123125
The following variant makes the loop typecheck, but
@@ -150,7 +152,7 @@ def runAll(xs: List[Proc]): Unit =
150152
The following variant of the while loop is again invalid:
151153
```scala
152154
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]
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:
166168
def 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
```
171173
And this doesn't either, since `Set` is invariant:
172174
```scala
@@ -184,12 +186,12 @@ This also works:
184186
def 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
189191
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
191193
```
192-
Even this should workL
194+
Even this should work:
193195
```scala
194196
def 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

211219
Work 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

Comments
 (0)