Skip to content

Commit b4ab9fc

Browse files
committed
Return to sealed.
1 parent 2237eb7 commit b4ab9fc

File tree

1 file changed

+23
-105
lines changed

1 file changed

+23
-105
lines changed
Lines changed: 23 additions & 105 deletions
Original file line numberDiff line numberDiff line change
@@ -1,83 +1,11 @@
11
A capture checking variant
22
==========================
33

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-
```
14-
The aim is to have a system where we detect all leaks on formation
15-
and not some just on access. In the TOPLAS version, we cannot box {cap}
16-
but we can box with some capability {x} and then widen under the box
17-
into {cap}. This was not a problem because we cannot unbox cap so the
18-
data could not be accessed. But now we want to be stricter and already
19-
prevent the formation. This strictness is necessary to ensure soundness
20-
of the modified (Var) rule described below.
21-
22-
NOTE: This restrictions might make things difficult. For instance,
23-
we want to have:
24-
25-
A^ -> B <: A^{x} -> B
26-
27-
but with the rule above this no longer holds.
28-
29-
- To compensate, and allow capture polymorphism in e.g. function arguments, we allow
30-
some subcapture slack to covariant cap when comparing the types of actual and expected types
31-
after rechecking. This has to be done selectively, so that the following are still guaranteed:
32-
33-
- No widening of function results.
34-
That way, library-defined encapsulators like `usingFile` still prevent leaks
35-
on formation.
36-
- No widening in assignments to vars.
37-
That way, we cannot assign local capabilities to global vars.
38-
- No widening in results of trys.
39-
That way, local throws capabilities cannot escape.
40-
41-
- The way to achieve this is to tweak the expected type. Interesting
42-
expected types are created by
43-
44-
1. function parameter types for their arguments,
45-
2. declared types of vals for their right hand sides,
46-
3. declared result types of defs for their right hand sides,
47-
2. declared types of vars for their initializers,
48-
4. declared types of vars for the right hand sides of assignments to them,
49-
5. declared types of seq literals for the elements in that seq literal,
50-
51-
In cases (1) and (2) above we map all covariant occurrences of cap
52-
in the original expected type to a wildcard (i.e. a fluid capture set). This still treats
53-
`try` blocks correctly, since even if the expected type of a try block contains wildcards,
54-
we still need to eliminate capabilities defined in the try through avoidance, and that
55-
will not be possible since we cannot widen to cap via subtyping.
56-
57-
- Technicalities for type comparers and type maps:
58-
59-
1. Subcapturing: When applying rule (sc-var), we need to make sure that the
60-
capture set of the info of a ref does not contain `cap`. In the case where
61-
this capture set is a variable, we can use `disallowRootCapability`.
62-
63-
2. Lubs of capture sets can now contain at the same time `cap` and other
64-
references.
65-
66-
3. Avoidance maps can have undefined results. We need to tweak the part
67-
of AvoidMap that widens from a TermRef `ref` to `ref.info`, so that
68-
this is forbidden if the `ref.info` contains `cap`. This is similar
69-
to the restriction of subcapturing. It could be achieved by disallowing
70-
the root capability in a capeture set that arises from mapping a capture
71-
set through avoidance, using a handler that issues an appropriate error message.
72-
73-
- There is no longer a need for mutable variables and results of try's to be boxed.
74-
75-
- The resulting system is significantly less expressive than the TOPLAS version since
76-
we no longer support return types or variables capturing `cap`. But we make
77-
up for it through the introduction of reach capabilities (see following items).
78-
79-
- For any immutable variable `x`, introduce a capability `x*` which stands for
80-
"all capabilities reachable through `x`". We have `{x} <: {x*} <: {cap}`.
4+
- Our starting point is the currently implemented system, where encapsulation is achieved by disallowing root capabilities in
5+
the types of sealed type variables. By contrast no restrictions apply
6+
to boxing or unboxing.
7+
8+
- We now treat all type variables as sealed (so no special `sealed` modifier is necessary anymore). A type variable cannot be instantiated to a type that contains a covariant occurrence of `cap`. The same restriction applies to the types of mutable variables and try expressions.
819

8210
- We modify the VAR rule as follows:
8311

@@ -89,9 +17,8 @@ A capture checking variant
8917
(2) all covariant occurrences of cap replaced by `x*`. (1) is standard,
9018
whereas (2) is new.
9119

92-
- Why is this sound? Covariant occurrences of cap must represent capabilities
93-
that are reachable from `x`, so they are included in the meaning of `{x*}`.
94-
20+
- Why is this sound? Covariant occurrences of cap must represent capabilities that are reachable from `x`, so they are included in the meaning of `{x*}`. At the same time, encapsulation is still maintained since no covariant occurrences of cap are allowed in instance types of
21+
type variables.
9522

9623
Examples:
9724

@@ -106,27 +33,27 @@ Note that type parameters no longer need (or can) be annotated with `sealed`.
10633
The following example does not work.
10734
```scala
10835
def runAll(xs: List[Proc]): Unit =
109-
var cur: List[Proc] = xs // error: xs: List[() ->{xs} Unit], can't be widened to List[Proc]
36+
var cur: List[Proc] = xs // error: Illegal type for var
11037
while cur.nonEmpty do
11138
val next: () => Unit = cur.head
11239
next()
11340
cur = cur.tail
11441

11542
usingFile: f =>
116-
cur = ((() => f.write()): (() ->{f*} Unit)) :: Nil // error since we cannot widen {f*} to {cap} under box
43+
cur = ((() => f.write()): (() ->{f*} Unit)) :: Nil
11744
```
11845
Same with refs:
11946
```scala
12047
def runAll(xs: List[Proc]): Unit =
121-
val cur = Ref[List[Proc]](xs: List[() ->{xs*} Unit]) // error, since we cannot widen {xs*} to {cap}
48+
val cur = Ref[List[Proc]](xs: List[() ->{xs*} Unit]) // error, illegal type for type argument to Ref
12249
while cur.get.nonEmpty do
12350
val next: () => Unit = cur.get.head
12451
next()
12552
cur.set(cur.get.tail: List[Proc])
12653

12754
usingFile: f =>
12855
cur.set:
129-
(() => f.write(): () ->{f*} Unit) :: Nil // error since we cannot widen {f*} to {cap}
56+
(() => f.write(): () ->{f*} Unit) :: Nil
13057
```
13158

13259
The following variant makes the loop typecheck, but
@@ -153,31 +80,23 @@ def runAll(xs: List[Proc]): Unit =
15380
cur.set(cur.get.tail: List[() ->{xs*} Unit])
15481

15582
usingFile: f =>
156-
cur = (() => f.write(): () ->{f*} Unit) :: Nil // error since {f*} !<: {xs*}
83+
cur.set:
84+
(() => f.write(): () ->{f*} Unit) :: Nil // error since {f*} !<: {xs*}
15785
```
15886

159-
The following variant of the while loop is again invalid:
160-
```scala
161-
def runAll(xs: List[Proc]): Unit =
162-
var cur: List[Proc] = xs // error, can't widen, xs: List[() ->{xs*} Unit]
163-
while cur.nonEmpty do
164-
val next: () ->{cur*} Unit = cur.head: // error: cur* not wf since cur is not stable
165-
next()
166-
cur = cur.tail
167-
```
16887
More examples. This works:
16988
```scala
17089
def cons(x: Proc, xs: List[Proc]): List[() ->{x, xs*} Unit] =
17190
List.cons[() ->{x, xs*} Unit](x, xs)
17291
```
173-
But this doesn't:
92+
And this works as well
17493
```scala
175-
def addOneProc(xs: List[Proc]) =
94+
def addOneProc(xs: List[Proc]): List[Proc] =
17695
def x: Proc = () => write("hello")
17796
val result: List[() ->{x, xs*} Unit] = x :: xs
178-
result // error: need to avoid `x` or `result` but cannot widen to cap in function result
97+
result // OK, we can widen () ->{x, xs*} Unit to cap here.
17998
```
180-
And this doesn't either, since `Set` is invariant:
99+
This doesn't work:
181100
```scala
182101
def cons(x: Proc, xs: Set[Proc]) =
183102
Set.include[Proc](x, xs) // error: can't instantiate type parameter to Proc
@@ -193,10 +112,10 @@ This also works:
193112
def compose1[A, B, C](f: A => B, g: B => C): A ->{f, g} C =
194113
z => g(f(z))
195114
```
196-
But this does not:
115+
And this works as well:
197116
```scala
198117
def compose2[A, B, C](f: A => B, g: B => C): A => C =
199-
z => g(f(z)) // can't widen {f, g} to `cap` in function result
118+
z => g(f(z))
200119
```
201120
Even this should work:
202121
```scala
@@ -214,6 +133,7 @@ def mapCompose[A](ps: List[(A => A, A => A)]): List[A ->{ps*} A] =
214133
// The closure is widened to the non-dependent function type
215134
// (f: A ->{ps*} A, g: A ->{ps*} A) -> A ->{ps*} A
216135
```
136+
But it does no work with `compose2``, since the type variable of `map` cannot be instantiated to `A => A`.`
217137

218138
Syntax Considerations:
219139

@@ -226,15 +146,13 @@ Syntax Considerations:
226146
Work items:
227147
===========
228148

229-
- Implement x* references.
230-
- internal representation: maybe have a synthetic private member `reach` of
231-
`Any` to which `x*` maps, i.e. `x*` is `x.reach`. Advantage: maps like substitutions
149+
- Implement `x*` references.
150+
- internal representation: maybe have a synthetic private member `*` of
151+
`Any` to which `x*` maps, i.e. `x*` is `x.*`. Advantage: maps like substitutions
232152
and asSeenFrom work out of the box.
233153
- subcapturing: `x <:< x*`.
234154
- Narrowing code: in `adaptBoxed` where `x.type` gets widened to `T^{x}`, also
235155
do the covariant `cap` to `x*` replacement.
236156
- Drop local roots
237-
- Implement adaptation that maps covariant cap-sets in expected type to fluid sets.
238-
- Implement restricted subtyping.
239157
- Drop sealed scheme.
240158

0 commit comments

Comments
 (0)