Skip to content

Commit 8030851

Browse files
committed
Don't propagate captures out of curried nested closures
1 parent 9c96538 commit 8030851

File tree

2 files changed

+27
-19
lines changed

2 files changed

+27
-19
lines changed

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

Lines changed: 17 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import ast.{tpd, untpd, Trees}
1212
import Trees.*
1313
import typer.RefChecks.{checkAllOverrides, checkSelfAgainstParents, OverridingPairsChecker}
1414
import typer.Checking.{checkBounds, checkAppliedTypesIn}
15-
import util.{SimpleIdentitySet, EqHashMap, SrcPos}
15+
import util.{SimpleIdentitySet, EqHashMap, SrcPos, Property}
1616
import transform.SymUtils.*
1717
import transform.{Recheck, PreRecheck}
1818
import Recheck.*
@@ -145,6 +145,9 @@ object CheckCaptures:
145145
traverseChildren(t)
146146
check.traverse(tp)
147147

148+
/** Attachment key for boxed curried closures */
149+
val BoxedClosure = Property.Key[Type]
150+
148151
class CheckCaptures extends Recheck, SymTransformer:
149152
thisPhase =>
150153

@@ -231,29 +234,15 @@ class CheckCaptures extends Recheck, SymTransformer:
231234
if sym.ownersIterator.exists(_.isTerm) then CaptureSet.Var()
232235
else CaptureSet.empty)
233236

234-
/**
235-
class anon1 extends Function1:
236-
def apply: Function1 =
237-
use(x)
238-
class anon2 extends Function1:
239-
use(y)
240-
def apply: Function1 = ...
241-
anon2()
242-
anon1()
243-
*/
244-
/** For all nested environments up to `limit` perform `op` */
237+
/** For all nested environments up to `limit` or a closed environment perform `op` */
245238
def forallOuterEnvsUpTo(limit: Symbol)(op: Env => Unit)(using Context): Unit =
246-
def stopsPropagation(env: Env) =
247-
val sym = env.owner
248-
env.isOutermost
249-
|| false && sym.is(Method) && sym.owner.isTerm && !sym.isConstructor
250239
def recur(env: Env): Unit =
251240
if env.isOpen && env.owner != limit then
252241
op(env)
253-
if !stopsPropagation(env) then
242+
if !env.isOutermost then
254243
var nextEnv = env.outer
255244
if env.owner.isConstructor then
256-
if nextEnv.owner != limit && !stopsPropagation(nextEnv) then
245+
if nextEnv.owner != limit && !nextEnv.isOutermost then
257246
nextEnv = nextEnv.outer
258247
recur(nextEnv)
259248
recur(curEnv)
@@ -491,6 +480,15 @@ class CheckCaptures extends Recheck, SymTransformer:
491480
recheckDef(mdef, meth)
492481
meth.updateInfoBetween(preRecheckPhase, thisPhase, completer)
493482
case _ =>
483+
mdef.rhs match
484+
case rhs @ closure(_, _, _) =>
485+
// In a curried closure `x => y => e` don't leak capabilities retained by
486+
// the second closure `y => e` into the first one. This is an approximation
487+
// of the CC rule which says that a closure contributes captures to its
488+
// environment only if a let-bound reference to the closure is used.
489+
capt.println(i"boxing $rhs")
490+
rhs.putAttachment(BoxedClosure, ())
491+
case _ =>
494492
case _ =>
495493
super.recheckBlock(block, pt)
496494

@@ -588,7 +586,7 @@ class CheckCaptures extends Recheck, SymTransformer:
588586
* adding all references in the boxed capture set to the current environment.
589587
*/
590588
override def recheck(tree: Tree, pt: Type = WildcardType)(using Context): Type =
591-
if tree.isTerm && pt.isBoxedCapturing then
589+
if tree.isTerm && (pt.isBoxedCapturing || tree.hasAttachment(BoxedClosure)) then
592590
val saved = curEnv
593591

594592
tree match
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import java.io.*
2+
import annotation.capability
3+
4+
def Test4(g: OutputStream^) =
5+
val xs: List[Int] = ???
6+
val later = (f: OutputStream^) => (y: Int) => xs.foreach(x => f.write(x + y))
7+
val _: (f: OutputStream^) ->{} Int ->{f} Unit = later
8+
9+
val later2 = () => (y: Int) => xs.foreach(x => g.write(x + y))
10+
val _: () ->{} Int ->{g} Unit = later2

0 commit comments

Comments
 (0)