From 28bb69c2074af9e68d9ca2269a22fb199983b440 Mon Sep 17 00:00:00 2001 From: odersky Date: Sat, 19 Jul 2025 10:53:26 +0200 Subject: [PATCH 1/2] Flatten nested capture sets in retainedElementsRaw Without the added clause, i23570 looks like this after typer: ```scala [[syntax trees at end of typer]] // i23570.scala package { final lazy module val i23570$package: i23570$package = new i23570$package() final module class i23570$package() extends Object() { this: i23570$package.type => def f[C >: scala.caps.CapSet <: scala.caps.CapSet^{cap}]( xs: List[(() -> Unit)^{C}]): List[(() -> Unit)^{C}] = xs.reverse def test(io: Object^{cap}, async: Object^{cap}): Unit = { val ok: List[() ->{scala.caps.CapSet^{io}} Unit] = f[_root_.scala.caps.CapSet^{io}](Nil) val x: List[() ->{scala.caps.CapSet^{io}} Unit] -> List[() ->{scala.caps.CapSet^{io}} Unit] = (xs: List[() ->{scala.caps.CapSet^{io}} Unit]) => f[_root_.scala.caps.CapSet^{io}](xs) val y: List[() ->{scala.caps.CapSet^{io, async}} Unit] -> List[() ->{scala.caps.CapSet^{io, async}} Unit] = (xs: List[() ->{scala.caps.CapSet^{io, async}} Unit]) => f[_root_.scala.caps.CapSet^{io, async}](xs) () } } } ``` Note the nested capture sets in the types of `x` and `y`. Fixes #23570 --- compiler/src/dotty/tools/dotc/cc/CaptureOps.scala | 2 ++ tests/pos-custom-args/captures/i23570.scala | 7 +++++++ 2 files changed, 9 insertions(+) create mode 100644 tests/pos-custom-args/captures/i23570.scala diff --git a/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala b/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala index d21e9c7d5064..6dbf7d05c474 100644 --- a/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala +++ b/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala @@ -94,6 +94,8 @@ extension (tp: Type) def retainedElementsRaw(using Context): List[Type] = tp match case OrType(tp1, tp2) => tp1.retainedElementsRaw ++ tp2.retainedElementsRaw + case AnnotatedType(tp1, ann) if tp1.derivesFrom(defn.Caps_CapSet) && ann.symbol.isRetains => + ann.tree.retainedSet.retainedElementsRaw case tp => // Nothing is a special type to represent the empty set if tp.isNothingType then Nil diff --git a/tests/pos-custom-args/captures/i23570.scala b/tests/pos-custom-args/captures/i23570.scala new file mode 100644 index 000000000000..44f378792317 --- /dev/null +++ b/tests/pos-custom-args/captures/i23570.scala @@ -0,0 +1,7 @@ +def f[C^](xs: List[() ->{C} Unit]): List[() ->{C} Unit] = + xs.reverse + +def test(io: Object^, async: Object^): Unit = + val ok = f[{io}](Nil) + val x = f[{io}] // was error + val y = f[{io, async}] // was error From 5a440b4a1ba7dd1cc03e9528d259de9e6c04ff7b Mon Sep 17 00:00:00 2001 From: odersky Date: Sat, 19 Jul 2025 10:55:27 +0200 Subject: [PATCH 2/2] Unrelated test --- tests/pos/cc-use-alternatives.scala | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 tests/pos/cc-use-alternatives.scala diff --git a/tests/pos/cc-use-alternatives.scala b/tests/pos/cc-use-alternatives.scala new file mode 100644 index 000000000000..ddfcc3b62f8b --- /dev/null +++ b/tests/pos/cc-use-alternatives.scala @@ -0,0 +1,24 @@ +import language.experimental.captureChecking +// no separation checking +import caps.{cap, use} + +def foo1(@use xs: List[() => Unit]): Unit = + var x: () ->{xs*} Unit = xs.head + var ys = xs + while ys.nonEmpty do + ys = ys.tail + x = ys.head + +def foo2(@use xs: List[() => Unit]): Unit = + var x: () => Unit = xs.head // note: this would fail separation checking + var ys = xs + while ys.nonEmpty do + ys = ys.tail + x = ys.head + +def foo3[@use C^](xs: List[() ->{C} Unit]): Unit = + var x: () ->{C} Unit = xs.head + var ys = xs + while ys.nonEmpty do + ys = ys.tail + x = ys.head