Skip to content

Commit aa69c58

Browse files
committed
Exclude caps consumed in paths in checkUse
1 parent 5a38c0d commit aa69c58

File tree

3 files changed

+74
-8
lines changed

3 files changed

+74
-8
lines changed

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

Lines changed: 65 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -559,6 +559,62 @@ class SepCheck(checker: CheckCaptures.CheckerAPI) extends tpd.TreeTraverser:
559559
sepApplyError(fn, parts, arg, app)
560560
end checkApply
561561

562+
/** Compute the set of capabilities that are consumed along a selection path.
563+
* For example, if we have the path `a.b` where `b` is a consume field with type `B^{x}`,
564+
* this returns the actual consumed capabilities `{x}` from the type's capture set.
565+
*
566+
* This walks the path and for each consume field encountered, collects the capabilities
567+
* from that field's widened type's capture set.
568+
*
569+
* @param cap The capability to analyze (e.g., `a.b`)
570+
* @return The set of actual consumed capabilities from all consume fields along the path
571+
*/
572+
def consumedByPath(cap: Capability)(using Context): Refs = cap match
573+
case ref: TermRef =>
574+
def recur(tp: Type, acc: Refs): Refs = tp match
575+
case ref: TermRef =>
576+
// This is a selection like a.b, or just a reference like a
577+
val fieldSym = ref.symbol
578+
val accWithCurrent =
579+
if fieldSym.exists && fieldSym.isConsumeParam then
580+
// This field is a consume parameter. Collect capabilities from its type's capture set.
581+
acc ++ ref.widen.captureSet.elems
582+
else
583+
acc
584+
// Also check if the current reference's type has consume fields
585+
val accWithConsumeFields = accWithCurrent ++ capsFromConsumeFields(ref)
586+
// Recurse on the prefix if it's a selection
587+
if ref.prefix ne NoPrefix then
588+
recur(ref.prefix, accWithConsumeFields)
589+
else
590+
accWithConsumeFields
591+
case _ =>
592+
acc
593+
594+
def capsFromConsumeFields(ref: TermRef): Refs =
595+
// For types with consume fields, use the capture set of the reference itself
596+
// and recursively check what those capabilities consume
597+
val tpe = ref.widen.dealias
598+
val cls = tpe.classSymbol
599+
if cls.exists then
600+
// Check if this class has any consume fields
601+
val hasConsumeFields = cls.info.decls.exists(m => m.isConsumeParam && m.isTerm)
602+
if hasConsumeFields then
603+
// Use the capture set of the reference itself (e.g., {a} for holder.a : A^{a})
604+
// Return both the capabilities in the capture set AND what they transitively consume
605+
var result = ref.widen.captureSet.elems
606+
for cap <- ref.widen.captureSet.elems do
607+
result = result ++ consumedByPath(cap)
608+
result
609+
else
610+
emptyRefs
611+
else
612+
emptyRefs
613+
614+
recur(ref, emptyRefs)
615+
case _ =>
616+
emptyRefs
617+
562618
/** 1. Check that the capabilities used at `tree` don't overlap with
563619
* capabilities hidden by a previous definition.
564620
* 2. Also check that none of the used capabilities was consumed before.
@@ -598,8 +654,15 @@ class SepCheck(checker: CheckCaptures.CheckerAPI) extends tpd.TreeTraverser:
598654
for ref <- used do
599655
val pos = consumed.clashing(ref)
600656
if pos != null then
601-
// println(i"consumed so far ${consumed.refs.toList} with peaks ${consumed.directPeaks.toList}, used = $used, exposed = ${ref.directPeaks }")
602-
consumeError(ref, pos, tree.srcPos)
657+
// Check if this reference should be exempted because consume fields along
658+
// the path own the consumed capabilities.
659+
val pathConsumed = consumedByPath(ref)
660+
661+
// Check if any capability consumed by the path was consumed at position `pos`.
662+
val shouldExempt = pathConsumed.exists(consumed.get(_) == pos)
663+
664+
if !shouldExempt then
665+
consumeError(ref, pos, tree.srcPos)
603666
end checkUse
604667

605668
/** If `tp` denotes some version of a singleton capability `x.type` the set `{x, x*}`

tests/neg-custom-args/captures/consume-in-constructor.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/consume-in-constructor.scala:18:10 --------------------------------------------
2-
18 | println(b) // error
1+
-- Error: tests/neg-custom-args/captures/consume-in-constructor.scala:20:10 --------------------------------------------
2+
20 | println(b) // error
33
| ^
44
| Separation failure: Illegal access to {b} which is hidden by the previous definition
55
| of value a2 with type A2{val b: B^{b²}}^.
@@ -9,8 +9,8 @@
99
| b is a value in class A2
1010
| b² is a value in method Test
1111
| cap is a fresh root capability created in value a2 when constructing instance A2
12-
-- Error: tests/neg-custom-args/captures/consume-in-constructor.scala:19:10 --------------------------------------------
13-
19 | println(a1) // error, since `b` was consumed before
12+
-- Error: tests/neg-custom-args/captures/consume-in-constructor.scala:21:10 --------------------------------------------
13+
21 | println(a1) // error, since `b` was consumed before
1414
| ^^
1515
| Separation failure: Illegal access to {b} which is hidden by the previous definition
1616
| of value a2 with type A2{val b: B^{b²}}^.
@@ -20,8 +20,8 @@
2020
| b is a value in class A2
2121
| b² is a value in method Test
2222
| cap is a fresh root capability created in value a2 when constructing instance A2
23-
-- Error: tests/neg-custom-args/captures/consume-in-constructor.scala:25:10 --------------------------------------------
24-
25 | println(b) // error, b is hidden in the type of a1
23+
-- Error: tests/neg-custom-args/captures/consume-in-constructor.scala:28:10 --------------------------------------------
24+
28 | println(b) // error, b is hidden in the type of a1
2525
| ^
2626
| Separation failure: Illegal access to {b} which is hidden by the previous definition
2727
| of value a1 with type A1^.

tests/neg-custom-args/captures/consume-in-constructor.scala

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import language.experimental.captureChecking
2+
import language.experimental.separationChecking
13
import caps.cap
24

35
class B
@@ -18,6 +20,7 @@ def Test =
1820
println(b) // error
1921
println(a1) // error, since `b` was consumed before
2022
println(a2) // OK since b belongs to a2
23+
println(a2.b) // OK since b belongs to a2
2124

2225
def Test2 =
2326
val b: B^ = B()

0 commit comments

Comments
 (0)