11package scala .util
2+
3+ import language .experimental .captureChecking
24import scala .annotation .implicitNotFound
35
46/** A boundary that can be exited by `break` calls.
@@ -27,30 +29,39 @@ import scala.annotation.implicitNotFound
2729 * ```
2830 */
2931object 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.\n Maybe 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
6677end boundary
0 commit comments