From eae9c0709f6192aec7e6eabc16994452fb965f7f Mon Sep 17 00:00:00 2001 From: odersky Date: Tue, 31 Dec 2024 18:21:57 +0100 Subject: [PATCH] Expand aliases when mapping explicit types in Setup This is necessary because the compiler is free in previous phases to dealias or not. Therefore, capture checking should not depend on aliasing. The main difference is that now arguments to type aliases are not necessarily boxed. They are boxed only if they need boxing in the dealiased type. --- .../src/dotty/tools/dotc/cc/CaptureOps.scala | 2 +- .../captures/boundschecks2.scala | 2 +- .../captures/boundschecks3.check | 4 ++++ .../captures/boundschecks3.scala | 13 ++++++++++ .../captures/box-adapt-cov.scala | 10 ++++---- .../captures/box-adapt-depfun.scala | 12 +++++----- .../captures/box-adapt-typefun.scala | 8 +++---- tests/neg-custom-args/captures/capt1.check | 12 +++++----- .../captures/explain-under-approx.check | 2 +- tests/neg-custom-args/captures/i15922.scala | 4 ++-- tests/neg-custom-args/captures/i16725.scala | 6 ++--- tests/neg-custom-args/captures/i19330.check | 2 +- tests/neg-custom-args/captures/i21401.check | 8 +++---- tests/neg/cc-ex-conformance.scala | 2 +- tests/neg/existential-mapping.check | 24 +++++++++---------- .../captures/i15749a.scala | 10 ++++---- 16 files changed, 68 insertions(+), 53 deletions(-) create mode 100644 tests/neg-custom-args/captures/boundschecks3.check create mode 100644 tests/neg-custom-args/captures/boundschecks3.scala rename tests/{neg-custom-args => pos-custom-args}/captures/i15749a.scala (51%) diff --git a/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala b/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala index 3aae27246b17..7a9e7d558580 100644 --- a/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala +++ b/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala @@ -56,7 +56,7 @@ object ccConfig: Feature.sourceVersion.stable.isAtLeast(SourceVersion.`3.6`) def followAliases(using Context): Boolean = - Feature.sourceVersion.stable.isAtLeast(SourceVersion.`3.7`) + Feature.sourceVersion.stable.isAtLeast(SourceVersion.`3.6`) end ccConfig /** Are we at checkCaptures phase? */ diff --git a/tests/neg-custom-args/captures/boundschecks2.scala b/tests/neg-custom-args/captures/boundschecks2.scala index 923758d722f9..99366a8e7aff 100644 --- a/tests/neg-custom-args/captures/boundschecks2.scala +++ b/tests/neg-custom-args/captures/boundschecks2.scala @@ -8,6 +8,6 @@ object test { val foo: C[Tree^] = ??? // error type T = C[Tree^] // error - val bar: T -> T = ??? + //val bar: T -> T = ??? // --> boundschecks3.scala for what happens if we uncomment val baz: C[Tree^] -> Unit = ??? // error } diff --git a/tests/neg-custom-args/captures/boundschecks3.check b/tests/neg-custom-args/captures/boundschecks3.check new file mode 100644 index 000000000000..36e1336e8f05 --- /dev/null +++ b/tests/neg-custom-args/captures/boundschecks3.check @@ -0,0 +1,4 @@ +-- Error: tests/neg-custom-args/captures/boundschecks3.scala:11:13 ----------------------------------------------------- +11 | val bar: T -> T = ??? // error, since `T` is expanded here. But the msg is not very good. + | ^^^^^^ + | test.C[box test.Tree^] captures the root capability `cap` in invariant position diff --git a/tests/neg-custom-args/captures/boundschecks3.scala b/tests/neg-custom-args/captures/boundschecks3.scala new file mode 100644 index 000000000000..f5e9652c0913 --- /dev/null +++ b/tests/neg-custom-args/captures/boundschecks3.scala @@ -0,0 +1,13 @@ +object test { + + class Tree + + def f[X <: Tree](x: X): Unit = () + + class C[X <: Tree](x: X) + + val foo: C[Tree^] = ??? // hidden error + type T = C[Tree^] // hidden error + val bar: T -> T = ??? // error, since `T` is expanded here. But the msg is not very good. + val baz: C[Tree^] -> Unit = ??? // hidden error +} diff --git a/tests/neg-custom-args/captures/box-adapt-cov.scala b/tests/neg-custom-args/captures/box-adapt-cov.scala index 2c1f15a5c77f..e1e6dd4cec1a 100644 --- a/tests/neg-custom-args/captures/box-adapt-cov.scala +++ b/tests/neg-custom-args/captures/box-adapt-cov.scala @@ -1,14 +1,14 @@ trait Cap def test1(io: Cap^) = { - type Op[X] = [T] -> Unit -> X + class Op[+X](val value: [T] -> Unit -> X) val f: Op[Cap^{io}] = ??? - val x: [T] -> Unit -> Cap^{io} = f // error + val x: [T] -> Unit -> Cap^{io} = f.value // error } def test2(io: Cap^) = { - type Op[X] = [T] -> Unit -> X^{io} + class Op[+X](val value: [T] -> Unit -> X^{io}) val f: Op[Cap^{io}] = ??? - val x: Unit -> Cap^{io} = f[Unit] // error - val x1: Unit ->{io} Cap^{io} = f[Unit] // ok + val x: Unit -> Cap^{io} = f.value[Unit] // error + val x1: Unit ->{io} Cap^{io} = f.value[Unit] // ok } diff --git a/tests/neg-custom-args/captures/box-adapt-depfun.scala b/tests/neg-custom-args/captures/box-adapt-depfun.scala index d1c1c73f8207..f673c657f753 100644 --- a/tests/neg-custom-args/captures/box-adapt-depfun.scala +++ b/tests/neg-custom-args/captures/box-adapt-depfun.scala @@ -1,23 +1,23 @@ trait Cap { def use(): Int } def test1(io: Cap^): Unit = { - type Id[X] = [T] -> (op: X ->{io} T) -> T + class Id[X](val value: [T] -> (op: X ->{io} T) -> T) val x: Id[Cap]^{io} = ??? - x(cap => cap.use()) // ok + x.value(cap => cap.use()) // ok } def test2(io: Cap^): Unit = { - type Id[X] = [T] -> (op: (x: X) ->{io} T) -> T + class Id[X](val value: [T] -> (op: (x: X) ->{io} T) -> T) val x: Id[Cap^{io}] = ??? - x(cap => cap.use()) + x.value(cap => cap.use()) // should work when the expected type is a dependent function } def test3(io: Cap^): Unit = { - type Id[X] = [T] -> (op: (x: X) ->{} T) -> T + class Id[X](val value: [T] -> (op: (x: X) ->{} T) -> T) val x: Id[Cap^{io}] = ??? - x(cap => cap.use()) // error + x.value(cap => cap.use()) // error } diff --git a/tests/neg-custom-args/captures/box-adapt-typefun.scala b/tests/neg-custom-args/captures/box-adapt-typefun.scala index 175acdda1c8f..76da047f42a9 100644 --- a/tests/neg-custom-args/captures/box-adapt-typefun.scala +++ b/tests/neg-custom-args/captures/box-adapt-typefun.scala @@ -1,13 +1,13 @@ trait Cap { def use(): Int } def test1(io: Cap^): Unit = { - type Op[X] = [T] -> X -> Unit + class Op[X](val value: [T] -> X -> Unit) val f: [T] -> (Cap^{io}) -> Unit = ??? - val op: Op[Cap^{io}] = f // error + val op: Op[Cap^{io}] = Op(f) // was error, now ok } def test2(io: Cap^): Unit = { - type Lazy[X] = [T] -> Unit -> X + class Lazy[X](val value: [T] -> Unit -> X) val f: Lazy[Cap^{io}] = ??? - val test: [T] -> Unit -> (Cap^{io}) = f // error + val test: [T] -> Unit -> (Cap^{io}) = f.value // error } diff --git a/tests/neg-custom-args/captures/capt1.check b/tests/neg-custom-args/captures/capt1.check index f63c55ca48c4..acf8faa7a969 100644 --- a/tests/neg-custom-args/captures/capt1.check +++ b/tests/neg-custom-args/captures/capt1.check @@ -36,15 +36,15 @@ -- Error: tests/neg-custom-args/captures/capt1.scala:34:16 ------------------------------------------------------------- 34 | val z2 = h[() -> Cap](() => x) // error // error | ^^^^^^^^^ - | Type variable X of method h cannot be instantiated to () -> box C^ since - | the part box C^ of that type captures the root capability `cap`. + | Type variable X of method h cannot be instantiated to () -> (ex$15: caps.Exists) -> C^{ex$15} since + | the part C^{ex$15} of that type captures the root capability `cap`. -- Error: tests/neg-custom-args/captures/capt1.scala:34:30 ------------------------------------------------------------- 34 | val z2 = h[() -> Cap](() => x) // error // error | ^ - | reference (x : C^) is not included in the allowed capture set {} - | of an enclosing function literal with expected type () -> box C^ + | reference (x : C^) is not included in the allowed capture set {} + | of an enclosing function literal with expected type () -> (ex$15: caps.Exists) -> C^{ex$15} -- Error: tests/neg-custom-args/captures/capt1.scala:36:13 ------------------------------------------------------------- 36 | val z3 = h[(() -> Cap) @retains(x)](() => x)(() => C()) // error | ^^^^^^^^^^^^^^^^^^^^^^^ - | Type variable X of method h cannot be instantiated to box () ->{x} Cap since - | the part Cap of that type captures the root capability `cap`. + | Type variable X of method h cannot be instantiated to box () ->{x} (ex$20: caps.Exists) -> C^{ex$20} since + | the part C^{ex$20} of that type captures the root capability `cap`. diff --git a/tests/neg-custom-args/captures/explain-under-approx.check b/tests/neg-custom-args/captures/explain-under-approx.check index bc59612ebef6..d4cafc7840b7 100644 --- a/tests/neg-custom-args/captures/explain-under-approx.check +++ b/tests/neg-custom-args/captures/explain-under-approx.check @@ -4,7 +4,7 @@ | Found: Future[Int]{val a: (async : Async)}^{async} | Required: Future[Int]^{col.futs*} | - | Note that reference ex$25.type + | Note that reference ex$22.type | cannot be included in outer capture set {cap} | | longer explanation available when compiling with `-explain` diff --git a/tests/neg-custom-args/captures/i15922.scala b/tests/neg-custom-args/captures/i15922.scala index 848a22fe5341..b8bcc346ef81 100644 --- a/tests/neg-custom-args/captures/i15922.scala +++ b/tests/neg-custom-args/captures/i15922.scala @@ -1,8 +1,8 @@ trait Cap { def use(): Int } -type Id[X] = [T] -> (op: X => T) -> T -def mkId[X](x: X): Id[X] = [T] => (op: X => T) => op(x) +class Id[+X](val value: [T] -> (op: X => T) -> T) +def mkId[X](x: X): Id[X] = Id([T] => (op: X => T) => op(x)) def withCap[X](op: (Cap^) => X): X = { val cap: Cap^ = new Cap { def use() = { println("cap is used"); 0 } } diff --git a/tests/neg-custom-args/captures/i16725.scala b/tests/neg-custom-args/captures/i16725.scala index 14ac04712f02..96cf44e72f3c 100644 --- a/tests/neg-custom-args/captures/i16725.scala +++ b/tests/neg-custom-args/captures/i16725.scala @@ -3,11 +3,11 @@ class IO extends caps.Capability: def brewCoffee(): Unit = ??? def usingIO[T](op: IO => T): T = ??? -type Wrapper[T] = [R] -> (f: T => R) -> R -def mk[T](x: T): Wrapper[T] = [R] => f => f(x) +class Wrapper[T](val value: [R] -> (f: T => R) -> R) +def mk[T](x: T): Wrapper[T] = Wrapper([R] => f => f(x)) def useWrappedIO(wrapper: Wrapper[IO]): () -> Unit = () => - wrapper: io => // error + wrapper.value: io => // error io.brewCoffee() def main(): Unit = val escaped = usingIO(io => useWrappedIO(mk(io))) // error diff --git a/tests/neg-custom-args/captures/i19330.check b/tests/neg-custom-args/captures/i19330.check index a3422c8ba26a..78219e0316ee 100644 --- a/tests/neg-custom-args/captures/i19330.check +++ b/tests/neg-custom-args/captures/i19330.check @@ -7,6 +7,6 @@ 22 | val bad: bar.T = foo(bar) // error | ^^^^^^^^ | Found: () => Logger^ - | Required: () ->{fresh} Logger^{fresh} + | Required: () ->{fresh} (ex$9: caps.Exists) -> Logger^{ex$9} | | longer explanation available when compiling with `-explain` diff --git a/tests/neg-custom-args/captures/i21401.check b/tests/neg-custom-args/captures/i21401.check index 679c451949bd..e7483e10bfa6 100644 --- a/tests/neg-custom-args/captures/i21401.check +++ b/tests/neg-custom-args/captures/i21401.check @@ -11,8 +11,8 @@ -- Error: tests/neg-custom-args/captures/i21401.scala:16:66 ------------------------------------------------------------ 16 | val leaked: [R, X <: Boxed[IO^] -> R] -> (op: X) -> R = usingIO[Res](mkRes) // error | ^^^ - | Type variable R of method usingIO cannot be instantiated to Res since - | the part box IO^ of that type captures the root capability `cap`. + | Type variable R of method usingIO cannot be instantiated to [R, X <: Boxed[box IO^] -> R] => (op: X) -> R since + | the part box IO^ of that type captures the root capability `cap`. -- Error: tests/neg-custom-args/captures/i21401.scala:17:29 ------------------------------------------------------------ 17 | val x: Boxed[IO^] = leaked[Boxed[IO^], Boxed[IO^] -> Boxed[IO^]](x => x) // error // error | ^^^^^^^^^^ @@ -21,8 +21,8 @@ -- Error: tests/neg-custom-args/captures/i21401.scala:17:52 ------------------------------------------------------------ 17 | val x: Boxed[IO^] = leaked[Boxed[IO^], Boxed[IO^] -> Boxed[IO^]](x => x) // error // error | ^^^^^^^^^^^^^^^^^^^^^^^^ - |Type variable X of value leaked cannot be instantiated to Boxed[box IO^] -> (ex$18: caps.Exists) -> Boxed[box IO^{ex$18}] since - |the part box IO^{ex$18} of that type captures the root capability `cap`. + |Type variable X of value leaked cannot be instantiated to Boxed[box IO^] -> (ex$20: caps.Exists) -> Boxed[box IO^{ex$20}] since + |the part box IO^{ex$20} of that type captures the root capability `cap`. -- Error: tests/neg-custom-args/captures/i21401.scala:18:21 ------------------------------------------------------------ 18 | val y: IO^{x*} = x.unbox // error | ^^^^^^^ diff --git a/tests/neg/cc-ex-conformance.scala b/tests/neg/cc-ex-conformance.scala index a953466daa9a..16e13376c5b3 100644 --- a/tests/neg/cc-ex-conformance.scala +++ b/tests/neg/cc-ex-conformance.scala @@ -21,5 +21,5 @@ def Test = val ex3: EX3 = ??? val ex4: EX4 = ??? val _: EX4 = ex3 // ok - val _: EX4 = ex4 + val _: EX4 = ex4 // error (???) Probably since we also introduce existentials on expansion val _: EX3 = ex4 // error diff --git a/tests/neg/existential-mapping.check b/tests/neg/existential-mapping.check index be7720433692..e739d6db993c 100644 --- a/tests/neg/existential-mapping.check +++ b/tests/neg/existential-mapping.check @@ -33,56 +33,56 @@ -- [E007] Type Mismatch Error: tests/neg/existential-mapping.scala:21:30 ----------------------------------------------- 21 | val _: A^ -> (x: C^) -> C = x5 // error | ^^ - | Found: (x5 : A^ -> (ex$27: caps.Exists) -> Fun[C^{ex$27}]) + | Found: (x5 : A^ -> (x: C^) -> (ex$27: caps.Exists) -> C^{ex$27}) | Required: A^ -> (x: C^) -> C | | longer explanation available when compiling with `-explain` -- [E007] Type Mismatch Error: tests/neg/existential-mapping.scala:24:30 ----------------------------------------------- 24 | val _: A^ -> (x: C^) => C = x6 // error | ^^ - | Found: (x6 : A^ -> (ex$33: caps.Exists) -> (x: C^) ->{fresh} C^{ex$33}) - | Required: A^ -> (ex$36: caps.Exists) -> (x: C^) ->{ex$36} C + | Found: (x6 : A^ -> (ex$36: caps.Exists) -> (x: C^) ->{ex$36} (ex$35: caps.Exists) -> C^{ex$35}) + | Required: A^ -> (ex$39: caps.Exists) -> (x: C^) ->{ex$39} C | | longer explanation available when compiling with `-explain` -- [E007] Type Mismatch Error: tests/neg/existential-mapping.scala:27:25 ----------------------------------------------- 27 | val _: (x: C^) => C = y1 // error | ^^ - | Found: (y1 : (x: C^) ->{fresh} (ex$38: caps.Exists) -> C^{ex$38}) + | Found: (y1 : (x: C^) ->{fresh} (ex$41: caps.Exists) -> C^{ex$41}) | Required: (x: C^) ->{fresh} C | | longer explanation available when compiling with `-explain` -- [E007] Type Mismatch Error: tests/neg/existential-mapping.scala:30:20 ----------------------------------------------- 30 | val _: C^ => C = y2 // error | ^^ - | Found: (y2 : C^ ->{fresh} (ex$42: caps.Exists) -> C^{ex$42}) + | Found: (y2 : C^ ->{fresh} (ex$45: caps.Exists) -> C^{ex$45}) | Required: C^ ->{fresh} C | | longer explanation available when compiling with `-explain` -- [E007] Type Mismatch Error: tests/neg/existential-mapping.scala:33:30 ----------------------------------------------- 33 | val _: A^ => (x: C^) => C = y3 // error | ^^ - | Found: (y3 : A^ ->{fresh} (ex$47: caps.Exists) -> (x: C^) ->{ex$47} (ex$46: caps.Exists) -> C^{ex$46}) - | Required: A^ ->{fresh} (ex$50: caps.Exists) -> (x: C^) ->{ex$50} C + | Found: (y3 : A^ ->{fresh} (ex$50: caps.Exists) -> (x: C^) ->{ex$50} (ex$49: caps.Exists) -> C^{ex$49}) + | Required: A^ ->{fresh} (ex$53: caps.Exists) -> (x: C^) ->{ex$53} C | | longer explanation available when compiling with `-explain` -- [E007] Type Mismatch Error: tests/neg/existential-mapping.scala:36:25 ----------------------------------------------- 36 | val _: A^ => C^ => C = y4 // error | ^^ - | Found: (y4 : A^ ->{fresh} (ex$53: caps.Exists) -> C^ ->{ex$53} (ex$52: caps.Exists) -> C^{ex$52}) - | Required: A^ ->{fresh} (ex$56: caps.Exists) -> C^ ->{ex$56} C + | Found: (y4 : A^ ->{fresh} (ex$56: caps.Exists) -> C^ ->{ex$56} (ex$55: caps.Exists) -> C^{ex$55}) + | Required: A^ ->{fresh} (ex$59: caps.Exists) -> C^ ->{ex$59} C | | longer explanation available when compiling with `-explain` -- [E007] Type Mismatch Error: tests/neg/existential-mapping.scala:39:30 ----------------------------------------------- 39 | val _: A^ => (x: C^) -> C = y5 // error | ^^ - | Found: (y5 : A^ ->{fresh} (ex$58: caps.Exists) -> Fun[C^{ex$58}]) + | Found: (y5 : A^ ->{fresh} (x: C^) -> (ex$61: caps.Exists) -> C^{ex$61}) | Required: A^ ->{fresh} (x: C^) -> C | | longer explanation available when compiling with `-explain` -- [E007] Type Mismatch Error: tests/neg/existential-mapping.scala:42:30 ----------------------------------------------- 42 | val _: A^ => (x: C^) => C = y6 // error | ^^ - | Found: (y6 : A^ ->{fresh} (ex$64: caps.Exists) -> (x: C^) ->{fresh} C^{ex$64}) - | Required: A^ ->{fresh} (ex$67: caps.Exists) -> (x: C^) ->{ex$67} C + | Found: (y6 : A^ ->{fresh} (ex$70: caps.Exists) -> (x: C^) ->{ex$70} (ex$69: caps.Exists) -> C^{ex$69}) + | Required: A^ ->{fresh} (ex$73: caps.Exists) -> (x: C^) ->{ex$73} C | | longer explanation available when compiling with `-explain` diff --git a/tests/neg-custom-args/captures/i15749a.scala b/tests/pos-custom-args/captures/i15749a.scala similarity index 51% rename from tests/neg-custom-args/captures/i15749a.scala rename to tests/pos-custom-args/captures/i15749a.scala index d3c1fce13322..184f980d6d70 100644 --- a/tests/neg-custom-args/captures/i15749a.scala +++ b/tests/pos-custom-args/captures/i15749a.scala @@ -6,19 +6,17 @@ object u extends Unit type Top = Any^ -type Wrapper[+T] = [X] -> (op: T ->{cap} X) -> X +class Wrapper[+T](val value: [X] -> (op: T ->{cap} X) -> X) def test = - def wrapper[T](x: T): Wrapper[T] = + def wrapper[T](x: T): Wrapper[T] = Wrapper: [X] => (op: T ->{cap} X) => op(x) def strictMap[A <: Top, B <: Top](mx: Wrapper[A])(f: A ->{cap} B): Wrapper[B] = - mx((x: A) => wrapper(f(x))) + mx.value((x: A) => wrapper(f(x))) def force[A](thunk: Unit ->{cap} A): A = thunk(u) def forceWrapper[A](@use mx: Wrapper[Unit ->{cap} A]): Wrapper[A] = - // Γ ⊢ mx: Wrapper[□ {cap} Unit => A] - // `force` should be typed as ∀(□ {cap} Unit -> A) A, but it can not - strictMap[Unit ->{mx*} A, A](mx)(t => force[A](t)) // error // should work + strictMap[Unit ->{mx*} A, A](mx)(t => force[A](t)) // was error when Wrapper was an alias type