Skip to content

Commit b0c9b3d

Browse files
committed
Separation checking for blocks
Check that a capability that gets hidden in the (result-)type of some definition is not used afterwards in the same or a nested scope.
1 parent 4c8a50f commit b0c9b3d

File tree

15 files changed

+328
-122
lines changed

15 files changed

+328
-122
lines changed

compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala

+43-35
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ import util.{SimpleIdentitySet, EqHashMap, EqHashSet, SrcPos, Property}
1818
import transform.{Recheck, PreRecheck, CapturedVars}
1919
import Recheck.*
2020
import scala.collection.mutable
21-
import CaptureSet.{withCaptureSetsExplained, IdempotentCaptRefMap, CompareResult, VarState}
21+
import CaptureSet.{withCaptureSetsExplained, IdempotentCaptRefMap, CompareResult}
2222
import CCState.*
2323
import StdNames.nme
2424
import NameKinds.{DefaultGetterName, WildcardParamName, UniqueNameKind}
@@ -253,6 +253,10 @@ object CheckCaptures:
253253
* the type of the formal paremeter corresponding to the argument.
254254
*/
255255
def formalType: Type
256+
257+
/** The "use set", i.e. the capture set marked as free at this node. */
258+
def markedFree: CaptureSet
259+
256260
end CheckerAPI
257261

258262
class CheckCaptures extends Recheck, SymTransformer:
@@ -298,9 +302,12 @@ class CheckCaptures extends Recheck, SymTransformer:
298302
*/
299303
private val sepCheckFormals = util.EqHashMap[Tree, Type]()
300304

305+
private val usedSet = util.EqHashMap[Tree, CaptureSet]()
306+
301307
extension [T <: Tree](tree: T)
302308
def needsSepCheck: Boolean = sepCheckFormals.contains(tree)
303309
def formalType: Type = sepCheckFormals.getOrElse(tree, NoType)
310+
def markedFree = usedSet.getOrElse(tree, CaptureSet.empty)
304311

305312
/** Instantiate capture set variables appearing contra-variantly to their
306313
* upper approximation.
@@ -404,17 +411,17 @@ class CheckCaptures extends Recheck, SymTransformer:
404411
/** Include `sym` in the capture sets of all enclosing environments nested in the
405412
* the environment in which `sym` is defined.
406413
*/
407-
def markFree(sym: Symbol, pos: SrcPos)(using Context): Unit =
408-
markFree(sym, sym.termRef, pos)
414+
def markFree(sym: Symbol, tree: Tree)(using Context): Unit =
415+
markFree(sym, sym.termRef, tree)
409416

410-
def markFree(sym: Symbol, ref: CaptureRef, pos: SrcPos)(using Context): Unit =
411-
if sym.exists && ref.isTracked then markFree(ref.captureSet, pos)
417+
def markFree(sym: Symbol, ref: CaptureRef, tree: Tree)(using Context): Unit =
418+
if sym.exists && ref.isTracked then markFree(ref.captureSet, tree)
412419

413420
/** Make sure the (projected) `cs` is a subset of the capture sets of all enclosing
414421
* environments. At each stage, only include references from `cs` that are outside
415422
* the environment's owner
416423
*/
417-
def markFree(cs: CaptureSet, pos: SrcPos)(using Context): Unit =
424+
def markFree(cs: CaptureSet, tree: Tree)(using Context): Unit =
418425
// A captured reference with the symbol `sym` is visible from the environment
419426
// if `sym` is not defined inside the owner of the environment.
420427
inline def isVisibleFromEnv(sym: Symbol, env: Env) =
@@ -436,7 +443,7 @@ class CheckCaptures extends Recheck, SymTransformer:
436443
val what = if ref.isType then "Capture set parameter" else "Local reach capability"
437444
report.error(
438445
em"""$what $c leaks into capture scope of ${env.ownerString}.
439-
|To allow this, the ${ref.symbol} should be declared with a @use annotation""", pos)
446+
|To allow this, the ${ref.symbol} should be declared with a @use annotation""", tree.srcPos)
440447
case _ =>
441448

442449
/** Avoid locally defined capability by charging the underlying type
@@ -456,7 +463,7 @@ class CheckCaptures extends Recheck, SymTransformer:
456463
CaptureSet.ofType(c.widen, followResult = false)
457464
capt.println(i"Widen reach $c to $underlying in ${env.owner}")
458465
underlying.disallowRootCapability: () =>
459-
report.error(em"Local capability $c in ${env.ownerString} cannot have `cap` as underlying capture set", pos)
466+
report.error(em"Local capability $c in ${env.ownerString} cannot have `cap` as underlying capture set", tree.srcPos)
460467
recur(underlying, env, lastEnv)
461468

462469
/** Avoid locally defined capability if it is a reach capability or capture set
@@ -479,7 +486,7 @@ class CheckCaptures extends Recheck, SymTransformer:
479486
val underlying = CaptureSet.ofTypeDeeply(c1.widen)
480487
capt.println(i"Widen reach $c to $underlying in ${env.owner}")
481488
underlying.disallowRootCapability: () =>
482-
report.error(em"Local reach capability $c leaks into capture scope of ${env.ownerString}", pos)
489+
report.error(em"Local reach capability $c leaks into capture scope of ${env.ownerString}", tree.srcPos)
483490
recur(underlying, env, null)
484491
case c: TypeRef if c.isParamPath =>
485492
checkUseDeclared(c, env, null)
@@ -496,22 +503,23 @@ class CheckCaptures extends Recheck, SymTransformer:
496503
then avoidLocalCapability(c, env, lastEnv)
497504
else avoidLocalReachCapability(c, env)
498505
isVisible
499-
checkSubset(included, env.captured, pos, provenance(env))
506+
checkSubset(included, env.captured, tree.srcPos, provenance(env))
500507
capt.println(i"Include call or box capture $included from $cs in ${env.owner} --> ${env.captured}")
501508
if !isOfNestedMethod(env) then
502509
recur(included, nextEnvToCharge(env, !_.owner.isStaticOwner), env)
503510
// Don't propagate out of methods inside terms. The use set of these methods
504511
// will be charged when that method is called.
505512

506513
recur(cs, curEnv, null)
514+
usedSet(tree) = tree.markedFree ++ cs
507515
end markFree
508516

509517
/** Include references captured by the called method in the current environment stack */
510-
def includeCallCaptures(sym: Symbol, resType: Type, pos: SrcPos)(using Context): Unit = resType match
518+
def includeCallCaptures(sym: Symbol, resType: Type, tree: Tree)(using Context): Unit = resType match
511519
case _: MethodOrPoly => // wait until method is fully applied
512520
case _ =>
513521
if sym.exists then
514-
if curEnv.isOpen then markFree(capturedVars(sym), pos)
522+
if curEnv.isOpen then markFree(capturedVars(sym), tree)
515523

516524
/** Under the sealed policy, disallow the root capability in type arguments.
517525
* Type arguments come either from a TypeApply node or from an AppliedType
@@ -535,23 +543,23 @@ class CheckCaptures extends Recheck, SymTransformer:
535543

536544
for case (arg: TypeTree, pname) <- args.lazyZip(paramNames) do
537545
def where = if sym.exists then i" in an argument of $sym" else ""
538-
val (addendum, pos) =
546+
val (addendum, errTree) =
539547
if arg.isInferred
540-
then ("\nThis is often caused by a local capability$where\nleaking as part of its result.", fn.srcPos)
541-
else if arg.span.exists then ("", arg.srcPos)
542-
else ("", fn.srcPos)
548+
then ("\nThis is often caused by a local capability$where\nleaking as part of its result.", fn)
549+
else if arg.span.exists then ("", arg)
550+
else ("", fn)
543551
disallowRootCapabilitiesIn(arg.nuType, NoSymbol,
544-
i"Type variable $pname of $sym", "be instantiated to", addendum, pos)
552+
i"Type variable $pname of $sym", "be instantiated to", addendum, errTree.srcPos)
545553

546554
val param = fn.symbol.paramNamed(pname)
547-
if param.isUseParam then markFree(arg.nuType.deepCaptureSet, pos)
555+
if param.isUseParam then markFree(arg.nuType.deepCaptureSet, errTree)
548556
end disallowCapInTypeArgs
549557

550558
override def recheckIdent(tree: Ident, pt: Type)(using Context): Type =
551559
val sym = tree.symbol
552560
if sym.is(Method) then
553561
// If ident refers to a parameterless method, charge its cv to the environment
554-
includeCallCaptures(sym, sym.info, tree.srcPos)
562+
includeCallCaptures(sym, sym.info, tree)
555563
else if !sym.isStatic then
556564
// Otherwise charge its symbol, but add all selections implied by the e
557565
// expected type `pt`.
@@ -569,7 +577,7 @@ class CheckCaptures extends Recheck, SymTransformer:
569577
var pathRef: CaptureRef = addSelects(sym.termRef, pt)
570578
if pathRef.derivesFrom(defn.Caps_Mutable) && pt.isValueType && !pt.isMutableType then
571579
pathRef = pathRef.readOnly
572-
markFree(sym, pathRef, tree.srcPos)
580+
markFree(sym, pathRef, tree)
573581
super.recheckIdent(tree, pt)
574582

575583
/** The expected type for the qualifier of a selection. If the selection
@@ -668,7 +676,7 @@ class CheckCaptures extends Recheck, SymTransformer:
668676
super.recheckFinish(argType, tree, pt)
669677
else
670678
val res = super.recheckApply(tree, pt)
671-
includeCallCaptures(meth, res, tree.srcPos)
679+
includeCallCaptures(meth, res, tree)
672680
res
673681

674682
/** Recheck argument, and, if formal parameter carries a `@use`,
@@ -681,7 +689,7 @@ class CheckCaptures extends Recheck, SymTransformer:
681689
if formal.hasUseAnnot then
682690
// The @use annotation is added to `formal` by `prepareFunction`
683691
capt.println(i"charging deep capture set of $arg: ${argType} = ${argType.deepCaptureSet}")
684-
markFree(argType.deepCaptureSet, arg.srcPos)
692+
markFree(argType.deepCaptureSet, arg)
685693
if formal.containsCap then
686694
sepCheckFormals(arg) = freshenedFormal
687695
argType
@@ -815,7 +823,7 @@ class CheckCaptures extends Recheck, SymTransformer:
815823
case fun => fun.symbol
816824
disallowCapInTypeArgs(tree.fun, meth, tree.args)
817825
val res = Existential.toCap(super.recheckTypeApply(tree, pt))
818-
includeCallCaptures(tree.symbol, res, tree.srcPos)
826+
includeCallCaptures(tree.symbol, res, tree)
819827
checkContains(tree)
820828
res
821829
end recheckTypeApply
@@ -1092,7 +1100,7 @@ class CheckCaptures extends Recheck, SymTransformer:
10921100
case AnnotatedType(_, annot) if annot.symbol == defn.RequiresCapabilityAnnot =>
10931101
annot.tree match
10941102
case Apply(_, cap :: Nil) =>
1095-
markFree(cap.symbol, tree.srcPos)
1103+
markFree(cap.symbol, tree)
10961104
case _ =>
10971105
case _ =>
10981106
super.recheckTyped(tree)
@@ -1147,7 +1155,7 @@ class CheckCaptures extends Recheck, SymTransformer:
11471155
super.recheck(tree, pt)
11481156
finally curEnv = saved
11491157
if tree.isTerm && !pt.isBoxedCapturing && pt != LhsProto then
1150-
markFree(res.boxedCaptureSet, tree.srcPos)
1158+
markFree(res.boxedCaptureSet, tree)
11511159
res
11521160
end recheck
11531161

@@ -1214,7 +1222,7 @@ class CheckCaptures extends Recheck, SymTransformer:
12141222
override def checkConformsExpr(actual: Type, expected: Type, tree: Tree, addenda: Addenda)(using Context): Type =
12151223
var expected1 = alignDependentFunction(expected, actual.stripCapturing)
12161224
val boxErrors = new mutable.ListBuffer[Message]
1217-
val actualBoxed = adapt(actual, expected1, tree.srcPos, boxErrors)
1225+
val actualBoxed = adapt(actual, expected1, tree, boxErrors)
12181226
//println(i"check conforms $actualBoxed <<< $expected1")
12191227

12201228
if actualBoxed eq actual then
@@ -1334,7 +1342,7 @@ class CheckCaptures extends Recheck, SymTransformer:
13341342
*
13351343
* @param alwaysConst always make capture set variables constant after adaptation
13361344
*/
1337-
def adaptBoxed(actual: Type, expected: Type, pos: SrcPos, covariant: Boolean, alwaysConst: Boolean, boxErrors: BoxErrors)(using Context): Type =
1345+
def adaptBoxed(actual: Type, expected: Type, tree: Tree, covariant: Boolean, alwaysConst: Boolean, boxErrors: BoxErrors)(using Context): Type =
13381346

13391347
def recur(actual: Type, expected: Type, covariant: Boolean): Type =
13401348

@@ -1401,7 +1409,7 @@ class CheckCaptures extends Recheck, SymTransformer:
14011409
if !leaked.subCaptures(cs).isOK then
14021410
report.error(
14031411
em"""$expected cannot be box-converted to ${actual.capturing(leaked)}
1404-
|since the additional capture set $leaked resulted from box conversion is not allowed in $actual""", pos)
1412+
|since the additional capture set $leaked resulted from box conversion is not allowed in $actual""", tree.srcPos)
14051413
cs
14061414

14071415
def adaptedType(resultBoxed: Boolean) =
@@ -1433,11 +1441,11 @@ class CheckCaptures extends Recheck, SymTransformer:
14331441
return actual
14341442
// Disallow future addition of `cap` to `criticalSet`.
14351443
criticalSet.disallowRootCapability: () =>
1436-
report.error(msg, pos)
1444+
report.error(msg, tree.srcPos)
14371445

14381446
if !insertBox then // we are unboxing
14391447
//debugShowEnvs()
1440-
markFree(criticalSet, pos)
1448+
markFree(criticalSet, tree)
14411449
end if
14421450

14431451
// Compute the adapted type.
@@ -1497,14 +1505,14 @@ class CheckCaptures extends Recheck, SymTransformer:
14971505
* - narrow nested captures of `x`'s underlying type to `{x*}`
14981506
* - do box adaptation
14991507
*/
1500-
def adapt(actual: Type, expected: Type, pos: SrcPos, boxErrors: BoxErrors)(using Context): Type =
1508+
def adapt(actual: Type, expected: Type, tree: Tree, boxErrors: BoxErrors)(using Context): Type =
15011509
if expected == LhsProto || expected.isSingleton && actual.isSingleton then
15021510
actual
15031511
else
15041512
val improvedVAR = improveCaptures(actual.widen.dealiasKeepAnnots, actual)
15051513
val improvedRO = improveReadOnly(improvedVAR, expected)
15061514
val adapted = adaptBoxed(
1507-
improvedRO.withReachCaptures(actual), expected, pos,
1515+
improvedRO.withReachCaptures(actual), expected, tree,
15081516
covariant = true, alwaysConst = false, boxErrors)
15091517
if adapted eq improvedVAR // no .rd improvement, no box-adaptation
15101518
then actual // might as well use actual instead of improved widened
@@ -1519,19 +1527,19 @@ class CheckCaptures extends Recheck, SymTransformer:
15191527
* But maybe we can then elide the check during the RefChecks phase under captureChecking?
15201528
*/
15211529
def checkOverrides = new TreeTraverser:
1522-
class OverridingPairsCheckerCC(clazz: ClassSymbol, self: Type, srcPos: SrcPos)(using Context) extends OverridingPairsChecker(clazz, self):
1530+
class OverridingPairsCheckerCC(clazz: ClassSymbol, self: Type, tree: Tree)(using Context) extends OverridingPairsChecker(clazz, self):
15231531
/** Check subtype with box adaptation.
15241532
* This function is passed to RefChecks to check the compatibility of overriding pairs.
15251533
* @param sym symbol of the field definition that is being checked
15261534
*/
15271535
override def checkSubType(actual: Type, expected: Type)(using Context): Boolean =
1528-
val expected1 = alignDependentFunction(addOuterRefs(expected, actual, srcPos), actual.stripCapturing)
1536+
val expected1 = alignDependentFunction(addOuterRefs(expected, actual, tree.srcPos), actual.stripCapturing)
15291537
val actual1 =
15301538
val saved = curEnv
15311539
try
15321540
curEnv = Env(clazz, EnvKind.NestedInOwner, capturedVars(clazz), outer0 = curEnv)
15331541
val adapted =
1534-
adaptBoxed(actual, expected1, srcPos, covariant = true, alwaysConst = true, null)
1542+
adaptBoxed(actual, expected1, tree, covariant = true, alwaysConst = true, null)
15351543
actual match
15361544
case _: MethodType =>
15371545
// We remove the capture set resulted from box adaptation for method types,

0 commit comments

Comments
 (0)