Skip to content
Closed
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 15 additions & 4 deletions library/src/scala/util/boundary.scala
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
package scala.util

import language.experimental.captureChecking
import scala.annotation.implicitNotFound

/** A boundary that can be exited by `break` calls.
Expand Down Expand Up @@ -27,18 +29,27 @@ import scala.annotation.implicitNotFound
* ```
*/
object boundary:
import caps.unsafe.unsafeAssumePure

/** User code should call `break.apply` instead of throwing this exception
* directly.
*/
final class Break[T] private[boundary](val label: Label[T], val value: T)
final class Break[T] private[boundary] (private[boundary] val label: Label[T]^{}, val value: T)
extends RuntimeException(
/*message*/ null, /*cause*/ null, /*enableSuppression=*/ false, /*writableStackTrace*/ false)
/*message*/ null, /*cause*/ null, /*enableSuppression=*/ false, /*writableStackTrace*/ false):

/** Compare the given [[Label]] to the one this [[Break]] was constructed with. */
inline def isSameLabelAs(other: Label[T]) = label eq other

object Break:
def apply[T](label: Label[T], value: T) =
// SAFETY: labels cannot leak from [[Break]], and is only used for equality comparison.
new Break(label.unsafeAssumePure, value)

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

/** Abort current computation and instead return `value` as the value of
* the enclosing `boundary` call that created `label`.
Expand All @@ -60,7 +71,7 @@ object boundary:
val local = Label[T]()
try body(using local)
catch case ex: Break[T] @unchecked =>
if ex.label eq local then ex.value
if ex.isSameLabelAs(local) then ex.value
else throw ex

end boundary
27 changes: 27 additions & 0 deletions tests/neg-custom-args/captures/boundary.check
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/boundary.scala:8:31 --------------------------------------
8 | boundary.break(l2)(using l1) // error
| ^^
| Found: (local : scala.util.boundary.Label[scala.util.boundary.Label[Unit]]^)
| Required: scala.util.boundary.Label[box scala.util.boundary.Label[Unit]^{local²}]^
|
| where: local is a value locally defined in object test
| local² is a value locally defined in object test
|
| longer explanation available when compiling with `-explain`
-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/boundary.scala:6:32 --------------------------------------
6 | boundary[boundary.Label[Unit]]: l1 ?=> // error
| ^
| Found: scala.util.boundary.Break[scala.util.boundary.Label[Unit]] @unchecked
| Required: scala.util.boundary.Break[box scala.util.boundary.Label[Unit]^] @unchecked
7 | boundary[Unit]: l2 ?=>
8 | boundary.break(l2)(using l1) // error
9 | ???
|--------------------------------------------------------------------------------------------------------------------
|Inline stack trace
|- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|This location contains code that was inlined from boundary.scala:73
73 | catch case ex: Break[T] @unchecked =>
| ^
--------------------------------------------------------------------------------------------------------------------
|
| longer explanation available when compiling with `-explain`
9 changes: 9 additions & 0 deletions tests/neg-custom-args/captures/boundary.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import language.experimental.captureChecking

import scala.util.boundary

object test:
boundary[boundary.Label[Unit]]: l1 ?=> // error
boundary[Unit]: l2 ?=>
boundary.break(l2)(using l1) // error
???
Loading