diff --git a/compiler/src/dotty/tools/dotc/core/StdNames.scala b/compiler/src/dotty/tools/dotc/core/StdNames.scala index 90e5544f19af..f05155b941fb 100644 --- a/compiler/src/dotty/tools/dotc/core/StdNames.scala +++ b/compiler/src/dotty/tools/dotc/core/StdNames.scala @@ -532,6 +532,7 @@ object StdNames { val isEmpty: N = "isEmpty" val isInstanceOf_ : N = "isInstanceOf" val isInstanceOfPM: N = "$isInstanceOf$" + val isSameLabelAs : N = "isSameLabelAs" val java: N = "java" val key: N = "key" val label: N = "label" diff --git a/compiler/src/dotty/tools/dotc/transform/DropBreaks.scala b/compiler/src/dotty/tools/dotc/transform/DropBreaks.scala index 5f26a6af6c3c..b1c0080705ad 100644 --- a/compiler/src/dotty/tools/dotc/transform/DropBreaks.scala +++ b/compiler/src/dotty/tools/dotc/transform/DropBreaks.scala @@ -63,10 +63,10 @@ class DropBreaks extends MiniPhase: */ def unapply(expr: Tree)(using Context): Option[(Symbol, Symbol)] = stripTyped(expr) match case If( - Apply(Select(Select(ex: Ident, label), eq), (lbl @ Ident(local)) :: Nil), + Apply(Select(ex: Ident, isSameLabelAs), (lbl @ Ident(local)) :: Nil), Select(ex2: Ident, value), Apply(throww, (ex3: Ident) :: Nil)) - if label == nme.label && eq == nme.eq && local == nme.local && value == nme.value + if isSameLabelAs == nme.isSameLabelAs && local == nme.local && value == nme.value && throww.symbol == defn.throwMethod && ex.symbol == ex2.symbol && ex.symbol == ex3.symbol => Some((ex.symbol, lbl.symbol)) diff --git a/library/src/scala/util/boundary.scala b/library/src/scala/util/boundary.scala index 3039fc70be90..e72a1a142661 100644 --- a/library/src/scala/util/boundary.scala +++ b/library/src/scala/util/boundary.scala @@ -1,4 +1,6 @@ package scala.util + +import language.experimental.captureChecking import scala.annotation.implicitNotFound /** A boundary that can be exited by `break` calls. @@ -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. */ + 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`. @@ -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 diff --git a/tests/neg-custom-args/captures/boundary.check b/tests/neg-custom-args/captures/boundary.check new file mode 100644 index 000000000000..4b51012191ed --- /dev/null +++ b/tests/neg-custom-args/captures/boundary.check @@ -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` diff --git a/tests/neg-custom-args/captures/boundary.scala b/tests/neg-custom-args/captures/boundary.scala new file mode 100644 index 000000000000..4f2a994d392e --- /dev/null +++ b/tests/neg-custom-args/captures/boundary.scala @@ -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 + ???