@@ -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 ]
@@ -273,7 +278,7 @@ class CheckCaptures extends Recheck, SymTransformer:
273278 extension [T <: Tree ](tree : T )
274279 def needsSepCheck : Boolean = sepCheckFormals.contains(tree)
275280 def formalType : Type = sepCheckFormals.getOrElse(tree, NoType )
276- def markedFree = usedSet.getOrElse(tree, CaptureSet .empty)
281+ def markedFree : CaptureSet = usedSet.getOrElse(tree, CaptureSet .empty)
277282
278283 /** Instantiate capture set variables appearing contra-variantly to their
279284 * upper approximation.
@@ -427,11 +432,13 @@ class CheckCaptures extends Recheck, SymTransformer:
427432 else
428433 i " \n of the enclosing ${owner.showLocated}"
429434
430- /** Does the given environment belong to a method that is (a) nested in a term
435+ /** Under deferredReaches:
436+ * Does the given environment belong to a method that is (a) nested in a term
431437 * and (b) not the method of an anonymous function?
432438 */
433439 def isOfNestedMethod (env : Env | Null )(using Context ) =
434- env != null
440+ ccConfig.deferredReaches
441+ && env != null
435442 && env.owner.is(Method )
436443 && env.owner.owner.isTerm
437444 && ! env.owner.isAnonymousFunction
@@ -460,28 +467,17 @@ class CheckCaptures extends Recheck, SymTransformer:
460467 ! sym.isContainedIn(env.owner)
461468 }
462469
463- /** If capability `c` refers to a parameter that is not @use declared, report an error.
464- * Exception under deferredReaches: If use comes from a nested closure, accept it.
465- */
466- def checkUseDeclared (c : Capability , env : Env , lastEnv : Env | Null ) =
467- if lastEnv != null && env.nestedClosure.exists && env.nestedClosure == lastEnv.owner then
468- assert(ccConfig.deferredReaches) // access is from a nested closure under deferredReaches, so it's OK
469- else c.paramPathRoot match
470- case ref : NamedType if ! ref.symbol.isUseParam =>
471- val what = if ref.isType then " Capture set parameter" else " Local reach capability"
472- report.error(
473- em """ $what $c leaks into capture scope of ${env.ownerString}.
474- |To allow this, the ${ref.symbol} should be declared with a @use annotation """ , tree.srcPos)
475- case _ =>
476-
477470 /** Avoid locally defined capability by charging the underlying type
478471 * (which may not be cap). This scheme applies only under the deferredReaches setting.
479472 */
480473 def avoidLocalCapability (c : Capability , env : Env , lastEnv : Env | Null ): Unit =
481474 if c.isParamPath then
482475 c match
483476 case Reach (_) | _ : TypeRef =>
484- checkUseDeclared(c, env, lastEnv)
477+ val accessFromNestedClosure =
478+ lastEnv != null && env.nestedClosure.exists && env.nestedClosure == lastEnv.owner
479+ if ! accessFromNestedClosure then
480+ checkUseDeclared(c, tree.srcPos)
485481 case _ =>
486482 else
487483 val underlying = c match
@@ -499,32 +495,22 @@ class CheckCaptures extends Recheck, SymTransformer:
499495 * parameter. This is the default.
500496 */
501497 def avoidLocalReachCapability (c : Capability , env : Env ): Unit = c match
502- case Reach (c1) =>
503- if c1.isParamPath then
504- checkUseDeclared(c, env, null )
505- else
506- // When a reach capabilty x* where `x` is not a parameter goes out
507- // of scope, we need to continue with `x`'s underlying deep capture set.
508- // It is an error if that set contains cap.
509- // The same is not an issue for normal capabilities since in a local
510- // definition `val x = e`, the capabilities of `e` have already been charged.
511- // Note: It's not true that the underlying capture set of a reach capability
512- // is always cap. Reach capabilities over paths depend on the prefix, which
513- // might turn a cap into something else.
514- // The path-use.scala neg test contains an example.
515- val underlying = CaptureSet .ofTypeDeeply(c1.widen)
516- capt.println(i " Widen reach $c to $underlying in ${env.owner}" )
517- if sepChecksEnabled then
518- recur(underlying.filter(! _.isTerminalCapability), env, null )
519- // we don't want to disallow underlying Fresh instances, since these are typically locally created
520- // fresh capabilities. We don't need to also follow the hidden set since separation
521- // checking makes ure that locally hidden references need to go to @consume parameters.
522- else
523- underlying.disallowRootCapability(ctx.owner): () =>
524- report.error(em " Local reach capability $c leaks into capture scope of ${env.ownerString}" , tree.srcPos)
525- recur(underlying, env, null )
526- case c : TypeRef if c.isParamPath =>
527- checkUseDeclared(c, env, null )
498+ case Reach (c1) if ! c1.isParamPath =>
499+ // Parameter reaches are rejected in checkEscapingUses.
500+ // When a reach capabilty x* where `x` is not a parameter goes out
501+ // of scope, we need to continue with `x`'s underlying deep capture set.
502+ // It is an error if that set contains cap.
503+ // The same is not an issue for normal capabilities since in a local
504+ // definition `val x = e`, the capabilities of `e` have already been charged.
505+ // Note: It's not true that the underlying capture set of a reach capability
506+ // is always cap. Reach capabilities over paths depend on the prefix, which
507+ // might turn a cap into something else.
508+ // The path-use.scala neg test contains an example.
509+ val underlying = CaptureSet .ofTypeDeeply(c1.widen)
510+ capt.println(i " Widen reach $c to $underlying in ${env.owner}" )
511+ recur(underlying.filter(! _.isTerminalCapability), env, null )
512+ // we don't want to disallow underlying Fresh instances, since these are typically locally created
513+ // fresh capabilities. We do check that they hide no parameter reach caps in checkEscapingUses
528514 case _ =>
529515
530516 def recur (cs : CaptureSet , env : Env , lastEnv : Env | Null ): Unit =
@@ -542,13 +528,26 @@ class CheckCaptures extends Recheck, SymTransformer:
542528 capt.println(i " Include call or box capture $included from $cs in ${env.owner} --> ${env.captured}" )
543529 if ! isOfNestedMethod(env) then
544530 recur(included, nextEnvToCharge(env, ! _.owner.isStaticOwner), env)
545- // Don 't propagate out of methods inside terms. The use set of these methods
546- // will be charged when that method is called.
531+ // Under deferredReaches, don 't propagate out of methods inside terms.
532+ // The use set of these methods will be charged when that method is called.
547533
548534 recur(cs, curEnv, null )
549- usedSet(tree) = tree.markedFree ++ cs
535+ useInfos += (( tree, cs, curEnv))
550536 end markFree
551537
538+ /** If capability `c` refers to a parameter that is not @use declared, report an error.
539+ */
540+ def checkUseDeclared (c : Capability , pos : SrcPos )(using Context ): Unit =
541+ c.paramPathRoot match
542+ case ref : NamedType if ! ref.symbol.isUseParam =>
543+ val what = if ref.isType then " Capture set parameter" else " Local reach capability"
544+ val owner = ref.symbol.owner
545+ val ownerStr = if owner.isAnonymousFunction then " enclosing function" else owner.show
546+ report.error(
547+ em """ $what $c leaks into capture scope of $ownerStr.
548+ |To allow this, the ${ref.symbol} should be declared with a @use annotation """ , pos)
549+ case _ =>
550+
552551 /** Include references captured by the called method in the current environment stack */
553552 def includeCallCaptures (sym : Symbol , resType : Type , tree : Tree )(using Context ): Unit = resType match
554553 case _ : MethodOrPoly => // wait until method is fully applied
@@ -1990,6 +1989,48 @@ class CheckCaptures extends Recheck, SymTransformer:
19901989 traverseChildren(t)
19911990 check.traverse(tp)
19921991
1992+ /** Check that no uses refer to reach capabilities of parameters of enclosing
1993+ * methods or classes.
1994+ */
1995+ def checkEscapingUses ()(using Context ) =
1996+ for (tree, uses, env) <- useInfos do
1997+ val seen = util.EqHashSet [Capability ]()
1998+
1999+ // The owner of the innermost environment of kind Boxed
2000+ def boxedOwner (env : Env ): Symbol =
2001+ if env.kind == EnvKind .Boxed then env.owner
2002+ else if isOfNestedMethod(env) then env.owner.owner
2003+ else if env.owner.isStaticOwner then NoSymbol
2004+ else boxedOwner(nextEnvToCharge(env, alwaysTrue))
2005+
2006+ def checkUseUnlessBoxed (c : Capability , croot : NamedType ) =
2007+ if ! boxedOwner(env).isContainedIn(croot.symbol.owner) then
2008+ checkUseDeclared(c, tree.srcPos)
2009+
2010+ def check (cs : CaptureSet ): Unit = cs.elems.foreach(checkElem)
2011+
2012+ def checkElem (c : Capability ): Unit =
2013+ if ! seen.contains(c) then
2014+ seen += c
2015+ c match
2016+ case Reach (c1) =>
2017+ c1.paramPathRoot match
2018+ case croot : NamedType => checkUseUnlessBoxed(c, croot)
2019+ case _ => check(CaptureSet .ofTypeDeeply(c1.widen))
2020+ case c : TypeRef =>
2021+ c.paramPathRoot match
2022+ case croot : NamedType => checkUseUnlessBoxed(c, croot)
2023+ case _ =>
2024+ case c : DerivedCapability =>
2025+ checkElem(c.underlying)
2026+ case c : FreshCap =>
2027+ check(c.hiddenSet)
2028+ case _ =>
2029+
2030+ check(uses)
2031+ end for
2032+ end checkEscapingUses
2033+
19932034 /** Check that arguments of TypeApplys and AppliedTypes conform to their bounds.
19942035 */
19952036 def postCheck (unit : tpd.Tree )(using Context ): Unit =
@@ -2018,7 +2059,11 @@ class CheckCaptures extends Recheck, SymTransformer:
20182059 end checker
20192060
20202061 checker.traverse(unit)(using ctx.withOwner(defn.RootClass ))
2021- if sepChecksEnabled then SepCheck (this ).traverse(unit)
2062+ checkEscapingUses()
2063+ if sepChecksEnabled then
2064+ for (tree, cs, env) <- useInfos do
2065+ usedSet(tree) = tree.markedFree ++ cs
2066+ SepCheck (this ).traverse(unit)
20222067 if ! ctx.reporter.errorsReported then
20232068 // We dont report errors here if previous errors were reported, because other
20242069 // errors often result in bad applied types, but flagging these bad types gives
0 commit comments