@@ -333,20 +333,22 @@ class CheckCaptures extends Recheck, SymTransformer:
333
333
then CaptureSet .Var (sym.owner, level = sym.ccLevel)
334
334
else CaptureSet .empty)
335
335
336
- /** For all nested environments up to `limit` or a closed environment perform `op`,
337
- * but skip environmenrts directly enclosing environments of kind ClosureResult.
336
+ /** The next environment enclosing `env` that needs to be charged
337
+ * with free references.
338
+ * Skips environments directly enclosing environments of kind ClosureResult.
339
+ * @param included Whether an environment is included in the range of
340
+ * environments to charge. Once `included` is false, no
341
+ * more environments need to be charged.
338
342
*/
339
- def forallOuterEnvsUpTo (limit : Symbol )(op : Env => Unit )(using Context ): Unit =
340
- def recur (env : Env , skip : Boolean ): Unit =
341
- if env.isOpen && env.owner != limit then
342
- if ! skip then op(env)
343
- if ! env.isOutermost then
344
- var nextEnv = env.outer
345
- if env.owner.isConstructor then
346
- if nextEnv.owner != limit && ! nextEnv.isOutermost then
347
- nextEnv = nextEnv.outer
348
- recur(nextEnv, skip = env.kind == EnvKind .ClosureResult )
349
- recur(curEnv, skip = false )
343
+ def nextEnvToCharge (env : Env , included : Env => Boolean )(using Context ): Env =
344
+ var nextEnv = env.outer
345
+ if env.owner.isConstructor then
346
+ if included(nextEnv) then nextEnv = nextEnv.outer
347
+ if env.kind == EnvKind .ClosureResult then
348
+ // skip this one
349
+ nextEnvToCharge(nextEnv, included)
350
+ else
351
+ nextEnv
350
352
351
353
/** A description where this environment comes from */
352
354
private def provenance (env : Env )(using Context ): String =
@@ -360,7 +362,6 @@ class CheckCaptures extends Recheck, SymTransformer:
360
362
else
361
363
i " \n of the enclosing ${owner.showLocated}"
362
364
363
-
364
365
/** Include `sym` in the capture sets of all enclosing environments nested in the
365
366
* the environment in which `sym` is defined.
366
367
*/
@@ -369,9 +370,12 @@ class CheckCaptures extends Recheck, SymTransformer:
369
370
370
371
def markFree (sym : Symbol , ref : TermRef , pos : SrcPos )(using Context ): Unit =
371
372
if sym.exists && ref.isTracked then
372
- forallOuterEnvsUpTo(sym.enclosure): env =>
373
- capt.println(i " Mark $sym with cs ${ref.captureSet} free in ${env.owner}" )
374
- checkElem(ref, env.captured, pos, provenance(env))
373
+ def recur (env : Env ): Unit =
374
+ if env.isOpen && env.owner != sym.enclosure then
375
+ capt.println(i " Mark $sym with cs ${ref.captureSet} free in ${env.owner}" )
376
+ checkElem(ref, env.captured, pos, provenance(env))
377
+ recur(nextEnvToCharge(env, _.owner != sym.enclosure))
378
+ recur(curEnv)
375
379
376
380
/** Make sure (projected) `cs` is a subset of the capture sets of all enclosing
377
381
* environments. At each stage, only include references from `cs` that are outside
@@ -386,46 +390,53 @@ class CheckCaptures extends Recheck, SymTransformer:
386
390
else
387
391
! sym.isContainedIn(env.owner)
388
392
389
- def checkSubsetEnv (cs : CaptureSet , env : Env )(using Context ): Unit =
390
- // Only captured references that are visible from the environment
391
- // should be included.
392
- val included = cs.filter: c =>
393
- c.stripReach match
394
- case ref : NamedType =>
395
- val refSym = ref.symbol
396
- val refOwner = refSym.owner
397
- val isVisible = isVisibleFromEnv(refOwner, env)
398
- if isVisible && ! ref.isRootCapability then
399
- ref match
400
- case ref : TermRef if ref.prefix `ne` NoPrefix =>
401
- // If c is a path of a class defined outside the environment,
402
- // we check the capture set of its info.
403
- checkSubsetEnv(ref.captureSetOfInfo, env)
404
- case _ =>
405
- if ! isVisible
406
- && (c.isReach || ref.isType)
407
- && (! ccConfig.useSealed || refSym.is(Param ))
408
- && refOwner == env.owner
409
- then
410
- if refSym.hasAnnotation(defn.UnboxAnnot ) then
411
- capt.println(i " exempt: $ref in $refOwner" )
412
- else
413
- // Reach capabilities that go out of scope have to be approximated
414
- // by their underlying capture set, which cannot be universal.
415
- // Reach capabilities of @unboxed parameters are exempted.
416
- val cs = CaptureSet .ofInfo(c)
417
- cs.disallowRootCapability: () =>
418
- report.error(em " Local reach capability $c leaks into capture scope of ${env.ownerString}" , pos)
419
- checkSubset(cs, env.captured, pos, provenance(env))
420
- isVisible
421
- case ref : ThisType => isVisibleFromEnv(ref.cls, env)
422
- case _ => false
423
- checkSubset(included, env.captured, pos, provenance(env))
424
- capt.println(i " Include call or box capture $included from $cs in ${env.owner} --> ${env.captured}" )
425
-
426
- if ! cs.isAlwaysEmpty then
427
- forallOuterEnvsUpTo(ctx.owner.topLevelClass): env =>
428
- checkSubsetEnv(cs, env)
393
+ def checkUseDeclared (c : CaptureRef , env : Env ) =
394
+ c.pathRoot match
395
+ case ref : NamedType if ! ref.symbol.hasAnnotation(defn.UnboxAnnot ) =>
396
+ val what = if ref.isType then " Capture set parameter" else " Local reach capability"
397
+ report.error(
398
+ em """ $what $c leaks into capture scope of ${env.ownerString}.
399
+ |To allow this, the ${ref.symbol} should be declared with a @use annotation """ , pos)
400
+ case _ =>
401
+
402
+ def recur (cs : CaptureSet , env : Env )(using Context ): Unit =
403
+ if env.isOpen && ! env.owner.isStaticOwner && ! cs.isAlwaysEmpty then
404
+ // Only captured references that are visible from the environment
405
+ // should be included.
406
+ val included = cs.filter: c =>
407
+ val isVisible = c.pathRoot match
408
+ case ref : NamedType => isVisibleFromEnv(ref.symbol.owner, env)
409
+ case ref : ThisType => isVisibleFromEnv(ref.cls, env)
410
+ case ref =>
411
+ false
412
+ if ! isVisible then
413
+ c match
414
+ case ReachCapability (c1) =>
415
+ if c1.isParamPath then
416
+ checkUseDeclared(c, env)
417
+ else
418
+ // When a reach capabilty x* where `x` is not a parameter goes out
419
+ // of scope, we need to continue with `x`'s underlying deep capture set.
420
+ // It is an error if that set contains cap.
421
+ // The same is not an issue for normal capabilities since in a local
422
+ // definition `val x = e`, the capabilities of `e` have already been charged.
423
+ // Note: It's not true that the underlying capture set of a reach capability
424
+ // is always cap. Reach capabilities over paths depend on the prefix, which
425
+ // might turn a cap into something else.
426
+ // The path-use.scala neg test contains an example.
427
+ val underlying = CaptureSet .ofTypeDeeply(c1.widen)
428
+ capt.println(i " Widen reach $c to $underlying in ${env.owner}" )
429
+ underlying.disallowRootCapability: () =>
430
+ report.error(em " Local reach capability $c leaks into capture scope of ${env.ownerString}" , pos)
431
+ recur(underlying, env)
432
+ case c : TypeRef if c.isParamPath =>
433
+ checkUseDeclared(c, env)
434
+ case _ =>
435
+ isVisible
436
+ checkSubset(included, env.captured, pos, provenance(env))
437
+ capt.println(i " Include call or box capture $included from $cs in ${env.owner} --> ${env.captured}" )
438
+ recur(included, nextEnvToCharge(env, ! _.owner.isStaticOwner))
439
+ recur(cs, curEnv)
429
440
end markFree
430
441
431
442
/** Include references captured by the called method in the current environment stack */
@@ -1144,13 +1155,8 @@ class CheckCaptures extends Recheck, SymTransformer:
1144
1155
(erefs /: erefs.elems): (erefs, eref) =>
1145
1156
eref match
1146
1157
case eref : ThisType if isPureContext(ctx.owner, eref.cls) =>
1147
- def isOuterRef (aref : Type ): Boolean = aref.pathRoot match
1148
- case aref : NamedType => eref.cls.isProperlyContainedIn(aref.symbol.owner)
1149
- case aref : ThisType => eref.cls.isProperlyContainedIn(aref.cls)
1150
- case _ => false
1151
-
1152
- val outerRefs = arefs.filter(isOuterRef)
1153
-
1158
+ val outerRefs = arefs.filter: aref =>
1159
+ eref.cls.isProperlyContainedIn(aref.pathOwner)
1154
1160
// Include implicitly added outer references in the capture set of the class of `eref`.
1155
1161
for outerRef <- outerRefs.elems do
1156
1162
if ! erefs.elems.contains(outerRef)
0 commit comments