@@ -46,8 +46,9 @@ object SepCheck:
4646 /** The role in which a checked type appears, used for composing error messages */
4747 enum TypeRole :
4848 case Result (sym : Symbol , inferred : Boolean )
49- case Argument (arg : Tree )
49+ case Argument (arg : Tree , meth : Symbol )
5050 case Qualifier (qual : Tree , meth : Symbol )
51+ case RHS (rhs : Tree , mvar : Symbol )
5152
5253 /** If this is a Result tole, the associated symbol, otherwise NoSymbol */
5354 def dclSym = this match
@@ -59,11 +60,20 @@ object SepCheck:
5960 case Result (sym, inferred) =>
6061 def inferredStr = if inferred then " inferred" else " "
6162 def resultStr = if sym.info.isInstanceOf [MethodicType ] then " result" else " "
62- i " $sym's $inferredStr$resultStr type "
63- case TypeRole .Argument (_) =>
63+ i " ${ sym.sanitizedDescription} 's $inferredStr$resultStr type "
64+ case TypeRole .Argument (_, _ ) =>
6465 " the argument's adapted type"
6566 case TypeRole .Qualifier (_, meth) =>
66- i " the type of the prefix to a call of $meth"
67+ i " the type of the prefix to a call of ${meth.sanitizedDescription}"
68+ case RHS (rhs, lhsSym) =>
69+ i " the type of the value assigned to $lhsSym"
70+
71+ /** A description how a reference was consumed in this role */
72+ def howConsumed (using Context ): String = this match
73+ case TypeRole .Result (meth, _) => i " returned in the result of ${meth.sanitizedDescription}"
74+ case TypeRole .Argument (_, meth) => i " passed as a consume parameter to ${meth.sanitizedDescription}"
75+ case TypeRole .Qualifier (_, meth) => i " used as a prefix to consume ${meth.sanitizedDescription}"
76+ case TypeRole .RHS (_, mvar) => i " consumed in an assignment to $mvar"
6777 end TypeRole
6878
6979 /** A class for segmented sets of consumed references.
@@ -74,13 +84,13 @@ object SepCheck:
7484 /** The references in the set. The array should be treated as immutable in client code */
7585 def refs : Array [Capability ]
7686
77- /** The associated source positoons. The array should be treated as immutable in client code */
78- def locs : Array [SrcPos ]
87+ /** The associated source positoons and type roles . The array should be treated as immutable in client code */
88+ def locs : Array [( SrcPos , TypeRole ) ]
7989
8090 /** The number of references in the set */
8191 def size : Int
8292
83- def toMap : Map [Capability , SrcPos ] = refs.take(size).zip(locs).toMap
93+ def toMap : Map [Capability , ( SrcPos , TypeRole ) ] = refs.take(size).zip(locs).toMap
8494
8595 def show (using Context ) =
8696 s " [ ${toMap.map((ref, loc) => i " $ref -> $loc" ).toList}] "
@@ -89,13 +99,13 @@ object SepCheck:
8999 /** A fixed consumed set consisting of the given references `refs` and
90100 * associated source positions `locs`
91101 */
92- class ConstConsumedSet (val refs : Array [Capability ], val locs : Array [SrcPos ]) extends ConsumedSet :
102+ class ConstConsumedSet (val refs : Array [Capability ], val locs : Array [( SrcPos , TypeRole ) ]) extends ConsumedSet :
93103 def size = refs.size
94104
95105 /** A mutable consumed set, which is initially empty */
96106 class MutConsumedSet extends ConsumedSet :
97107 var refs : Array [Capability ] = new Array (4 )
98- var locs : Array [SrcPos ] = new Array (4 )
108+ var locs : Array [( SrcPos , TypeRole ) ] = new Array (4 )
99109 var size = 0
100110 var directPeaks : Refs = emptyRefs
101111
@@ -110,12 +120,12 @@ object SepCheck:
110120 locs = double(locs)
111121
112122 /** If `ref` is in the set, its associated source position, otherwise `null` */
113- def get (ref : Capability ): SrcPos | Null =
123+ def get (ref : Capability ): ( SrcPos , TypeRole ) | Null =
114124 var i = 0
115125 while i < size && (refs(i) ne ref) do i += 1
116126 if i < size then locs(i) else null
117127
118- def clashing (ref : Capability )(using Context ): SrcPos | Null =
128+ def clashing (ref : Capability )(using Context ): ( SrcPos , TypeRole ) | Null =
119129 val refPeaks = ref.directPeaks
120130 if ! directPeaks.sharedPeaks(refPeaks).isEmpty then
121131 var i = 0
@@ -126,7 +136,7 @@ object SepCheck:
126136 else null
127137
128138 /** If `ref` is not yet in the set, add it with given source position */
129- def put (ref : Capability , loc : SrcPos )(using Context ): Unit =
139+ def put (ref : Capability , loc : ( SrcPos , TypeRole ) )(using Context ): Unit =
130140 if get(ref) == null then
131141 ensureCapacity(1 )
132142 refs(size) = ref
@@ -448,21 +458,22 @@ class SepCheck(checker: CheckCaptures.CheckerAPI) extends tpd.TreeTraverser:
448458 * @param loc the position where the capability was consumed
449459 * @param pos the position where the capability was used again
450460 */
451- def consumeError (ref : Capability , loc : SrcPos , pos : SrcPos )(using Context ): Unit =
461+ def consumeError (ref : Capability , loc : (SrcPos , TypeRole ), pos : SrcPos )(using Context ): Unit =
462+ val (locPos, role) = loc
452463 report.error(
453- em """ Separation failure: Illegal access to $ref, which was passed to a
454- |consume parameter or was used as a prefix to a consume method on line ${loc.line + 1 }
455- |and therefore is no longer available. """ ,
464+ em """ Separation failure: Illegal access to $ref, which was ${role.howConsumed}
465+ |on line ${locPos.line + 1 } and therefore is no longer available. """ ,
456466 pos)
457467
458468 /** Report a failure where a capability is consumed in a loop.
459469 * @param ref the capability
460470 * @param pos the position where the capability was consumed
461471 */
462- def consumeInLoopError (ref : Capability , pos : SrcPos )(using Context ): Unit =
472+ def consumeInLoopError (ref : Capability , loc : (SrcPos , TypeRole ))(using Context ): Unit =
473+ val (pos, role) = loc
463474 report.error(
464475 em """ Separation failure: $ref appears in a loop, therefore it cannot
465- |be passed to a consume parameter or be used as a prefix of a consume method call . """ ,
476+ |be ${role.howConsumed} . """ ,
466477 pos)
467478
468479 // ------------ Checks -----------------------------------------------------
@@ -535,7 +546,7 @@ class SepCheck(checker: CheckCaptures.CheckerAPI) extends tpd.TreeTraverser:
535546
536547 if arg.needsSepCheck then
537548 // println(i"testing $arg, formal = ${arg.formalType}, peaks = ${argPeaks.actual}/${argPeaks.hidden} against ${currentPeaks.actual}")
538- checkType(arg.formalType, arg.srcPos, TypeRole .Argument (arg))
549+ checkType(arg.formalType, arg.srcPos, TypeRole .Argument (arg, fn.symbol ))
539550 // 2. test argPeaks.hidden against previously captured actuals
540551 if ! argPeaks.hidden.sharedPeaks(currentPeaks.actual).isEmpty then
541552 val clashing = clashingPart(argPeaks.hidden, _.actual)
@@ -558,6 +569,35 @@ class SepCheck(checker: CheckCaptures.CheckerAPI) extends tpd.TreeTraverser:
558569 sepApplyError(fn, parts, arg, app)
559570 end checkApply
560571
572+ def checkAssign (tree : Assign )(using Context ): Unit =
573+ val Assign (lhs, rhs) = tree
574+ lhs.nuType match
575+ case lhsType : ObjectCapability if lhsType.pathOwner.exists =>
576+ val lhsOwner = lhsType.pathOwner
577+
578+ /** A reference escapes into an outer var if it would have produced a
579+ * level error if it did not have an Unscoped, unprefixed FreshCap
580+ * in some underlying capture set.
581+ */
582+ def escapes (ref : Capability ): Boolean = ref.pathRoot match
583+ case ref @ FreshCap (NoPrefix )
584+ if ref.classifier.derivesFrom(defn.Caps_Unscoped ) =>
585+ // we have an escaping reference if the FreshCap's adjustded owner
586+ // is properly contained inside the scope of the variable.
587+ ref.adjustOwner(ref.ccOwner).isProperlyContainedIn(lhsOwner)
588+ case _ =>
589+ ref.visibility.isProperlyContainedIn(lhsOwner) // ref itself is not levelOK
590+ && ! ref.isTerminalCapability // ... and ...
591+ && ref.captureSetOfInfo.elems.exists(escapes) // some underlying capability escapes
592+
593+ val escaping = spanCaptures(rhs).filter(escapes)
594+ capt.println(i " check assign $tree, $lhsOwner, escaping = $escaping, ${escaping.directFootprint.nonPeaks}" )
595+ checkConsumedRefs(escaping.directFootprint.nonPeaks, rhs.nuType,
596+ TypeRole .RHS (rhs, lhs.symbol),
597+ s " the value assigned to ${lhs.symbol} refers to " , tree.srcPos)
598+ case _ =>
599+ end checkAssign
600+
561601 /** 1. Check that the capabilities used at `tree` don't overlap with
562602 * capabilities hidden by a previous definition.
563603 * 2. Also check that none of the used capabilities was consumed before.
@@ -567,7 +607,6 @@ class SepCheck(checker: CheckCaptures.CheckerAPI) extends tpd.TreeTraverser:
567607 if ! used.isEmpty then
568608 capt.println(i " check use $tree: $used" )
569609 val usedPeaks = used.allPeaks
570- val overlap = defsShadow.allPeaks.sharedPeaks(usedPeaks)
571610 if ! defsShadow.allPeaks.sharedPeaks(usedPeaks).isEmpty then
572611 // Drop all Selects unless they select from a `this`
573612 def pathRoot (tree : Tree ): Tree = tree match
@@ -595,10 +634,10 @@ class SepCheck(checker: CheckCaptures.CheckerAPI) extends tpd.TreeTraverser:
595634 end if
596635
597636 for ref <- used do
598- val pos = consumed.clashing(ref)
599- if pos != null then
637+ val loc = consumed.clashing(ref)
638+ if loc != null then
600639 // println(i"consumed so far ${consumed.refs.toList} with peaks ${consumed.directPeaks.toList}, used = $used, exposed = ${ref.directPeaks }")
601- consumeError(ref, pos , tree.srcPos)
640+ consumeError(ref, loc , tree.srcPos)
602641 end checkUse
603642
604643 /** If `tp` denotes some version of a singleton capability `x.type` the set `{x, x*}`
@@ -673,10 +712,10 @@ class SepCheck(checker: CheckCaptures.CheckerAPI) extends tpd.TreeTraverser:
673712 pos)
674713
675714 role match
676- case _ : TypeRole .Argument | _ : TypeRole .Qualifier =>
715+ case _ : TypeRole .Argument | _ : TypeRole .Qualifier | _ : TypeRole . RHS =>
677716 for ref <- refsToCheck do
678717 if ! ref.stripReadOnly.isKnownClassifiedAs(defn.Caps_SharedCapability ) then
679- consumed.put(ref, pos)
718+ consumed.put(ref, ( pos, role) )
680719 case _ =>
681720 end checkConsumedRefs
682721
@@ -696,7 +735,7 @@ class SepCheck(checker: CheckCaptures.CheckerAPI) extends tpd.TreeTraverser:
696735 */
697736 extension (refs : Refs ) def pruned =
698737 val deductedType = role match
699- case TypeRole .Argument (arg) => arg.tpe
738+ case TypeRole .Argument (arg, _ ) => arg.tpe
700739 case _ => tpe
701740 refs.deductSymRefs(role.dclSym).deduct(explicitRefs(deductedType))
702741
@@ -809,7 +848,7 @@ class SepCheck(checker: CheckCaptures.CheckerAPI) extends tpd.TreeTraverser:
809848 val refs = tpe.deepCaptureSet.elems
810849 val toCheck = refs.transHiddenSet.directFootprint.nonPeaks.deduct(refs.directFootprint.nonPeaks)
811850 checkConsumedRefs(toCheck, tpe, role, i " ${role.description} $tpe hides " , pos)
812- case TypeRole .Argument (arg) =>
851+ case TypeRole .Argument (arg, _ ) =>
813852 if tpe.hasAnnotation(defn.ConsumeAnnot ) then
814853 val capts = spanCaptures(arg).directFootprint.nonPeaks
815854 checkConsumedRefs(capts, tpe, role, i " argument to consume parameter with type ${arg.nuType} refers to " , pos)
@@ -982,6 +1021,9 @@ class SepCheck(checker: CheckCaptures.CheckerAPI) extends tpd.TreeTraverser:
9821021 tree.tpe match
9831022 case _ : MethodOrPoly =>
9841023 case _ => traverseApply(tree)
1024+ case tree : Assign =>
1025+ traverseChildren(tree)
1026+ checkAssign(tree)
9851027 case _ : Block | _ : Template =>
9861028 traverseSection(tree)
9871029 case tree : ValDef =>
@@ -1029,8 +1071,8 @@ class SepCheck(checker: CheckCaptures.CheckerAPI) extends tpd.TreeTraverser:
10291071 case tree : WhileDo =>
10301072 val loopConsumed = consumed.segment(traverseChildren(tree))
10311073 if loopConsumed.size != 0 then
1032- val (ref, pos ) = loopConsumed.toMap.head
1033- consumeInLoopError(ref, pos )
1074+ val (ref, loc ) = loopConsumed.toMap.head
1075+ consumeInLoopError(ref, loc )
10341076 case _ =>
10351077 traverseChildren(tree)
10361078end SepCheck
0 commit comments