@@ -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,46 +385,53 @@ 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 match
389
- case ref : NamedType =>
390
- val refSym = ref.symbol
391
- val refOwner = refSym.owner
392
- val isVisible = isVisibleFromEnv(refOwner, env)
393
- if isVisible && ! ref.isRootCapability then
394
- ref match
395
- case ref : TermRef if ref.prefix `ne` NoPrefix =>
396
- // If c is a path of a class defined outside the environment,
397
- // we check the capture set of its info.
398
- checkSubsetEnv(ref.captureSetOfInfo, env)
399
- case _ =>
400
- if ! isVisible
401
- && (c.isReach || ref.isType)
402
- && (! ccConfig.useSealed || refSym.is(Param ))
403
- && refOwner == env.owner
404
- then
405
- if refSym.hasAnnotation(defn.UnboxAnnot ) then
406
- capt.println(i " exempt: $ref in $refOwner" )
407
- else
408
- // Reach capabilities that go out of scope have to be approximated
409
- // by their underlying capture set, which cannot be universal.
410
- // Reach capabilities of @unboxed parameters are exempted.
411
- val cs = CaptureSet .ofInfo(c)
412
- cs.disallowRootCapability: () =>
413
- report.error(em " Local reach capability $c leaks into capture scope of ${env.ownerString}" , pos)
414
- checkSubset(cs, env.captured, pos, provenance(env))
415
- isVisible
416
- case ref : ThisType => isVisibleFromEnv(ref.cls, env)
417
- case _ => false
418
- checkSubset(included, env.captured, pos, provenance(env))
419
- capt.println(i " Include call or box capture $included from $cs in ${env.owner} --> ${env.captured}" )
420
-
421
- if ! cs.isAlwaysEmpty then
422
- forallOuterEnvsUpTo(ctx.owner.topLevelClass): env =>
423
- checkSubsetEnv(cs, env)
388
+ def checkUseDeclared (c : CaptureRef , env : Env ) =
389
+ c.pathRoot match
390
+ case ref : NamedType if ! ref.symbol.hasAnnotation(defn.UnboxAnnot ) =>
391
+ val what = if ref.isType then " Capture set parameter" else " Local reach capability"
392
+ report.error(
393
+ em """ $what $c leaks into capture scope of ${env.ownerString}.
394
+ |To allow this, the ${ref.symbol} should be declared with a @use annotation """ , pos)
395
+ case _ =>
396
+
397
+ def recur (cs : CaptureSet , env : Env )(using Context ): Unit =
398
+ if env.isOpen && ! env.owner.isStaticOwner && ! cs.isAlwaysEmpty then
399
+ // Only captured references that are visible from the environment
400
+ // should be included.
401
+ val included = cs.filter: c =>
402
+ val isVisible = c.pathRoot match
403
+ case ref : NamedType => isVisibleFromEnv(ref.symbol.owner, env)
404
+ case ref : ThisType => isVisibleFromEnv(ref.cls, env)
405
+ case ref =>
406
+ false
407
+ if ! isVisible then
408
+ c match
409
+ case ReachCapability (c1) =>
410
+ if c1.isParamPath then
411
+ checkUseDeclared(c, env)
412
+ else
413
+ // When a reach capabilty x* where `x` is not a parameter goes out
414
+ // of scope, we need to continue with `x`'s underlying deep capture set.
415
+ // It is an error if that set contains cap.
416
+ // The same is not an issue for normal capabilities since in a local
417
+ // definition `val x = e`, the capabilities of `e` have already been charged.
418
+ // Note: It's not true that the underlying capture set of a reach capability
419
+ // is always cap. Reach capabilities over paths depend on the prefix, which
420
+ // might turn a cap into something else.
421
+ // The path-use.scala neg test contains an example.
422
+ val underlying = CaptureSet .ofTypeDeeply(c1.widen)
423
+ capt.println(i " Widen reach $c to $underlying in ${env.owner}" )
424
+ underlying.disallowRootCapability: () =>
425
+ report.error(em " Local reach capability $c leaks into capture scope of ${env.ownerString}" , pos)
426
+ recur(underlying, env)
427
+ case c : TypeRef if c.isParamPath =>
428
+ checkUseDeclared(c, env)
429
+ case _ =>
430
+ isVisible
431
+ checkSubset(included, env.captured, pos, provenance(env))
432
+ capt.println(i " Include call or box capture $included from $cs in ${env.owner} --> ${env.captured}" )
433
+ recur(included, nextEnvToCharge(env, ! _.owner.isStaticOwner))
434
+ recur(cs, curEnv)
424
435
end markFree
425
436
426
437
/** Include references captured by the called method in the current environment stack */
@@ -1139,13 +1150,8 @@ class CheckCaptures extends Recheck, SymTransformer:
1139
1150
(erefs /: erefs.elems): (erefs, eref) =>
1140
1151
eref match
1141
1152
case eref : ThisType if isPureContext(ctx.owner, eref.cls) =>
1142
- def isOuterRef (aref : Type ): Boolean = aref.pathRoot match
1143
- case aref : NamedType => eref.cls.isProperlyContainedIn(aref.symbol.owner)
1144
- case aref : ThisType => eref.cls.isProperlyContainedIn(aref.cls)
1145
- case _ => false
1146
-
1147
- val outerRefs = arefs.filter(isOuterRef)
1148
-
1153
+ val outerRefs = arefs.filter: aref =>
1154
+ eref.cls.isProperlyContainedIn(aref.pathOwner)
1149
1155
// Include implicitly added outer references in the capture set of the class of `eref`.
1150
1156
for outerRef <- outerRefs.elems do
1151
1157
if ! erefs.elems.contains(outerRef)
0 commit comments