@@ -328,20 +328,22 @@ class CheckCaptures extends Recheck, SymTransformer:
328328 then CaptureSet .Var (sym.owner, level = sym.ccLevel)
329329 else CaptureSet .empty)
330330
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.
333337 */
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
345347
346348 /** A description where this environment comes from */
347349 private def provenance (env : Env )(using Context ): String =
@@ -355,7 +357,6 @@ class CheckCaptures extends Recheck, SymTransformer:
355357 else
356358 i " \n of the enclosing ${owner.showLocated}"
357359
358-
359360 /** Include `sym` in the capture sets of all enclosing environments nested in the
360361 * the environment in which `sym` is defined.
361362 */
@@ -364,9 +365,12 @@ class CheckCaptures extends Recheck, SymTransformer:
364365
365366 def markFree (sym : Symbol , ref : TermRef , pos : SrcPos )(using Context ): Unit =
366367 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)
370374
371375 /** Make sure (projected) `cs` is a subset of the capture sets of all enclosing
372376 * environments. At each stage, only include references from `cs` that are outside
@@ -381,46 +385,53 @@ class CheckCaptures extends Recheck, SymTransformer:
381385 else
382386 ! sym.isContainedIn(env.owner)
383387
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)
424435 end markFree
425436
426437 /** Include references captured by the called method in the current environment stack */
@@ -1139,13 +1150,8 @@ class CheckCaptures extends Recheck, SymTransformer:
11391150 (erefs /: erefs.elems): (erefs, eref) =>
11401151 eref match
11411152 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)
11491155 // Include implicitly added outer references in the capture set of the class of `eref`.
11501156 for outerRef <- outerRefs.elems do
11511157 if ! erefs.elems.contains(outerRef)
0 commit comments