@@ -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.
@@ -462,28 +467,17 @@ class CheckCaptures extends Recheck, SymTransformer:
462467 ! sym.isContainedIn(env.owner)
463468 }
464469
465- /** If capability `c` refers to a parameter that is not @use declared, report an error.
466- * Exception under deferredReaches: If use comes from a nested closure, accept it.
467- */
468- def checkUseDeclared (c : Capability , env : Env , lastEnv : Env | Null ) =
469- if lastEnv != null && env.nestedClosure.exists && env.nestedClosure == lastEnv.owner then
470- assert(ccConfig.deferredReaches) // access is from a nested closure under deferredReaches, so it's OK
471- else c.paramPathRoot match
472- case ref : NamedType if ! ref.symbol.isUseParam =>
473- val what = if ref.isType then " Capture set parameter" else " Local reach capability"
474- report.error(
475- em """ $what $c leaks into capture scope of ${env.ownerString}.
476- |To allow this, the ${ref.symbol} should be declared with a @use annotation """ , tree.srcPos)
477- case _ =>
478-
479470 /** Avoid locally defined capability by charging the underlying type
480471 * (which may not be cap). This scheme applies only under the deferredReaches setting.
481472 */
482473 def avoidLocalCapability (c : Capability , env : Env , lastEnv : Env | Null ): Unit =
483474 if c.isParamPath then
484475 c match
485476 case Reach (_) | _ : TypeRef =>
486- 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)
487481 case _ =>
488482 else
489483 val underlying = c match
@@ -501,32 +495,22 @@ class CheckCaptures extends Recheck, SymTransformer:
501495 * parameter. This is the default.
502496 */
503497 def avoidLocalReachCapability (c : Capability , env : Env ): Unit = c match
504- case Reach (c1) =>
505- if c1.isParamPath then
506- checkUseDeclared(c, env, null )
507- else
508- // When a reach capabilty x* where `x` is not a parameter goes out
509- // of scope, we need to continue with `x`'s underlying deep capture set.
510- // It is an error if that set contains cap.
511- // The same is not an issue for normal capabilities since in a local
512- // definition `val x = e`, the capabilities of `e` have already been charged.
513- // Note: It's not true that the underlying capture set of a reach capability
514- // is always cap. Reach capabilities over paths depend on the prefix, which
515- // might turn a cap into something else.
516- // The path-use.scala neg test contains an example.
517- val underlying = CaptureSet .ofTypeDeeply(c1.widen)
518- capt.println(i " Widen reach $c to $underlying in ${env.owner}" )
519- if sepChecksEnabled then
520- recur(underlying.filter(! _.isTerminalCapability), env, null )
521- // we don't want to disallow underlying Fresh instances, since these are typically locally created
522- // fresh capabilities. We don't need to also follow the hidden set since separation
523- // checking makes ure that locally hidden references need to go to @consume parameters.
524- else
525- underlying.disallowRootCapability(ctx.owner): () =>
526- report.error(em " Local reach capability $c leaks into capture scope of ${env.ownerString}" , tree.srcPos)
527- recur(underlying, env, null )
528- case c : TypeRef if c.isParamPath =>
529- 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
530514 case _ =>
531515
532516 def recur (cs : CaptureSet , env : Env , lastEnv : Env | Null ): Unit =
@@ -542,15 +526,28 @@ class CheckCaptures extends Recheck, SymTransformer:
542526 isVisible
543527 checkSubset(included, env.captured, tree.srcPos, provenance(env))
544528 capt.println(i " Include call or box capture $included from $cs in ${env.owner} --> ${env.captured}" )
545- if ! isOfNestedMethod(env) || true then
529+ if ! isOfNestedMethod(env) then
546530 recur(included, nextEnvToCharge(env, ! _.owner.isStaticOwner), env)
547- // Don 't propagate out of methods inside terms. The use set of these methods
548- // 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.
549533
550534 recur(cs, curEnv, null )
551- usedSet(tree) = tree.markedFree ++ cs
535+ useInfos += (( tree, cs, curEnv))
552536 end markFree
553537
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+
554551 /** Include references captured by the called method in the current environment stack */
555552 def includeCallCaptures (sym : Symbol , resType : Type , tree : Tree )(using Context ): Unit = resType match
556553 case _ : MethodOrPoly => // wait until method is fully applied
@@ -1992,6 +1989,48 @@ class CheckCaptures extends Recheck, SymTransformer:
19921989 traverseChildren(t)
19931990 check.traverse(tp)
19941991
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+
19952034 /** Check that arguments of TypeApplys and AppliedTypes conform to their bounds.
19962035 */
19972036 def postCheck (unit : tpd.Tree )(using Context ): Unit =
@@ -2020,7 +2059,11 @@ class CheckCaptures extends Recheck, SymTransformer:
20202059 end checker
20212060
20222061 checker.traverse(unit)(using ctx.withOwner(defn.RootClass ))
2023- 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)
20242067 if ! ctx.reporter.errorsReported then
20252068 // We dont report errors here if previous errors were reported, because other
20262069 // errors often result in bad applied types, but flagging these bad types gives
0 commit comments