@@ -26,6 +26,7 @@ import reporting.{trace, Message, OverrideError}
26
26
import Annotations .Annotation
27
27
import Capabilities .*
28
28
import dotty .tools .dotc .cc .CaptureSet .MutAdaptFailure
29
+ import dotty .tools .dotc .util .common .alwaysTrue
29
30
30
31
/** The capture checker */
31
32
object CheckCaptures :
@@ -253,8 +254,12 @@ class CheckCaptures extends Recheck, SymTransformer:
253
254
*/
254
255
private val sepCheckFormals = util.EqHashMap [Tree , Type ]()
255
256
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 ]()
258
263
259
264
/** The set of symbols that were rechecked via a completer */
260
265
private val completed = new mutable.HashSet [Symbol ]
@@ -273,7 +278,7 @@ class CheckCaptures extends Recheck, SymTransformer:
273
278
extension [T <: Tree ](tree : T )
274
279
def needsSepCheck : Boolean = sepCheckFormals.contains(tree)
275
280
def formalType : Type = sepCheckFormals.getOrElse(tree, NoType )
276
- def markedFree = usedSet.getOrElse(tree, CaptureSet .empty)
281
+ def markedFree : CaptureSet = usedSet.getOrElse(tree, CaptureSet .empty)
277
282
278
283
/** Instantiate capture set variables appearing contra-variantly to their
279
284
* upper approximation.
@@ -462,28 +467,17 @@ class CheckCaptures extends Recheck, SymTransformer:
462
467
! sym.isContainedIn(env.owner)
463
468
}
464
469
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
-
479
470
/** Avoid locally defined capability by charging the underlying type
480
471
* (which may not be cap). This scheme applies only under the deferredReaches setting.
481
472
*/
482
473
def avoidLocalCapability (c : Capability , env : Env , lastEnv : Env | Null ): Unit =
483
474
if c.isParamPath then
484
475
c match
485
476
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)
487
481
case _ =>
488
482
else
489
483
val underlying = c match
@@ -501,32 +495,22 @@ class CheckCaptures extends Recheck, SymTransformer:
501
495
* parameter. This is the default.
502
496
*/
503
497
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
530
514
case _ =>
531
515
532
516
def recur (cs : CaptureSet , env : Env , lastEnv : Env | Null ): Unit =
@@ -542,15 +526,28 @@ class CheckCaptures extends Recheck, SymTransformer:
542
526
isVisible
543
527
checkSubset(included, env.captured, tree.srcPos, provenance(env))
544
528
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
546
530
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.
549
533
550
534
recur(cs, curEnv, null )
551
- usedSet(tree) = tree.markedFree ++ cs
535
+ useInfos += (( tree, cs, curEnv))
552
536
end markFree
553
537
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
+
554
551
/** Include references captured by the called method in the current environment stack */
555
552
def includeCallCaptures (sym : Symbol , resType : Type , tree : Tree )(using Context ): Unit = resType match
556
553
case _ : MethodOrPoly => // wait until method is fully applied
@@ -1992,6 +1989,48 @@ class CheckCaptures extends Recheck, SymTransformer:
1992
1989
traverseChildren(t)
1993
1990
check.traverse(tp)
1994
1991
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
+
1995
2034
/** Check that arguments of TypeApplys and AppliedTypes conform to their bounds.
1996
2035
*/
1997
2036
def postCheck (unit : tpd.Tree )(using Context ): Unit =
@@ -2020,7 +2059,11 @@ class CheckCaptures extends Recheck, SymTransformer:
2020
2059
end checker
2021
2060
2022
2061
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)
2024
2067
if ! ctx.reporter.errorsReported then
2025
2068
// We dont report errors here if previous errors were reported, because other
2026
2069
// errors often result in bad applied types, but flagging these bad types gives
0 commit comments