@@ -142,6 +142,8 @@ object SepCheck:
142
142
143
143
val EmptyConsumedSet = ConstConsumedSet (Array (), Array ())
144
144
145
+ case class PeaksPair (actual : Refs , formal : Refs )
146
+
145
147
class SepCheck (checker : CheckCaptures .CheckerAPI ) extends tpd.TreeTraverser :
146
148
import checker .*
147
149
import SepCheck .*
@@ -194,6 +196,52 @@ class SepCheck(checker: CheckCaptures.CheckerAPI) extends tpd.TreeTraverser:
194
196
val elems : Refs = refs.filter(! _.isMaxCapability)
195
197
recur(elems, elems.toList)
196
198
199
+ /** The members of type Fresh.Cap(...) or Fresh.Cap(...).rd in the transitive closure
200
+ * of this set
201
+ */
202
+ private def freshElems (using Context ): Refs =
203
+ def recur (seen : Refs , acc : Refs , newElems : List [CaptureRef ]): Refs = newElems match
204
+ case newElem :: newElems1 =>
205
+ if seen.contains(newElem) then
206
+ recur(seen, acc, newElems1)
207
+ else newElem.stripReadOnly match
208
+ case Fresh .Cap (_) =>
209
+ recur(seen, acc + newElem, newElems1)
210
+ // case _: TypeRef | _: TypeParamRef =>
211
+ // recur(seen + newElem, acc, newElems1)
212
+ case _ =>
213
+ recur(seen + newElem, acc, newElem.captureSetOfInfo.elems.toList ++ newElems1)
214
+ case Nil => acc
215
+ recur(emptyRefs, emptyRefs, refs.toList)
216
+
217
+ private def peaks (using Context ): Refs =
218
+ def recur (seen : Refs , acc : Refs , newElems : List [CaptureRef ]): Refs = newElems match
219
+ case newElem :: newElems1 =>
220
+ if seen.contains(newElem) then
221
+ recur(seen, acc, newElems1)
222
+ else newElem.stripReadOnly match
223
+ case Fresh .Cap (hidden) =>
224
+ if hidden.deps.isEmpty then recur(seen + newElem, acc + newElem, newElems1)
225
+ else
226
+ val superCaps =
227
+ if newElem.isReadOnly then hidden.superCaps.map(_.readOnly)
228
+ else hidden.superCaps
229
+ recur(seen + newElem, acc, superCaps ++ newElems)
230
+ case _ =>
231
+ if newElem.isMaxCapability
232
+ // || newElem.isInstanceOf[TypeRef | TypeParamRef]
233
+ then recur(seen + newElem, acc, newElems1)
234
+ else recur(seen + newElem, acc, newElem.captureSetOfInfo.elems.toList ++ newElems1)
235
+ case Nil => acc
236
+ recur(emptyRefs, emptyRefs, refs.toList)
237
+
238
+ /** The shared peaks between `refs` and `other` */
239
+ private def sharedWith (other : Refs )(using Context ): Refs =
240
+ def common (refs1 : Refs , refs2 : Refs ) =
241
+ refs1.filter: ref =>
242
+ ! ref.isReadOnly && refs2.exists(_.stripReadOnly eq ref)
243
+ common(refs, other) ++ common(other, refs)
244
+
197
245
/** The overlap of two footprint sets F1 and F2. This contains all exclusive references `r`
198
246
* such that one of the following is true:
199
247
* 1.
@@ -266,6 +314,11 @@ class SepCheck(checker: CheckCaptures.CheckerAPI) extends tpd.TreeTraverser:
266
314
recur(refs)
267
315
end containsHidden
268
316
317
+ def hiddenSet (using Context ): Refs =
318
+ freshElems.flatMap:
319
+ case Fresh .Cap (hidden) => hidden.elems
320
+ case ReadOnlyCapability (Fresh .Cap (hidden)) => hidden.elems.map(_.readOnly)
321
+
269
322
/** Subtract all elements that are covered by some element in `others` from this set. */
270
323
private def deduct (others : Refs )(using Context ): Refs =
271
324
refs.filter: ref =>
@@ -297,29 +350,21 @@ class SepCheck(checker: CheckCaptures.CheckerAPI) extends tpd.TreeTraverser:
297
350
298
351
/** Report a separation failure in an application `fn(args)`
299
352
* @param fn the function
300
- * @param args the flattened argument lists
301
- * @param argIdx the index of the failing argument in `args`, starting at 0
302
- * @param overlap the overlap causing the failure
303
- * @param hiddenInArg the hidxden set of the type of the failing argument
304
- * @param footprints a sequence of partial footprints, and the index of the
305
- * last argument they cover.
306
- * @param deps cross argument dependencies: maps argument trees to
307
- * those other arguments that where mentioned by coorresponding
308
- * formal parameters.
353
+ * @param parts the function prefix followed by the flattened argument list
354
+ * @param polyArg the clashing argument to a polymorphic formal
355
+ * @param clashing the argument with which it clashes
309
356
*/
310
- private def sepApplyError (fn : Tree , args : List [Tree ], argIdx : Int ,
311
- overlap : Refs , hiddenInArg : Refs , footprints : List [(Refs , Int )],
312
- deps : collection.Map [Tree , List [Tree ]])(using Context ): Unit =
313
- val arg = args(argIdx)
357
+ def sepApplyError (fn : Tree , parts : List [Tree ], polyArg : Tree , clashing : Tree )(using Context ): Unit =
358
+ val polyArgIdx = parts.indexOf(polyArg).ensuring(_ >= 0 ) - 1
359
+ val clashIdx = parts.indexOf(clashing).ensuring(_ >= 0 )
314
360
def paramName (mt : Type , idx : Int ): Option [Name ] = mt match
315
361
case mt @ MethodType (pnames) =>
316
362
if idx < pnames.length then Some (pnames(idx)) else paramName(mt.resType, idx - pnames.length)
317
363
case mt : PolyType => paramName(mt.resType, idx)
318
364
case _ => None
319
- def formalName = paramName(fn.nuType.widen, argIdx ) match
365
+ def formalName = paramName(fn.nuType.widen, polyArgIdx ) match
320
366
case Some (pname) => i " $pname "
321
367
case _ => " "
322
- def whatStr = if overlap.size == 1 then " this capability is" else " these capabilities are"
323
368
def qualifier = methPart(fn) match
324
369
case Select (qual, _) => qual
325
370
case _ => EmptyTree
@@ -329,43 +374,45 @@ class SepCheck(checker: CheckCaptures.CheckerAPI) extends tpd.TreeTraverser:
329
374
def funStr =
330
375
if isShowableMethod then i " ${fn.symbol}: ${fn.symbol.info}"
331
376
else i " a function of type ${funType.widen}"
332
- val clashIdx = footprints
333
- .collect:
334
- case (fp, idx) if ! hiddenInArg.overlapWith(fp).isEmpty => idx
335
- .head
336
- def whereStr = clashIdx match
377
+ def clashArgStr = clashIdx match
337
378
case 0 => " function prefix"
338
379
case 1 => " first argument "
339
380
case 2 => " second argument"
340
381
case 3 => " third argument "
341
382
case n => s " ${n}th argument "
342
- def clashTree =
343
- if clashIdx == 0 then qualifier
344
- else args(clashIdx - 1 )
345
383
def clashTypeStr =
346
384
if clashIdx == 0 && ! isShowableMethod then " " // we already mentioned the type in `funStr`
347
- else i " with type ${clashTree.nuType}"
348
- def clashCaptures = captures(clashTree)
349
- def hiddenCaptures = formalCaptures(arg).hidden
350
- def clashFootprint = clashCaptures.footprint
351
- def hiddenFootprint = hiddenCaptures.footprint
352
- def declaredFootprint = deps(arg).map(captures(_)).foldLeft(emptyRefs)(_ ++ _).footprint
353
- def footprintOverlap = hiddenFootprint.overlapWith(clashFootprint).deduct(declaredFootprint)
385
+ else i " with type ${clashing.nuType}"
386
+ val hiddenSet = formalCaptures(polyArg).hiddenSet
387
+ val clashSet = captures(clashing)
388
+ val hiddenFootprint = hiddenSet.footprint
389
+ val clashFootprint = clashSet.footprint
390
+ val overlapStr =
391
+ // The overlap of footprints, or, of this empty the set of shared peaks.
392
+ // We prefer footprint overlap since it tends to be more informative.
393
+ val overlap = hiddenFootprint.overlapWith(clashFootprint)
394
+ if ! overlap.isEmpty then i " ${CaptureSet (overlap)}"
395
+ else
396
+ val sharedPeaks = hiddenSet.peaks.sharedWith(clashSet.peaks)
397
+ assert(! sharedPeaks.isEmpty,
398
+ i " no overlap for $polyArg: $hiddenSet} vs $clashing: $clashSet" )
399
+ sharedPeaks.nth(0 ) match
400
+ case fresh @ Fresh .Cap (hidden) =>
401
+ if hidden.owner.exists then i " cap of ${hidden.owner}" else i " $fresh"
402
+
354
403
report.error(
355
- em """ Separation failure: argument of type ${arg .nuType}
404
+ em """ Separation failure: argument of type ${polyArg .nuType}
356
405
|to $funStr
357
- |corresponds to capture-polymorphic formal parameter ${formalName}of type ${arg .formalType}
358
- |and captures ${CaptureSet (overlap)} , but $whatStr also passed separately
359
- |in the ${whereStr .trim}$clashTypeStr.
406
+ |corresponds to capture-polymorphic formal parameter ${formalName}of type ${polyArg .formalType}
407
+ |and hides capabilities ${CaptureSet (hiddenSet)} .
408
+ |Some of these overlap with the captures of the ${clashArgStr .trim}$clashTypeStr.
360
409
|
361
- | Capture set of $whereStr : ${CaptureSet (clashCaptures)}
362
- | Hidden set of current argument : ${CaptureSet (hiddenCaptures)}
363
- | Footprint of $whereStr : ${CaptureSet (clashFootprint)}
364
- | Hidden footprint of current argument : ${CaptureSet (hiddenFootprint)}
365
- | Declared footprint of current argument: ${CaptureSet (declaredFootprint)}
366
- | Undeclared overlap of footprints : ${CaptureSet (footprintOverlap)}""" ,
367
- arg.srcPos)
368
- end sepApplyError
410
+ | Hidden set of current argument : ${CaptureSet (hiddenSet)}
411
+ | Hidden footprint of current argument : ${CaptureSet (hiddenSet.footprint)}
412
+ | Capture set of $clashArgStr : ${CaptureSet (clashSet)}
413
+ | Footprint set of $clashArgStr : ${CaptureSet (clashSet.footprint)}
414
+ | The two sets overlap at : $overlapStr""" ,
415
+ polyArg.srcPos)
369
416
370
417
/** Report a use/definition failure, where a previously hidden capability is
371
418
* used again.
@@ -445,37 +492,58 @@ class SepCheck(checker: CheckCaptures.CheckerAPI) extends tpd.TreeTraverser:
445
492
* formal parameters.
446
493
*/
447
494
private def checkApply (fn : Tree , args : List [Tree ], deps : collection.Map [Tree , List [Tree ]])(using Context ): Unit =
448
- val fnCaptures = methPart(fn) match
449
- case Select (qual, _) => qual.nuType.captureSet
450
- case _ => CaptureSet .empty
451
- capt.println(i " check separate $fn( $args), fnCaptures = $fnCaptures, argCaptures = ${args.map(arg => CaptureSet (formalCaptures(arg)))}, deps = ${deps.toList}" )
452
- var footprint = fnCaptures.elems.footprint
453
- val footprints = mutable.ListBuffer [(Refs , Int )]((footprint, 0 ))
454
- val indexedArgs = args.zipWithIndex
455
-
456
- // First, compute all footprints of arguments to monomorphic pararameters,
457
- // separately in `footprints`, and their union in `footprint`.
458
- for (arg, idx) <- indexedArgs do
459
- if ! arg.needsSepCheck then
460
- footprint = footprint ++ captures(arg).footprint.deductCapturesOf(deps(arg))
461
- footprints += ((footprint, idx + 1 ))
462
-
463
- // Then, for each argument to a polymorphic parameter:
464
- // - check formal type via checkType
465
- // - check that hidden set of argument does not overlap with current footprint
466
- // - add footprint of the deep capture set of actual type of argument
467
- // to global footprint(s)
468
- for (arg, idx) <- indexedArgs do
495
+ val (qual, fnCaptures) = methPart(fn) match
496
+ case Select (qual, _) => (qual, qual.nuType.captureSet)
497
+ case _ => (fn, CaptureSet .empty)
498
+ var currentPeaks = PeaksPair (fnCaptures.elems.peaks, emptyRefs)
499
+ val peaksOfTree : Map [Tree , PeaksPair ] =
500
+ ((qual -> currentPeaks) :: args.map: arg =>
501
+ arg -> PeaksPair (
502
+ captures(arg).peaks,
503
+ if arg.needsSepCheck then formalCaptures(arg).hiddenSet.peaks else emptyRefs)
504
+ ).toMap
505
+ capt.println(
506
+ i """ check separate $fn( $args), fnCaptures = $fnCaptures,
507
+ | formalCaptures = ${args.map(arg => CaptureSet (formalCaptures(arg)))},
508
+ | actualCaptures = ${args.map(arg => CaptureSet (captures(arg)))},
509
+ | formalPeaks = ${peaksOfTree.values.map(_.formal).toList}
510
+ | actualPeaks = ${peaksOfTree.values.map(_.actual).toList}
511
+ | deps = ${deps.toList}""" )
512
+ val parts = qual :: args
513
+
514
+ for arg <- args do
515
+ val argPeaks = peaksOfTree(arg)
516
+ val argDeps = deps(arg)
517
+
518
+ def clashingPart (argPeaks : Refs , selector : PeaksPair => Refs ): Tree =
519
+ parts.iterator.takeWhile(_ ne arg).find: prev =>
520
+ ! argDeps.contains(prev)
521
+ && ! selector(peaksOfTree(prev)).sharedWith(argPeaks).isEmpty
522
+ .getOrElse(EmptyTree )
523
+
524
+ // 1. test argPeaks.actual against previously captured formals
525
+ if ! argPeaks.actual.sharedWith(currentPeaks.formal).isEmpty then
526
+ val clashing = clashingPart(argPeaks.actual, _.formal)
527
+ if ! clashing.isEmpty then sepApplyError(fn, parts, clashing, arg)
528
+ else assert(! argDeps.isEmpty)
529
+
469
530
if arg.needsSepCheck then
470
- val ac = formalCaptures(arg )
531
+ // println(i"testing $arg, ${argPeaks.actual}/${argPeaks.formal} against ${currentPeaks.actual}" )
471
532
checkType(arg.formalType, arg.srcPos, TypeRole .Argument (arg))
472
- val hiddenInArg = ac.hidden.footprint
473
- // println(i"check sep $arg: $ac, footprint so far = $footprint, hidden = $hiddenInArg")
474
- val overlap = hiddenInArg.overlapWith(footprint).deductCapturesOf(deps(arg))
475
- if ! overlap.isEmpty then
476
- sepApplyError(fn, args, idx, overlap, hiddenInArg, footprints.toList, deps)
477
- footprint ++= captures(arg).footprint
478
- footprints += ((footprint, idx + 1 ))
533
+ // 2. test argPeaks.formal against previously hidden actuals
534
+ if ! argPeaks.formal.sharedWith(currentPeaks.actual).isEmpty then
535
+ val clashing = clashingPart(argPeaks.formal, _.actual)
536
+ if ! clashing.isEmpty then
537
+ if ! clashing.needsSepCheck then
538
+ // if clashing needs a separation check then we already got an erro
539
+ // in (1) at position of clashing. No need to report it twice.
540
+ // println(i"CLASH $arg / ${argPeaks.formal} vs $clashing / ${peaksOfTree(clashing).actual} / ${captures(clashing).peaks}")
541
+ sepApplyError(fn, parts, arg, clashing)
542
+ else assert(! argDeps.isEmpty)
543
+
544
+ currentPeaks = PeaksPair (
545
+ currentPeaks.actual ++ argPeaks.actual,
546
+ currentPeaks.formal ++ argPeaks.formal)
479
547
end checkApply
480
548
481
549
/** The def/use overlap between the references `hiddenByDef` hidden by
@@ -757,7 +825,7 @@ class SepCheck(checker: CheckCaptures.CheckerAPI) extends tpd.TreeTraverser:
757
825
case dep : TermParamRef =>
758
826
argMap(dep.binder)(dep.paramNum) :: Nil
759
827
case dep : ThisType if dep.cls == fn.symbol.owner =>
760
- val Select (qual, _) = fn : @ unchecked
828
+ val Select (qual, _) = fn : @ unchecked // TODO can we use fn instead?
761
829
qual :: Nil
762
830
case _ =>
763
831
Nil
0 commit comments