@@ -328,20 +328,22 @@ class CheckCaptures extends Recheck, SymTransformer:
328
328
then CaptureSet .Var (sym.owner, level = sym.ccLevel)
329
329
else CaptureSet .empty)
330
330
331
- /** For all nested environments up to `limit` or a closed environment perform `op`,
332
- * but skip environmenrts directly enclosing environments of kind ClosureResult.
331
+ /** The next environment enclosing `env` that needs to be charged
332
+ * with free references.
333
+ * Skips environments directly enclosing environments of kind ClosureResult.
334
+ * @param included Whether an environment is included in the range of
335
+ * environments to charge. Once `included` is false, no
336
+ * more environments need to be charged.
333
337
*/
334
- def forallOuterEnvsUpTo (limit : Symbol )(op : Env => Unit )(using Context ): Unit =
335
- def recur (env : Env , skip : Boolean ): Unit =
336
- if env.isOpen && env.owner != limit then
337
- if ! skip then op(env)
338
- if ! env.isOutermost then
339
- var nextEnv = env.outer
340
- if env.owner.isConstructor then
341
- if nextEnv.owner != limit && ! nextEnv.isOutermost then
342
- nextEnv = nextEnv.outer
343
- recur(nextEnv, skip = env.kind == EnvKind .ClosureResult )
344
- recur(curEnv, skip = false )
338
+ def nextEnvToCharge (env : Env , included : Env => Boolean )(using Context ): Env =
339
+ var nextEnv = env.outer
340
+ if env.owner.isConstructor then
341
+ if included(nextEnv) then nextEnv = nextEnv.outer
342
+ if env.kind == EnvKind .ClosureResult then
343
+ // skip this one
344
+ nextEnvToCharge(nextEnv, included)
345
+ else
346
+ nextEnv
345
347
346
348
/** A description where this environment comes from */
347
349
private def provenance (env : Env )(using Context ): String =
@@ -355,7 +357,6 @@ class CheckCaptures extends Recheck, SymTransformer:
355
357
else
356
358
i " \n of the enclosing ${owner.showLocated}"
357
359
358
-
359
360
/** Include `sym` in the capture sets of all enclosing environments nested in the
360
361
* the environment in which `sym` is defined.
361
362
*/
@@ -364,9 +365,12 @@ class CheckCaptures extends Recheck, SymTransformer:
364
365
365
366
def markFree (sym : Symbol , ref : TermRef , pos : SrcPos )(using Context ): Unit =
366
367
if sym.exists && ref.isTracked then
367
- forallOuterEnvsUpTo(sym.enclosure): env =>
368
- capt.println(i " Mark $sym with cs ${ref.captureSet} free in ${env.owner}" )
369
- checkElem(ref, env.captured, pos, provenance(env))
368
+ def recur (env : Env ): Unit =
369
+ if env.isOpen && env.owner != sym.enclosure then
370
+ capt.println(i " Mark $sym with cs ${ref.captureSet} free in ${env.owner}" )
371
+ checkElem(ref, env.captured, pos, provenance(env))
372
+ recur(nextEnvToCharge(env, _.owner != sym.enclosure))
373
+ recur(curEnv)
370
374
371
375
/** Make sure (projected) `cs` is a subset of the capture sets of all enclosing
372
376
* environments. At each stage, only include references from `cs` that are outside
@@ -381,20 +385,30 @@ class CheckCaptures extends Recheck, SymTransformer:
381
385
else
382
386
! sym.isContainedIn(env.owner)
383
387
384
- def checkSubsetEnv (cs : CaptureSet , env : Env )(using Context ): Unit =
385
- // Only captured references that are visible from the environment
386
- // should be included.
387
- val included = cs.filter: c =>
388
- c.stripReach.pathRoot match
389
- case ref : NamedType => isVisibleFromEnv(ref.symbol.owner, env)
390
- case ref : ThisType => isVisibleFromEnv(ref.cls, env)
391
- case _ => false
392
- checkSubset(included, env.captured, pos, provenance(env))
393
- capt.println(i " Include call or box capture $included from $cs in ${env.owner} --> ${env.captured}" )
394
-
395
- if ! cs.isAlwaysEmpty then
396
- forallOuterEnvsUpTo(ctx.owner.topLevelClass): env =>
397
- checkSubsetEnv(cs, env)
388
+ def recur (cs : CaptureSet , env : Env )(using Context ): Unit =
389
+ if env.isOpen && ! env.owner.isStaticOwner && ! cs.isAlwaysEmpty then
390
+ // Only captured references that are visible from the environment
391
+ // should be included.
392
+ val included = cs.filter: c =>
393
+ val isVisible = c.stripReach.pathRoot match
394
+ case ref : NamedType => isVisibleFromEnv(ref.symbol.owner, env)
395
+ case ref : ThisType => isVisibleFromEnv(ref.cls, env)
396
+ case _ => false
397
+ c match
398
+ case ReachCapability (c1) if ! isVisible && ! c1.isParamPath =>
399
+ // When a reach capabilty x* where `x` is not a parameter goes out
400
+ // of scope, we need to continue with `x`'s underlying deep capture set.
401
+ // The same is not an issue for normal capabilities since in a local
402
+ // definition `val x = e`, the capabilities of `e` have already been charged.
403
+ val underlying = CaptureSet .ofTypeDeeply(c1.widen)
404
+ capt.println(i " Widen reach $c to $underlying in ${env.owner}" )
405
+ recur(underlying, env)
406
+ case _ =>
407
+ isVisible
408
+ checkSubset(included, env.captured, pos, provenance(env))
409
+ capt.println(i " Include call or box capture $included from $cs in ${env.owner} --> ${env.captured}" )
410
+ recur(included, nextEnvToCharge(env, ! _.owner.isStaticOwner))
411
+ recur(cs, curEnv)
398
412
end markFree
399
413
400
414
/** Include references captured by the called method in the current environment stack */
0 commit comments