@@ -98,7 +98,7 @@ object SepCheck:
9898 var refs : Array [Capability ] = new Array (4 )
9999 var locs : Array [SrcPos ] = new Array (4 )
100100 var size = 0
101- var peaks : Refs = emptyRefs
101+ var exposedPeaks : Refs = emptyRefs
102102
103103 private def double [T <: AnyRef : ClassTag ](xs : Array [T ]): Array [T ] =
104104 val xs1 = new Array [T ](xs.length * 2 )
@@ -117,10 +117,10 @@ object SepCheck:
117117 if i < size then locs(i) else null
118118
119119 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
122122 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
124124 i += 1
125125 assert(i < size)
126126 locs(i)
@@ -133,7 +133,7 @@ object SepCheck:
133133 refs(size) = ref
134134 locs(size) = loc
135135 size += 1
136- peaks = peaks ++ ref.peaks
136+ exposedPeaks = exposedPeaks ++ ref.exposedPeaks
137137
138138 /** Add all references with their associated positions from `that` which
139139 * are not yet in the set.
@@ -146,14 +146,14 @@ object SepCheck:
146146 */
147147 def segment (op : => Unit ): ConsumedSet =
148148 val start = size
149- val savedPeaks = peaks
149+ val savedPeaks = exposedPeaks
150150 try
151151 op
152152 if size == start then EmptyConsumedSet
153153 else ConstConsumedSet (refs.slice(start, size), locs.slice(start, size))
154154 finally
155155 size = start
156- peaks = savedPeaks
156+ exposedPeaks = savedPeaks
157157 end MutConsumedSet
158158
159159 val EmptyConsumedSet = ConstConsumedSet (Array (), Array ())
@@ -164,6 +164,9 @@ object SepCheck:
164164
165165 extension (refs : Refs )
166166
167+ private def peaks_ (using Context ): Refs =
168+ refs.filter(_.isTerminalCapability)
169+
167170 /** The footprint of a set of references `refs` the smallest set `F` such that
168171 * 1. all capabilities in `refs` satisfying (1) are in `F`
169172 * 2. if `f in F` then the footprint of `f`'s info is also in `F`.
@@ -179,6 +182,26 @@ object SepCheck:
179182 val elems : Refs = refs.filter(retain)
180183 recur(elems, elems.toList)
181184
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+
182205 private def peaks (using Context ): Refs =
183206 def recur (seen : Refs , acc : Refs , newElems : List [Capability ]): Refs = trace(i " peaks $acc, $newElems = " ):
184207 newElems match
@@ -199,15 +222,43 @@ object SepCheck:
199222 then recur(seen + newElem, acc, newElems1)
200223 else recur(seen + newElem, acc, newElem.captureSetOfInfo.dropEmpties().elems.toList ++ newElems1)
201224 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)
203227
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+ */
205233 private def sharedWith (other : Refs )(using Context ): Refs =
206234 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
209242 common(refs, other) ++ common(other, refs)
210243
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+
211262 /** The overlap of two footprint sets F1 and F2. This contains all exclusive references `r`
212263 * such that one of the following is true:
213264 * 1.
@@ -282,6 +333,7 @@ object SepCheck:
282333
283334 extension (ref : Capability )
284335 def peaks (using Context ): Refs = SimpleIdentitySet (ref).peaks
336+ def exposedPeaks (using Context ): Refs = SimpleIdentitySet (ref).exposedPeaks
285337
286338class SepCheck (checker : CheckCaptures .CheckerAPI ) extends tpd.TreeTraverser :
287339 import checker .*
@@ -313,7 +365,7 @@ class SepCheck(checker: CheckCaptures.CheckerAPI) extends tpd.TreeTraverser:
313365 arg.formalType.orElse(arg.nuType).deepCaptureSet.elems
314366
315367 /** 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 =
317369 tree.nuType.spanCaptureSet.elems
318370
319371 /** The deep capture set if the type of `tree` */
@@ -480,15 +532,15 @@ class SepCheck(checker: CheckCaptures.CheckerAPI) extends tpd.TreeTraverser:
480532 capt.println(
481533 i """ check separate $fn( $args), fnCaptures = $fnCaptures,
482534 | formalCaptures = ${args.map(arg => CaptureSet (formalCaptures(arg)))},
483- | actualCaptures = ${args.map(arg => CaptureSet (captures (arg)))},
535+ | actualCaptures = ${args.map(arg => CaptureSet (spanCaptures (arg)))},
484536 | resultPeaks = ${resultPeaks},
485537 | deps = ${deps.toList}""" )
486538 val parts = qual :: args
487539 var reported : SimpleIdentitySet [Tree ] = SimpleIdentitySet .empty
488540
489541 for arg <- args do
490542 val argPeaks = PeaksPair (
491- captures (arg).peaks,
543+ spanCaptures (arg).peaks,
492544 if arg.needsSepCheck then formalCaptures(arg).hiddenSet.peaks else emptyRefs)
493545 val argDeps = deps(arg)
494546
@@ -516,7 +568,7 @@ class SepCheck(checker: CheckCaptures.CheckerAPI) extends tpd.TreeTraverser:
516568 val clashing = clashingPart(argPeaks.hidden, _.actual)
517569 if ! clashing.isEmpty then
518570 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}")
520572 sepApplyError(fn, parts, arg, clashing)
521573 else assert(! argDeps.isEmpty)
522574
@@ -563,7 +615,9 @@ class SepCheck(checker: CheckCaptures.CheckerAPI) extends tpd.TreeTraverser:
563615
564616 for ref <- used do
565617 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)
567621 end checkUse
568622
569623 /** 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:
776830 checkConsumedRefs(toCheck, tpe, role, i " ${role.description} $tpe hides " , pos)
777831 case TypeRole .Argument (arg) =>
778832 if tpe.hasAnnotation(defn.ConsumeAnnot ) then
779- val capts = captures (arg).footprint
833+ val capts = spanCaptures (arg).footprint
780834 checkConsumedRefs(capts, tpe, role, i " argument to consume parameter with type ${arg.nuType} refers to " , pos)
781835 case _ =>
782836
@@ -901,8 +955,8 @@ class SepCheck(checker: CheckCaptures.CheckerAPI) extends tpd.TreeTraverser:
901955 def checkValOrDefDef (tree : ValOrDefDef )(using Context ): Unit =
902956 if ! tree.symbol.isOneOf(TermParamOrAccessor ) && ! isUnsafeAssumeSeparate(tree.rhs) then
903957 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))
906960
907961 def inSection [T ](op : => T )(using Context ): T =
908962 val savedDefsShadow = defsShadow
@@ -918,8 +972,17 @@ class SepCheck(checker: CheckCaptures.CheckerAPI) extends tpd.TreeTraverser:
918972 */
919973 def skippable (sym : Symbol )(using Context ): Boolean =
920974 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
922976 // 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.
923986
924987 /** Traverse `tree` and perform separation checks everywhere */
925988 def traverse (tree : Tree )(using Context ): Unit =
@@ -929,7 +992,7 @@ class SepCheck(checker: CheckCaptures.CheckerAPI) extends tpd.TreeTraverser:
929992 case tree @ Select (qual, _) if tree.symbol.is(Method ) && tree.symbol.isConsumeParam =>
930993 traverseChildren(tree)
931994 checkConsumedRefs(
932- captures (qual).footprint, qual.nuType,
995+ spanCaptures (qual).footprint, qual.nuType,
933996 TypeRole .Qualifier (qual, tree.symbol),
934997 i " call prefix of consume ${tree.symbol} refers to " , qual.srcPos)
935998 case tree : GenericApply =>
0 commit comments