From 70af983595fa24c2b93784486f51c3d1fc29d2dd Mon Sep 17 00:00:00 2001 From: odersky Date: Wed, 30 Oct 2024 19:48:25 +0100 Subject: [PATCH] Drop curried use scheme Drop the scheme where we only charge the last arrow of a curried lambda with elements used in the body. On the one hand, this is unsound without compensation measures (like, restricting to reach capabilities, or taking all capture sets of a named curried function as the underlying reference). On the other hand, this should be generalized to all closures and anonymous functions forming the right hand sides of methods. --- compiler/src/dotty/tools/dotc/cc/CaptureOps.scala | 3 +++ .../src/dotty/tools/dotc/cc/CheckCaptures.scala | 9 ++++++--- compiler/src/dotty/tools/dotc/cc/Setup.scala | 2 +- .../captures/curried-closures.scala | 2 +- tests/neg-custom-args/captures/i21620.check | 13 +++++++++++++ tests/neg-custom-args/captures/i21620.scala | 11 +++++++++++ tests/neg-custom-args/captures/leaked-curried.check | 6 ++++-- tests/pos-custom-args/captures/i21620.scala | 11 ----------- 8 files changed, 39 insertions(+), 18 deletions(-) rename tests/{pos-custom-args => neg-custom-args}/captures/curried-closures.scala (91%) delete mode 100644 tests/pos-custom-args/captures/i21620.scala diff --git a/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala b/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala index 6543d316f03e..6c5b34c3a8a9 100644 --- a/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala +++ b/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala @@ -40,6 +40,9 @@ object ccConfig: */ inline val handleEtaExpansionsSpecially = false + /** If enabled we drop inner uses in outer arrows of a curried function */ + inline val DropOuterUsesInCurried = false + /** If true, use existential capture set variables */ def useExistentials(using Context) = Feature.sourceVersion.stable.isAtLeast(SourceVersion.`3.5`) diff --git a/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala b/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala index 44c9a0f6a531..607b4a6de5e3 100644 --- a/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala +++ b/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala @@ -33,7 +33,8 @@ object CheckCaptures: case NestedInOwner // environment is a temporary one nested in the owner's environment, // and does not have a different actual owner symbol // (this happens when doing box adaptation). - case ClosureResult // environment is for the result of a closure + case ClosureResult // environment is for the result of a closure, + // used only under ccConfig.DropOuterUsesInCurried case Boxed // environment is inside a box (in which case references are not counted) /** A class describing environments. @@ -180,7 +181,9 @@ object CheckCaptures: if ccConfig.useSealed then check.traverse(tp) end disallowRootCapabilitiesIn - /** Attachment key for bodies of closures, provided they are values */ + /** Attachment key for bodies of closures, provided they are values. + * Used only under ccConfig.DropOuterUsesInCurried + */ val ClosureBodyValue = Property.Key[Unit] /** A prototype that indicates selection with an immutable value */ @@ -728,7 +731,7 @@ class CheckCaptures extends Recheck, SymTransformer: override def recheckClosureBlock(mdef: DefDef, expr: Closure, pt: Type)(using Context): Type = mdef.rhs match - case rhs @ closure(_, _, _) => + case rhs @ closure(_, _, _) if ccConfig.DropOuterUsesInCurried => // In a curried closure `x => y => e` don't leak capabilities retained by // the second closure `y => e` into the first one. This is an approximation // of the CC rule which says that a closure contributes captures to its diff --git a/compiler/src/dotty/tools/dotc/cc/Setup.scala b/compiler/src/dotty/tools/dotc/cc/Setup.scala index 665bdb446c86..943b9f2f273a 100644 --- a/compiler/src/dotty/tools/dotc/cc/Setup.scala +++ b/compiler/src/dotty/tools/dotc/cc/Setup.scala @@ -294,7 +294,7 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: case _ => res ) val fntpe = defn.PolyFunctionOf(mt) - if !encl.isEmpty && resDecomposed.isEmpty then + if !encl.isEmpty && (!ccConfig.DropOuterUsesInCurried || resDecomposed.isEmpty) then val cs = CaptureSet(encl.map(_.paramRefs.head)*) CapturingType(fntpe, cs, boxed = false) else fntpe diff --git a/tests/pos-custom-args/captures/curried-closures.scala b/tests/neg-custom-args/captures/curried-closures.scala similarity index 91% rename from tests/pos-custom-args/captures/curried-closures.scala rename to tests/neg-custom-args/captures/curried-closures.scala index 262dd4b66b92..426f0df85022 100644 --- a/tests/pos-custom-args/captures/curried-closures.scala +++ b/tests/neg-custom-args/captures/curried-closures.scala @@ -30,5 +30,5 @@ def Test4(g: OutputStream^) = val _: (f: OutputStream^) ->{} Int ->{f} Unit = later val later2 = () => (y: Int) => xs.foreach(x => g.write(x + y)) - val _: () ->{} Int ->{g} Unit = later2 + val _: () ->{} Int ->{g} Unit = later2 // error, inferred type is () ->{later2} Int ->{g} Unit diff --git a/tests/neg-custom-args/captures/i21620.check b/tests/neg-custom-args/captures/i21620.check index 3a09ba978574..ddfcdafab36f 100644 --- a/tests/neg-custom-args/captures/i21620.check +++ b/tests/neg-custom-args/captures/i21620.check @@ -4,6 +4,12 @@ | A pure expression does nothing in statement position | | longer explanation available when compiling with `-explain` +-- [E129] Potential Issue Warning: tests/neg-custom-args/captures/i21620.scala:14:4 ------------------------------------ +14 | x + | ^ + | A pure expression does nothing in statement position + | + | longer explanation available when compiling with `-explain` -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/i21620.scala:9:31 ---------------------------------------- 9 | val _: () -> () ->{x} Unit = f // error | ^ @@ -11,3 +17,10 @@ | Required: () -> () ->{x} Unit | | longer explanation available when compiling with `-explain` +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/i21620.scala:20:33 --------------------------------------- +20 | val _: () ->{} () ->{x} Unit = f // error, but could be OK + | ^ + | Found: () ->{f} () ->{x} Unit + | Required: () -> () ->{x} Unit + | + | longer explanation available when compiling with `-explain` diff --git a/tests/neg-custom-args/captures/i21620.scala b/tests/neg-custom-args/captures/i21620.scala index a21a41a10863..3b7ba5a9fd5a 100644 --- a/tests/neg-custom-args/captures/i21620.scala +++ b/tests/neg-custom-args/captures/i21620.scala @@ -8,3 +8,14 @@ def test(x: C^) = () => foo() val _: () -> () ->{x} Unit = f // error () + +def test2(x: C^) = + def foo() = + x + () + val f = () => + // println() // uncomenting would give an error, but with + // a different way of handling curried functions should be OK + () => foo() + val _: () ->{} () ->{x} Unit = f // error, but could be OK + () diff --git a/tests/neg-custom-args/captures/leaked-curried.check b/tests/neg-custom-args/captures/leaked-curried.check index 3f0a9800a4ec..63359e7bb8b8 100644 --- a/tests/neg-custom-args/captures/leaked-curried.check +++ b/tests/neg-custom-args/captures/leaked-curried.check @@ -1,8 +1,10 @@ -- Error: tests/neg-custom-args/captures/leaked-curried.scala:14:20 ---------------------------------------------------- 14 | () => () => io // error | ^^ - |(io : Cap^) cannot be referenced here; it is not included in the allowed capture set {} of the self type of class Fuzz + | (io : Cap^) cannot be referenced here; it is not included in the allowed capture set {} + | of an enclosing function literal with expected type () -> () ->{io} (ex$7: caps.Exists) -> Cap^{ex$7} -- Error: tests/neg-custom-args/captures/leaked-curried.scala:17:20 ---------------------------------------------------- 17 | () => () => io // error | ^^ - |(io : Cap^) cannot be referenced here; it is not included in the allowed capture set {} of the self type of class Foo + | (io : Cap^) cannot be referenced here; it is not included in the allowed capture set {} + | of an enclosing function literal with expected type () -> () ->{io} (ex$15: caps.Exists) -> Cap^{ex$15} diff --git a/tests/pos-custom-args/captures/i21620.scala b/tests/pos-custom-args/captures/i21620.scala deleted file mode 100644 index b2c382aa4c75..000000000000 --- a/tests/pos-custom-args/captures/i21620.scala +++ /dev/null @@ -1,11 +0,0 @@ -class C -def test(x: C^) = - def foo() = - x - () - val f = () => - // println() // uncomenting would give an error, but with - // a different way of handling curried functions should be OK - () => foo() - val _: () -> () ->{x} Unit = f - ()