Skip to content

Commit d89138a

Browse files
committed
Tighten consume checks
1. Strip readonly modifier before checking o=for overlaps 2. Don't reset consume to empty before entering defs
1 parent 731c9aa commit d89138a

File tree

6 files changed

+69
-13
lines changed

6 files changed

+69
-13
lines changed

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

+2
Original file line numberDiff line numberDiff line change
@@ -482,6 +482,8 @@ extension (tp: Type)
482482
else tp.superType.derivesFromCapTrait(cls)
483483
case ReachCapability(tp1) =>
484484
tp1.widen.derivesFromCapTraitDeeply(cls)
485+
case ReadOnlyCapability(tp1) =>
486+
tp1.derivesFromCapTrait(cls)
485487
case tp: (TypeProxy & ValueType) =>
486488
tp.superType.derivesFromCapTrait(cls)
487489
case tp: AndType =>

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

+6-12
Original file line numberDiff line numberDiff line change
@@ -164,13 +164,6 @@ class SepCheck(checker: CheckCaptures.CheckerAPI) extends tpd.TreeTraverser:
164164
/** The set of references that were consumed so far in the current method */
165165
private var consumed: MutConsumedSet = MutConsumedSet()
166166

167-
/** Run `op`` with a fresh, initially empty consumed set. */
168-
private def withFreshConsumed(op: => Unit): Unit =
169-
val saved = consumed
170-
consumed = MutConsumedSet()
171-
op
172-
consumed = saved
173-
174167
/** Infos aboput Labeled expressions enclosing the current traversal point.
175168
* For each labeled expression, it's label name, and a list buffer containing
176169
* all consumed sets of return expressions referring to that label.
@@ -499,7 +492,7 @@ class SepCheck(checker: CheckCaptures.CheckerAPI) extends tpd.TreeTraverser:
499492
else assert(!argDeps.isEmpty)
500493

501494
if arg.needsSepCheck then
502-
//println(i"testing $arg, ${argPeaks.actual}/${argPeaks.formal} against ${currentPeaks.actual}")
495+
//println(i"testing $arg, formal = ${arg.formalType}, peaks = ${argPeaks.actual}/${argPeaks.hidden} against ${currentPeaks.actual}")
503496
checkType(arg.formalType, arg.srcPos, TypeRole.Argument(arg))
504497
// 2. test argPeaks.hidden against previously captured actuals
505498
if !argPeaks.hidden.sharedWith(currentPeaks.actual).isEmpty then
@@ -548,6 +541,7 @@ class SepCheck(checker: CheckCaptures.CheckerAPI) extends tpd.TreeTraverser:
548541
def checkUse(tree: Tree)(using Context): Unit =
549542
val used = tree.markedFree.elems
550543
if !used.isEmpty then
544+
capt.println(i"check use $tree: $used")
551545
val usedPeaks = used.peaks
552546
val overlap = defsShadow.peaks.sharedWith(usedPeaks)
553547
if !defsShadow.peaks.sharedWith(usedPeaks).isEmpty then
@@ -569,7 +563,7 @@ class SepCheck(checker: CheckCaptures.CheckerAPI) extends tpd.TreeTraverser:
569563
sepUseError(tree, null, used, defsShadow)
570564

571565
for ref <- used do
572-
val pos = consumed.get(ref)
566+
val pos = consumed.get(ref.stripReadOnly)
573567
if pos != null then consumeError(ref, pos, tree.srcPos)
574568
end checkUse
575569

@@ -656,7 +650,7 @@ class SepCheck(checker: CheckCaptures.CheckerAPI) extends tpd.TreeTraverser:
656650
case _: TypeRole.Argument | _: TypeRole.Qualifier =>
657651
for ref <- refsToCheck do
658652
if !ref.derivesFromSharedCapability then
659-
consumed.put(ref, pos)
653+
consumed.put(ref.stripReadOnly, pos)
660654
case _ =>
661655
end checkConsumedRefs
662656

@@ -913,7 +907,7 @@ class SepCheck(checker: CheckCaptures.CheckerAPI) extends tpd.TreeTraverser:
913907
checkValOrDefDef(tree)
914908
case tree: DefDef =>
915909
inSection:
916-
withFreshConsumed:
910+
consumed.segment:
917911
for params <- tree.paramss; case param: ValDef <- params do
918912
pushDef(param, emptyRefs)
919913
traverseChildren(tree)
@@ -945,7 +939,7 @@ class SepCheck(checker: CheckCaptures.CheckerAPI) extends tpd.TreeTraverser:
945939
val caseConsumed = for cas <- cases yield consumed.segment(traverse(cas))
946940
caseConsumed.foreach(consumed ++= _)
947941
case tree: TypeDef if tree.symbol.isClass =>
948-
withFreshConsumed:
942+
consumed.segment:
949943
traverseChildren(tree)
950944
case tree: WhileDo =>
951945
val loopConsumed = consumed.segment(traverseChildren(tree))
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
-- Error: tests/neg-custom-args/captures/sep-consume.scala:17:2 --------------------------------------------------------
2+
17 | x.put(42) // error
3+
| ^
4+
| Separation failure: Illegal access to (x : Ref^), which was passed to a
5+
| @consume parameter or was used as a prefix to a @consume method on line 16
6+
| and therefore is no longer available.
7+
-- Error: tests/neg-custom-args/captures/sep-consume.scala:18:2 --------------------------------------------------------
8+
18 | x.get // error
9+
| ^
10+
| Separation failure: Illegal access to x.rd, which was passed to a
11+
| @consume parameter or was used as a prefix to a @consume method on line 16
12+
| and therefore is no longer available.
13+
-- Error: tests/neg-custom-args/captures/sep-consume.scala:19:16 -------------------------------------------------------
14+
19 | par(rx, () => x.put(42)) // error
15+
| ^
16+
| Separation failure: Illegal access to (x : Ref^), which was passed to a
17+
| @consume parameter or was used as a prefix to a @consume method on line 16
18+
| and therefore is no longer available.
19+
-- Error: tests/neg-custom-args/captures/sep-consume.scala:20:16 -------------------------------------------------------
20+
20 | par(rx, () => x.get) // error
21+
| ^
22+
| Separation failure: Illegal access to x.rd, which was passed to a
23+
| @consume parameter or was used as a prefix to a @consume method on line 16
24+
| and therefore is no longer available.
25+
-- Error: tests/neg-custom-args/captures/sep-consume.scala:24:16 -------------------------------------------------------
26+
24 | def foo = bad(f) // error
27+
| ^
28+
| Separation failure: argument to @consume parameter with type (f : () ->{x.rd} Unit) refers to non-local value f
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import language.experimental.captureChecking
2+
import caps.*
3+
4+
class Ref extends Mutable:
5+
private var _data = 0
6+
def get: Int = _data
7+
mut def put(x: Int): Unit = _data = x
8+
9+
// require f and g to be non-interfering
10+
def par(f: () => Unit, g: () => Unit): Unit = ???
11+
12+
def bad(@consume op: () ->{cap.rd} Unit): () => Unit = op
13+
14+
def test2(@consume x: Ref^): Unit =
15+
val f: () ->{x.rd} Unit = () => x.get
16+
val rx: () => Unit = bad(f) // hides x.rd in the resulting `cap`
17+
x.put(42) // error
18+
x.get // error
19+
par(rx, () => x.put(42)) // error
20+
par(rx, () => x.get) // error
21+
22+
def test3(@consume x: Ref^): Unit =
23+
val f: () ->{x.rd} Unit = () => x.get
24+
def foo = bad(f) // error
25+
foo()
26+
foo()

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

+6
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,9 @@
88
| ^^
99
| Separation failure: argument to @consume parameter with type (io : Object^) refers to parameter io.
1010
| The parameter needs to be annotated with @consume to allow this.
11+
-- Error: tests/neg-custom-args/captures/sepchecks5.scala:20:24 --------------------------------------------------------
12+
20 | par(f2)(() => println(io)) // error
13+
| ^^
14+
| Separation failure: Illegal access to (io : Object^), which was passed to a
15+
| @consume parameter or was used as a prefix to a @consume method on line 19
16+
| and therefore is no longer available.

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -17,5 +17,5 @@ def test(io: Object^): Unit =
1717
par(f1)(() => println(io)) // !!! separation failure
1818

1919
val f2 = g(io) // error
20-
par(f2)(() => println(io)) // !!! separation failure
20+
par(f2)(() => println(io)) // error
2121

0 commit comments

Comments
 (0)