Skip to content

Commit f43149a

Browse files
committed
Require that consume parameters are covered by a FreshCap
1 parent 503ba4a commit f43149a

File tree

6 files changed

+148
-14
lines changed

6 files changed

+148
-14
lines changed

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import tpd.*
1313
import Annotations.Annotation
1414
import CaptureSet.VarState
1515
import Capabilities.*
16+
import Mutability.isMutableType
1617
import StdNames.nme
1718
import config.Feature
1819
import NameKinds.TryOwnerName
@@ -529,6 +530,9 @@ extension (cls: ClassSymbol)
529530
.foldLeft(defn.AnyClass)(leastClassifier)
530531
else defn.AnyClass
531532

533+
def isSeparate(using Context): Boolean =
534+
cls.typeRef.isMutableType
535+
532536
extension (sym: Symbol)
533537

534538
/** This symbol is one of `retains` or `retainsCap` */

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

Lines changed: 59 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ import util.chaining.tap
2121
import transform.{Recheck, PreRecheck, CapturedVars}
2222
import Recheck.*
2323
import scala.collection.mutable
24-
import CaptureSet.{withCaptureSetsExplained, IncludeFailure, MutAdaptFailure}
24+
import CaptureSet.{withCaptureSetsExplained, IncludeFailure, MutAdaptFailure, VarState}
2525
import CCState.*
2626
import StdNames.nme
2727
import NameKinds.{DefaultGetterName, WildcardParamName, UniqueNameKind}
@@ -916,7 +916,10 @@ class CheckCaptures extends Recheck, SymTransformer:
916916
val getter = cls.info.member(getterName).suchThat(_.isRefiningParamAccessor).symbol
917917
if !getter.is(Private) && getter.hasTrackedParts then
918918
refined = refined.refinedOverride(getterName, argType.unboxed) // Yichen you might want to check this
919-
allCaptures ++= argType.captureSet
919+
if getter.hasAnnotation(defn.ConsumeAnnot) then
920+
() // We make sure in checkClassDef, point (6), that consume parameters don't
921+
// contribute to the class capture set
922+
else allCaptures ++= argType.captureSet
920923
(refined, allCaptures)
921924

922925
/** Augment result type of constructor with refinements and captures.
@@ -971,8 +974,6 @@ class CheckCaptures extends Recheck, SymTransformer:
971974
setup.fieldsWithExplicitTypes // pick fields with explicit types for classes in this compilation unit
972975
.getOrElse(cls, cls.info.decls.toList) // pick all symbols in class scope for other classes
973976

974-
val isSeparate = cls.typeRef.isMutableType
975-
976977
/** The classifiers of the fresh caps in the span capture sets of all fields
977978
* in the given class `cls`.
978979
*/
@@ -997,7 +998,7 @@ class CheckCaptures extends Recheck, SymTransformer:
997998
false
998999

9991000
def maybeRO(ref: Capability) =
1000-
if !isSeparate && impliedReadOnly(cls) then ref.readOnly else ref
1001+
if !cls.isSeparate && impliedReadOnly(cls) then ref.readOnly else ref
10011002

10021003
def fresh =
10031004
FreshCap(Origin.NewInstance(core)).tap: fresh =>
@@ -1006,7 +1007,7 @@ class CheckCaptures extends Recheck, SymTransformer:
10061007
report.echo(infos.mkString("\n"), ctx.owner.srcPos)
10071008

10081009
var implied = impliedClassifiers(cls)
1009-
if isSeparate then implied = cls.classifier :: implied
1010+
if cls.isSeparate then implied = dominators(cls.classifier :: Nil, implied)
10101011
knownFresh.getOrElseUpdate(cls, implied) match
10111012
case Nil => CaptureSet.empty
10121013
case cl :: Nil =>
@@ -1377,29 +1378,47 @@ class CheckCaptures extends Recheck, SymTransformer:
13771378
/** Recheck classDef by enforcing the following class-specific capture set relations:
13781379
* 1. The capture set of a class includes the capture sets of its parents.
13791380
* 2. The capture set of the self type of a class includes the capture set of the class.
1380-
* 3. The capture set of the self type of a class includes the capture set of every class parameter,
1381-
* unless the parameter is marked @constructorOnly or @untrackedCaptures.
1381+
* 3. The capture set of the self type of a class includes the capture set of every class
1382+
* parameter, unless the parameter is marked @constructorOnly or @untrackedCaptures.
13821383
* 4. If the class extends a pure base class, the capture set of the self type must be empty.
1383-
* Also, check that trait parents represented as applied types don't have cap in their
1384-
* type arguments. Other generic parents are represented as TypeApplys, where the same check
1385-
* is already done in the TypeApply.
1384+
* 5. Check that trait parents represented as applied types don't have cap in their
1385+
* type arguments. Charge deep capture sets of type arguments to non-reserved typevars
1386+
* to the environment. Other generic parents are represented as TypeApplys, where the
1387+
* same check is already done in the TypeApply.
1388+
* 6. Consume parameters are only allowed for classes producing a fresh cap
1389+
* for their constructor, and they don't contribute to the capture set. Example:
1390+
*
1391+
* class A(consume val x: B^) extends caps.Separate
1392+
* val a = A(b)
1393+
*
1394+
* Here, `a` is of type A{val x: B^}^, and the outer `^` does not hide `b`.
1395+
* That's necessary since we would otherwise get consume/use conflicts on `b`.
13861396
*/
13871397
override def recheckClassDef(tree: TypeDef, impl: Template, cls: ClassSymbol)(using Context): Type =
13881398
if Feature.enabled(Feature.separationChecking) then sepChecksEnabled = true
13891399
val localSet = capturedVars(cls)
1400+
1401+
// (1) Capture set of a class includes the capture sets of its parents
13901402
for parent <- impl.parents do // (1)
13911403
checkSubset(capturedVars(parent.tpe.classSymbol), localSet, parent.srcPos,
13921404
i"\nof the references allowed to be captured by $cls")
1405+
13931406
val saved = curEnv
13941407
curEnv = Env(cls, EnvKind.Regular, localSet, curEnv)
13951408
try
1409+
// (2) Capture set of self type includes capture set of class
13961410
val thisSet = cls.classInfo.selfType.captureSet.withDescription(i"of the self type of $cls")
1397-
checkSubset(localSet, thisSet, tree.srcPos) // (2)
1411+
checkSubset(localSet, thisSet, tree.srcPos)
1412+
1413+
// (3) Capture set of self type includes capture sets of parameters
13981414
for param <- cls.paramGetters do
13991415
if !param.hasAnnotation(defn.ConstructorOnlyAnnot)
1400-
&& !param.hasAnnotation(defn.UntrackedCapturesAnnot) then
1416+
&& !param.hasAnnotation(defn.UntrackedCapturesAnnot)
1417+
then
14011418
withCapAsRoot: // OK? We need this here since self types use `cap` instead of `fresh`
1402-
checkSubset(param.termRef.captureSet, thisSet, param.srcPos) // (3)
1419+
checkSubset(param.termRef.captureSet, thisSet, param.srcPos)
1420+
1421+
// (4) If class extends Pure, capture set of self type is empty
14031422
for pureBase <- cls.pureBaseClass do // (4)
14041423
def selfTypeTree = impl.body
14051424
.collect:
@@ -1410,15 +1429,41 @@ class CheckCaptures extends Recheck, SymTransformer:
14101429
checkSubset(thisSet,
14111430
CaptureSet.empty.withDescription(i"of pure base class $pureBase"),
14121431
selfTypeTree.srcPos, cs1description = " captured by this self type")
1432+
1433+
// (5) Check AppliedType parents
14131434
for case tpt: TypeTree <- impl.parents do
14141435
tpt.tpe match
14151436
case AppliedType(fn, args) =>
14161437
markFreeTypeArgs(tpt, fn.typeSymbol, args.map(TypeTree(_)))
14171438
case _ =>
1439+
1440+
// (6) Check that consume parameters are covered by an implied FreshCap
1441+
for getter <- cls.paramGetters do
1442+
if !getter.is(Private) // Setup makes sure that getters with capture sets are not private
1443+
&& getter.hasAnnotation(defn.ConsumeAnnot)
1444+
then
1445+
val implied = captureSetImpliedByFields(cls, cls.appliedRef)
1446+
val getterCS = getter.info.captureSet
1447+
1448+
val hasCoveringFresh = implied.elems.exists:
1449+
case fresh: FreshCap =>
1450+
getterCS.elems.forall: elem =>
1451+
given VarState = VarState.Unrecorded // make sure we don't add to fresh's hidden set
1452+
fresh.maxSubsumes(elem, canAddHidden = true)
1453+
case _ =>
1454+
false
1455+
1456+
if !hasCoveringFresh then
1457+
report.error(
1458+
em"""A consume parameter is only allowed for classes producing a `cap` in their constructor.
1459+
|This can be achieved by having the class extend caps.Separate.""",
1460+
getter.srcPos)
1461+
14181462
super.recheckClassDef(tree, impl, cls)
14191463
finally
14201464
completed += cls
14211465
curEnv = saved
1466+
end recheckClassDef
14221467

14231468
/** If type is of the form `T @requiresCapability(x)`,
14241469
* mark `x` as free in the current environment. This is used to require the
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
-- Error: tests/neg-custom-args/captures/consume-in-constructor.scala:11:21 --------------------------------------------
2+
11 |class A3(consume val b: B^) // error
3+
| ^
4+
| A consume parameter is only allowed for classes producing a `cap` in their constructor.
5+
| This can be achieved by having the class extend caps.Separate.
6+
-- Error: tests/neg-custom-args/captures/consume-in-constructor.scala:20:10 --------------------------------------------
7+
20 | println(b) // error
8+
| ^
9+
|Separation failure: Illegal access to (b : B^), which was passed as a consume parameter to constructor of class A2
10+
|on line 18 and therefore is no longer available.
11+
|
12+
|where: ^ refers to a fresh root capability in the type of value b
13+
-- Error: tests/neg-custom-args/captures/consume-in-constructor.scala:21:10 --------------------------------------------
14+
21 | println(a1) // error, since `b` was consumed before
15+
| ^^
16+
|Separation failure: Illegal access to (a1 : A1{val b: B^{b²}}^{cap, b²}), which was passed as a consume parameter to constructor of class A2
17+
|on line 18 and therefore is no longer available.
18+
|
19+
|where: b is a value in class A1
20+
| b² is a value in method Test
21+
| cap is a fresh root capability in the type of value a1
22+
-- Error: tests/neg-custom-args/captures/consume-in-constructor.scala:27:10 --------------------------------------------
23+
27 | println(b) // error, b is hidden in the type of a1
24+
| ^
25+
| Separation failure: Illegal access to {b} which is hidden by the previous definition
26+
| of value a1 with type A1^.
27+
| This type hides capabilities {cap, b}
28+
|
29+
| where: ^ refers to a fresh root capability in the type of value a1
30+
| cap is a fresh root capability created in value a1 when constructing instance A1
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import caps.{cap, Mutable}
2+
3+
class B
4+
5+
class A1(val b: B^):
6+
val bb: B^ = B()
7+
8+
class A2(consume val b: B^):
9+
val bb: B^ = B()
10+
11+
class A3(consume val b: B^) // error
12+
class A4(consume val b: B^) extends Mutable { var x: Int = 1 } // ok
13+
def Test =
14+
val b: B^ = B()
15+
val a1 = A1(b)
16+
val _: A1^{cap, b} = a1
17+
println(b) // OK since a1's type mentions `b` explicitly
18+
val a2 = A2(b)
19+
val _: A2^{cap, b} = a2
20+
println(b) // error
21+
println(a1) // error, since `b` was consumed before
22+
println(a2) // OK since b belongs to a2
23+
24+
def Test2 =
25+
val b: B^ = B()
26+
val a1: A1^ = A1(b)
27+
println(b) // error, b is hidden in the type of a1
28+
29+
30+
31+
32+
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/implied-ro.scala:12:16 -----------------------------------
2+
12 | val _: C^{} = c // error
3+
| ^
4+
| Found: (c : C^{cap.rd})
5+
| Required: C
6+
|
7+
| Note that capability cap.rd is not included in capture set {}.
8+
|
9+
| where: cap is a fresh root capability classified as Unscoped created in value c when constructing instance C
10+
|
11+
| longer explanation available when compiling with `-explain`
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import caps.*
2+
class Ref extends Mutable, Unscoped
3+
4+
class C:
5+
val r: Ref = Ref()
6+
7+
def test =
8+
val c = C()
9+
val _: C^{cap.rd} = c
10+
val _: C^{cap.only[Unscoped]} = c
11+
val _: C^{cap.only[Unscoped].rd} = c
12+
val _: C^{} = c // error

0 commit comments

Comments
 (0)