@@ -39,10 +39,25 @@ object CheckCaptures:
39
39
sym
40
40
end Pre
41
41
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
+ */
42
48
case class Env (owner : Symbol , captured : CaptureSet , isBoxed : Boolean , outer0 : Env | Null ):
43
49
def outer = outer0.nn
50
+
51
+ def isOutermost = outer0 == null
52
+
53
+ /** If an environment is open it tracks free references */
44
54
def isOpen = ! captured.isAlwaysEmpty && ! isBoxed
55
+ end Env
45
56
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
+ */
46
61
final class SubstParamsMap (from : BindingType , to : List [Type ])(using Context )
47
62
extends ApproximatingTypeMap , IdempotentCaptRefMap :
48
63
def apply (tp : Type ): Type = tp match
@@ -56,7 +71,7 @@ object CheckCaptures:
56
71
case _ =>
57
72
mapOver(tp)
58
73
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.
60
75
* This check is performed at Typer.
61
76
*/
62
77
def checkWellformed (ann : Tree )(using Context ): Unit =
@@ -149,75 +164,90 @@ class CheckCaptures extends Recheck, SymTransformer:
149
164
case _ =>
150
165
traverseChildren(t)
151
166
167
+ /** If `tpt` is an inferred type, interpolate capture set variables appearing contra-
168
+ * variantly in it.
169
+ */
152
170
private def interpolateVarsIn (tpt : Tree )(using Context ): Unit =
153
171
if tpt.isInstanceOf [InferredTypeTree ] then
154
172
interpolator().traverse(tpt.knownType)
155
173
.showing(i " solved vars in ${tpt.knownType}" , capt)
156
174
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 */
157
195
private var curEnv : Env = Env (NoSymbol , CaptureSet .empty, isBoxed = false , null )
158
196
159
197
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
+ */
160
202
def capturedVars (sym : Symbol )(using Context ) =
161
203
myCapturedVars.getOrElseUpdate(sym,
162
204
if sym.ownersIterator.exists(_.isTerm) then CaptureSet .Var ()
163
205
else CaptureSet .empty)
164
206
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
+ */
165
223
def markFree (sym : Symbol , pos : SrcPos )(using Context ): Unit =
166
224
if sym.exists then
167
225
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 =>
170
228
capt.println(i " Mark $sym with cs ${ref.captureSet} free in ${env.owner}" )
171
229
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
+ }
176
231
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
185
243
}
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)
203
246
}
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" )
208
247
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)
221
251
222
252
/** A specialized implementation of the selection rule.
223
253
*
@@ -434,7 +464,7 @@ class CheckCaptures extends Recheck, SymTransformer:
434
464
finally curEnv = saved
435
465
else super .recheck(tree, pt)
436
466
if tree.isTerm then
437
- includeBoxedCaptures (res, tree.srcPos)
467
+ markFree (res.boxedCaptureSet , tree.srcPos)
438
468
res
439
469
440
470
override def recheckFinish (tpe : Type , tree : Tree , pt : Type )(using Context ): Type =
@@ -526,7 +556,7 @@ class CheckCaptures extends Recheck, SymTransformer:
526
556
capt.println(i " ABORTING $actual vs $expected" )
527
557
actual
528
558
else
529
- if covariant == actual.isBoxed then includeBoxedCaptures (refs, pos)
559
+ if covariant == actual.isBoxed then markFree (refs, pos)
530
560
CapturingType (parent1, refs, boxed = ! actual.isBoxed)
531
561
else if parent1 eq parent then actual
532
562
else CapturingType (parent1, refs, boxed = actual.isBoxed)
0 commit comments