Skip to content

Commit

Permalink
Drop curried use scheme
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
odersky committed Oct 30, 2024
1 parent fc46e5c commit 70af983
Show file tree
Hide file tree
Showing 8 changed files with 39 additions and 18 deletions.
3 changes: 3 additions & 0 deletions compiler/src/dotty/tools/dotc/cc/CaptureOps.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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`)
Expand Down
9 changes: 6 additions & 3 deletions compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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 */
Expand Down Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/cc/Setup.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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

13 changes: 13 additions & 0 deletions tests/neg-custom-args/captures/i21620.check
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,23 @@
| 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
| ^
| Found: () ->{f} () ->{x} Unit
| 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`
11 changes: 11 additions & 0 deletions tests/neg-custom-args/captures/i21620.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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
()
6 changes: 4 additions & 2 deletions tests/neg-custom-args/captures/leaked-curried.check
Original file line number Diff line number Diff line change
@@ -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}
11 changes: 0 additions & 11 deletions tests/pos-custom-args/captures/i21620.scala

This file was deleted.

0 comments on commit 70af983

Please sign in to comment.