Skip to content

Commit 0a0befa

Browse files
committed
Add capture checking to util.boundary
1 parent 332fceb commit 0a0befa

File tree

1 file changed

+17
-6
lines changed

1 file changed

+17
-6
lines changed

library/src/scala/util/boundary.scala

+17-6
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
package scala.util
2+
3+
import language.experimental.captureChecking
24
import scala.annotation.implicitNotFound
35

46
/** A boundary that can be exited by `break` calls.
@@ -27,30 +29,39 @@ import scala.annotation.implicitNotFound
2729
* ```
2830
*/
2931
object boundary:
32+
import caps.unsafe.unsafeAssumePure
3033

3134
/** User code should call `break.apply` instead of throwing this exception
3235
* directly.
3336
*/
34-
final class Break[T] private[boundary](val label: Label[T], val value: T)
37+
final class Break[T] private[boundary] (private[boundary] val label: Label[T]^{}, val value: T)
3538
extends RuntimeException(
36-
/*message*/ null, /*cause*/ null, /*enableSuppression=*/ false, /*writableStackTrace*/ false)
39+
/*message*/ null, /*cause*/ null, /*enableSuppression=*/ false, /*writableStackTrace*/ false):
40+
41+
/** Compare the given [[Label]] to the one this [[Break]] was constructed with. */
42+
inline def isSameLabelAs(other: Label[T]) = label eq other
43+
44+
object Break:
45+
def apply[T](label: Label[T], value: T) =
46+
// SAFETY: labels cannot leak from [[Break]], and is only used for equality comparison.
47+
new Break(label.unsafeAssumePure, value)
3748

3849
/** Labels are targets indicating which boundary will be exited by a `break`.
3950
*/
4051
@implicitNotFound("explain=A Label is generated from an enclosing `scala.util.boundary` call.\nMaybe that boundary is missing?")
41-
final class Label[-T]
52+
final class Label[-T] extends caps.Capability
4253

4354
/** Abort current computation and instead return `value` as the value of
4455
* the enclosing `boundary` call that created `label`.
4556
*/
4657
def break[T](value: T)(using label: Label[T]): Nothing =
47-
throw Break(label, value)
58+
throw Break(label.unsafeAssumePure, value)
4859

4960
/** Abort current computation and instead continue after the `boundary` call that
5061
* created `label`.
5162
*/
5263
def break()(using label: Label[Unit]): Nothing =
53-
throw Break(label, ())
64+
throw Break(label.unsafeAssumePure, ())
5465

5566
/** Run `body` with freshly generated label as implicit argument. Catch any
5667
* breaks associated with that label and return their results instead of
@@ -60,7 +71,7 @@ object boundary:
6071
val local = Label[T]()
6172
try body(using local)
6273
catch case ex: Break[T] @unchecked =>
63-
if ex.label eq local then ex.value
74+
if ex.isSameLabelAs(local) then ex.value
6475
else throw ex
6576

6677
end boundary

0 commit comments

Comments
 (0)