@@ -39,10 +39,25 @@ object CheckCaptures:
3939 sym
4040 end Pre
4141
42+ /** A class describing environments.
43+ * @param owner the current owner
44+ * @param captured the caputure set containing all references to tracked free variables outside of boxes
45+ * @param isBoxed true if the environment is inside a box (in which case references are not counted)
46+ * @param outer0 the next enclosing environment or null
47+ */
4248 case class Env (owner : Symbol , captured : CaptureSet , isBoxed : Boolean , outer0 : Env | Null ):
4349 def outer = outer0.nn
50+
51+ def isOutermost = outer0 == null
52+
53+ /** If an environment is open it tracks free references */
4454 def isOpen = ! captured.isAlwaysEmpty && ! isBoxed
55+ end Env
4556
57+ /** Similar normal substParams, but this is an approximating type map that
58+ * maps parameters in contravariant capture sets to the empty set.
59+ * TODO: check what happens with non-variant.
60+ */
4661 final class SubstParamsMap (from : BindingType , to : List [Type ])(using Context )
4762 extends ApproximatingTypeMap , IdempotentCaptRefMap :
4863 def apply (tp : Type ): Type = tp match
@@ -56,7 +71,7 @@ object CheckCaptures:
5671 case _ =>
5772 mapOver(tp)
5873
59- /** Check that a @retains annotation only mentions references that can be tracked
74+ /** Check that a @retains annotation only mentions references that can be tracked.
6075 * This check is performed at Typer.
6176 */
6277 def checkWellformed (ann : Tree )(using Context ): Unit =
@@ -149,75 +164,90 @@ class CheckCaptures extends Recheck, SymTransformer:
149164 case _ =>
150165 traverseChildren(t)
151166
167+ /** If `tpt` is an inferred type, interpolate capture set variables appearing contra-
168+ * variantly in it.
169+ */
152170 private def interpolateVarsIn (tpt : Tree )(using Context ): Unit =
153171 if tpt.isInstanceOf [InferredTypeTree ] then
154172 interpolator().traverse(tpt.knownType)
155173 .showing(i " solved vars in ${tpt.knownType}" , capt)
156174
175+ /** Assert subcapturing `cs1 <: cs2` */
176+ def assertSub (cs1 : CaptureSet , cs2 : CaptureSet )(using Context ) =
177+ assert(cs1.subCaptures(cs2, frozen = false ).isOK, i " $cs1 is not a subset of $cs2" )
178+
179+ /** Check subcapturing `{elem} <: cs`, report error on failure */
180+ def checkElem (elem : CaptureRef , cs : CaptureSet , pos : SrcPos )(using Context ) =
181+ val res = elem.singletonCaptureSet.subCaptures(cs, frozen = false )
182+ if ! res.isOK then
183+ report.error(i " $elem cannot be referenced here; it is not included in the allowed capture set ${res.blocking}" , pos)
184+
185+ /** Check subcapturing `cs1 <: cs2`, report error on failure */
186+ def checkSubset (cs1 : CaptureSet , cs2 : CaptureSet , pos : SrcPos )(using Context ) =
187+ val res = cs1.subCaptures(cs2, frozen = false )
188+ if ! res.isOK then
189+ def header =
190+ if cs1.elems.size == 1 then i " reference ${cs1.elems.toList}%, % is not "
191+ else i " references $cs1 are not all "
192+ report.error(i " $header included in allowed capture set ${res.blocking}" , pos)
193+
194+ /** The current environment */
157195 private var curEnv : Env = Env (NoSymbol , CaptureSet .empty, isBoxed = false , null )
158196
159197 private val myCapturedVars : util.EqHashMap [Symbol , CaptureSet ] = EqHashMap ()
198+
199+ /** If `sym` is a class or method nested inside a term, a capture set variable representing
200+ * the captured variables of the environment associated with `sym`.
201+ */
160202 def capturedVars (sym : Symbol )(using Context ) =
161203 myCapturedVars.getOrElseUpdate(sym,
162204 if sym.ownersIterator.exists(_.isTerm) then CaptureSet .Var ()
163205 else CaptureSet .empty)
164206
207+ /** For all nested environments up to `limit` perform `op` */
208+ def forallOuterEnvsUpTo (limit : Symbol )(op : Env => Unit )(using Context ): Unit =
209+ def recur (env : Env ): Unit =
210+ if env.isOpen && env.owner != limit then
211+ op(env)
212+ if ! env.isOutermost then
213+ var nextEnv = env.outer
214+ if env.owner.isConstructor then
215+ if nextEnv.owner != limit && ! nextEnv.isOutermost then
216+ recur(nextEnv.outer)
217+ else recur(nextEnv)
218+ recur(curEnv)
219+
220+ /** Include `sym` in the capture sets of all enclosing environments nested in the
221+ * the environment in which `sym` is defined.
222+ */
165223 def markFree (sym : Symbol , pos : SrcPos )(using Context ): Unit =
166224 if sym.exists then
167225 val ref = sym.termRef
168- def recur ( env : Env ) : Unit =
169- if env.isOpen && env.owner != sym.enclosure then
226+ if ref.isTracked then
227+ forallOuterEnvsUpTo(sym.enclosure) { env =>
170228 capt.println(i " Mark $sym with cs ${ref.captureSet} free in ${env.owner}" )
171229 checkElem(ref, env.captured, pos)
172- if env.owner.isConstructor then
173- if env.outer.owner != sym.enclosure then recur(env.outer.outer)
174- else recur(env.outer)
175- if ref.isTracked then recur(curEnv)
230+ }
176231
177- def includeCallCaptures (sym : Symbol , pos : SrcPos )(using Context ): Unit =
178- if curEnv.isOpen then
179- val ownEnclosure = ctx.owner.enclosingMethodOrClass
180- var targetSet = capturedVars(sym)
181- if ! targetSet.isAlwaysEmpty && sym.enclosure == ownEnclosure then
182- targetSet = targetSet.filter {
183- case ref : TermRef => ref.symbol.enclosure != ownEnclosure
184- case _ => true
232+ /** Make sure (projected) `cs` is a subset of the capture sets of all enclosing
233+ * environments. At each stage, only include references from `cs` that are outside
234+ * the environment's owner
235+ */
236+ def markFree (cs : CaptureSet , pos : SrcPos )(using Context ): Unit =
237+ if ! cs.isAlwaysEmpty then
238+ forallOuterEnvsUpTo(ctx.owner.topLevelClass) { env =>
239+ val included = cs.filter {
240+ case ref : TermRef => env.owner.isProperlyContainedIn(ref.symbol.owner)
241+ case ref : ThisType => env.owner.isProperlyContainedIn(ref.cls)
242+ case _ => false
185243 }
186- def includeIn (env : Env ) =
187- capt.println(i " Include call capture $targetSet in ${env.owner}" )
188- checkSubset(targetSet, env.captured, pos)
189- includeIn(curEnv)
190- if curEnv.owner.isTerm && curEnv.outer.owner.isClass then
191- includeIn(curEnv.outer)
192-
193- def includeBoxedCaptures (tp : Type , pos : SrcPos )(using Context ): Unit =
194- includeBoxedCaptures(tp.boxedCaptureSet, pos)
195-
196- def includeBoxedCaptures (refs : CaptureSet , pos : SrcPos )(using Context ): Unit =
197- if curEnv.isOpen then
198- val ownEnclosure = ctx.owner.enclosingMethodOrClass
199- val targetSet = refs.filter {
200- case ref : TermRef => ref.symbol.enclosure != ownEnclosure
201- case ref : ThisType => true
202- case _ => false
244+ capt.println(i " Include call capture $included in ${env.owner}" )
245+ checkSubset(included, env.captured, pos)
203246 }
204- checkSubset(targetSet, curEnv.captured, pos)
205-
206- def assertSub (cs1 : CaptureSet , cs2 : CaptureSet )(using Context ) =
207- assert(cs1.subCaptures(cs2, frozen = false ).isOK, i " $cs1 is not a subset of $cs2" )
208247
209- def checkElem (elem : CaptureRef , cs : CaptureSet , pos : SrcPos )(using Context ) =
210- val res = elem.singletonCaptureSet.subCaptures(cs, frozen = false )
211- if ! res.isOK then
212- report.error(i " $elem cannot be referenced here; it is not included in the allowed capture set ${res.blocking}" , pos)
213-
214- def checkSubset (cs1 : CaptureSet , cs2 : CaptureSet , pos : SrcPos )(using Context ) =
215- val res = cs1.subCaptures(cs2, frozen = false )
216- if ! res.isOK then
217- def header =
218- if cs1.elems.size == 1 then i " reference ${cs1.elems.toList}%, % is not "
219- else i " references $cs1 are not all "
220- report.error(i " $header included in allowed capture set ${res.blocking}" , pos)
248+ /** Include references captured by the called method in the current environment stack */
249+ def includeCallCaptures (sym : Symbol , pos : SrcPos )(using Context ): Unit =
250+ if sym.exists && curEnv.isOpen then markFree(capturedVars(sym), pos)
221251
222252 /** A specialized implementation of the selection rule.
223253 *
@@ -434,7 +464,7 @@ class CheckCaptures extends Recheck, SymTransformer:
434464 finally curEnv = saved
435465 else super .recheck(tree, pt)
436466 if tree.isTerm then
437- includeBoxedCaptures (res, tree.srcPos)
467+ markFree (res.boxedCaptureSet , tree.srcPos)
438468 res
439469
440470 override def recheckFinish (tpe : Type , tree : Tree , pt : Type )(using Context ): Type =
@@ -526,7 +556,7 @@ class CheckCaptures extends Recheck, SymTransformer:
526556 capt.println(i " ABORTING $actual vs $expected" )
527557 actual
528558 else
529- if covariant == actual.isBoxed then includeBoxedCaptures (refs, pos)
559+ if covariant == actual.isBoxed then markFree (refs, pos)
530560 CapturingType (parent1, refs, boxed = ! actual.isBoxed)
531561 else if parent1 eq parent then actual
532562 else CapturingType (parent1, refs, boxed = actual.isBoxed)
0 commit comments