Skip to content

Commit acf8286

Browse files
committed
Reorganize the way capture sets are included in the current environment stack
1 parent a0cbb87 commit acf8286

File tree

1 file changed

+80
-50
lines changed

1 file changed

+80
-50
lines changed

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

Lines changed: 80 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -39,10 +39,25 @@ object CheckCaptures:
3939
sym
4040
end Pre
4141

42+
/** A class describing environments.
43+
* @param owner the current owner
44+
* @param captured the caputure set containing all references to tracked free variables outside of boxes
45+
* @param isBoxed true if the environment is inside a box (in which case references are not counted)
46+
* @param outer0 the next enclosing environment or null
47+
*/
4248
case class Env(owner: Symbol, captured: CaptureSet, isBoxed: Boolean, outer0: Env | Null):
4349
def outer = outer0.nn
50+
51+
def isOutermost = outer0 == null
52+
53+
/** If an environment is open it tracks free references */
4454
def isOpen = !captured.isAlwaysEmpty && !isBoxed
55+
end Env
4556

57+
/** Similar normal substParams, but this is an approximating type map that
58+
* maps parameters in contravariant capture sets to the empty set.
59+
* TODO: check what happens with non-variant.
60+
*/
4661
final class SubstParamsMap(from: BindingType, to: List[Type])(using Context)
4762
extends ApproximatingTypeMap, IdempotentCaptRefMap:
4863
def apply(tp: Type): Type = tp match
@@ -56,7 +71,7 @@ object CheckCaptures:
5671
case _ =>
5772
mapOver(tp)
5873

59-
/** Check that a @retains annotation only mentions references that can be tracked
74+
/** Check that a @retains annotation only mentions references that can be tracked.
6075
* This check is performed at Typer.
6176
*/
6277
def checkWellformed(ann: Tree)(using Context): Unit =
@@ -149,75 +164,90 @@ class CheckCaptures extends Recheck, SymTransformer:
149164
case _ =>
150165
traverseChildren(t)
151166

167+
/** If `tpt` is an inferred type, interpolate capture set variables appearing contra-
168+
* variantly in it.
169+
*/
152170
private def interpolateVarsIn(tpt: Tree)(using Context): Unit =
153171
if tpt.isInstanceOf[InferredTypeTree] then
154172
interpolator().traverse(tpt.knownType)
155173
.showing(i"solved vars in ${tpt.knownType}", capt)
156174

175+
/** Assert subcapturing `cs1 <: cs2` */
176+
def assertSub(cs1: CaptureSet, cs2: CaptureSet)(using Context) =
177+
assert(cs1.subCaptures(cs2, frozen = false).isOK, i"$cs1 is not a subset of $cs2")
178+
179+
/** Check subcapturing `{elem} <: cs`, report error on failure */
180+
def checkElem(elem: CaptureRef, cs: CaptureSet, pos: SrcPos)(using Context) =
181+
val res = elem.singletonCaptureSet.subCaptures(cs, frozen = false)
182+
if !res.isOK then
183+
report.error(i"$elem cannot be referenced here; it is not included in the allowed capture set ${res.blocking}", pos)
184+
185+
/** Check subcapturing `cs1 <: cs2`, report error on failure */
186+
def checkSubset(cs1: CaptureSet, cs2: CaptureSet, pos: SrcPos)(using Context) =
187+
val res = cs1.subCaptures(cs2, frozen = false)
188+
if !res.isOK then
189+
def header =
190+
if cs1.elems.size == 1 then i"reference ${cs1.elems.toList}%, % is not"
191+
else i"references $cs1 are not all"
192+
report.error(i"$header included in allowed capture set ${res.blocking}", pos)
193+
194+
/** The current environment */
157195
private var curEnv: Env = Env(NoSymbol, CaptureSet.empty, isBoxed = false, null)
158196

159197
private val myCapturedVars: util.EqHashMap[Symbol, CaptureSet] = EqHashMap()
198+
199+
/** If `sym` is a class or method nested inside a term, a capture set variable representing
200+
* the captured variables of the environment associated with `sym`.
201+
*/
160202
def capturedVars(sym: Symbol)(using Context) =
161203
myCapturedVars.getOrElseUpdate(sym,
162204
if sym.ownersIterator.exists(_.isTerm) then CaptureSet.Var()
163205
else CaptureSet.empty)
164206

207+
/** For all nested environments up to `limit` perform `op` */
208+
def forallOuterEnvsUpTo(limit: Symbol)(op: Env => Unit)(using Context): Unit =
209+
def recur(env: Env): Unit =
210+
if env.isOpen && env.owner != limit then
211+
op(env)
212+
if !env.isOutermost then
213+
var nextEnv = env.outer
214+
if env.owner.isConstructor then
215+
if nextEnv.owner != limit && !nextEnv.isOutermost then
216+
recur(nextEnv.outer)
217+
else recur(nextEnv)
218+
recur(curEnv)
219+
220+
/** Include `sym` in the capture sets of all enclosing environments nested in the
221+
* the environment in which `sym` is defined.
222+
*/
165223
def markFree(sym: Symbol, pos: SrcPos)(using Context): Unit =
166224
if sym.exists then
167225
val ref = sym.termRef
168-
def recur(env: Env): Unit =
169-
if env.isOpen && env.owner != sym.enclosure then
226+
if ref.isTracked then
227+
forallOuterEnvsUpTo(sym.enclosure) { env =>
170228
capt.println(i"Mark $sym with cs ${ref.captureSet} free in ${env.owner}")
171229
checkElem(ref, env.captured, pos)
172-
if env.owner.isConstructor then
173-
if env.outer.owner != sym.enclosure then recur(env.outer.outer)
174-
else recur(env.outer)
175-
if ref.isTracked then recur(curEnv)
230+
}
176231

177-
def includeCallCaptures(sym: Symbol, pos: SrcPos)(using Context): Unit =
178-
if curEnv.isOpen then
179-
val ownEnclosure = ctx.owner.enclosingMethodOrClass
180-
var targetSet = capturedVars(sym)
181-
if !targetSet.isAlwaysEmpty && sym.enclosure == ownEnclosure then
182-
targetSet = targetSet.filter {
183-
case ref: TermRef => ref.symbol.enclosure != ownEnclosure
184-
case _ => true
232+
/** Make sure (projected) `cs` is a subset of the capture sets of all enclosing
233+
* environments. At each stage, only include references from `cs` that are outside
234+
* the environment's owner
235+
*/
236+
def markFree(cs: CaptureSet, pos: SrcPos)(using Context): Unit =
237+
if !cs.isAlwaysEmpty then
238+
forallOuterEnvsUpTo(ctx.owner.topLevelClass) { env =>
239+
val included = cs.filter {
240+
case ref: TermRef => env.owner.isProperlyContainedIn(ref.symbol.owner)
241+
case ref: ThisType => env.owner.isProperlyContainedIn(ref.cls)
242+
case _ => false
185243
}
186-
def includeIn(env: Env) =
187-
capt.println(i"Include call capture $targetSet in ${env.owner}")
188-
checkSubset(targetSet, env.captured, pos)
189-
includeIn(curEnv)
190-
if curEnv.owner.isTerm && curEnv.outer.owner.isClass then
191-
includeIn(curEnv.outer)
192-
193-
def includeBoxedCaptures(tp: Type, pos: SrcPos)(using Context): Unit =
194-
includeBoxedCaptures(tp.boxedCaptureSet, pos)
195-
196-
def includeBoxedCaptures(refs: CaptureSet, pos: SrcPos)(using Context): Unit =
197-
if curEnv.isOpen then
198-
val ownEnclosure = ctx.owner.enclosingMethodOrClass
199-
val targetSet = refs.filter {
200-
case ref: TermRef => ref.symbol.enclosure != ownEnclosure
201-
case ref: ThisType => true
202-
case _ => false
244+
capt.println(i"Include call capture $included in ${env.owner}")
245+
checkSubset(included, env.captured, pos)
203246
}
204-
checkSubset(targetSet, curEnv.captured, pos)
205-
206-
def assertSub(cs1: CaptureSet, cs2: CaptureSet)(using Context) =
207-
assert(cs1.subCaptures(cs2, frozen = false).isOK, i"$cs1 is not a subset of $cs2")
208247

209-
def checkElem(elem: CaptureRef, cs: CaptureSet, pos: SrcPos)(using Context) =
210-
val res = elem.singletonCaptureSet.subCaptures(cs, frozen = false)
211-
if !res.isOK then
212-
report.error(i"$elem cannot be referenced here; it is not included in the allowed capture set ${res.blocking}", pos)
213-
214-
def checkSubset(cs1: CaptureSet, cs2: CaptureSet, pos: SrcPos)(using Context) =
215-
val res = cs1.subCaptures(cs2, frozen = false)
216-
if !res.isOK then
217-
def header =
218-
if cs1.elems.size == 1 then i"reference ${cs1.elems.toList}%, % is not"
219-
else i"references $cs1 are not all"
220-
report.error(i"$header included in allowed capture set ${res.blocking}", pos)
248+
/** Include references captured by the called method in the current environment stack */
249+
def includeCallCaptures(sym: Symbol, pos: SrcPos)(using Context): Unit =
250+
if sym.exists && curEnv.isOpen then markFree(capturedVars(sym), pos)
221251

222252
/** A specialized implementation of the selection rule.
223253
*
@@ -434,7 +464,7 @@ class CheckCaptures extends Recheck, SymTransformer:
434464
finally curEnv = saved
435465
else super.recheck(tree, pt)
436466
if tree.isTerm then
437-
includeBoxedCaptures(res, tree.srcPos)
467+
markFree(res.boxedCaptureSet, tree.srcPos)
438468
res
439469

440470
override def recheckFinish(tpe: Type, tree: Tree, pt: Type)(using Context): Type =
@@ -526,7 +556,7 @@ class CheckCaptures extends Recheck, SymTransformer:
526556
capt.println(i"ABORTING $actual vs $expected")
527557
actual
528558
else
529-
if covariant == actual.isBoxed then includeBoxedCaptures(refs, pos)
559+
if covariant == actual.isBoxed then markFree(refs, pos)
530560
CapturingType(parent1, refs, boxed = !actual.isBoxed)
531561
else if parent1 eq parent then actual
532562
else CapturingType(parent1, refs, boxed = actual.isBoxed)

0 commit comments

Comments
 (0)