@@ -26,6 +26,7 @@ import reporting.{trace, Message, OverrideError}
2626import Annotations .Annotation
2727import Capabilities .*
2828import dotty .tools .dotc .cc .CaptureSet .MutAdaptFailure
29+ import dotty .tools .dotc .util .common .alwaysTrue
2930
3031/** The capture checker */
3132object CheckCaptures :
@@ -253,8 +254,12 @@ class CheckCaptures extends Recheck, SymTransformer:
253254 */
254255 private val sepCheckFormals = util.EqHashMap [Tree , Type ]()
255256
256- /** The references used at identifier or application trees */
257- private val usedSet = util.EqHashMap [Tree , CaptureSet ]()
257+ /** The references used at identifier or application trees, including the
258+ * environment at the reference point.
259+ */
260+ private val useInfos = mutable.ArrayBuffer [(Tree , CaptureSet , Env )]()
261+
262+ private var usedSet = util.EqHashMap [Tree , CaptureSet ]()
258263
259264 /** The set of symbols that were rechecked via a completer */
260265 private val completed = new mutable.HashSet [Symbol ]
@@ -270,7 +275,7 @@ class CheckCaptures extends Recheck, SymTransformer:
270275 extension [T <: Tree ](tree : T )
271276 def needsSepCheck : Boolean = sepCheckFormals.contains(tree)
272277 def formalType : Type = sepCheckFormals.getOrElse(tree, NoType )
273- def markedFree = usedSet.getOrElse(tree, CaptureSet .empty)
278+ def markedFree : CaptureSet = usedSet.getOrElse(tree, CaptureSet .empty)
274279
275280 /** Instantiate capture set variables appearing contra-variantly to their
276281 * upper approximation.
@@ -459,28 +464,17 @@ class CheckCaptures extends Recheck, SymTransformer:
459464 ! sym.isContainedIn(env.owner)
460465 }
461466
462- /** If capability `c` refers to a parameter that is not @use declared, report an error.
463- * Exception under deferredReaches: If use comes from a nested closure, accept it.
464- */
465- def checkUseDeclared (c : Capability , env : Env , lastEnv : Env | Null ) =
466- if lastEnv != null && env.nestedClosure.exists && env.nestedClosure == lastEnv.owner then
467- assert(ccConfig.deferredReaches) // access is from a nested closure under deferredReaches, so it's OK
468- else c.paramPathRoot match
469- case ref : NamedType if ! ref.symbol.isUseParam =>
470- val what = if ref.isType then " Capture set parameter" else " Local reach capability"
471- report.error(
472- em """ $what $c leaks into capture scope of ${env.ownerString}.
473- |To allow this, the ${ref.symbol} should be declared with a @use annotation """ , tree.srcPos)
474- case _ =>
475-
476467 /** Avoid locally defined capability by charging the underlying type
477468 * (which may not be cap). This scheme applies only under the deferredReaches setting.
478469 */
479470 def avoidLocalCapability (c : Capability , env : Env , lastEnv : Env | Null ): Unit =
480471 if c.isParamPath then
481472 c match
482473 case Reach (_) | _ : TypeRef =>
483- checkUseDeclared(c, env, lastEnv)
474+ val accessFromNestedClosure =
475+ lastEnv != null && env.nestedClosure.exists && env.nestedClosure == lastEnv.owner
476+ if ! accessFromNestedClosure then
477+ checkUseDeclared(c, tree.srcPos)
484478 case _ =>
485479 else
486480 val underlying = c match
@@ -498,32 +492,22 @@ class CheckCaptures extends Recheck, SymTransformer:
498492 * parameter. This is the default.
499493 */
500494 def avoidLocalReachCapability (c : Capability , env : Env ): Unit = c match
501- case Reach (c1) =>
502- if c1.isParamPath then
503- checkUseDeclared(c, env, null )
504- else
505- // When a reach capabilty x* where `x` is not a parameter goes out
506- // of scope, we need to continue with `x`'s underlying deep capture set.
507- // It is an error if that set contains cap.
508- // The same is not an issue for normal capabilities since in a local
509- // definition `val x = e`, the capabilities of `e` have already been charged.
510- // Note: It's not true that the underlying capture set of a reach capability
511- // is always cap. Reach capabilities over paths depend on the prefix, which
512- // might turn a cap into something else.
513- // The path-use.scala neg test contains an example.
514- val underlying = CaptureSet .ofTypeDeeply(c1.widen)
515- capt.println(i " Widen reach $c to $underlying in ${env.owner}" )
516- if ccConfig.useSepChecks then
517- recur(underlying.filter(! _.isTerminalCapability), env, null )
518- // we don't want to disallow underlying Fresh instances, since these are typically locally created
519- // fresh capabilities. We don't need to also follow the hidden set since separation
520- // checking makes ure that locally hidden references need to go to @consume parameters.
521- else
522- underlying.disallowRootCapability(ctx.owner): () =>
523- report.error(em " Local reach capability $c leaks into capture scope of ${env.ownerString}" , tree.srcPos)
524- recur(underlying, env, null )
525- case c : TypeRef if c.isParamPath =>
526- checkUseDeclared(c, env, null )
495+ case Reach (c1) if ! c1.isParamPath =>
496+ // Parameter reaches are rejected in checkEscapingUses.
497+ // When a reach capabilty x* where `x` is not a parameter goes out
498+ // of scope, we need to continue with `x`'s underlying deep capture set.
499+ // It is an error if that set contains cap.
500+ // The same is not an issue for normal capabilities since in a local
501+ // definition `val x = e`, the capabilities of `e` have already been charged.
502+ // Note: It's not true that the underlying capture set of a reach capability
503+ // is always cap. Reach capabilities over paths depend on the prefix, which
504+ // might turn a cap into something else.
505+ // The path-use.scala neg test contains an example.
506+ val underlying = CaptureSet .ofTypeDeeply(c1.widen)
507+ capt.println(i " Widen reach $c to $underlying in ${env.owner}" )
508+ recur(underlying.filter(! _.isTerminalCapability), env, null )
509+ // we don't want to disallow underlying Fresh instances, since these are typically locally created
510+ // fresh capabilities. We do check that they hide no parameter reach caps in checkEscapingUses
527511 case _ =>
528512
529513 def recur (cs : CaptureSet , env : Env , lastEnv : Env | Null ): Unit =
@@ -539,15 +523,28 @@ class CheckCaptures extends Recheck, SymTransformer:
539523 isVisible
540524 checkSubset(included, env.captured, tree.srcPos, provenance(env))
541525 capt.println(i " Include call or box capture $included from $cs in ${env.owner} --> ${env.captured}" )
542- if ! isOfNestedMethod(env) || true then
526+ if ! isOfNestedMethod(env) then
543527 recur(included, nextEnvToCharge(env, ! _.owner.isStaticOwner), env)
544- // Don 't propagate out of methods inside terms. The use set of these methods
545- // will be charged when that method is called.
528+ // Under deferredReaches, don 't propagate out of methods inside terms.
529+ // The use set of these methods will be charged when that method is called.
546530
547531 recur(cs, curEnv, null )
548- usedSet(tree) = tree.markedFree ++ cs
532+ useInfos += (( tree, cs, curEnv))
549533 end markFree
550534
535+ /** If capability `c` refers to a parameter that is not @use declared, report an error.
536+ */
537+ def checkUseDeclared (c : Capability , pos : SrcPos )(using Context ): Unit =
538+ c.paramPathRoot match
539+ case ref : NamedType if ! ref.symbol.isUseParam =>
540+ val what = if ref.isType then " Capture set parameter" else " Local reach capability"
541+ val owner = ref.symbol.owner
542+ val ownerStr = if owner.isAnonymousFunction then " enclosing function" else owner.show
543+ report.error(
544+ em """ $what $c leaks into capture scope of $ownerStr.
545+ |To allow this, the ${ref.symbol} should be declared with a @use annotation """ , pos)
546+ case _ =>
547+
551548 /** Include references captured by the called method in the current environment stack */
552549 def includeCallCaptures (sym : Symbol , resType : Type , tree : Tree )(using Context ): Unit = resType match
553550 case _ : MethodOrPoly => // wait until method is fully applied
@@ -1988,6 +1985,48 @@ class CheckCaptures extends Recheck, SymTransformer:
19881985 traverseChildren(t)
19891986 check.traverse(tp)
19901987
1988+ /** Check that no uses refer to reach capabilities of parameters of enclosing
1989+ * methods or classes.
1990+ */
1991+ def checkEscapingUses ()(using Context ) =
1992+ for (tree, uses, env) <- useInfos do
1993+ val seen = util.EqHashSet [Capability ]()
1994+
1995+ // The owner of the innermost environment of kind Boxed
1996+ def boxedOwner (env : Env ): Symbol =
1997+ if env.kind == EnvKind .Boxed then env.owner
1998+ else if isOfNestedMethod(env) then env.owner.owner
1999+ else if env.owner.isStaticOwner then NoSymbol
2000+ else boxedOwner(nextEnvToCharge(env, alwaysTrue))
2001+
2002+ def checkUseUnlessBoxed (c : Capability , croot : NamedType ) =
2003+ if ! boxedOwner(env).isContainedIn(croot.symbol.owner) then
2004+ checkUseDeclared(c, tree.srcPos)
2005+
2006+ def check (cs : CaptureSet ): Unit = cs.elems.foreach(checkElem)
2007+
2008+ def checkElem (c : Capability ): Unit =
2009+ if ! seen.contains(c) then
2010+ seen += c
2011+ c match
2012+ case Reach (c1) =>
2013+ c1.paramPathRoot match
2014+ case croot : NamedType => checkUseUnlessBoxed(c, croot)
2015+ case _ => check(CaptureSet .ofTypeDeeply(c1.widen))
2016+ case c : TypeRef =>
2017+ c.paramPathRoot match
2018+ case croot : NamedType => checkUseUnlessBoxed(c, croot)
2019+ case _ =>
2020+ case c : DerivedCapability =>
2021+ checkElem(c.underlying)
2022+ case c : FreshCap =>
2023+ check(c.hiddenSet)
2024+ case _ =>
2025+
2026+ check(uses)
2027+ end for
2028+ end checkEscapingUses
2029+
19912030 /** Check that arguments of TypeApplys and AppliedTypes conform to their bounds.
19922031 */
19932032 def postCheck (unit : tpd.Tree )(using Context ): Unit =
@@ -2016,7 +2055,11 @@ class CheckCaptures extends Recheck, SymTransformer:
20162055 end checker
20172056
20182057 checker.traverse(unit)(using ctx.withOwner(defn.RootClass ))
2019- if ccConfig.useSepChecks then SepCheck (this ).traverse(unit)
2058+ checkEscapingUses()
2059+ if ccConfig.useSepChecks then
2060+ for (tree, cs, env) <- useInfos do
2061+ usedSet(tree) = tree.markedFree ++ cs
2062+ SepCheck (this ).traverse(unit)
20202063 if ! ctx.reporter.errorsReported then
20212064 // We dont report errors here if previous errors were reported, because other
20222065 // errors often result in bad applied types, but flagging these bad types gives
0 commit comments