Skip to content

Commit 9865f93

Browse files
committed
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.
1 parent fc46e5c commit 9865f93

File tree

9 files changed

+55
-83
lines changed

9 files changed

+55
-83
lines changed

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

+3
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,9 @@ object ccConfig:
4040
*/
4141
inline val handleEtaExpansionsSpecially = false
4242

43+
/** If enabled we drop inner uses in outer arrows of a curried function */
44+
inline val DropOuterUsesInCurried = false
45+
4346
/** If true, use existential capture set variables */
4447
def useExistentials(using Context) =
4548
Feature.sourceVersion.stable.isAtLeast(SourceVersion.`3.5`)

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

+6-3
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,8 @@ object CheckCaptures:
3333
case NestedInOwner // environment is a temporary one nested in the owner's environment,
3434
// and does not have a different actual owner symbol
3535
// (this happens when doing box adaptation).
36-
case ClosureResult // environment is for the result of a closure
36+
case ClosureResult // environment is for the result of a closure,
37+
// used only under ccConfig.DropOuterUsesInCurried
3738
case Boxed // environment is inside a box (in which case references are not counted)
3839

3940
/** A class describing environments.
@@ -180,7 +181,9 @@ object CheckCaptures:
180181
if ccConfig.useSealed then check.traverse(tp)
181182
end disallowRootCapabilitiesIn
182183

183-
/** Attachment key for bodies of closures, provided they are values */
184+
/** Attachment key for bodies of closures, provided they are values.
185+
* Used only under ccConfig.DropOuterUsesInCurried
186+
*/
184187
val ClosureBodyValue = Property.Key[Unit]
185188

186189
/** A prototype that indicates selection with an immutable value */
@@ -728,7 +731,7 @@ class CheckCaptures extends Recheck, SymTransformer:
728731

729732
override def recheckClosureBlock(mdef: DefDef, expr: Closure, pt: Type)(using Context): Type =
730733
mdef.rhs match
731-
case rhs @ closure(_, _, _) =>
734+
case rhs @ closure(_, _, _) if ccConfig.DropOuterUsesInCurried =>
732735
// In a curried closure `x => y => e` don't leak capabilities retained by
733736
// the second closure `y => e` into the first one. This is an approximation
734737
// of the CC rule which says that a closure contributes captures to its

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -294,7 +294,7 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI:
294294
case _ => res
295295
)
296296
val fntpe = defn.PolyFunctionOf(mt)
297-
if !encl.isEmpty && resDecomposed.isEmpty then
297+
if !encl.isEmpty && (!ccConfig.DropOuterUsesInCurried || resDecomposed.isEmpty) then
298298
val cs = CaptureSet(encl.map(_.paramRefs.head)*)
299299
CapturingType(fntpe, cs, boxed = false)
300300
else fntpe

tests/pos-custom-args/captures/curried-closures.scala tests/neg-custom-args/captures/curried-closures.scala

+1-1
Original file line numberDiff line numberDiff line change
@@ -30,5 +30,5 @@ def Test4(g: OutputStream^) =
3030
val _: (f: OutputStream^) ->{} Int ->{f} Unit = later
3131

3232
val later2 = () => (y: Int) => xs.foreach(x => g.write(x + y))
33-
val _: () ->{} Int ->{g} Unit = later2
33+
val _: () ->{} Int ->{g} Unit = later2 // error, inferred type is () ->{later2} Int ->{g} Unit
3434

tests/neg-custom-args/captures/i21620.check

+13
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,23 @@
44
| A pure expression does nothing in statement position
55
|
66
| longer explanation available when compiling with `-explain`
7+
-- [E129] Potential Issue Warning: tests/neg-custom-args/captures/i21620.scala:14:4 ------------------------------------
8+
14 | x
9+
| ^
10+
| A pure expression does nothing in statement position
11+
|
12+
| longer explanation available when compiling with `-explain`
713
-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/i21620.scala:9:31 ----------------------------------------
814
9 | val _: () -> () ->{x} Unit = f // error
915
| ^
1016
| Found: () ->{f} () ->{x} Unit
1117
| Required: () -> () ->{x} Unit
1218
|
1319
| longer explanation available when compiling with `-explain`
20+
-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/i21620.scala:20:33 ---------------------------------------
21+
20 | val _: () ->{} () ->{x} Unit = f // error, but could be OK
22+
| ^
23+
| Found: () ->{f} () ->{x} Unit
24+
| Required: () -> () ->{x} Unit
25+
|
26+
| longer explanation available when compiling with `-explain`

tests/neg-custom-args/captures/i21620.scala

+11
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,14 @@ def test(x: C^) =
88
() => foo()
99
val _: () -> () ->{x} Unit = f // error
1010
()
11+
12+
def test2(x: C^) =
13+
def foo() =
14+
x
15+
()
16+
val f = () =>
17+
// println() // uncomenting would give an error, but with
18+
// a different way of handling curried functions should be OK
19+
() => foo()
20+
val _: () ->{} () ->{x} Unit = f // error, but could be OK
21+
()
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
-- Error: tests/neg-custom-args/captures/leaked-curried.scala:14:20 ----------------------------------------------------
22
14 | () => () => io // error
33
| ^^
4-
|(io : Cap^) cannot be referenced here; it is not included in the allowed capture set {} of the self type of class Fuzz
4+
| (io : Cap^) cannot be referenced here; it is not included in the allowed capture set {}
5+
| of an enclosing function literal with expected type () -> () ->{io} (ex$7: caps.Exists) -> Cap^{ex$7}
56
-- Error: tests/neg-custom-args/captures/leaked-curried.scala:17:20 ----------------------------------------------------
67
17 | () => () => io // error
78
| ^^
8-
|(io : Cap^) cannot be referenced here; it is not included in the allowed capture set {} of the self type of class Foo
9+
| (io : Cap^) cannot be referenced here; it is not included in the allowed capture set {}
10+
| of an enclosing function literal with expected type () -> () ->{io} (ex$15: caps.Exists) -> Cap^{ex$15}
+16-65
Original file line numberDiff line numberDiff line change
@@ -1,72 +1,23 @@
11
import language.experimental.saferExceptions
2+
import language.experimental.erasedDefinitions
23

34
class Ex1 extends Exception("Ex1")
45
class Ex2 extends Exception("Ex2")
56
class Ex3 extends Exception("Ex3")
6-
7-
def foo0(i: Int): (CanThrow[Ex1], CanThrow[Ex2]) ?-> Unit =
8-
if i > 0 then throw new Ex1 else throw new Ex2
9-
10-
/* Does not work yet curried dependent CFTs are not yet handled in typer
11-
12-
def foo01(i: Int): (ct: CanThrow[Ex1]) ?-> CanThrow[Ex2] ?->{ct} Unit =
13-
if i > 0 then throw new Ex1 else throw new Ex2
14-
*/
15-
16-
def foo1(i: Int): Unit throws Ex1 throws Ex2 =
17-
if i > 0 then throw new Ex1 else throw new Ex1
18-
19-
def foo2(i: Int): Unit throws Ex1 | Ex2 =
20-
if i > 0 then throw new Ex1 else throw new Ex2
21-
22-
def foo3(i: Int): Unit throws (Ex1 | Ex2) =
23-
if i > 0 then throw new Ex1 else throw new Ex2
24-
25-
def foo4(i: Int)(using CanThrow[Ex1], CanThrow[Ex2]): Unit =
26-
if i > 0 then throw new Ex1 else throw new Ex2
27-
28-
def foo5(i: Int)(using CanThrow[Ex1])(using CanThrow[Ex2]): Unit =
29-
if i > 0 then throw new Ex1 else throw new Ex2
30-
31-
def foo6(i: Int)(using CanThrow[Ex1 | Ex2]): Unit =
32-
if i > 0 then throw new Ex1 else throw new Ex2
33-
34-
def foo7(i: Int)(using CanThrow[Ex1]): Unit throws Ex1 | Ex2 =
35-
if i > 0 then throw new Ex1 else throw new Ex2
36-
7+
/*
378
def foo8(i: Int)(using CanThrow[Ex2]): Unit throws Ex2 | Ex1 =
38-
if i > 0 then throw new Ex1 else throw new Ex2
39-
9+
throw new Ex2
10+
*/
4011
def foo9(i: Int): Unit throws Ex1 | Ex2 | Ex3 =
41-
if i > 0 then throw new Ex1
42-
else if i < 0 then throw new Ex2
43-
else throw new Ex3
44-
45-
def test(): Unit =
46-
try
47-
foo1(1)
48-
foo2(1)
49-
foo3(1)
50-
foo4(1)
51-
foo5(1)
52-
foo6(1)
53-
foo7(1)
54-
foo8(1)
55-
catch
56-
case _: Ex1 =>
57-
case _: Ex2 =>
58-
59-
try
60-
try
61-
foo1(1)
62-
foo2(1)
63-
foo3(1)
64-
foo4(1)
65-
foo5(1)
66-
// foo6(1) // As explained in the docs this won't work until we find a way to aggregate capabilities
67-
foo7(1)
68-
foo8(1)
69-
catch
70-
case _: Ex1 =>
71-
catch
72-
case _: Ex2 =>
12+
throw new Ex3
13+
/*
14+
15+
def foo9a(i: Int):
16+
(erased x$0: CanThrow[Ex3]^) ?-> (erased x$1: CanThrow[Ex2]^) ?->
17+
(erased x$2: CanThrow[Ex1]^) ?->{x$1, x$0} Unit
18+
=
19+
(erased x3: CanThrow[Ex3])
20+
?=> (erased x2: CanThrow[Ex2])
21+
?=> (erased x1: CanThrow[Ex1])
22+
?=> throw new Ex3
23+
*/

tests/pos-custom-args/captures/i21620.scala

-11
This file was deleted.

0 commit comments

Comments
 (0)