From bf37dce1fe81cd1183433f81ec62d0a6b7aaf95b Mon Sep 17 00:00:00 2001 From: Liu Fengyun Date: Wed, 14 Aug 2019 09:58:59 +0200 Subject: [PATCH 1/4] Add rescue to support streamline error handling This implements inline `rescue` like those in Ruby [1], but more powerful in that users can specify the exception to be handled. The `rescue` method will be very convenient in scripting. [1] https://stackoverflow.com/questions/15396791/ruby-oneline-rescue --- tests/run/rescue.scala | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 tests/run/rescue.scala diff --git a/tests/run/rescue.scala b/tests/run/rescue.scala new file mode 100644 index 000000000000..25eb86eaffc2 --- /dev/null +++ b/tests/run/rescue.scala @@ -0,0 +1,13 @@ +object lib { + inline def (op: => T) rescue[T] (fallback: => T) = try op catch { case _: Throwable => fallback } + inline def (op: => T) rescue[T, E <: Throwable] (fallback: E => T) = try op catch { case ex: E => fallback(ex) } +} + +import lib._ + +@main def Test = { + assert((9 / 1 rescue 1) == 9) + assert((9 / 0 rescue 1) == 1) + assert(((9 / 0 rescue { ex: NullPointerException => 5 }) rescue 10) == 10) + assert(((9 / 0 rescue { ex: ArithmeticException => 5 }) rescue 10) == 5) +} From b96a908aa6b9c9116eda0efbca53d9e5d96b91b3 Mon Sep 17 00:00:00 2001 From: Liu Fengyun Date: Wed, 14 Aug 2019 10:10:46 +0200 Subject: [PATCH 2/4] add test for partial function handler --- tests/run/rescue.scala | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tests/run/rescue.scala b/tests/run/rescue.scala index 25eb86eaffc2..547e7e25a38d 100644 --- a/tests/run/rescue.scala +++ b/tests/run/rescue.scala @@ -10,4 +10,9 @@ import lib._ assert((9 / 0 rescue 1) == 1) assert(((9 / 0 rescue { ex: NullPointerException => 5 }) rescue 10) == 10) assert(((9 / 0 rescue { ex: ArithmeticException => 5 }) rescue 10) == 5) + + assert((9 / 0 rescue { + case ex: NullPointerException => 4 + case ex: ArithmeticException => 3 + }) == 3) } From 910f6b3c6010c4b407ab592b4887fb65c244b0fd Mon Sep 17 00:00:00 2001 From: Liu Fengyun Date: Wed, 14 Aug 2019 13:20:43 +0200 Subject: [PATCH 3/4] Use partial functions in interface (thanks @smarter) Without the change, exceptions other than the following will not be propagated. Instead, users will get `scala.MatchError`. op rescue { case ex: NullPointerException => 4 case ex: ArithmeticException => 3 } --- tests/run/rescue.scala | 43 ++++++++++++++++++++++++++++++++++-------- 1 file changed, 35 insertions(+), 8 deletions(-) diff --git a/tests/run/rescue.scala b/tests/run/rescue.scala index 547e7e25a38d..6e66ca25c8d2 100644 --- a/tests/run/rescue.scala +++ b/tests/run/rescue.scala @@ -1,6 +1,16 @@ object lib { - inline def (op: => T) rescue[T] (fallback: => T) = try op catch { case _: Throwable => fallback } - inline def (op: => T) rescue[T, E <: Throwable] (fallback: E => T) = try op catch { case ex: E => fallback(ex) } + inline def (op: => T) rescue[T] (fallback: => T) = + try op + catch { + case _: Throwable => fallback + } + + inline def (op: => T) rescue[T, E <: Throwable] (fallback: PartialFunction[E, T]) = + try op + catch { + case ex: E => + if (fallback.isDefinedAt(ex)) fallback(ex) else throw ex + } } import lib._ @@ -8,11 +18,28 @@ import lib._ @main def Test = { assert((9 / 1 rescue 1) == 9) assert((9 / 0 rescue 1) == 1) - assert(((9 / 0 rescue { ex: NullPointerException => 5 }) rescue 10) == 10) - assert(((9 / 0 rescue { ex: ArithmeticException => 5 }) rescue 10) == 5) + assert(((9 / 0 rescue { case ex: NullPointerException => 5 }) rescue 10) == 10) + assert(((9 / 0 rescue { case ex: ArithmeticException => 5 }) rescue 10) == 5) + + assert( + { + 9 / 0 rescue { + case ex: NullPointerException => 4 + case ex: ArithmeticException => 3 + } + } == 3 + ) - assert((9 / 0 rescue { - case ex: NullPointerException => 4 - case ex: ArithmeticException => 3 - }) == 3) + assert( + { + { + val a = 9 / 0 rescue { + case ex: NullPointerException => 4 + } + a * a + } rescue { + case ex: ArithmeticException => 3 + } + } == 3 + ) } From d31a8bb7f3a75dc5691de4bbf338000d31363ff0 Mon Sep 17 00:00:00 2001 From: Liu Fengyun Date: Wed, 14 Aug 2019 14:09:04 +0200 Subject: [PATCH 4/4] Only rescue non-fatal errors However, with explict cases, user may match fatal errors except for non-local returns, which users should not catch. --- tests/run/rescue.scala | 37 +++++++++++++++++++++++++++++++++++-- 1 file changed, 35 insertions(+), 2 deletions(-) diff --git a/tests/run/rescue.scala b/tests/run/rescue.scala index 6e66ca25c8d2..22e7f3da2b32 100644 --- a/tests/run/rescue.scala +++ b/tests/run/rescue.scala @@ -1,15 +1,20 @@ +import scala.util.control.NonFatal +import scala.util.control.NonLocalReturns._ + object lib { inline def (op: => T) rescue[T] (fallback: => T) = try op catch { - case _: Throwable => fallback + case NonFatal(_) => fallback // ReturnThrowable is fatal error, thus ignored } inline def (op: => T) rescue[T, E <: Throwable] (fallback: PartialFunction[E, T]) = try op catch { + // case ex: ReturnThrowable[_] => throw ex // bug #7041 case ex: E => - if (fallback.isDefinedAt(ex)) fallback(ex) else throw ex + // user should never match `ReturnThrowable`, which breaks semantics of non-local return + if (fallback.isDefinedAt(ex) && !ex.isInstanceOf[ReturnThrowable[_]]) fallback(ex) else throw ex } } @@ -30,6 +35,8 @@ import lib._ } == 3 ) + (9 / 0) rescue { case ex: ArithmeticException => 4 } + assert( { { @@ -42,4 +49,30 @@ import lib._ } } == 3 ) + + assert(foo(10) == 40) + assert(bar(10) == 40) + + // should not catch fatal errors + assert( + try { { throw new OutOfMemoryError(); true } rescue false } + catch { case _: OutOfMemoryError => true } + ) + + // should catch any errors specified, including fatal errors + assert( + try { { throw new OutOfMemoryError(); true } rescue { case _: OutOfMemoryError => true } } + catch { case _: OutOfMemoryError => false } + ) + + // should not catch NonLocalReturns + def foo(x: Int): Int = returning[Int] { + { throwReturn[Int](4 * x) : Int } rescue 10 + } + + // should catch specified exceptions, but not NonLocalReturn + def bar(x: Int): Int = returning[Int] { + { throwReturn[Int](4 * x) : Int } rescue { case _ => 10 } + } + }