@@ -98,7 +98,7 @@ object SepCheck:
98
98
var refs : Array [Capability ] = new Array (4 )
99
99
var locs : Array [SrcPos ] = new Array (4 )
100
100
var size = 0
101
- var peaks : Refs = emptyRefs
101
+ var exposedPeaks : Refs = emptyRefs
102
102
103
103
private def double [T <: AnyRef : ClassTag ](xs : Array [T ]): Array [T ] =
104
104
val xs1 = new Array [T ](xs.length * 2 )
@@ -117,10 +117,10 @@ object SepCheck:
117
117
if i < size then locs(i) else null
118
118
119
119
def clashing (ref : Capability )(using Context ): SrcPos | Null =
120
- val refPeaks = ref.peaks
121
- if ! peaks .sharedWith(refPeaks).isEmpty then
120
+ val refPeaks = ref.exposedPeaks
121
+ if ! exposedPeaks .sharedWith(refPeaks).isEmpty then
122
122
var i = 0
123
- while i < size && refs(i).peaks .sharedWith(refPeaks).isEmpty do
123
+ while i < size && refs(i).exposedPeaks .sharedWith(refPeaks).isEmpty do
124
124
i += 1
125
125
assert(i < size)
126
126
locs(i)
@@ -133,7 +133,7 @@ object SepCheck:
133
133
refs(size) = ref
134
134
locs(size) = loc
135
135
size += 1
136
- peaks = peaks ++ ref.peaks
136
+ exposedPeaks = exposedPeaks ++ ref.exposedPeaks
137
137
138
138
/** Add all references with their associated positions from `that` which
139
139
* are not yet in the set.
@@ -146,14 +146,14 @@ object SepCheck:
146
146
*/
147
147
def segment (op : => Unit ): ConsumedSet =
148
148
val start = size
149
- val savedPeaks = peaks
149
+ val savedPeaks = exposedPeaks
150
150
try
151
151
op
152
152
if size == start then EmptyConsumedSet
153
153
else ConstConsumedSet (refs.slice(start, size), locs.slice(start, size))
154
154
finally
155
155
size = start
156
- peaks = savedPeaks
156
+ exposedPeaks = savedPeaks
157
157
end MutConsumedSet
158
158
159
159
val EmptyConsumedSet = ConstConsumedSet (Array (), Array ())
@@ -164,6 +164,9 @@ object SepCheck:
164
164
165
165
extension (refs : Refs )
166
166
167
+ private def peaks_ (using Context ): Refs =
168
+ refs.filter(_.isTerminalCapability)
169
+
167
170
/** The footprint of a set of references `refs` the smallest set `F` such that
168
171
* 1. all capabilities in `refs` satisfying (1) are in `F`
169
172
* 2. if `f in F` then the footprint of `f`'s info is also in `F`.
@@ -179,6 +182,26 @@ object SepCheck:
179
182
val elems : Refs = refs.filter(retain)
180
183
recur(elems, elems.toList)
181
184
185
+ private def exposedPeaks (using Context ): Refs =
186
+ footPrint(followHidden = false ).peaks_
187
+
188
+ private def footPrint (followHidden : Boolean )(using Context ): Refs =
189
+ def recur (seen : Refs , acc : Refs , newElems : List [Capability ]): Refs = trace(i " peaks $acc, $newElems = " ):
190
+ newElems match
191
+ case newElem :: newElems1 =>
192
+ if seen.contains(newElem) then
193
+ recur(seen, acc, newElems1)
194
+ else newElem.stripRestricted.stripReadOnly match
195
+ case _ : FreshCap if ! newElem.isKnownClassifiedAs(defn.Caps_SharedCapability ) =>
196
+ val hiddens = if followHidden then newElem.hiddenElems.toList else Nil
197
+ recur(seen + newElem, acc + newElem, hiddens ++ newElems1)
198
+ case _ if newElem.isTerminalCapability =>
199
+ recur(seen + newElem, acc, newElems1)
200
+ case _ =>
201
+ recur(seen + newElem, acc, newElem.captureSetOfInfo.dropEmpties().elems.toList ++ newElems1)
202
+ case Nil => acc
203
+ recur(emptyRefs, emptyRefs, refs.toList)
204
+
182
205
private def peaks (using Context ): Refs =
183
206
def recur (seen : Refs , acc : Refs , newElems : List [Capability ]): Refs = trace(i " peaks $acc, $newElems = " ):
184
207
newElems match
@@ -199,15 +222,43 @@ object SepCheck:
199
222
then recur(seen + newElem, acc, newElems1)
200
223
else recur(seen + newElem, acc, newElem.captureSetOfInfo.dropEmpties().elems.toList ++ newElems1)
201
224
case Nil => acc
202
- recur(emptyRefs, emptyRefs, refs.toList)
225
+ if ccConfig.newScheme then footPrint(followHidden = true ).peaks_
226
+ else recur(emptyRefs, emptyRefs, refs.toList)
203
227
204
- /** The shared peaks between `refs` and `other` */
228
+ /** The shared elements between `refs` and `other`.
229
+ * These are the core capabilities and fresh capabilities that appear
230
+ * in a (possibly classified or readOnly) version in both sets and that
231
+ * that appear in a non-readOnly version in at least one of the sets.
232
+ */
205
233
private def sharedWith (other : Refs )(using Context ): Refs =
206
234
def common (refs1 : Refs , refs2 : Refs ) =
207
- refs1.filter: ref =>
208
- ! ref.isReadOnly && refs2.exists(_.stripReadOnly eq ref)
235
+ var acc : Refs = emptyRefs
236
+ refs1.foreach: ref =>
237
+ if ! ref.isReadOnly then
238
+ val coreRef = ref.stripRestricted
239
+ if refs2.exists(_.stripRestricted.stripReadOnly eq coreRef) then
240
+ acc += coreRef
241
+ acc
209
242
common(refs, other) ++ common(other, refs)
210
243
244
+ /** The shared peaks between `refs` and `other` */
245
+ private def sharedPeaks (other : Refs )(using Context ): Refs =
246
+ refs.peaks_.sharedWith(other.peaks_)
247
+
248
+ /** Reduce a non-empty footprint set to
249
+ * 1. all its non-terminial capabilities if that set is nonempty, or
250
+ * 2. one of its non-hidden capabilities if one exists, or
251
+ * 3. one of its capabilities.
252
+ */
253
+ def reduced (using Context ): Refs =
254
+ assert(! refs.isEmpty)
255
+ val concrete = refs.filter(! _.isTerminalCapability)
256
+ if ! concrete.isEmpty then concrete
257
+ else
258
+ val notHidden = refs -- refs.flatMap(_.hiddenElems)
259
+ if ! notHidden.isEmpty then SimpleIdentitySet (notHidden.nth(0 ))
260
+ else SimpleIdentitySet (refs.nth(0 ))
261
+
211
262
/** The overlap of two footprint sets F1 and F2. This contains all exclusive references `r`
212
263
* such that one of the following is true:
213
264
* 1.
@@ -282,6 +333,7 @@ object SepCheck:
282
333
283
334
extension (ref : Capability )
284
335
def peaks (using Context ): Refs = SimpleIdentitySet (ref).peaks
336
+ def exposedPeaks (using Context ): Refs = SimpleIdentitySet (ref).exposedPeaks
285
337
286
338
class SepCheck (checker : CheckCaptures .CheckerAPI ) extends tpd.TreeTraverser :
287
339
import checker .*
@@ -313,7 +365,7 @@ class SepCheck(checker: CheckCaptures.CheckerAPI) extends tpd.TreeTraverser:
313
365
arg.formalType.orElse(arg.nuType).deepCaptureSet.elems
314
366
315
367
/** The span capture set if the type of `tree` */
316
- private def captures (tree : Tree )(using Context ): Refs =
368
+ private def spanCaptures (tree : Tree )(using Context ): Refs =
317
369
tree.nuType.spanCaptureSet.elems
318
370
319
371
/** The deep capture set if the type of `tree` */
@@ -480,15 +532,15 @@ class SepCheck(checker: CheckCaptures.CheckerAPI) extends tpd.TreeTraverser:
480
532
capt.println(
481
533
i """ check separate $fn( $args), fnCaptures = $fnCaptures,
482
534
| formalCaptures = ${args.map(arg => CaptureSet (formalCaptures(arg)))},
483
- | actualCaptures = ${args.map(arg => CaptureSet (captures (arg)))},
535
+ | actualCaptures = ${args.map(arg => CaptureSet (spanCaptures (arg)))},
484
536
| resultPeaks = ${resultPeaks},
485
537
| deps = ${deps.toList}""" )
486
538
val parts = qual :: args
487
539
var reported : SimpleIdentitySet [Tree ] = SimpleIdentitySet .empty
488
540
489
541
for arg <- args do
490
542
val argPeaks = PeaksPair (
491
- captures (arg).peaks,
543
+ spanCaptures (arg).peaks,
492
544
if arg.needsSepCheck then formalCaptures(arg).hiddenSet.peaks else emptyRefs)
493
545
val argDeps = deps(arg)
494
546
@@ -516,7 +568,7 @@ class SepCheck(checker: CheckCaptures.CheckerAPI) extends tpd.TreeTraverser:
516
568
val clashing = clashingPart(argPeaks.hidden, _.actual)
517
569
if ! clashing.isEmpty then
518
570
if ! reported.contains(clashing) then
519
- // println(i"CLASH $arg / ${argPeaks.formal} vs $clashing / ${peaksOfTree(clashing).actual} / ${captures (clashing).peaks}")
571
+ // println(i"CLASH $arg / ${argPeaks.formal} vs $clashing / ${peaksOfTree(clashing).actual} / ${spanCaptures (clashing).peaks}")
520
572
sepApplyError(fn, parts, arg, clashing)
521
573
else assert(! argDeps.isEmpty)
522
574
@@ -563,7 +615,9 @@ class SepCheck(checker: CheckCaptures.CheckerAPI) extends tpd.TreeTraverser:
563
615
564
616
for ref <- used do
565
617
val pos = consumed.clashing(ref)
566
- if pos != null then consumeError(ref, pos, tree.srcPos)
618
+ if pos != null then
619
+ // println(i"consumed so far ${consumed.refs.toList} with peaks ${consumed.exposedPeaks.toList}, used = $used, exposed = ${ref.exposedPeaks}")
620
+ consumeError(ref, pos, tree.srcPos)
567
621
end checkUse
568
622
569
623
/** If `tp` denotes some version of a singleton capability `x.type` the set `{x, x*}`
@@ -776,7 +830,7 @@ class SepCheck(checker: CheckCaptures.CheckerAPI) extends tpd.TreeTraverser:
776
830
checkConsumedRefs(toCheck, tpe, role, i " ${role.description} $tpe hides " , pos)
777
831
case TypeRole .Argument (arg) =>
778
832
if tpe.hasAnnotation(defn.ConsumeAnnot ) then
779
- val capts = captures (arg).footprint
833
+ val capts = spanCaptures (arg).footprint
780
834
checkConsumedRefs(capts, tpe, role, i " argument to consume parameter with type ${arg.nuType} refers to " , pos)
781
835
case _ =>
782
836
@@ -901,8 +955,8 @@ class SepCheck(checker: CheckCaptures.CheckerAPI) extends tpd.TreeTraverser:
901
955
def checkValOrDefDef (tree : ValOrDefDef )(using Context ): Unit =
902
956
if ! tree.symbol.isOneOf(TermParamOrAccessor ) && ! isUnsafeAssumeSeparate(tree.rhs) then
903
957
checkType(tree.tpt, tree.symbol)
904
- capt.println(i " sep check def ${tree.symbol}: ${tree.tpt} with ${captures (tree.tpt).hiddenSet.footprint}" )
905
- pushDef(tree, captures (tree.tpt).hiddenSet.deductSymRefs(tree.symbol))
958
+ capt.println(i " sep check def ${tree.symbol}: ${tree.tpt} with ${spanCaptures (tree.tpt).hiddenSet.footprint}" )
959
+ pushDef(tree, spanCaptures (tree.tpt).hiddenSet.deductSymRefs(tree.symbol))
906
960
907
961
def inSection [T ](op : => T )(using Context ): T =
908
962
val savedDefsShadow = defsShadow
@@ -918,8 +972,17 @@ class SepCheck(checker: CheckCaptures.CheckerAPI) extends tpd.TreeTraverser:
918
972
*/
919
973
def skippable (sym : Symbol )(using Context ): Boolean =
920
974
sym.isInlineMethod
921
- // We currently skip inline method bodies since these seem to generan
975
+ // We currently skip inline method bodies since these seem to generate
922
976
// spurious recheck completions. Test case is i20237.scala
977
+ || sym.is(Synthetic ) && sym.name.startsWith(" _" )
978
+ // Approximation of case class getters _1, _2, ... . We can't separation check them
979
+ // or colltest5/CollectionStrawManCC5_1.scala would fail with an error in
980
+ // case class Filter. TODO Investigate to decide what needs to be done
981
+ // - Can we make the accessors work somehow?
982
+ // - If not, should we disable just accessors or all synthetic methods?
983
+ // Reporting an error in a synthetic method is very frustrating since we don't have
984
+ // a position with source code to show. On the other hand, skipping all synthetic members
985
+ // might cause soundness issues.
923
986
924
987
/** Traverse `tree` and perform separation checks everywhere */
925
988
def traverse (tree : Tree )(using Context ): Unit =
@@ -929,7 +992,7 @@ class SepCheck(checker: CheckCaptures.CheckerAPI) extends tpd.TreeTraverser:
929
992
case tree @ Select (qual, _) if tree.symbol.is(Method ) && tree.symbol.isConsumeParam =>
930
993
traverseChildren(tree)
931
994
checkConsumedRefs(
932
- captures (qual).footprint, qual.nuType,
995
+ spanCaptures (qual).footprint, qual.nuType,
933
996
TypeRole .Qualifier (qual, tree.symbol),
934
997
i " call prefix of consume ${tree.symbol} refers to " , qual.srcPos)
935
998
case tree : GenericApply =>
0 commit comments