From c40a2b7d0f8eb7df260cc49a6dacaaafab552e9f Mon Sep 17 00:00:00 2001 From: Eugene Yokota Date: Sun, 10 Jun 2018 20:29:18 -0400 Subject: [PATCH 01/10] Adds AnyTap and AnyPipe Fixes https://github.com/scala/bug/issues/5324 This implements an opt-in enrichment for any type called tap and pipe: ```scala scala> import scala.util.chainingOps._ import scala.util.chainingOps._ scala> val xs = List(1, 2, 3).tap(ys => println("debug " + ys.toString)) debug List(1, 2, 3) xs: List[Int] = List(1, 2, 3) scala> val times6 = (_: Int) * 6 times6: Int => Int = $$Lambda$1727/1479800269@10fbbdb scala> (1 + 2 + 3).pipe(times6) res0: Int = 36 scala> (1 - 2 - 3).pipe(times6).pipe(scala.math.abs) res1: Int = 24 ``` --- src/library/scala/util/ChainingOps.scala | 42 +++++++++++++++++++++ src/library/scala/util/package.scala | 8 ++++ test/junit/scala/util/ChainingOpsTest.scala | 28 ++++++++++++++ 3 files changed, 78 insertions(+) create mode 100644 src/library/scala/util/ChainingOps.scala create mode 100644 src/library/scala/util/package.scala create mode 100644 test/junit/scala/util/ChainingOpsTest.scala diff --git a/src/library/scala/util/ChainingOps.scala b/src/library/scala/util/ChainingOps.scala new file mode 100644 index 00000000..fd204bf2 --- /dev/null +++ b/src/library/scala/util/ChainingOps.scala @@ -0,0 +1,42 @@ +package scala +package util + +trait ChainingSyntax { + implicit final def scalaUtilChainingOps[A](a: A): ChainingOps[A] = new ChainingOps(a) +} + +/** Adds chaining methods `tap` and `pipe` to every type. + */ +final class ChainingOps[A](val self: A) extends AnyVal { + /** Applies `f` to the value for its side effects, and returns the original value. + * + * {{{ + * val xs = List(1, 2, 3) + * .tap(ys => println("debug " + ys.toString)) + * // xs == List(1, 2, 3) + * }}} + * + * @param f the function to apply to the value. + * @tparam U the result type of the function `f`. + * @return the original value `self`. + */ + def tap[U](f: A => U): self.type = { + f(self) + self + } + + /** Converts the value by applying the function `f`. + * + * {{{ + * val times6 = (_: Int) * 6 + * val i = (1 - 2 - 3).pipe(times6).pipe(scala.math.abs) + * // i == 24 + * }}} + * + * @param f the function to apply to the value. + * @tparam B the result type of the function `f`. + * @return a new value resulting from applying the given function + * `f` to this value. + */ + def pipe[B](f: A => B): B = f(self) +} diff --git a/src/library/scala/util/package.scala b/src/library/scala/util/package.scala new file mode 100644 index 00000000..e8eb3563 --- /dev/null +++ b/src/library/scala/util/package.scala @@ -0,0 +1,8 @@ +package scala + +package object util { + /** + * Adds chaining methods `tap` and `pipe` to every type. See [[ChainingOps]]. + */ + object chainingOps extends ChainingSyntax +} diff --git a/test/junit/scala/util/ChainingOpsTest.scala b/test/junit/scala/util/ChainingOpsTest.scala new file mode 100644 index 00000000..7c249497 --- /dev/null +++ b/test/junit/scala/util/ChainingOpsTest.scala @@ -0,0 +1,28 @@ +package scala.util + +import org.junit.Assert._ +import org.junit.Test + +class ChainingOpsTest { + import scala.util.chainingOps._ + + @Test + def testAnyTap: Unit = { + var x: Int = 0 + val result = List(1, 2, 3) + .tap(xs => x = xs.head) + + assertEquals(1, x) + assertEquals(List(1, 2, 3), result) + } + + @Test + def testAnyPipe: Unit = { + val times6 = (_: Int) * 6 + val result = (1 - 2 - 3) + .pipe(times6) + .pipe(scala.math.abs) + + assertEquals(24, result) + } +} From a4714bc508287c1d80f74700ab3da1651a1ecd95 Mon Sep 17 00:00:00 2001 From: Martijn Hoekstra Date: Thu, 6 Sep 2018 15:05:48 +0200 Subject: [PATCH 02/10] merge AnyTap/AnyPipe --- .../scala-2.11_2.12/scala/util/compat}/ChainingOps.scala | 5 ++--- .../main/scala-2.11_2.12/scala/util/compat}/package.scala | 4 ++-- .../src/main/scala-2.13/scala/util/compat/package.scala | 8 ++++++++ .../src/test/scala/test}/scala/util/ChainingOpsTest.scala | 4 ++-- 4 files changed, 14 insertions(+), 7 deletions(-) rename {src/library/scala/util => compat/src/main/scala-2.11_2.12/scala/util/compat}/ChainingOps.scala (94%) rename {src/library/scala/util => compat/src/main/scala-2.11_2.12/scala/util/compat}/package.scala (76%) create mode 100644 compat/src/main/scala-2.13/scala/util/compat/package.scala rename {test/junit => compat/src/test/scala/test}/scala/util/ChainingOpsTest.scala (86%) diff --git a/src/library/scala/util/ChainingOps.scala b/compat/src/main/scala-2.11_2.12/scala/util/compat/ChainingOps.scala similarity index 94% rename from src/library/scala/util/ChainingOps.scala rename to compat/src/main/scala-2.11_2.12/scala/util/compat/ChainingOps.scala index fd204bf2..0f615791 100644 --- a/src/library/scala/util/ChainingOps.scala +++ b/compat/src/main/scala-2.11_2.12/scala/util/compat/ChainingOps.scala @@ -1,5 +1,4 @@ -package scala -package util +package scala.util.compat trait ChainingSyntax { implicit final def scalaUtilChainingOps[A](a: A): ChainingOps[A] = new ChainingOps(a) @@ -20,7 +19,7 @@ final class ChainingOps[A](val self: A) extends AnyVal { * @tparam U the result type of the function `f`. * @return the original value `self`. */ - def tap[U](f: A => U): self.type = { + def tap[U](f: A => U): A = { f(self) self } diff --git a/src/library/scala/util/package.scala b/compat/src/main/scala-2.11_2.12/scala/util/compat/package.scala similarity index 76% rename from src/library/scala/util/package.scala rename to compat/src/main/scala-2.11_2.12/scala/util/compat/package.scala index e8eb3563..10324f99 100644 --- a/src/library/scala/util/package.scala +++ b/compat/src/main/scala-2.11_2.12/scala/util/compat/package.scala @@ -1,6 +1,6 @@ -package scala +package scala.util -package object util { +package object compat { /** * Adds chaining methods `tap` and `pipe` to every type. See [[ChainingOps]]. */ diff --git a/compat/src/main/scala-2.13/scala/util/compat/package.scala b/compat/src/main/scala-2.13/scala/util/compat/package.scala new file mode 100644 index 00000000..8c667044 --- /dev/null +++ b/compat/src/main/scala-2.13/scala/util/compat/package.scala @@ -0,0 +1,8 @@ +package scala.util + +package object compat { + /** + * Adds chaining methods `tap` and `pipe` to every type. See [[ChainingOps]]. + */ + object chainingOps extends ChainingSyntax +} diff --git a/test/junit/scala/util/ChainingOpsTest.scala b/compat/src/test/scala/test/scala/util/ChainingOpsTest.scala similarity index 86% rename from test/junit/scala/util/ChainingOpsTest.scala rename to compat/src/test/scala/test/scala/util/ChainingOpsTest.scala index 7c249497..aeb26331 100644 --- a/test/junit/scala/util/ChainingOpsTest.scala +++ b/compat/src/test/scala/test/scala/util/ChainingOpsTest.scala @@ -1,10 +1,10 @@ -package scala.util +package scala.util.compat import org.junit.Assert._ import org.junit.Test class ChainingOpsTest { - import scala.util.chainingOps._ + import scala.util.compat.chainingOps._ @Test def testAnyTap: Unit = { From 2c1015a9c7fadcbe37fd3774e880ed5b52467285 Mon Sep 17 00:00:00 2001 From: NthPortal Date: Wed, 8 Aug 2018 18:03:28 -0400 Subject: [PATCH 03/10] [squash] add null checks --- src/library/scala/util/Using.scala | 288 ++++++++++++++++ test/junit/scala/util/UsingTest.scala | 477 ++++++++++++++++++++++++++ 2 files changed, 765 insertions(+) create mode 100644 src/library/scala/util/Using.scala create mode 100644 test/junit/scala/util/UsingTest.scala diff --git a/src/library/scala/util/Using.scala b/src/library/scala/util/Using.scala new file mode 100644 index 00000000..8c2d8304 --- /dev/null +++ b/src/library/scala/util/Using.scala @@ -0,0 +1,288 @@ +package scala.util + +import java.util.concurrent.atomic.AtomicBoolean + +import scala.util.control.{ControlThrowable, NonFatal} + +/** A utility for performing automatic resource management. It can be used to perform an + * operation using resources, after which it will close the resources, in reverse order + * of their creation. The resource opening, operation, and resource closing are wrapped + * in a `Try`. + * + * If more than one exception is thrown by the operation and closing resources, + * the exception thrown ''first'' is returned within the `Try`, with the other exceptions + * [[java.lang.Throwable.addSuppressed(Throwable) added as suppressed exceptions]] + * to the one thrown first. This is the case ''unless'' a later exception is + * [[scala.util.control.NonFatal fatal]], and the one preceding it is not. In that case, + * the first exception is added as a suppressed exception to the fatal one, and the fatal + * one is thrown. If an exception is a + * [[scala.util.control.ControlThrowable ControlThrowable]], no exception will be added to + * it as a suppressed exception. + * + * @example + * {{{ + * val lines: Try[List[String]] = Using(resource1) { r1 => + * r1.lines.toList + * } + * }}} + * + * @example + * {{{ + * val lines: Try[Seq[String]] = for { + * r1 <- Using(resource1) + * r2 <- Using(resource2) + * r3 <- Using(resource3) + * r4 <- Using(resource4) + * } yield { + * // use your resources here + * r1.lines ++ r2.lines ++ r3.lines ++ r4.lines + * } + * }}} + */ +final class Using[R](resource: => R) { + private[this] val used = new AtomicBoolean(false) + + /** Performs an operation using a resource, and then closes the resource, + * even if the operation throws an exception. + * + * @param f the operation to perform + * @param r an implicit [[Using.Resource]] + * @tparam A the return type of the operation + * @throws java.lang.IllegalStateException if the resource has already been used + * @return a [[scala.util.Try `Try`]] containing the result of the operation, or + * an exception if one was thrown by the operation or by closing the resource + */ + @throws[IllegalStateException]("if the resource has already been used") + @inline def apply[A](f: R => A)(implicit r: Using.Resource[R]): Try[A] = map(f) + + /** Performs an operation using a resource, and then closes the resource, + * even if the operation throws an exception. + * + * @param f the operation to perform + * @param r an implicit [[Using.Resource]] + * @tparam A the return type of the operation + * @throws java.lang.IllegalStateException if the resource has already been used + * @return a [[scala.util.Try `Try`]] containing the result of the operation, or + * an exception if one was thrown by the operation or by closing the resource + */ + @throws[IllegalStateException]("if the resource has already been used") + def map[A](f: R => A)(implicit r: Using.Resource[R]): Try[A] = Try { useWith(f) } + + /** Performs an operation which returns a [[scala.util.Try `Try`]] using a resource, + * and then closes the resource, even if the operation throws an exception. + * + * @param f the `Try`-returning operation to perform + * @param r an implicit [[Using.Resource]] + * @tparam A the return type of the operation + * @throws java.lang.IllegalStateException if the resource has already been used + * @return the result of the inner operation, or a [[scala.util.Try `Try`]] + * containing an exception if one was thrown by the operation or by + * closing the resource + */ + @throws[IllegalStateException]("if the resource has already been used") + def flatMap[A](f: R => Try[A])(implicit r: Using.Resource[R]): Try[A] = + map { + r => f(r).get // otherwise inner Failure will be lost on exceptional close + } + + @inline private[this] def useWith[A](f: R => A)(implicit r: Using.Resource[R]): A = + if (used.getAndSet(true)) throw new IllegalStateException("resource has already been used") + else Using.resource(resource)(f) +} + +/** @define recommendUsing It is highly recommended to use the `Using` construct, + * which safely wraps resource usage and management in a `Try`. + * @define multiResourceSuppressionBehavior If more than one exception is thrown by the operation and closing resources, + * the exception thrown ''first'' is thrown, with the other exceptions + * [[java.lang.Throwable.addSuppressed(Throwable) added as suppressed exceptions]] + * to the one thrown first. This is the case ''unless'' a later exception is + * [[scala.util.control.NonFatal fatal]], and the one preceding it is not. In that case, + * the first exception is added as a suppressed exception to the fatal one, and the fatal + * one is thrown. If an exception is a + * [[scala.util.control.ControlThrowable ControlThrowable]], no exception will be added to + * it as a suppressed exception. + */ +object Using { + /** Creates a `Using` from the given resource. + * + * @note If the resource does not have an implicit [[Resource]] in + * scope, the returned `Using` will be useless. + */ + def apply[R](resource: => R): Using[R] = new Using(resource) + + /** Performs an operation using a resource, and then closes the resource, + * even if the operation throws an exception. This method behaves similarly + * to Java's try-with-resources. + * + * $recommendUsing + * + * If both the operation and closing the resource throw exceptions, the one thrown + * when closing the resource is + * [[java.lang.Throwable.addSuppressed(Throwable) added as a suppressed exception]] + * to the one thrown by the operation, ''unless'' the exception thrown when closing + * the resource is [[scala.util.control.NonFatal fatal]], and the one thrown by the + * operation is not. In that case, the exception thrown by the operation is added + * as a suppressed exception to the one thrown when closing the resource. If an + * exception is a [[scala.util.control.ControlThrowable ControlThrowable]], no + * exception will be added to it as a suppressed exception. + * + * @param resource the resource + * @param body the operation to perform with the resource + * @tparam R the type of the resource + * @tparam A the return type of the operation + * @return the result of the operation, if neither the operation nor + * closing the resource throws + */ + def resource[R: Resource, A](resource: R)(body: R => A): A = { + if (resource == null) throw new NullPointerException("null resource") + + @inline def safeAddSuppressed(t: Throwable, suppressed: Throwable): Unit = { + // don't `addSuppressed` to something which is a `ControlThrowable` + if (!t.isInstanceOf[ControlThrowable]) t.addSuppressed(suppressed) + } + + var primary: Throwable = null + try { + body(resource) + } catch { + case t: Throwable => + primary = t + null.asInstanceOf[A] // compiler doesn't know `finally` will throw + } finally { + if (primary eq null) implicitly[Resource[R]].close(resource) + else { + var toThrow = primary + try { + implicitly[Resource[R]].close(resource) + } catch { + case other: Throwable => + if (NonFatal(primary) && !NonFatal(other)) { + // `other` is fatal, `primary` is not + toThrow = other + safeAddSuppressed(other, primary) + } else { + // `toThrow` is already `primary` + safeAddSuppressed(primary, other) + } + } finally { + throw toThrow + } + } + } + } + + /** Performs an operation using two resources, and then closes the resources + * in reverse order, even if the operation throws an exception. This method + * behaves similarly to Java's try-with-resources. + * + * $recommendUsing + * + * $multiResourceSuppressionBehavior + * + * @param resource1 the first resource + * @param resource2 the second resource + * @param body the operation to perform using the resources + * @tparam R1 the type of the first resource + * @tparam R2 the type of the second resource + * @tparam A the return type of the operation + * @return the result of the operation, if neither the operation nor + * closing the resources throws + */ + def resources[R1: Resource, R2: Resource, A]( + resource1: R1, + resource2: => R2 + )(body: (R1, R2) => A + ): A = + resource(resource1) { r1 => + resource(resource2) { r2 => + body(r1, r2) + } + } + + /** Performs an operation using three resources, and then closes the resources + * in reverse order, even if the operation throws an exception. This method + * behaves similarly to Java's try-with-resources. + * + * $recommendUsing + * + * $multiResourceSuppressionBehavior + * + * @param resource1 the first resource + * @param resource2 the second resource + * @param resource3 the third resource + * @param body the operation to perform using the resources + * @tparam R1 the type of the first resource + * @tparam R2 the type of the second resource + * @tparam R3 the type of the third resource + * @tparam A the return type of the operation + * @return the result of the operation, if neither the operation nor + * closing the resources throws + */ + def resources[R1: Resource, R2: Resource, R3: Resource, A]( + resource1: R1, + resource2: => R2, + resource3: => R3 + )(body: (R1, R2, R3) => A + ): A = + resource(resource1) { r1 => + resource(resource2) { r2 => + resource(resource3) { r3 => + body(r1, r2, r3) + } + } + } + + /** Performs an operation using four resources, and then closes the resources + * in reverse order, even if the operation throws an exception. This method + * behaves similarly to Java's try-with-resources. + * + * $recommendUsing + * + * $multiResourceSuppressionBehavior + * + * @param resource1 the first resource + * @param resource2 the second resource + * @param resource3 the third resource + * @param resource4 the fourth resource + * @param body the operation to perform using the resources + * @tparam R1 the type of the first resource + * @tparam R2 the type of the second resource + * @tparam R3 the type of the third resource + * @tparam R4 the type of the fourth resource + * @tparam A the return type of the operation + * @return the result of the operation, if neither the operation nor + * closing the resources throws + */ + def resources[R1: Resource, R2: Resource, R3: Resource, R4: Resource, A]( + resource1: R1, + resource2: => R2, + resource3: => R3, + resource4: => R4 + )(body: (R1, R2, R3, R4) => A + ): A = + resource(resource1) { r1 => + resource(resource2) { r2 => + resource(resource3) { r3 => + resource(resource4) { r4 => + body(r1, r2, r3, r4) + } + } + } + } + + /** A typeclass describing a resource which can be closed. + * + * @tparam R the type of the resource + */ + trait Resource[R] { + def close(resource: R): Unit + } + + object Resource { + private[this] val autoCloseableResource: Resource[AutoCloseable] = (resource: AutoCloseable) => resource.close() + + /** Returns an implicit `Resource` for [[java.lang.AutoCloseable `AutoClosable`s]]. */ + implicit def AutoClosable[C <: AutoCloseable]: Resource[C] = autoCloseableResource.asInstanceOf[Resource[C]] + } + +} diff --git a/test/junit/scala/util/UsingTest.scala b/test/junit/scala/util/UsingTest.scala new file mode 100644 index 00000000..269f4714 --- /dev/null +++ b/test/junit/scala/util/UsingTest.scala @@ -0,0 +1,477 @@ +package scala.util + +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 +import org.junit.Test +import org.junit.Assert._ + +import scala.reflect.ClassTag +import scala.util.control.ControlThrowable + +@RunWith(classOf[JUnit4]) +class UsingTest { + import UsingTest._ + + /* raw `Using.resource` that doesn't use `Try` */ + + @Test + def usingResourceThatThrowsException(): Unit = { + val exception = use(new ExceptionResource, new UsingException(_)) + assertThrowableClass[UsingException](exception) + assertSingleSuppressed[ClosingException](exception) + + val error = use(new ExceptionResource, new Error(_)) + assertThrowableClass[Error](error) + assertSingleSuppressed[ClosingException](error) + + val fatal = use(new ExceptionResource, new OutOfMemoryError(_)) + assertThrowableClass[OutOfMemoryError](fatal) + assertSingleSuppressed[ClosingException](fatal) + + val control = use(new ExceptionResource, new UsingMarker(_)) + assertThrowableClass[UsingMarker](control) + assertNoSuppressed(control) + } + + @Test + def usingResourceThatThrowsError(): Unit = { + val exception = use(new ErrorResource, new UsingException(_)) + assertThrowableClass[UsingException](exception) + assertSingleSuppressed[ClosingError](exception) + + val error = use(new ErrorResource, new Error(_)) + assertThrowableClass[Error](error) + assertSingleSuppressed[ClosingError](error) + + val fatal = use(new ErrorResource, new OutOfMemoryError(_)) + assertThrowableClass[OutOfMemoryError](fatal) + assertSingleSuppressed[ClosingError](fatal) + + val control = use(new ErrorResource, new UsingMarker(_)) + assertThrowableClass[UsingMarker](control) + assertNoSuppressed(control) + } + + @Test + def usingResourceThatThrowsFatal(): Unit = { + val exception = use(new FatalResource, new UsingException(_)) + assertThrowableClass[StackOverflowError](exception) + assertSingleSuppressed[UsingException](exception) + + val error = use(new FatalResource, new Error(_)) + assertThrowableClass[StackOverflowError](error) + assertSingleSuppressed[Error](error) + + val fatal = use(new FatalResource, new OutOfMemoryError(_)) + assertThrowableClass[OutOfMemoryError](fatal) + assertSingleSuppressed[StackOverflowError](fatal) + + val control = use(new FatalResource, new UsingMarker(_)) + assertThrowableClass[UsingMarker](control) + assertNoSuppressed(control) + } + + @Test + def usingResourceThatThrowsControlThrowable(): Unit = { + val exception = use(new MarkerResource, new UsingException(_)) + assertThrowableClass[ClosingMarker](exception) + assertNoSuppressed(exception) + + val error = use(new MarkerResource, new Error(_)) + assertThrowableClass[ClosingMarker](error) + assertNoSuppressed(error) + + val fatal = use(new MarkerResource, new OutOfMemoryError(_)) + assertThrowableClass[OutOfMemoryError](fatal) + assertSingleSuppressed[ClosingMarker](fatal) + + val control = use(new MarkerResource, new UsingMarker(_)) + assertThrowableClass[UsingMarker](control) + assertNoSuppressed(control) + } + + /* safe `Using` that returns `Try` */ + + @Test + def safeUsingResourceThatThrowsException(): Unit = { + val exception = UseWrapped(new ExceptionResource, new UsingException(_)) + assertThrowableClass[UsingException](exception) + assertSingleSuppressed[ClosingException](exception) + + val error = UseWrapped(new ExceptionResource, new Error(_)) + assertThrowableClass[Error](error) + assertSingleSuppressed[ClosingException](error) + + val fatal = UseWrapped.catching(new ExceptionResource, new OutOfMemoryError(_)) + assertThrowableClass[OutOfMemoryError](fatal) + assertSingleSuppressed[ClosingException](fatal) + + val control = UseWrapped.catching(new ExceptionResource, new UsingMarker(_)) + assertThrowableClass[UsingMarker](control) + assertNoSuppressed(control) + } + + @Test + def safeUsingResourceThatThrowsError(): Unit = { + val exception = UseWrapped(new ErrorResource, new UsingException(_)) + assertThrowableClass[UsingException](exception) + assertSingleSuppressed[ClosingError](exception) + + val error = UseWrapped(new ErrorResource, new Error(_)) + assertThrowableClass[Error](error) + assertSingleSuppressed[ClosingError](error) + + val fatal = UseWrapped.catching(new ErrorResource, new OutOfMemoryError(_)) + assertThrowableClass[OutOfMemoryError](fatal) + assertSingleSuppressed[ClosingError](fatal) + + val control = UseWrapped.catching(new ErrorResource, new UsingMarker(_)) + assertThrowableClass[UsingMarker](control) + assertNoSuppressed(control) + } + + @Test + def safeUsingResourceThatThrowsFatal(): Unit = { + val exception = UseWrapped.catching(new FatalResource, new UsingException(_)) + assertThrowableClass[StackOverflowError](exception) + assertSingleSuppressed[UsingException](exception) + + val error = UseWrapped.catching(new FatalResource, new Error(_)) + assertThrowableClass[StackOverflowError](error) + assertSingleSuppressed[Error](error) + + val fatal = UseWrapped.catching(new FatalResource, new OutOfMemoryError(_)) + assertThrowableClass[OutOfMemoryError](fatal) + assertSingleSuppressed[StackOverflowError](fatal) + + val control = UseWrapped.catching(new FatalResource, new UsingMarker(_)) + assertThrowableClass[UsingMarker](control) + assertNoSuppressed(control) + } + + @Test + def safeUsingResourceThatThrowsControlThrowable(): Unit = { + val exception = UseWrapped.catching(new MarkerResource, new UsingException(_)) + assertThrowableClass[ClosingMarker](exception) + assertNoSuppressed(exception) + + val error = UseWrapped.catching(new MarkerResource, new Error(_)) + assertThrowableClass[ClosingMarker](error) + assertNoSuppressed(error) + + val fatal = UseWrapped.catching(new MarkerResource, new OutOfMemoryError(_)) + assertThrowableClass[OutOfMemoryError](fatal) + assertSingleSuppressed[ClosingMarker](fatal) + + val control = UseWrapped.catching(new MarkerResource, new UsingMarker(_)) + assertThrowableClass[UsingMarker](control) + assertNoSuppressed(control) + } + + /* nested resource usage returns the correct exception */ + + @Test + def usingMultipleResourcesPropagatesCorrectlySimple(): Unit = { + val usingException = catchThrowable { + Using.resource(new ExceptionResource) { _ => + Using.resource(new ErrorResource) { _ => + throw new UsingException("nested `Using.resource`") + } + } + } + + // uncomment to debug actual suppression nesting + //usingException.printStackTrace() + + /* + UsingException + |- ClosingError + |- ClosingException + */ + assertThrowableClass[UsingException](usingException) + val suppressed = usingException.getSuppressed + assertEquals(suppressed.length, 2) + val closingError = suppressed(0) + val closingException = suppressed(1) + assertThrowableClass[ClosingError](closingError) + assertThrowableClass[ClosingException](closingException) + } + + @Test + def usingMultipleResourcesPropagatesCorrectlyComplex(): Unit = { + val fatal = catchThrowable { + Using.resource(new ExceptionResource) { _ => + Using.resource(new FatalResource) { _ => + Using.resource(new ErrorResource) { _ => + throw new UsingException("nested `Using.resource`") + } + } + } + } + + // uncomment to debug actual suppression nesting + //fatal.printStackTrace() + + /* + StackOverflowError + |- UsingException + | |- ClosingError + |- ClosingException + */ + assertThrowableClass[StackOverflowError](fatal) + val firstLevelSuppressed = fatal.getSuppressed + assertEquals(firstLevelSuppressed.length, 2) + val usingException = firstLevelSuppressed(0) + val closingException = firstLevelSuppressed(1) + assertThrowableClass[UsingException](usingException) + assertThrowableClass[ClosingException](closingException) + assertSingleSuppressed[ClosingError](usingException) + } + + @Test + def safeUsingMultipleResourcesPropagatesCorrectlySimple(): Unit = { + val scala.util.Failure(usingException) = for { + _ <- Using(new ExceptionResource) + _ <- Using(new ErrorResource) + } yield { + throw new UsingException("nested `Using`") + } + + // uncomment to debug actual suppression nesting + //usingException.printStackTrace() + + /* + UsingException + |- ClosingError + |- ClosingException + */ + assertThrowableClass[UsingException](usingException) + val suppressed = usingException.getSuppressed + assertEquals(suppressed.length, 2) + val closingError = suppressed(0) + val closingException = suppressed(1) + assertThrowableClass[ClosingError](closingError) + assertThrowableClass[ClosingException](closingException) + } + + @Test + def safeUsingMultipleResourcesPropagatesCorrectlyComplex(): Unit = { + val fatal = catchThrowable { + for { + _ <- Using(new ExceptionResource) + _ <- Using(new FatalResource) + _ <- Using(new ErrorResource) + } yield { + throw new UsingException("nested `Using`") + } + } + + // uncomment to debug actual suppression nesting + //fatal.printStackTrace() + + /* + StackOverflowError + |- UsingException + | |- ClosingError + |- ClosingException + */ + assertThrowableClass[StackOverflowError](fatal) + val firstLevelSuppressed = fatal.getSuppressed + assertEquals(firstLevelSuppressed.length, 2) + val usingException = firstLevelSuppressed(0) + val closingException = firstLevelSuppressed(1) + assertThrowableClass[UsingException](usingException) + assertThrowableClass[ClosingException](closingException) + assertSingleSuppressed[ClosingError](usingException) + } + + /* works when throwing no exceptions */ + + @Test + def usingResourceWithNoThrow(): Unit = { + val res = Using.resource(new NoOpResource) { r => + r.identity("test") + } + assertEquals(res, "test") + } + + @Test + def safeUsingResourceWithNoThrow(): Unit = { + val res = Using(new NoOpResource) { r => + r.identity("test") + } + assertEquals(res, scala.util.Success("test")) + } + + /* using multiple resources close in the correct order */ + + @Test + def using2Resources(): Unit = { + val group = new ResourceGroup + val res = Using.resources( + group.newResource(), + group.newResource(), + ) { (r1, r2) => + r1.identity(1) + r2.identity(1) + } + assertEquals(res, 2) + } + + @Test + def using3Resources(): Unit = { + val group = new ResourceGroup + val res = Using.resources( + group.newResource(), + group.newResource(), + group.newResource(), + ) { (r1, r2, r3) => + r1.identity(1) + + r2.identity(1) + + r3.identity(1) + } + assertEquals(res, 3) + } + + @Test + def using4Resources(): Unit = { + val group = new ResourceGroup + val res = Using.resources( + group.newResource(), + group.newResource(), + group.newResource(), + group.newResource(), + ) { (r1, r2, r3, r4) => + r1.identity(1) + + r2.identity(1) + + r3.identity(1) + + r4.identity(1) + } + assertEquals(res, 4) + } + + @Test + def safeUsing2Resources(): Unit = { + val group = new ResourceGroup + val res = for { + r1 <- Using(group.newResource()) + r2 <- Using(group.newResource()) + } yield { + r1.identity(1) + r2.identity(1) + } + assertEquals(res, scala.util.Success(2)) + } + + @Test + def safeUsing3Resources(): Unit = { + val group = new ResourceGroup + val res = for { + r1 <- Using(group.newResource()) + r2 <- Using(group.newResource()) + r3 <- Using(group.newResource()) + } yield { + r1.identity(1) + + r2.identity(1) + + r3.identity(1) + } + assertEquals(res, scala.util.Success(3)) + } + + /* misc */ + + @Test + def usingDisallowsNull(): Unit = { + val npe = catchThrowable(Using.resource(null: AutoCloseable)(_ => "test")) + assertThrowableClass[NullPointerException](npe) + } + + @Test + def safeUsingDisallowsNull(): Unit = { + val npe = Using(null: AutoCloseable)(_ => "test").failed.get + assertThrowableClass[NullPointerException](npe) + } +} + +object UsingTest { + final class ClosingException(message: String) extends Exception(message) + final class UsingException(message: String) extends Exception(message) + final class ClosingError(message: String) extends Error(message) + final class UsingError(message: String) extends Error(message) + final class ClosingMarker(message: String) extends Throwable(message) with ControlThrowable + final class UsingMarker(message: String) extends Throwable(message) with ControlThrowable + + abstract class BaseResource extends AutoCloseable { + final def identity[A](a: A): A = a + } + + final class NoOpResource extends BaseResource { + override def close(): Unit = () + } + + abstract class CustomResource(t: String => Throwable) extends BaseResource { + override final def close(): Unit = throw t("closing " + getClass.getSimpleName) + } + + final class ExceptionResource extends CustomResource(new ClosingException(_)) + final class ErrorResource extends CustomResource(new ClosingError(_)) + final class FatalResource extends CustomResource(new StackOverflowError(_)) + final class MarkerResource extends CustomResource(new ClosingMarker(_)) + + def assertThrowableClass[T <: Throwable: ClassTag](t: Throwable): Unit = { + assertEquals(t.getClass, implicitly[ClassTag[T]].runtimeClass) + } + + def assertSingleSuppressed[T <: Throwable: ClassTag](t: Throwable): Unit = { + val suppressed = t.getSuppressed + assertEquals(suppressed.length, 1) + assertThrowableClass[T](suppressed(0)) + } + + def assertNoSuppressed(t: Throwable): Unit = { + assertEquals(t.getSuppressed.length, 0) + } + + def catchThrowable(thunk: => Any): Throwable = { + try { + thunk + throw new AssertionError("unreachable") + } catch { + case t: Throwable => t + } + } + + object UseWrapped { + def apply(resource: => BaseResource, t: String => Throwable): Throwable = + Using(resource)(opThrowing(t)).failed.get + + def catching(resource: => BaseResource, t: String => Throwable): Throwable = + catchThrowable(Using(resource)(opThrowing(t))) + } + + def use(resource: BaseResource, t: String => Throwable): Throwable = + catchThrowable(Using.resource(resource)(opThrowing(t))) + + private def opThrowing(t: String => Throwable): BaseResource => Nothing = + r => { + r.identity("test") + throw t("exception using resource") + } + + final class ResourceGroup { + // tracks the number of open resources + private var openCount: Int = 0 + + def newResource(): BaseResource = { + openCount += 1 + new CountingResource(openCount) + } + + private final class CountingResource(countWhenCreated: Int) extends BaseResource { + override def close(): Unit = { + assertEquals(countWhenCreated, openCount) + openCount -= 1 + } + } + + } + +} From ffee126bc1eaf5b25233da9a895e92f3c05007e4 Mon Sep 17 00:00:00 2001 From: NthPortal Date: Wed, 8 Aug 2018 18:26:20 -0400 Subject: [PATCH 04/10] [squash] make constructor private; rename `close` to `release` --- src/library/scala/util/Using.scala | 56 +++++++++++++++--------------- 1 file changed, 28 insertions(+), 28 deletions(-) diff --git a/src/library/scala/util/Using.scala b/src/library/scala/util/Using.scala index 8c2d8304..d71eecbc 100644 --- a/src/library/scala/util/Using.scala +++ b/src/library/scala/util/Using.scala @@ -5,11 +5,11 @@ import java.util.concurrent.atomic.AtomicBoolean import scala.util.control.{ControlThrowable, NonFatal} /** A utility for performing automatic resource management. It can be used to perform an - * operation using resources, after which it will close the resources, in reverse order - * of their creation. The resource opening, operation, and resource closing are wrapped + * operation using resources, after which it will release the resources, in reverse order + * of their creation. The resource opening, operation, and resource releasing are wrapped * in a `Try`. * - * If more than one exception is thrown by the operation and closing resources, + * If more than one exception is thrown by the operation and releasing resources, * the exception thrown ''first'' is returned within the `Try`, with the other exceptions * [[java.lang.Throwable.addSuppressed(Throwable) added as suppressed exceptions]] * to the one thrown first. This is the case ''unless'' a later exception is @@ -39,10 +39,10 @@ import scala.util.control.{ControlThrowable, NonFatal} * } * }}} */ -final class Using[R](resource: => R) { +final class Using[R] private(resource: => R) { private[this] val used = new AtomicBoolean(false) - /** Performs an operation using a resource, and then closes the resource, + /** Performs an operation using a resource, and then releases the resource, * even if the operation throws an exception. * * @param f the operation to perform @@ -50,12 +50,12 @@ final class Using[R](resource: => R) { * @tparam A the return type of the operation * @throws java.lang.IllegalStateException if the resource has already been used * @return a [[scala.util.Try `Try`]] containing the result of the operation, or - * an exception if one was thrown by the operation or by closing the resource + * an exception if one was thrown by the operation or by releasing the resource */ @throws[IllegalStateException]("if the resource has already been used") @inline def apply[A](f: R => A)(implicit r: Using.Resource[R]): Try[A] = map(f) - /** Performs an operation using a resource, and then closes the resource, + /** Performs an operation using a resource, and then releases the resource, * even if the operation throws an exception. * * @param f the operation to perform @@ -63,13 +63,13 @@ final class Using[R](resource: => R) { * @tparam A the return type of the operation * @throws java.lang.IllegalStateException if the resource has already been used * @return a [[scala.util.Try `Try`]] containing the result of the operation, or - * an exception if one was thrown by the operation or by closing the resource + * an exception if one was thrown by the operation or by releasing the resource */ @throws[IllegalStateException]("if the resource has already been used") def map[A](f: R => A)(implicit r: Using.Resource[R]): Try[A] = Try { useWith(f) } /** Performs an operation which returns a [[scala.util.Try `Try`]] using a resource, - * and then closes the resource, even if the operation throws an exception. + * and then releases the resource, even if the operation throws an exception. * * @param f the `Try`-returning operation to perform * @param r an implicit [[Using.Resource]] @@ -77,12 +77,12 @@ final class Using[R](resource: => R) { * @throws java.lang.IllegalStateException if the resource has already been used * @return the result of the inner operation, or a [[scala.util.Try `Try`]] * containing an exception if one was thrown by the operation or by - * closing the resource + * releasing the resource */ @throws[IllegalStateException]("if the resource has already been used") def flatMap[A](f: R => Try[A])(implicit r: Using.Resource[R]): Try[A] = map { - r => f(r).get // otherwise inner Failure will be lost on exceptional close + r => f(r).get // otherwise inner Failure will be lost on exceptional release } @inline private[this] def useWith[A](f: R => A)(implicit r: Using.Resource[R]): A = @@ -92,7 +92,7 @@ final class Using[R](resource: => R) { /** @define recommendUsing It is highly recommended to use the `Using` construct, * which safely wraps resource usage and management in a `Try`. - * @define multiResourceSuppressionBehavior If more than one exception is thrown by the operation and closing resources, + * @define multiResourceSuppressionBehavior If more than one exception is thrown by the operation and releasing resources, * the exception thrown ''first'' is thrown, with the other exceptions * [[java.lang.Throwable.addSuppressed(Throwable) added as suppressed exceptions]] * to the one thrown first. This is the case ''unless'' a later exception is @@ -110,19 +110,19 @@ object Using { */ def apply[R](resource: => R): Using[R] = new Using(resource) - /** Performs an operation using a resource, and then closes the resource, + /** Performs an operation using a resource, and then releases the resource, * even if the operation throws an exception. This method behaves similarly * to Java's try-with-resources. * * $recommendUsing * - * If both the operation and closing the resource throw exceptions, the one thrown - * when closing the resource is + * If both the operation and releasing the resource throw exceptions, the one thrown + * when releasing the resource is * [[java.lang.Throwable.addSuppressed(Throwable) added as a suppressed exception]] - * to the one thrown by the operation, ''unless'' the exception thrown when closing + * to the one thrown by the operation, ''unless'' the exception thrown when releasing * the resource is [[scala.util.control.NonFatal fatal]], and the one thrown by the * operation is not. In that case, the exception thrown by the operation is added - * as a suppressed exception to the one thrown when closing the resource. If an + * as a suppressed exception to the one thrown when releasing the resource. If an * exception is a [[scala.util.control.ControlThrowable ControlThrowable]], no * exception will be added to it as a suppressed exception. * @@ -131,7 +131,7 @@ object Using { * @tparam R the type of the resource * @tparam A the return type of the operation * @return the result of the operation, if neither the operation nor - * closing the resource throws + * releasing the resource throws */ def resource[R: Resource, A](resource: R)(body: R => A): A = { if (resource == null) throw new NullPointerException("null resource") @@ -149,11 +149,11 @@ object Using { primary = t null.asInstanceOf[A] // compiler doesn't know `finally` will throw } finally { - if (primary eq null) implicitly[Resource[R]].close(resource) + if (primary eq null) implicitly[Resource[R]].release(resource) else { var toThrow = primary try { - implicitly[Resource[R]].close(resource) + implicitly[Resource[R]].release(resource) } catch { case other: Throwable => if (NonFatal(primary) && !NonFatal(other)) { @@ -171,7 +171,7 @@ object Using { } } - /** Performs an operation using two resources, and then closes the resources + /** Performs an operation using two resources, and then releases the resources * in reverse order, even if the operation throws an exception. This method * behaves similarly to Java's try-with-resources. * @@ -186,7 +186,7 @@ object Using { * @tparam R2 the type of the second resource * @tparam A the return type of the operation * @return the result of the operation, if neither the operation nor - * closing the resources throws + * releasing the resources throws */ def resources[R1: Resource, R2: Resource, A]( resource1: R1, @@ -199,7 +199,7 @@ object Using { } } - /** Performs an operation using three resources, and then closes the resources + /** Performs an operation using three resources, and then releases the resources * in reverse order, even if the operation throws an exception. This method * behaves similarly to Java's try-with-resources. * @@ -216,7 +216,7 @@ object Using { * @tparam R3 the type of the third resource * @tparam A the return type of the operation * @return the result of the operation, if neither the operation nor - * closing the resources throws + * releasing the resources throws */ def resources[R1: Resource, R2: Resource, R3: Resource, A]( resource1: R1, @@ -232,7 +232,7 @@ object Using { } } - /** Performs an operation using four resources, and then closes the resources + /** Performs an operation using four resources, and then releases the resources * in reverse order, even if the operation throws an exception. This method * behaves similarly to Java's try-with-resources. * @@ -251,7 +251,7 @@ object Using { * @tparam R4 the type of the fourth resource * @tparam A the return type of the operation * @return the result of the operation, if neither the operation nor - * closing the resources throws + * releasing the resources throws */ def resources[R1: Resource, R2: Resource, R3: Resource, R4: Resource, A]( resource1: R1, @@ -270,12 +270,12 @@ object Using { } } - /** A typeclass describing a resource which can be closed. + /** A typeclass describing a resource which can be released. * * @tparam R the type of the resource */ trait Resource[R] { - def close(resource: R): Unit + def release(resource: R): Unit } object Resource { From 74f5689dd112d90fa9d6fc288ccb6879e55b1ee0 Mon Sep 17 00:00:00 2001 From: NthPortal Date: Wed, 8 Aug 2018 19:09:04 -0400 Subject: [PATCH 05/10] [squash] add test that Using catches exception during resource creation --- test/junit/scala/util/UsingTest.scala | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/test/junit/scala/util/UsingTest.scala b/test/junit/scala/util/UsingTest.scala index 269f4714..365cf51a 100644 --- a/test/junit/scala/util/UsingTest.scala +++ b/test/junit/scala/util/UsingTest.scala @@ -389,6 +389,12 @@ class UsingTest { val npe = Using(null: AutoCloseable)(_ => "test").failed.get assertThrowableClass[NullPointerException](npe) } + + @Test + def safeUsingCatchesOpeningException(): Unit = { + val ex = Using({ throw new RuntimeException }: AutoCloseable)(_ => "test").failed.get + assertThrowableClass[RuntimeException](ex) + } } object UsingTest { From a66cdd75bd852ad7f52a3ad2b45cc174f4f65307 Mon Sep 17 00:00:00 2001 From: NthPortal Date: Wed, 8 Aug 2018 21:36:13 -0400 Subject: [PATCH 06/10] [squash] contravariant Resource --- src/library/scala/util/Using.scala | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/library/scala/util/Using.scala b/src/library/scala/util/Using.scala index d71eecbc..71669c42 100644 --- a/src/library/scala/util/Using.scala +++ b/src/library/scala/util/Using.scala @@ -274,15 +274,13 @@ object Using { * * @tparam R the type of the resource */ - trait Resource[R] { + trait Resource[-R] { def release(resource: R): Unit } object Resource { - private[this] val autoCloseableResource: Resource[AutoCloseable] = (resource: AutoCloseable) => resource.close() - - /** Returns an implicit `Resource` for [[java.lang.AutoCloseable `AutoClosable`s]]. */ - implicit def AutoClosable[C <: AutoCloseable]: Resource[C] = autoCloseableResource.asInstanceOf[Resource[C]] + /** An implicit `Resource` for [[java.lang.AutoCloseable `AutoClosable`s]]. */ + implicit val autoCloseableResource: Resource[AutoCloseable] = (resource: AutoCloseable) => resource.close() } } From ea87916e1a767e6ffb85071c08baf1554add556e Mon Sep 17 00:00:00 2001 From: NthPortal Date: Thu, 9 Aug 2018 00:24:32 -0400 Subject: [PATCH 07/10] [squash] scaladoc --- src/library/scala/util/Using.scala | 1 + 1 file changed, 1 insertion(+) diff --git a/src/library/scala/util/Using.scala b/src/library/scala/util/Using.scala index 71669c42..62ed6922 100644 --- a/src/library/scala/util/Using.scala +++ b/src/library/scala/util/Using.scala @@ -275,6 +275,7 @@ object Using { * @tparam R the type of the resource */ trait Resource[-R] { + /** Releases the specified resource. */ def release(resource: R): Unit } From 405edc35f706cef10c78a4ad9c0d09251dd264c1 Mon Sep 17 00:00:00 2001 From: NthPortal Date: Thu, 9 Aug 2018 09:40:23 -0400 Subject: [PATCH 08/10] [squash] fix typo --- src/library/scala/util/Using.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/library/scala/util/Using.scala b/src/library/scala/util/Using.scala index 62ed6922..b799cf13 100644 --- a/src/library/scala/util/Using.scala +++ b/src/library/scala/util/Using.scala @@ -280,7 +280,7 @@ object Using { } object Resource { - /** An implicit `Resource` for [[java.lang.AutoCloseable `AutoClosable`s]]. */ + /** An implicit `Resource` for [[java.lang.AutoCloseable `AutoCloseable`s]]. */ implicit val autoCloseableResource: Resource[AutoCloseable] = (resource: AutoCloseable) => resource.close() } From a7af40c66a6c67ad5f1a1a8895352d287599a744 Mon Sep 17 00:00:00 2001 From: Martijn Hoekstra Date: Thu, 6 Sep 2018 15:23:12 +0200 Subject: [PATCH 09/10] adapted Using de-SAM, no trailing commas --- .../scala/util/compat}/Using.scala | 7 +++++-- .../scala/util/compat/package.scala | 19 +++++++++++-------- .../scala/test}/scala/util/UsingTest.scala | 8 ++++---- 3 files changed, 20 insertions(+), 14 deletions(-) rename {src/library/scala/util => compat/src/main/scala-2.11_2.12/scala/util/compat}/Using.scala (98%) rename {test/junit => compat/src/test/scala/test}/scala/util/UsingTest.scala (99%) diff --git a/src/library/scala/util/Using.scala b/compat/src/main/scala-2.11_2.12/scala/util/compat/Using.scala similarity index 98% rename from src/library/scala/util/Using.scala rename to compat/src/main/scala-2.11_2.12/scala/util/compat/Using.scala index b799cf13..a4202ffa 100644 --- a/src/library/scala/util/Using.scala +++ b/compat/src/main/scala-2.11_2.12/scala/util/compat/Using.scala @@ -1,8 +1,9 @@ -package scala.util +package scala.util.compat import java.util.concurrent.atomic.AtomicBoolean import scala.util.control.{ControlThrowable, NonFatal} +import scala.util._ /** A utility for performing automatic resource management. It can be used to perform an * operation using resources, after which it will release the resources, in reverse order @@ -281,7 +282,9 @@ object Using { object Resource { /** An implicit `Resource` for [[java.lang.AutoCloseable `AutoCloseable`s]]. */ - implicit val autoCloseableResource: Resource[AutoCloseable] = (resource: AutoCloseable) => resource.close() + implicit val autoCloseableResource: Resource[AutoCloseable] = new Resource[AutoCloseable] { + def release(resource: AutoCloseable) = resource.close() + } } } diff --git a/compat/src/main/scala-2.13/scala/util/compat/package.scala b/compat/src/main/scala-2.13/scala/util/compat/package.scala index 8c667044..9b3be24e 100644 --- a/compat/src/main/scala-2.13/scala/util/compat/package.scala +++ b/compat/src/main/scala-2.13/scala/util/compat/package.scala @@ -1,8 +1,11 @@ -package scala.util - -package object compat { - /** - * Adds chaining methods `tap` and `pipe` to every type. See [[ChainingOps]]. - */ - object chainingOps extends ChainingSyntax -} +package scala.util + +package object compat { + /** + * Adds chaining methods `tap` and `pipe` to every type. See [[ChainingOps]]. + */ + object chainingOps extends ChainingSyntax + + type Using[R] = scala.util.Using[R] + val Using = scala.util.Using +} diff --git a/test/junit/scala/util/UsingTest.scala b/compat/src/test/scala/test/scala/util/UsingTest.scala similarity index 99% rename from test/junit/scala/util/UsingTest.scala rename to compat/src/test/scala/test/scala/util/UsingTest.scala index 365cf51a..e10c7529 100644 --- a/test/junit/scala/util/UsingTest.scala +++ b/compat/src/test/scala/test/scala/util/UsingTest.scala @@ -1,4 +1,4 @@ -package scala.util +package scala.util.compat import org.junit.runner.RunWith import org.junit.runners.JUnit4 @@ -310,7 +310,7 @@ class UsingTest { val group = new ResourceGroup val res = Using.resources( group.newResource(), - group.newResource(), + group.newResource() ) { (r1, r2) => r1.identity(1) + r2.identity(1) } @@ -323,7 +323,7 @@ class UsingTest { val res = Using.resources( group.newResource(), group.newResource(), - group.newResource(), + group.newResource() ) { (r1, r2, r3) => r1.identity(1) + r2.identity(1) + @@ -339,7 +339,7 @@ class UsingTest { group.newResource(), group.newResource(), group.newResource(), - group.newResource(), + group.newResource() ) { (r1, r2, r3, r4) => r1.identity(1) + r2.identity(1) + From 742f928e932fd473ae37a348d2163f8a9a2d39bc Mon Sep 17 00:00:00 2001 From: Martijn Hoekstra Date: Fri, 21 Sep 2018 10:11:17 +0200 Subject: [PATCH 10/10] provide Resource for all closables --- .../main/scala-2.11_2.12/scala/util/compat/Using.scala | 10 ++++++---- compat/src/test/scala/test/scala/util/UsingTest.scala | 9 +++++---- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/compat/src/main/scala-2.11_2.12/scala/util/compat/Using.scala b/compat/src/main/scala-2.11_2.12/scala/util/compat/Using.scala index a4202ffa..6e64db23 100644 --- a/compat/src/main/scala-2.11_2.12/scala/util/compat/Using.scala +++ b/compat/src/main/scala-2.11_2.12/scala/util/compat/Using.scala @@ -139,7 +139,8 @@ object Using { @inline def safeAddSuppressed(t: Throwable, suppressed: Throwable): Unit = { // don't `addSuppressed` to something which is a `ControlThrowable` - if (!t.isInstanceOf[ControlThrowable]) t.addSuppressed(suppressed) + // nor to scalaJS + if (!t.isInstanceOf[ControlThrowable] && (1.0.toString == "1.0")) t.addSuppressed(suppressed) } var primary: Throwable = null @@ -281,9 +282,10 @@ object Using { } object Resource { - /** An implicit `Resource` for [[java.lang.AutoCloseable `AutoCloseable`s]]. */ - implicit val autoCloseableResource: Resource[AutoCloseable] = new Resource[AutoCloseable] { - def release(resource: AutoCloseable) = resource.close() + import java.io.Closeable + /** An implicit `Resource` for [[java.lang.Closeable `Closeable`s]]. */ + implicit val closeableResource: Resource[Closeable] = new Resource[Closeable] { + def release(resource: Closeable) = resource.close() } } diff --git a/compat/src/test/scala/test/scala/util/UsingTest.scala b/compat/src/test/scala/test/scala/util/UsingTest.scala index e10c7529..8744357f 100644 --- a/compat/src/test/scala/test/scala/util/UsingTest.scala +++ b/compat/src/test/scala/test/scala/util/UsingTest.scala @@ -7,6 +7,7 @@ import org.junit.Assert._ import scala.reflect.ClassTag import scala.util.control.ControlThrowable +import java.io.Closeable @RunWith(classOf[JUnit4]) class UsingTest { @@ -380,19 +381,19 @@ class UsingTest { @Test def usingDisallowsNull(): Unit = { - val npe = catchThrowable(Using.resource(null: AutoCloseable)(_ => "test")) + val npe = catchThrowable(Using.resource(null: Closeable)(_ => "test")) assertThrowableClass[NullPointerException](npe) } @Test def safeUsingDisallowsNull(): Unit = { - val npe = Using(null: AutoCloseable)(_ => "test").failed.get + val npe = Using(null: Closeable)(_ => "test").failed.get assertThrowableClass[NullPointerException](npe) } @Test def safeUsingCatchesOpeningException(): Unit = { - val ex = Using({ throw new RuntimeException }: AutoCloseable)(_ => "test").failed.get + val ex = Using({ throw new RuntimeException }: Closeable)(_ => "test").failed.get assertThrowableClass[RuntimeException](ex) } } @@ -405,7 +406,7 @@ object UsingTest { final class ClosingMarker(message: String) extends Throwable(message) with ControlThrowable final class UsingMarker(message: String) extends Throwable(message) with ControlThrowable - abstract class BaseResource extends AutoCloseable { + abstract class BaseResource extends Closeable { final def identity[A](a: A): A = a }