Skip to content

Commit c0505d0

Browse files
committed
Don't automatically add cap when creating instances of capability classes
To make up for this, refine the scheme to determine an implied fresh cap from the class fields.
1 parent 0b65d39 commit c0505d0

File tree

14 files changed

+80
-78
lines changed

14 files changed

+80
-78
lines changed

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

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -961,7 +961,6 @@ object Capabilities:
961961
case UnapplyInstance(info: MethodType)
962962
case LocalInstance(restpe: Type)
963963
case NewInstance(tp: Type)
964-
case NewCapability(tp: Type)
965964
case LambdaExpected(respt: Type)
966965
case LambdaActual(restp: Type)
967966
case OverriddenType(member: Symbol)
@@ -992,9 +991,6 @@ object Capabilities:
992991
i" when instantiating expected result type $restpe of function literal"
993992
case NewInstance(tp) =>
994993
i" when constructing instance $tp"
995-
case NewCapability(tp) =>
996-
val kind = if tp.derivesFromMutable then "mutable" else "Capability instance"
997-
i" when constructing $kind $tp"
998994
case LambdaExpected(respt) =>
999995
i" when instantiating expected result type $respt of lambda"
1000996
case LambdaActual(restp: Type) =>

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

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -881,16 +881,12 @@ class CheckCaptures extends Recheck, SymTransformer:
881881
* annotations avoid problematic intersections of capture sets when those
882882
* parameters are selected.
883883
*
884-
* Second half: union of initial capture set and all capture sets of arguments
885-
* to tracked parameters. The initial capture set `initCs` is augmented with
886-
* a fresh cap if `core` extends Capability.
884+
* Second half: union of initial capture set, all capture sets of arguments
885+
* to tracked parameters, and the capture set implied by the fields of the class.
887886
*/
888887
def addParamArgRefinements(core: Type, initCs: CaptureSet): (Type, CaptureSet) =
889888
var refined: Type = core
890-
var allCaptures: CaptureSet =
891-
if core.derivesFromCapability
892-
then initCs ++ FreshCap(Origin.NewCapability(core)).singletonCaptureSet
893-
else initCs ++ impliedByFields(core)
889+
var allCaptures: CaptureSet = initCs ++ impliedByFields(core)
894890
for (getterName, argType) <- mt.paramNames.lazyZip(argTypes) do
895891
val getter = cls.info.member(getterName).suchThat(_.isRefiningParamAccessor).symbol
896892
if !getter.is(Private) && getter.hasTrackedParts then
@@ -917,28 +913,33 @@ class CheckCaptures extends Recheck, SymTransformer:
917913
/** The additional capture set implied by the capture sets of its fields. This
918914
* is either empty or, if some fields have a terminal capability in their span
919915
* capture sets, it consists of a single fresh cap that subsumes all these terminal
920-
* capabiltities. Class parameters are not counted.
916+
* capabiltities. Class parameters are not counted. If the type is a mutable type,
917+
* we add a fresh cap in any case -- this is because we can currently hide
918+
* mutability in array vals, an example is neg-customargs/captures/matrix.scala.
921919
*/
922920
def impliedByFields(core: Type): CaptureSet =
923921
var infos: List[String] = Nil
924922
def pushInfo(msg: => String) =
925923
if ctx.settings.YccVerbose.value then infos = msg :: infos
926924

927925
/** The classifiers of the fresh caps in the span capture sets of all fields
928-
* in the given class `cls`.
926+
* in the given class `cls`. Mutable types get at least a fresh classified
927+
* as mutable.
929928
*/
930929
def impliedClassifiers(cls: Symbol): List[ClassSymbol] = cls match
931930
case cls: ClassSymbol =>
932-
val fieldClassifiers =
931+
val fields = cls.info.decls.toList
932+
var fieldClassifiers =
933933
for
934-
sym <- cls.info.decls.toList
935-
if contributesFreshToClass(sym)
934+
sym <- fields if contributesFreshToClass(sym)
936935
case fresh: FreshCap <- sym.info.spanCaptureSet.elems
937936
.filter(_.isTerminalCapability)
938937
.map(_.stripReadOnly)
939938
.toList
940939
_ = pushInfo(i"Note: ${sym.showLocated} captures a $fresh")
941940
yield fresh.hiddenSet.classifier
941+
if cls.typeRef.isMutableType then
942+
fieldClassifiers = defn.Caps_Mutable :: fieldClassifiers
942943
val parentClassifiers =
943944
cls.parentSyms.map(impliedClassifiers).filter(_.nonEmpty)
944945
if fieldClassifiers.isEmpty && parentClassifiers.isEmpty

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -332,7 +332,7 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI:
332332
if !tptToCheck.isEmpty then report.error(msg, tptToCheck.srcPos)
333333

334334
/** If C derives from Capability and we have a C^cs in source, we leave it as is
335-
* instead of expanding it to C^{cap.rd}^cs. We do this by stripping capability-generated
335+
* instead of expanding it to C^{cap}^cs. We do this by stripping capability-generated
336336
* universal capture sets from the parent of a CapturingType.
337337
*/
338338
def stripImpliedCaptureSet(tp: Type): Type = tp match

docs/_docs/reference/experimental/capture-checking/basics.md

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -219,9 +219,12 @@ This widening is called _avoidance_; it is not specific to capture checking but
219219

220220
## Capability Classes
221221

222-
Classes like `CanThrow` or `FileSystem` have the property that their values are always intended to be capabilities. We can make this intention explicit and save boilerplate by letting these classes extend the `SharedCapability` class defined in object `cap`.
222+
Classes like `CanThrow` or `FileSystem` have the property that their values are always intended to be capabilities. We can make this intention explicit and save boilerplate by letting these classes extend the `SharedCapability` class defined in object `caps`.
223+
224+
A type extending `SharedCapability` always comes with a capture set. If no capture set is given explicitly, we assume the capture set is `{cap}`.
225+
226+
This means we could equivalently express the `FileSystem` and `Logger` classes as follows:
223227

224-
The capture set of type extending `SharedCapability` is always `{cap}`. This means we could equivalently express the `FileSystem` and `Logger` classes as follows:
225228
```scala
226229
import caps.SharedCapability
227230

@@ -234,9 +237,10 @@ def test(using fs: FileSystem) =
234237
val l: Logger^{fs} = Logger()
235238
...
236239
```
237-
In this version, `FileSystem` is a capability class, which means that the `{cap}` capture set is implied on the parameters of `Logger` and `test`.
240+
In this version, `FileSystem` is a capability class, which means that the occurrences of `FileSystem` in the types of the parameters of `Logger` and `test` are implicitly expanded to `FileSystem^`. On the other hand, types like `FileSystem^{f}` or
241+
`FileSystem^{}` are kept as written.
238242

239-
Another, unrelated change in the version of the last example here is that the `FileSystem` capability is now passed as an implicit parameter. It is quite natural to model capabilities with implicit parameters since it greatly reduces the wiring overhead once multiple capabilities are in play.
243+
Another, unrelated change in the last version of the `Logger` example is that the `FileSystem` capability is now passed as an implicit parameter. It is quite natural to model capabilities with implicit parameters since it greatly reduces the wiring overhead once multiple capabilities are in play.
240244

241245
## Escape Checking
242246

tests/neg-custom-args/captures/boundary.check

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,13 @@
22
4 | boundary[AnyRef^]:
33
5 | l1 ?=> // error // error
44
| ^
5-
|Found: scala.util.boundary.Label[Object^'s1]^
6-
|Required: scala.util.boundary.Label[Object^²]^³
5+
| Found: scala.util.boundary.Label[Object^'s1]
6+
| Required: scala.util.boundary.Label[Object^]^²
77
|
8-
|Note that capability cap cannot be included in outer capture set 's1.
8+
| Note that capability cap cannot be included in outer capture set 's1.
99
|
10-
|where: ^ refers to a fresh root capability classified as Control created in value local when constructing Capability instance scala.util.boundary.Label[Object^'s1]
11-
| ^² and cap refer to the universal root capability
12-
| ^³ refers to a fresh root capability classified as Control in the type of value local
10+
| where: ^ and cap refer to the universal root capability
11+
| ^² refers to a fresh root capability classified as Control in the type of value local
1312
6 | boundary[Unit]: l2 ?=>
1413
7 | boundary.break(l2)(using l1) // error
1514
8 | ???

tests/neg-custom-args/captures/extending-cap-classes.check

Lines changed: 2 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,5 @@
1-
-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/extending-cap-classes.scala:7:15 -------------------------
2-
7 | val x2: C1 = new C2 // error
3-
| ^^^^^^
4-
|Found: C2^
5-
|Required: C1
6-
|
7-
|Note that capability cap is not included in capture set {}.
8-
|
9-
|where: ^ and cap refer to a fresh root capability classified as SharedCapability created in value x2 when constructing Capability instance C2
10-
|
11-
| longer explanation available when compiling with `-explain`
12-
-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/extending-cap-classes.scala:8:15 -------------------------
13-
8 | val x3: C1 = new C3 // error
14-
| ^^^^^^
15-
|Found: C3^
16-
|Required: C1
17-
|
18-
|Note that capability cap is not included in capture set {}.
19-
|
20-
|where: ^ and cap refer to a fresh root capability classified as SharedCapability created in value x3 when constructing Capability instance C3
21-
|
22-
| longer explanation available when compiling with `-explain`
23-
-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/extending-cap-classes.scala:13:15 ------------------------
24-
13 | val z2: C1 = y2 // error
1+
-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/extending-cap-classes.scala:14:15 ------------------------
2+
14 | val z2: C1 = y2 // error
253
| ^^
264
| Found: (y2 : C2)
275
| Required: C1

tests/neg-custom-args/captures/extending-cap-classes.scala

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,13 @@ class C3 extends C2
44

55
def test =
66
val x1: C1 = new C1
7-
val x2: C1 = new C2 // error
8-
val x3: C1 = new C3 // error
7+
val x2: C1 = new C2 // was error, now ok
8+
val x3: C1 = new C3 // was error, now ok
99

1010
val y2: C2 = new C2
1111
val y3: C3 = new C3
12+
val y2ok: C2^{} = new C2
1213

1314
val z2: C1 = y2 // error
15+
val z2ok: C1 = y2ok
1416

tests/neg-custom-args/captures/i21614.check

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,12 @@
1313
-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/i21614.scala:15:12 ---------------------------------------
1414
15 | files.map(new Logger(_)) // error, Q: can we improve the error message?
1515
| ^^^^^^^^^^^^^
16-
|Found: (_$1: File^'s1) ->{C} Logger{val f: File^{_$1}}^{cap, _$1}
16+
|Found: (_$1: File^'s1) ->{C} Logger{val f: File^{_$1}}^{_$1}
1717
|Required: File^{C} => Logger{val f: File^'s2}^'s3
1818
|
1919
|Note that capability C is not classified as trait SharedCapability, therefore it
2020
|cannot be included in capture set 's1 of parameter _$1 of SharedCapability elements.
2121
|
22-
|where: => refers to a fresh root capability created in method mkLoggers2 when checking argument to parameter f of method map
23-
| cap is a root capability associated with the result type of (_$1: File^'s1): Logger{val f: File^{_$1}}^{cap, _$1}
22+
|where: => refers to a fresh root capability created in method mkLoggers2 when checking argument to parameter f of method map
2423
|
2524
| longer explanation available when compiling with `-explain`

tests/neg-custom-args/captures/i23726.check

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
-- Error: tests/neg-custom-args/captures/i23726.scala:10:5 -------------------------------------------------------------
2-
10 | f1(a) // error, as expected
1+
-- Error: tests/neg-custom-args/captures/i23726.scala:11:5 -------------------------------------------------------------
2+
11 | f1(a) // error, as expected
33
| ^
44
|Separation failure: argument of type (a : Ref^)
55
|to a function of type (x: Ref^) -> List[() ->{a, x} Unit]
@@ -15,8 +15,8 @@
1515
|
1616
|where: ^ refers to a fresh root capability classified as Mutable in the type of value a
1717
| ^² refers to a fresh root capability classified as Mutable created in method test1 when checking argument to parameter x of method apply
18-
-- Error: tests/neg-custom-args/captures/i23726.scala:15:5 -------------------------------------------------------------
19-
15 | f3(b) // error
18+
-- Error: tests/neg-custom-args/captures/i23726.scala:16:5 -------------------------------------------------------------
19+
16 | f3(b) // error
2020
| ^
2121
|Separation failure: argument of type (b : Ref^)
2222
|to a function of type (x: Ref^) -> (op: () ->{b} Unit) -> List[() ->{op} Unit]
@@ -32,8 +32,8 @@
3232
|
3333
|where: ^ refers to a fresh root capability classified as Mutable in the type of value b
3434
| ^² refers to a fresh root capability classified as Mutable created in method test1 when checking argument to parameter x of method apply
35-
-- Error: tests/neg-custom-args/captures/i23726.scala:23:5 -------------------------------------------------------------
36-
23 | f7(a) // error
35+
-- Error: tests/neg-custom-args/captures/i23726.scala:24:5 -------------------------------------------------------------
36+
24 | f7(a) // error
3737
| ^
3838
|Separation failure: argument of type (a : Ref^)
3939
|to a function of type (x: Ref^) ->{a, b} (y: List[Ref^{a, b}]) ->{a, b} Unit

tests/neg-custom-args/captures/i23726.scala

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import language.experimental.captureChecking
22
import language.experimental.separationChecking
33
import caps.*
4-
class Ref extends Mutable
4+
class Ref extends Mutable:
5+
update def set = ???
56
def swap(a: Ref^, b: Ref^): Unit = ()
67
def test1(): Unit =
78
val a = Ref()

0 commit comments

Comments
 (0)