From 08321d46fe45d5c1dfc5e606210fab74e47fb0ae Mon Sep 17 00:00:00 2001 From: Yichen Xu Date: Sun, 16 Oct 2022 02:19:58 +0200 Subject: [PATCH 1/5] only create boxed environment for function values and references --- .../dotty/tools/dotc/cc/CheckCaptures.scala | 16 ++++++- tests/neg-custom-args/captures/i16114.scala | 46 +++++++++++++++++++ 2 files changed, 61 insertions(+), 1 deletion(-) create mode 100644 tests/neg-custom-args/captures/i16114.scala diff --git a/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala b/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala index 1ecf42a90aaa..65ea403870dd 100644 --- a/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala +++ b/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala @@ -507,6 +507,19 @@ class CheckCaptures extends Recheck, SymTransformer: recheckFinish(result, arg, pt) */ + extension (tree: Tree) + private def isFunctionLiteral(using Context): Boolean = tree match + case Block((defTree @ DefDef(_, _, _, _)) :: Nil, Closure(_, meth, _)) => + val defSym = defTree.symbol + val methSym = meth.symbol + defSym.eq(methSym) + case _ => + false + + private def isIdent: Boolean = tree match + case Ident(_) => true + case _ => false + /** If expected type `pt` is boxed, don't propagate free variables. * Otherwise, if the result type is boxed, simulate an unboxing by * adding all references in the boxed capture set to the current environment. @@ -514,7 +527,8 @@ class CheckCaptures extends Recheck, SymTransformer: override def recheck(tree: Tree, pt: Type = WildcardType)(using Context): Type = if tree.isTerm && pt.isBoxedCapturing then val saved = curEnv - curEnv = Env(curEnv.owner, nestedInOwner = false, CaptureSet.Var(), isBoxed = true, curEnv) + if tree.isIdent || tree.isFunctionLiteral then + curEnv = Env(curEnv.owner, nestedInOwner = false, CaptureSet.Var(), isBoxed = true, curEnv) try super.recheck(tree, pt) finally curEnv = saved else diff --git a/tests/neg-custom-args/captures/i16114.scala b/tests/neg-custom-args/captures/i16114.scala new file mode 100644 index 000000000000..cc491226f9df --- /dev/null +++ b/tests/neg-custom-args/captures/i16114.scala @@ -0,0 +1,46 @@ +trait Cap { def use(): Int; def close(): Unit } +def mkCap(): {*} Cap = ??? + +def expect[T](x: T): x.type = x + +def withCap[T](op: ({*} Cap) => T): T = { + val cap: {*} Cap = mkCap() + val result = op(cap) + cap.close() + result +} + +def main(fs: {*} Cap): Unit = { + def badOp(io: {*} Cap): {} Unit -> Unit = { + val op1: {io} Unit -> Unit = (x: Unit) => // error // limitation + expect[{*} Cap] { + io.use() + fs + } + + val op2: {fs} Unit -> Unit = (x: Unit) => // error // limitation + expect[{*} Cap] { + fs.use() + io + } + + val op3: {io} Unit -> Unit = (x: Unit) => // ok + expect[{*} Cap] { + io.use() + io + } + + val op4: {} Unit -> Unit = (x: Unit) => // ok + expect[{*} Cap](io) + + val op: {} Unit -> Unit = (x: Unit) => // error + expect[{*} Cap] { + io.use() + io + } + op + } + + val leaked: {} Unit -> Unit = withCap(badOp) + leaked(()) +} From 7ae54f1585a1f70c25dc69a7cf2700a1cab3aaca Mon Sep 17 00:00:00 2001 From: Yichen Xu Date: Mon, 3 Oct 2022 15:20:19 +0200 Subject: [PATCH 2/5] fix boxmap test MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The result should capture {f}, since the expression `box(f(x))` should be translated as: let z = f(x) in let y = □ z in box(y) Denoting the above term as `t`, we can see that `f ∈ cv(t)`. --- tests/pos-custom-args/captures/boxmap.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/pos-custom-args/captures/boxmap.scala b/tests/pos-custom-args/captures/boxmap.scala index 5642763b5511..7bd58df80235 100644 --- a/tests/pos-custom-args/captures/boxmap.scala +++ b/tests/pos-custom-args/captures/boxmap.scala @@ -9,7 +9,7 @@ def box[T <: Top](x: T): Box[T] = def map[A <: Top, B <: Top](b: Box[A])(f: A => B): Box[B] = b[Box[B]]((x: A) => box(f(x))) -def lazymap[A <: Top, B <: Top](b: Box[A])(f: A => B): (() -> Box[B]) = +def lazymap[A <: Top, B <: Top](b: Box[A])(f: A => B): {f} (() -> Box[B]) = () => b[Box[B]]((x: A) => box(f(x))) def test[A <: Top, B <: Top] = From 4805007fc26a4e385d3a19c2205b48730de479f9 Mon Sep 17 00:00:00 2001 From: Yichen Xu Date: Mon, 3 Oct 2022 15:29:03 +0200 Subject: [PATCH 3/5] create boxed environment for all ref trees --- compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala b/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala index 65ea403870dd..2a664f290aa4 100644 --- a/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala +++ b/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala @@ -516,9 +516,9 @@ class CheckCaptures extends Recheck, SymTransformer: case _ => false - private def isIdent: Boolean = tree match - case Ident(_) => true - case _ => false + private def isRefTree: Boolean = tree match + case _: RefTree => true + case _ => false /** If expected type `pt` is boxed, don't propagate free variables. * Otherwise, if the result type is boxed, simulate an unboxing by @@ -527,7 +527,8 @@ class CheckCaptures extends Recheck, SymTransformer: override def recheck(tree: Tree, pt: Type = WildcardType)(using Context): Type = if tree.isTerm && pt.isBoxedCapturing then val saved = curEnv - if tree.isIdent || tree.isFunctionLiteral then + if tree.isRefTree || tree.isFunctionLiteral then + curEnv = Env(curEnv.owner, CaptureSet.Var(), isBoxed = true, curEnv) curEnv = Env(curEnv.owner, nestedInOwner = false, CaptureSet.Var(), isBoxed = true, curEnv) try super.recheck(tree, pt) finally curEnv = saved From 8bab4296235c583081ecede9b8bb650c62aa8e91 Mon Sep 17 00:00:00 2001 From: Yichen Xu Date: Tue, 4 Oct 2022 11:34:30 +0200 Subject: [PATCH 4/5] update documentation of recheck about top-level boxes --- compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala b/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala index 2a664f290aa4..76def7d4c364 100644 --- a/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala +++ b/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala @@ -520,7 +520,8 @@ class CheckCaptures extends Recheck, SymTransformer: case _: RefTree => true case _ => false - /** If expected type `pt` is boxed, don't propagate free variables. + /** If expected type `pt` is boxed and the tree is a function or a reference, + * don't propagate free variables. * Otherwise, if the result type is boxed, simulate an unboxing by * adding all references in the boxed capture set to the current environment. */ @@ -528,7 +529,6 @@ class CheckCaptures extends Recheck, SymTransformer: if tree.isTerm && pt.isBoxedCapturing then val saved = curEnv if tree.isRefTree || tree.isFunctionLiteral then - curEnv = Env(curEnv.owner, CaptureSet.Var(), isBoxed = true, curEnv) curEnv = Env(curEnv.owner, nestedInOwner = false, CaptureSet.Var(), isBoxed = true, curEnv) try super.recheck(tree, pt) finally curEnv = saved From a25677eac79a1f88bce5f495b4e442eddfebcd75 Mon Sep 17 00:00:00 2001 From: Yichen Xu Date: Sun, 16 Oct 2022 20:00:20 +0200 Subject: [PATCH 5/5] use pattern matching and `closureDef` extractor --- .../dotty/tools/dotc/cc/CheckCaptures.scala | 21 ++++++------------- 1 file changed, 6 insertions(+), 15 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala b/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala index 76def7d4c364..844857b609b5 100644 --- a/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala +++ b/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala @@ -507,19 +507,6 @@ class CheckCaptures extends Recheck, SymTransformer: recheckFinish(result, arg, pt) */ - extension (tree: Tree) - private def isFunctionLiteral(using Context): Boolean = tree match - case Block((defTree @ DefDef(_, _, _, _)) :: Nil, Closure(_, meth, _)) => - val defSym = defTree.symbol - val methSym = meth.symbol - defSym.eq(methSym) - case _ => - false - - private def isRefTree: Boolean = tree match - case _: RefTree => true - case _ => false - /** If expected type `pt` is boxed and the tree is a function or a reference, * don't propagate free variables. * Otherwise, if the result type is boxed, simulate an unboxing by @@ -528,8 +515,12 @@ class CheckCaptures extends Recheck, SymTransformer: override def recheck(tree: Tree, pt: Type = WildcardType)(using Context): Type = if tree.isTerm && pt.isBoxedCapturing then val saved = curEnv - if tree.isRefTree || tree.isFunctionLiteral then - curEnv = Env(curEnv.owner, nestedInOwner = false, CaptureSet.Var(), isBoxed = true, curEnv) + + tree match + case _: RefTree | closureDef(_) => + curEnv = Env(curEnv.owner, nestedInOwner = false, CaptureSet.Var(), isBoxed = true, curEnv) + case _ => + try super.recheck(tree, pt) finally curEnv = saved else