From 6626f3146fc793f0286601f7ed66c08ef121abc3 Mon Sep 17 00:00:00 2001 From: Yang Bo Date: Tue, 18 Jun 2019 11:12:34 -0700 Subject: [PATCH 1/5] Create standalone TryCatch and TryFinally type classes instead of the Catch keyword --- .../main/scala/com/thoughtworks/dsl/Dsl.scala | 287 +++++++++++++++++- .../dsl/compilerplugins/BangNotation.scala | 99 +++--- .../com/thoughtworks/dsl/domains/scalaz.scala | 58 +++- .../thoughtworks/dsl/domains/RaiiSpec.scala | 5 +- .../com/thoughtworks/dsl/keywords/Fork.scala | 75 ++++- .../com/thoughtworks/dsl/keywords/Using.scala | 27 +- .../scala/com/thoughtworks/dsl/package.scala | 38 ++- .../thoughtworks/dsl/MockPingPongServer.scala | 2 +- 8 files changed, 528 insertions(+), 63 deletions(-) diff --git a/Dsl/src/main/scala/com/thoughtworks/dsl/Dsl.scala b/Dsl/src/main/scala/com/thoughtworks/dsl/Dsl.scala index 39ed2c1c3..ebecceb14 100644 --- a/Dsl/src/main/scala/com/thoughtworks/dsl/Dsl.scala +++ b/Dsl/src/main/scala/com/thoughtworks/dsl/Dsl.scala @@ -6,8 +6,9 @@ import com.thoughtworks.enableMembersIf import scala.annotation._ import scala.collection._ import scala.collection.mutable.Builder -import scala.concurrent.Future +import scala.concurrent.{ExecutionContext, Future} import scala.language.higherKinds +import scala.util.control.Exception.Catcher import scala.util.{Failure, Success, Try} import scala.util.control.{NonFatal, TailCalls} import scala.util.control.TailCalls.TailRec @@ -262,4 +263,288 @@ object Dsl extends LowPriorityDsl0 { } + private def catchNativeException[A](futureContinuation: Future[A] !! A): Future[A] = { + try { + futureContinuation(Future.successful) + } catch { + case NonFatal(e) => + Future.failed(e) + } + } + + /** The type class to support `try` ... `catch` ... `finally` expression for `OutputDomain`. + * + * !-notation is allowed by default for `? !! Throwable` and [[scala.concurrent.Future Future]] domains, + * with the help of this type class. + */ + @implicitNotFound("The `try` ... `catch` ... `finally` expression cannot contain !-notation inside a function that returns ${OuterDomain}.") + trait TryCatchFinally[Value, OuterDomain, BlockDomain, FinalizerDomain] { + def tryCatchFinally(block: BlockDomain !! Value, + catcher: Catcher[BlockDomain !! Value], + finalizer: FinalizerDomain !! Unit, + outerSuccessHandler: Value => OuterDomain): OuterDomain + } + + object TryCatchFinally { + implicit final class Ops[Value, OuterDomain, BlockDomain, FinalizerDomain]( + outerSuccessHandler: Value => OuterDomain)( + implicit typeClass: TryCatchFinally[Value, OuterDomain, BlockDomain, FinalizerDomain] + ) { + def apply(block: BlockDomain !! Value, + catcher: Catcher[BlockDomain !! Value], + finalizer: FinalizerDomain !! Unit): OuterDomain = + typeClass.tryCatchFinally(block, catcher, finalizer, outerSuccessHandler) + } + + def cpsApply[Value, OuterDomain, BlockDomain, FinalizerDomain]( + ops: Ops[Value, OuterDomain, BlockDomain, FinalizerDomain]) = ops + + implicit def fromTryCatchTryFinally[Value, OuterDomain, BlockDomain, FinalizerDomain]( + implicit tryFinally: TryFinally[Value, OuterDomain, BlockDomain, FinalizerDomain], + tryCatch: TryCatch[Value, BlockDomain, BlockDomain] + ): TryCatchFinally[Value, OuterDomain, BlockDomain, FinalizerDomain] = + new TryCatchFinally[Value, OuterDomain, BlockDomain, FinalizerDomain] { + def tryCatchFinally(block: BlockDomain !! Value, + catcher: Catcher[BlockDomain !! Value], + finalizer: FinalizerDomain !! Unit, + outerSuccessHandler: Value => OuterDomain): OuterDomain = { + tryFinally.tryFinally({ + tryCatch.tryCatch(block, catcher, _) + }, finalizer, outerSuccessHandler) + } + } + } + + @implicitNotFound("The `try` ... `catch` expression cannot contain !-notation inside a function that returns ${OuterDomain}.") + trait TryCatch[Value, OuterDomain, BlockDomain] { + def tryCatch(block: BlockDomain !! Value, + catcher: Catcher[BlockDomain !! Value], + outerSuccessHandler: Value => OuterDomain): OuterDomain + } + + private[dsl] trait LowPriorityTryCatch { + implicit def liftFunction1TryCatch[Value, OuterDomain, BlockDomain, State]( + implicit restTryCatch: TryCatch[Value, OuterDomain, BlockDomain]) + : TryCatch[Value, State => OuterDomain, State => BlockDomain] = + new TryCatch[Value, State => OuterDomain, State => BlockDomain] { + def tryCatch(block: (State => BlockDomain) !! Value, + catcher: Catcher[(State => BlockDomain) !! Value], + outerSuccessHandler: Value => State => OuterDomain): State => OuterDomain = { state => + def withState(blockContinuation: (State => BlockDomain) !! Value) = { blockHandler: (Value => BlockDomain) => + blockContinuation { value: Value => (state: State) => + blockHandler(value) + }(state) + } + + restTryCatch.tryCatch(withState(block), catcher.andThen(withState _), outerSuccessHandler(_)(state)) + } + } + } + + object TryCatch extends LowPriorityTryCatch { + + implicit final class Ops[Value, OuterDomain, BlockDomain](outerSuccessHandler: Value => OuterDomain)( + implicit typeClass: TryCatch[Value, OuterDomain, BlockDomain] + ) { + def apply(block: BlockDomain !! Value, catcher: Catcher[BlockDomain !! Value]) = { + typeClass.tryCatch(block, catcher, outerSuccessHandler) + } + } + + def cpsApply[Value, OuterDomain, BlockDomain](ops: Ops[Value, OuterDomain, BlockDomain]) = ops + + implicit def futureTryCatch[BlockValue, OuterValue]( + implicit executionContext: ExecutionContext): TryCatch[BlockValue, Future[OuterValue], Future[BlockValue]] = + new TryCatch[BlockValue, Future[OuterValue], Future[BlockValue]] { + def tryCatch(block: Future[BlockValue] !! BlockValue, + catcher: Catcher[Future[BlockValue] !! BlockValue], + outerSuccessHandler: BlockValue => Future[OuterValue]): Future[OuterValue] = { + catchNativeException(block) + .recoverWith { + case e: Throwable => + def recover(): Future[BlockValue] = { + (try { + catcher.lift(e) + } catch { + case NonFatal(extractorException) => + return Future.failed(extractorException) + }) match { + case None => + Future.failed(e) + case Some(recovered) => + catchNativeException(recovered) + } + } + recover() + } + .flatMap(outerSuccessHandler) + } + } + + implicit def throwableContinuationTryCatch[LeftDomain, Value] + : TryCatch[Value, LeftDomain !! Throwable, LeftDomain !! Throwable] = { + new TryCatch[Value, LeftDomain !! Throwable, LeftDomain !! Throwable] { + def tryCatch(block: LeftDomain !! Throwable !! Value, + catcher: Catcher[LeftDomain !! Throwable !! Value], + outerSuccessHandler: Value => LeftDomain !! Throwable): LeftDomain !! Throwable = { + outerFailureHandler => + def innerFailureHandler(e: Throwable): LeftDomain = { + catcher.lift(e) match { + case None => + outerFailureHandler(e) + case Some(recovered) => + @inline + def recoveredHandler(): LeftDomain = { + locally { + try { + recovered(outerSuccessHandler) + } catch { + case NonFatal(nativeThrown) => + return outerFailureHandler(nativeThrown) + } + }(outerFailureHandler) + } + + recoveredHandler() + } + } + + def runBlock(): LeftDomain = { + (try { + block { a => hookedFailureHandler => + @inline + def successHandler(): LeftDomain = { + locally { + try { + outerSuccessHandler(a) + } catch { + case NonFatal(nativeThrown) => + return outerFailureHandler(nativeThrown) + } + }(outerFailureHandler) + } + + successHandler() + } + } catch { + case NonFatal(e) => + return innerFailureHandler(e) + })(innerFailureHandler) + } + runBlock() + } + } + + } + + } + + @implicitNotFound("The `try` ... `finally` expression cannot contain !-notation inside a function that returns ${OuterDomain}.") + trait TryFinally[Value, OuterDomain, BlockDomain, FinalizerDomain] { + def tryFinally(block: BlockDomain !! Value, + finalizer: FinalizerDomain !! Unit, + outerSuccessHandler: Value => OuterDomain): OuterDomain + } + + private[dsl] trait LowPriorityTryFinally { + implicit def liftFunction1TryCatch[Value, OuterDomain, BlockDomain, FinalizerDomain, State]( + implicit restTryFinally: TryFinally[Value, OuterDomain, BlockDomain, FinalizerDomain]) + : TryFinally[Value, State => OuterDomain, State => BlockDomain, State => FinalizerDomain] = + new TryFinally[Value, State => OuterDomain, State => BlockDomain, State => FinalizerDomain] { + def tryFinally(block: (State => BlockDomain) !! Value, + finalizer: (State => FinalizerDomain) !! Unit, + outerSuccessHandler: Value => State => OuterDomain): State => OuterDomain = { state => + def withState[Domain, Value](blockContinuation: (State => Domain) !! Value) = { blockHandler: (Value => Domain) => + blockContinuation { value: Value => (state: State) => + blockHandler(value) + }(state) + } + + restTryFinally.tryFinally(withState(block), withState(finalizer), outerSuccessHandler(_)(state)) + } + } + } + + object TryFinally extends LowPriorityTryFinally { + + implicit final class Ops[Value, OuterDomain, BlockDomain, FinalizerDomain] @inline()( + outerSuccessHandler: Value => OuterDomain)( + implicit typeClass: TryFinally[Value, OuterDomain, BlockDomain, FinalizerDomain]) { + @inline + def apply(block: BlockDomain !! Value, finalizer: FinalizerDomain !! Unit): OuterDomain = { + typeClass.tryFinally(block, finalizer, outerSuccessHandler) + } + } + + def cpsApply[Value, OuterDomain, BlockDomain, FinalizerDomain]( + ops: Ops[Value, OuterDomain, BlockDomain, FinalizerDomain]) = ops + + implicit def futureTryFinally[BlockValue, OuterValue](implicit executionContext: ExecutionContext) + : TryFinally[BlockValue, Future[OuterValue], Future[BlockValue], Future[Unit]] = + new TryFinally[BlockValue, Future[OuterValue], Future[BlockValue], Future[Unit]] { + def tryFinally(block: Future[BlockValue] !! BlockValue, + finalizer: Future[Unit] !! Unit, + outerSuccessHandler: BlockValue => Future[OuterValue]): Future[OuterValue] = { + @inline + def injectFinalizer[A](f: Unit => Future[A]): Future[A] = { + catchNativeException(finalizer).flatMap(f) + } + catchNativeException(block) + .recoverWith { + case e: Throwable => + injectFinalizer { _: Unit => + Future.failed(e) + } + } + .flatMap { a => + injectFinalizer { _: Unit => + outerSuccessHandler(a) + } + } + } + } + + implicit def throwableContinuationTryFinally[LeftDomain, Value] + : TryFinally[Value, LeftDomain !! Throwable, LeftDomain !! Throwable, LeftDomain !! Throwable] = { + new TryFinally[Value, LeftDomain !! Throwable, LeftDomain !! Throwable, LeftDomain !! Throwable] { + def tryFinally(block: LeftDomain !! Throwable !! Value, + finalizer: LeftDomain !! Throwable !! Unit, + outerSuccessHandler: Value => LeftDomain !! Throwable): LeftDomain !! Throwable = { + outerFailureHandler => + @inline + def injectFinalizer(finalizerHandler: Unit => LeftDomain !! Throwable): LeftDomain = { + locally { + try { + finalizer(finalizerHandler) + } catch { + case NonFatal(e) => + return outerFailureHandler(e) + } + }(outerFailureHandler) + } + + @inline + def hookedFailureHandler(e: Throwable) = + injectFinalizer { _: Unit => + _(e) + } + + def runBlock(): LeftDomain = { + (try { + block { value => hookedFailureHandler => + injectFinalizer { _: Unit => + outerSuccessHandler(value) + } + } + } catch { + case NonFatal(e) => + return hookedFailureHandler(e) + })(hookedFailureHandler) + } + runBlock() + } + } + } + } + } diff --git a/compilerplugins-BangNotation/src/main/scala/com/thoughtworks/dsl/compilerplugins/BangNotation.scala b/compilerplugins-BangNotation/src/main/scala/com/thoughtworks/dsl/compilerplugins/BangNotation.scala index 27012e4f5..bbd9e261e 100644 --- a/compilerplugins-BangNotation/src/main/scala/com/thoughtworks/dsl/compilerplugins/BangNotation.scala +++ b/compilerplugins-BangNotation/src/main/scala/com/thoughtworks/dsl/compilerplugins/BangNotation.scala @@ -1,14 +1,12 @@ package com.thoughtworks.dsl package compilerplugins -import com.thoughtworks.dsl.Dsl.{ResetAnnotation, nonTypeConstraintReset, shift} +import com.thoughtworks.dsl.Dsl.{ResetAnnotation, shift} import com.thoughtworks.dsl.compilerplugins.BangNotation.HasReturn -import scala.annotation.tailrec -import scala.tools.nsc.plugins.{Plugin, PluginComponent} -import scala.tools.nsc.transform.Transform +import scala.tools.nsc.plugins.Plugin import scala.tools.nsc.typechecker.ContextMode -import scala.tools.nsc.{Global, Mode, Phase} +import scala.tools.nsc.{Global, Mode} private object BangNotation { sealed trait HasReturn object HasReturn { @@ -157,17 +155,6 @@ final class BangNotation(override val global: Global) extends Plugin { tree.productIterator.exists(hasCpsAttachment) } - private lazy val catchIdent: Tree = { - try { - Ident(rootMirror.staticModule("_root_.com.thoughtworks.dsl.keywords.Catch")) - } catch { - case e: ScalaReflectionException => - abort("""The BangNotation compiler plug-in requires the runtime library `keywords-catch` to enable !-notation in `try` / `catch` / `finally` expressions: - libraryDependencies += "com.thoughtworks.dsl" %% "keywords-catch" % "latest.release" -""") - } - } - private val whileName = currentUnit.freshTermName("while") private val whileDef = { val domainName = currentUnit.freshTypeName("Domain") @@ -390,38 +377,56 @@ final class BangNotation(override val global: Global) extends Plugin { // This CaseDef tree contains some bang notations, and will be translated by enclosing Try or Match tree, not here EmptyTree case Try(block, catches, finalizer) => - val finalizerName = currentUnit.freshTermName("finalizer") val resultName = currentUnit.freshTermName("result") - - q""" - $catchIdent.tryCatch { ($resultName: $tpe) => ${{ - cpsAttachment(finalizer) { finalizerValue => - q""" - ..${notPure(finalizerValue)} - ${continue(q"$resultName")} - """ - } - }}}.apply( - { $finalizerName: ${TypeTree()} => ${cpsAttachment(block) { blockValue => - q"$finalizerName.apply($blockValue)" - }}}, - { - case ..${catches.map { caseDef => - atPos(caseDef.pos) { - treeCopy.CaseDef( - caseDef, - caseDef.pat, - caseDef.guard, - q"""{ $finalizerName: ${TypeTree()} => ${{ - cpsAttachment(caseDef.body) { bodyValue => - q"$finalizerName.apply($bodyValue)" - } - }}}""" - ) - } - }}} - ) - """ + val handlerName = currentUnit.freshTermName("handler") + + def transformBlock() = + q"""{ $handlerName: ${TypeTree()} => ${cpsAttachment(block) { blockValue => + q"$handlerName.apply($blockValue)" + }}}""" + + def transformCatches() = + q"""{case ..${catches.map { caseDef => + atPos(caseDef.pos) { + treeCopy.CaseDef( + caseDef, + caseDef.pat, + caseDef.guard, + q"""{ $handlerName: ${TypeTree()} => ${{ + cpsAttachment(caseDef.body) { bodyValue => + q"$handlerName.apply($bodyValue)" + } + }}}""" + ) + } + }}}""" + + def transformFinalizer() = + q"""{ $handlerName: ${TypeTree()} => + ${cpsAttachment(finalizer) { finalizerValue => + q"$handlerName($finalizerValue: ${typeOf[Unit]})" + }}}""" + + def endTry() = q"""{ ($resultName: $tpe) => ${continue(q"$resultName")}}""" + + finalizer match { + case EmptyTree => + q"""_root_.com.thoughtworks.dsl.Dsl.TryCatch.Ops(${endTry()}).apply( + ${transformBlock()}, + ${transformCatches()} + )""" + case _ if catches.isEmpty => + q"""_root_.com.thoughtworks.dsl.Dsl.TryFinally.Ops(${endTry()}).apply( + ${transformBlock()}, + ${transformFinalizer()} + )""" + case _ => + q"""_root_.com.thoughtworks.dsl.Dsl.TryCatchFinally.Ops(${endTry()}).apply( + ${transformBlock()}, + ${transformCatches()}, + ${transformFinalizer()} + )""" + } case Assign(lhs, rhs) => cpsAttachment(rhs) { rhsValue => continue(treeCopy.Assign(tree, lhs, rhsValue)) diff --git a/domains-scalaz/src/main/scala/com/thoughtworks/dsl/domains/scalaz.scala b/domains-scalaz/src/main/scala/com/thoughtworks/dsl/domains/scalaz.scala index 44945b637..092b5616b 100644 --- a/domains-scalaz/src/main/scala/com/thoughtworks/dsl/domains/scalaz.scala +++ b/domains-scalaz/src/main/scala/com/thoughtworks/dsl/domains/scalaz.scala @@ -9,6 +9,7 @@ import scala.language.implicitConversions import _root_.scalaz.{Applicative, Bind, Monad, MonadError, MonadTrans, Unapply} import com.thoughtworks.dsl.keywords.Catch.CatchDsl import com.thoughtworks.dsl.keywords.{Catch, Monadic, Return} +import com.thoughtworks.dsl.Dsl.{TryCatch, TryFinally} import scala.util.control.Exception.Catcher import scala.util.control.{ControlThrowable, NonFatal} @@ -128,7 +129,7 @@ object scalaz { protected type MonadThrowable[F[_]] = MonadError[F, Throwable] - implicit def scalazCatchDsl[F[_], A, B](implicit monadError: MonadThrowable[F]): CatchDsl[F[A], F[B], A] = + private[dsl] def scalazCatchDsl[F[_], A, B](implicit monadError: MonadThrowable[F]): CatchDsl[F[A], F[B], A] = new CatchDsl[F[A], F[B], A] { def tryCatch(block: F[A] !! A, catcher: Catcher[F[A] !! A], handler: A => F[B]): F[B] = { import _root_.scalaz.syntax.all._ @@ -143,6 +144,61 @@ object scalaz { } } + @inline private def catchNativeException[F[_], A](continuation: F[A] !! A)( + implicit monadThrowable: MonadThrowable[F]): F[A] = { + try { + continuation(monadThrowable.pure(_)) + } catch { + case NonFatal(e) => + monadThrowable.raiseError(e) + } + } + + implicit def scalazTryFinally[F[_], A, B]( + implicit monadError: MonadThrowable[F]): TryFinally[A, F[B], F[A], F[Unit]] = + new TryFinally[A, F[B], F[A], F[Unit]] { + def tryFinally(block: F[A] !! A, finalizer: F[Unit] !! Unit, outerSuccessHandler: A => F[B]): F[B] = { + @inline + def injectFinalizer[A](f: Unit => F[A]): F[A] = { + monadError.bind(catchNativeException(finalizer))(f) + } + monadError.bind(monadError.handleError(catchNativeException(block)) { e: Throwable => + injectFinalizer { _: Unit => + monadError.raiseError(e) + } + }) { a => + injectFinalizer { _: Unit => + outerSuccessHandler(a) + } + } + } + } + + implicit def scalazTryCatch[F[_], A, B](implicit monadError: MonadThrowable[F]): TryCatch[A, F[B], F[A]] = + new TryCatch[A, F[B], F[A]] { + def tryCatch(block: F[A] !! A, catcher: Catcher[F[A] !! A], outerSuccessHandler: A => F[B]): F[B] = { + import monadError.monadErrorSyntax._ + catchNativeException(block) + .handleError { e => + def recover(): F[A] = { + (try { + catcher.lift(e) + } catch { + case NonFatal(extractorException) => + return monadError.raiseError(extractorException) + }) match { + case None => + monadError.raiseError(e) + case Some(recovered) => + catchNativeException(recovered) + } + } + recover() + } + .flatMap(outerSuccessHandler) + } + } + implicit def scalazReturnDsl[F[_], A, B](implicit applicative: Applicative[F], restReturnDsl: Dsl[Return[A], B, Nothing]) = new Dsl[Return[A], F[B], Nothing] { diff --git a/domains-task/.jvm/src/test/scala/com/thoughtworks/dsl/domains/RaiiSpec.scala b/domains-task/.jvm/src/test/scala/com/thoughtworks/dsl/domains/RaiiSpec.scala index 5735af552..926b3d92e 100644 --- a/domains-task/.jvm/src/test/scala/com/thoughtworks/dsl/domains/RaiiSpec.scala +++ b/domains-task/.jvm/src/test/scala/com/thoughtworks/dsl/domains/RaiiSpec.scala @@ -2,7 +2,7 @@ package com.thoughtworks.dsl package domains import com.thoughtworks.dsl.Dsl.{!!, Continuation, reset} -import com.thoughtworks.dsl.keywords.{Catch, Shift, Yield} +import com.thoughtworks.dsl.keywords.{Shift, Yield} import org.scalatest.{FreeSpec, Matchers} import com.thoughtworks.dsl.domains.task._ @@ -30,7 +30,8 @@ final class RaiiSpec extends FreeSpec with Matchers { */ def successContinuation[LeftDomain](domain: LeftDomain): (LeftDomain !! Throwable) @reset = Continuation.empty(domain) - def failureContinuation[LeftDomain](throwable: Throwable): (LeftDomain !! Throwable) @reset = Continuation.now(throwable) + def failureContinuation[LeftDomain](throwable: Throwable): (LeftDomain !! Throwable) @reset = + Continuation.now(throwable) "Given a continuation that throws an exception" - { object MyException extends Exception diff --git a/keywords-Fork/src/main/scala/com/thoughtworks/dsl/keywords/Fork.scala b/keywords-Fork/src/main/scala/com/thoughtworks/dsl/keywords/Fork.scala index 31eefb5a7..8ee0adb55 100644 --- a/keywords-Fork/src/main/scala/com/thoughtworks/dsl/keywords/Fork.scala +++ b/keywords-Fork/src/main/scala/com/thoughtworks/dsl/keywords/Fork.scala @@ -6,12 +6,14 @@ import java.util.concurrent.atomic.AtomicInteger import com.thoughtworks.dsl.Dsl import com.thoughtworks.dsl.Dsl.{!!, Keyword} import com.thoughtworks.dsl.keywords.Catch.{CatchDsl, DslCatch} +import com.thoughtworks.dsl.Dsl.{TryCatch, TryCatchFinally, TryFinally} import com.thoughtworks.enableMembersIf import scala.collection._ import scala.collection.generic.CanBuildFrom import scala.collection.mutable.Builder import scala.language.implicitConversions +import scala.util.control.NonFatal final case class Fork[Element](elements: Traversable[Element]) extends AnyVal with Keyword[Fork[Element], Element] @@ -101,7 +103,7 @@ object Fork { isTraversableOnce: RightDomain => TraversableOnce[WidenElement], canBuildFrom: Factory[WidenElement, RightDomain], continueDsl: Dsl[Continue, LeftDomain, Nothing], - catchDsl: DslCatch[LeftDomain, LeftDomain, Unit] + tryCatchFinally: TryCatchFinally[Unit, LeftDomain, LeftDomain, LeftDomain] ): Dsl[Fork[NarrowElement], LeftDomain !! RightDomain, NarrowElement] = new Dsl[Fork[NarrowElement], LeftDomain !! RightDomain, NarrowElement] { def cpsApply(fork: Fork[NarrowElement], @@ -118,11 +120,7 @@ object Fork { builder ++= result } } catch { - case MultipleException(throwableSet) => - exceptionBuilder.synchronized[Unit] { - exceptionBuilder ++= throwableSet - } - case e: Throwable => + case NonFatal(e) => exceptionBuilder.synchronized[Unit] { exceptionBuilder += e } @@ -152,6 +150,71 @@ object Fork { } } + @deprecated("[[keywords.Catch]] will be removed in favor of [[Dsl.TryCatch]].", "Dsl.scala 1.4.0") + private[Fork] def forkContinuationDsl[NarrowElement, LeftDomain, WidenElement, RightDomain]( + implicit eachDsl: Dsl[ForEach[NarrowElement], LeftDomain, NarrowElement], + booleanEachDsl: Dsl[ForEach[Boolean], LeftDomain, Boolean], + isTraversableOnce: RightDomain => TraversableOnce[WidenElement], + canBuildFrom: Factory[WidenElement, RightDomain], + continueDsl: Dsl[Continue, LeftDomain, Nothing], + catchDsl: DslCatch[LeftDomain, LeftDomain, Unit] + ): Dsl[Fork[NarrowElement], LeftDomain !! RightDomain, NarrowElement] = + new Dsl[Fork[NarrowElement], LeftDomain !! RightDomain, NarrowElement] { + def cpsApply(fork: Fork[NarrowElement], + mapper: NarrowElement => LeftDomain !! RightDomain): LeftDomain !! RightDomain = _ { + val builder: mutable.Builder[WidenElement, RightDomain] = newBuilder[WidenElement, RightDomain] + val exceptionBuilder = Set.newBuilder[Throwable] + val counter = new AtomicInteger(1) + if (!ForEach(Seq(true, false))) { + val element = !ForEach(fork.elements) + counter.incrementAndGet() + def tryCatch(): LeftDomain !! Unit = { + Catch + .tryCatch(_) + .apply( + _ { + val result = !Shift(mapper(element)) + builder.synchronized[Unit] { + builder ++= result + } + if (counter.decrementAndGet() > 0) { + !Continue + } + }, { + case NonFatal(e) => + _ { + exceptionBuilder.synchronized[Unit] { + exceptionBuilder += e + } + if (counter.decrementAndGet() > 0) { + !Continue + } + } + } + ) + } + !Shift(tryCatch()) + } else { + if (counter.decrementAndGet() > 0) { + !Continue + } + } + + val exceptions = exceptionBuilder.result() + if (exceptions.isEmpty) { + builder.result() + } else { + val i = exceptions.iterator + val firstException = i.next() + if (i.hasNext) { + throw MultipleException(exceptions) + } else { + throw firstException + } + } + } + } + @deprecated("Use Dsl[Catch[...], ...] as implicit parameters instead of CatchDsl[...]", "Dsl.scala 1.2.0") private[Fork] def forkContinuationDsl[NarrowElement, LeftDomain, WidenElement, RightDomain]( implicit eachDsl: Dsl[ForEach[NarrowElement], LeftDomain, NarrowElement], diff --git a/keywords-Using/src/main/scala/com/thoughtworks/dsl/keywords/Using.scala b/keywords-Using/src/main/scala/com/thoughtworks/dsl/keywords/Using.scala index 584b839f1..8063f24b4 100644 --- a/keywords-Using/src/main/scala/com/thoughtworks/dsl/keywords/Using.scala +++ b/keywords-Using/src/main/scala/com/thoughtworks/dsl/keywords/Using.scala @@ -4,6 +4,7 @@ package keywords import com.thoughtworks.dsl.Dsl import com.thoughtworks.dsl.Dsl.{!!, Keyword} import com.thoughtworks.dsl.keywords.Catch.{CatchDsl, DslCatch} +import com.thoughtworks.dsl.Dsl.TryFinally import scala.concurrent.{ExecutionContext, Future} import scala.language.implicitConversions @@ -60,9 +61,31 @@ object Using { def apply[R <: AutoCloseable](r: => R)( implicit dummyImplicit: DummyImplicit = DummyImplicit.dummyImplicit): Using[R] = new Using(r _) - implicit def throwableContinuationUsingDsl[Domain, Value, R <: AutoCloseable]( + @deprecated("[[keywords.Catch]] will be removed in favor of [[Dsl.TryCatch]].", "Dsl.scala 1.4.0") + private[Using] def throwableContinuationUsingDsl[Domain, Value, R <: AutoCloseable]( implicit catchDsl: DslCatch[Domain, Domain, Value], shiftDsl: Dsl[Shift[Domain, Value], Domain, Value] + ) + : Dsl[Using[R], Domain !! Value, R] = { + (keyword: Using[R], handler: R => Domain !! Value) => (outerHandler: Value => Domain) => + val r = keyword.open() + Catch + .tryCatch { value: Value => + r.close() + outerHandler(value) + } + .apply(Shift(handler(r)).cpsApply(_), { + case NonFatal(e) => + r.close() + _ { + throw e + } + }) + } + + implicit def continuationUsingDsl[Domain, Value, R <: AutoCloseable]( + implicit tryFinally: TryFinally[Value, Domain, Domain, Domain], + shiftDsl: Dsl[Shift[Domain, Value], Domain, Value] ): Dsl[Using[R], Domain !! Value, R] = { (keyword: Using[R], handler: R => Domain !! Value) => _ { val r = keyword.open() @@ -74,7 +97,7 @@ object Using { } } - @deprecated("Use Dsl[Catch[...], ...] as implicit parameters instead of CatchDsl[...]", "Dsl.scala 1.2.0") + @deprecated("[[keywords.Catch]] will be removed in favor of [[Dsl.TryCatch]].", "Dsl.scala 1.2.0") private[Using] def throwableContinuationUsingDsl[Domain, Value, R <: AutoCloseable]( implicit catchDsl: CatchDsl[Domain, Domain, Value], shiftDsl: Dsl[Shift[Domain, Value], Domain, Value] diff --git a/package/src/main/scala/com/thoughtworks/dsl/package.scala b/package/src/main/scala/com/thoughtworks/dsl/package.scala index 1c6b8b804..a602cd442 100644 --- a/package/src/main/scala/com/thoughtworks/dsl/package.scala +++ b/package/src/main/scala/com/thoughtworks/dsl/package.scala @@ -332,6 +332,38 @@ package com.thoughtworks * stream should be(Stream("line1", "line2", "line3")) * isClosed should be(true) * }}} + * + * @example `try` / `catch` / `finally` expressions are also supported in functions that return `Stream[String] !! Throwable`, + * because of the [[scala.Throwable]] part in the signature. + * + * {{{ + * import com.thoughtworks.dsl.Dsl.!!, keywords._ + * var finallyBlockInvoked = 0 + * class MyException extends Exception + * def f: Stream[String] !! Throwable = { + * while (true) { + * try { + * !new Yield("yield value") + * if (true) { + * throw new MyException + * } + * } catch { + * case e: RuntimeException => + * throw new AssertionError("Should not catch an RuntimeException", e) + * } finally { + * finallyBlockInvoked += 1 + * } + * } + * throw new AssertionError("Unreachable code") + * } + * + * val result = f { e => + * e should be(a[MyException]) + * Stream.empty + * } + * result should be(Stream("yield value")) + * finallyBlockInvoked should be(1) + * }}} * @example If you don't need to collaborate to [[scala.Stream Stream]] or other domains, * you can use `TailRec[Unit] !! Throwable !! A` * or the alias [[domains.task.Task]] as the return type, @@ -397,7 +429,7 @@ package com.thoughtworks * {{{ * import com.thoughtworks.dsl.domains.task.Task.blockingAwait * - * val url = new URL("http://localhost:4001/ping") + * val url = new URL("http://localhost:8085/ping") * val fileContent = blockingAwait(httpClient(url)) * fileContent should startWith("HTTP/1.1 200 OK") * }}} @@ -413,8 +445,8 @@ package com.thoughtworks * import com.thoughtworks.dsl.keywords.Fork * import com.thoughtworks.dsl.keywords.Return * val Urls = Seq( - * new URL("http://localhost:4001/ping"), - * new URL("http://localhost:4001/pong") + * new URL("http://localhost:8085/ping"), + * new URL("http://localhost:8085/pong") * ) * def parallelTask: Task[Seq[String]] = { * val url = !Fork(Urls) diff --git a/package/src/test/scala/com/thoughtworks/dsl/MockPingPongServer.scala b/package/src/test/scala/com/thoughtworks/dsl/MockPingPongServer.scala index 4737b427a..21875f96e 100644 --- a/package/src/test/scala/com/thoughtworks/dsl/MockPingPongServer.scala +++ b/package/src/test/scala/com/thoughtworks/dsl/MockPingPongServer.scala @@ -27,7 +27,7 @@ trait MockPingPongServer extends BeforeAndAfterAll { this: Suite => complete("PONG!") } } - concurrent.Await.result(akka.http.scaladsl.Http().bindAndHandle(route, "localhost", 4001), Duration.Inf) + concurrent.Await.result(akka.http.scaladsl.Http().bindAndHandle(route, "localhost", 8085), Duration.Inf) } override protected def afterAll(): Unit = { From 7bee9d48ff047466e03007fc51f7b97ede37f3a9 Mon Sep 17 00:00:00 2001 From: Yang Bo Date: Thu, 20 Jun 2019 23:04:49 -0700 Subject: [PATCH 2/5] Deprecate keyword Catch --- .../src/main/scala/com/thoughtworks/dsl/keywords/Catch.scala | 2 ++ 1 file changed, 2 insertions(+) diff --git a/keywords-Catch/src/main/scala/com/thoughtworks/dsl/keywords/Catch.scala b/keywords-Catch/src/main/scala/com/thoughtworks/dsl/keywords/Catch.scala index dbaa93b31..0a0e95eaa 100644 --- a/keywords-Catch/src/main/scala/com/thoughtworks/dsl/keywords/Catch.scala +++ b/keywords-Catch/src/main/scala/com/thoughtworks/dsl/keywords/Catch.scala @@ -13,6 +13,7 @@ import scala.util.control.NonFatal /** * @author 杨博 (Yang Bo) */ +@deprecated("[[keywords.Catch]] will be removed in favor of [[Dsl.TryCatch]].", "Dsl.scala 1.4.0") final case class Catch[Domain, Value](block: Domain !! Value, catcher: Catcher[Domain !! Value]) extends Keyword[Catch[Domain, Value], Value] @@ -47,6 +48,7 @@ private[keywords] trait LowPriorityCatch0 extends LowPriorityCatch1 { this: Catc } } +@deprecated("[[keywords.Catch]] will be removed in favor of [[Dsl.TryCatch]].", "Dsl.scala 1.4.0") object Catch extends LowPriorityCatch0 { type DslCatch[InnerDomain, OuterDomain, Value] = Dsl[Catch[InnerDomain, Value], OuterDomain, Value] From 54989250f71306f83f0d98f94cd66dbb90c115c1 Mon Sep 17 00:00:00 2001 From: Yang Bo Date: Thu, 20 Jun 2019 23:05:39 -0700 Subject: [PATCH 3/5] Make keywords-Catch an optional dependency for Scala 2.12+ --- build.sbt | 47 ++++++++++++++++++++++++++++++++++++----------- 1 file changed, 36 insertions(+), 11 deletions(-) diff --git a/build.sbt b/build.sbt index 334100664..9de5b8d96 100644 --- a/build.sbt +++ b/build.sbt @@ -38,7 +38,7 @@ lazy val `keywords-Fork` = scalacOptions += raw"""-Xplugin:${(packageBin in `compilerplugins-BangNotation` in Compile).value}""", scalacOptions += raw"""-Xplugin:${(packageBin in `compilerplugins-ResetEverywhere` in Compile).value}""" ) - .dependsOn(Dsl, `keywords-Shift`, `keywords-Catch`, `keywords-Continue`, `keywords-ForEach`) + .dependsOn(Dsl, `keywords-Shift`, `keywords-Catch` % Optional, `keywords-Continue`, `keywords-ForEach`) lazy val `keywords-ForkJS` = `keywords-Fork`.js lazy val `keywords-ForkJVM` = `keywords-Fork`.jvm @@ -96,7 +96,11 @@ lazy val `keywords-AsynchronousIo` = scalacOptions += raw"""-Xplugin:${(packageBin in `compilerplugins-BangNotation` in Compile).value}""", scalacOptions += raw"""-Xplugin:${(packageBin in `compilerplugins-ResetEverywhere` in Compile).value}""" ) - .dependsOn(`keywords-Shift`, `keywords-Each` % Test, `keywords-Using` % Test, `comprehension` % Test, `domains-task` % Test) + .dependsOn(`keywords-Shift`, + `keywords-Each` % Test, + `keywords-Using` % Test, + `comprehension` % Test, + `domains-task` % Test) lazy val `keywords-AsynchronousIoJS` = `keywords-AsynchronousIo`.js lazy val `keywords-AsynchronousIoJVM` = `keywords-AsynchronousIo`.jvm @@ -111,27 +115,27 @@ lazy val `keywords-Shift` = lazy val `keywords-ShiftJS` = `keywords-Shift`.js lazy val `keywords-ShiftJVM` = `keywords-Shift`.jvm -lazy val `keywords-Using` = +lazy val `keywords-Catch` = crossProject(JSPlatform, JVMPlatform) .crossType(CrossType.Pure) .settings( scalacOptions += raw"""-Xplugin:${(packageBin in `compilerplugins-BangNotation` in Compile).value}""", scalacOptions += raw"""-Xplugin:${(packageBin in `compilerplugins-ResetEverywhere` in Compile).value}""" ) - .dependsOn(Dsl, `keywords-Shift`, `keywords-Catch`) -lazy val `keywords-UsingJS` = `keywords-Using`.js -lazy val `keywords-UsingJVM` = `keywords-Using`.jvm + .dependsOn(Dsl, `keywords-Shift`, `keywords-Yield` % Test) +lazy val `keywords-CatchJS` = `keywords-Catch`.js +lazy val `keywords-CatchJVM` = `keywords-Catch`.jvm -lazy val `keywords-Catch` = +lazy val `keywords-Using` = crossProject(JSPlatform, JVMPlatform) .crossType(CrossType.Pure) .settings( scalacOptions += raw"""-Xplugin:${(packageBin in `compilerplugins-BangNotation` in Compile).value}""", scalacOptions += raw"""-Xplugin:${(packageBin in `compilerplugins-ResetEverywhere` in Compile).value}""" ) - .dependsOn(Dsl, `keywords-Shift`, `keywords-Yield` % Test) -lazy val `keywords-CatchJS` = `keywords-Catch`.js -lazy val `keywords-CatchJVM` = `keywords-Catch`.jvm + .dependsOn(Dsl, `keywords-Shift`, `keywords-Catch` % Optional) +lazy val `keywords-UsingJS` = `keywords-Using`.js +lazy val `keywords-UsingJVM` = `keywords-Using`.jvm lazy val `keywords-Map` = crossProject(JSPlatform, JVMPlatform) @@ -253,7 +257,7 @@ lazy val `domains-scalaz` = scalacOptions += raw"""-Xplugin:${(packageBin in `compilerplugins-ResetEverywhere` in Compile).value}""" ) .dependsOn(Dsl, - `keywords-Catch`, + `keywords-Catch` % Optional, `keywords-Monadic`, `keywords-Return`, `keywords-Shift` % Test, @@ -310,6 +314,27 @@ lazy val `package` = project DslJVM ) +// Replace `keywords-Catch` % Optional to `keywords-Catch` for Scala 2.11 +for (catchProject <- `keywords-Catch`.projects.values.toSeq) yield { + Global / buildDependencies := { + val oldBuildDependencies = (Global / buildDependencies).value + val catchProjectRef = (catchProject / thisProjectRef).value + if (scalaBinaryVersion.value == "2.11") { + internal.BuildDependencies( + oldBuildDependencies.classpath.mapValues(_.map { + case ResolvedClasspathDependency(`catchProjectRef`, Some(Optional.name)) => + ResolvedClasspathDependency(catchProjectRef, None) + case dep => + dep + }), + oldBuildDependencies.aggregate + ) + } else { + oldBuildDependencies + } + } +} + organization in ThisBuild := "com.thoughtworks.dsl" scalacOptions in ThisBuild ++= { From f5b0334908612e3c612fa7ec722f81da77c005e5 Mon Sep 17 00:00:00 2001 From: Yang Bo Date: Thu, 20 Jun 2019 23:24:20 -0700 Subject: [PATCH 4/5] Remove unused imports --- .../main/scala/com/thoughtworks/dsl/domains/scalaz.scala | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/domains-scalaz/src/main/scala/com/thoughtworks/dsl/domains/scalaz.scala b/domains-scalaz/src/main/scala/com/thoughtworks/dsl/domains/scalaz.scala index 092b5616b..48c94c64e 100644 --- a/domains-scalaz/src/main/scala/com/thoughtworks/dsl/domains/scalaz.scala +++ b/domains-scalaz/src/main/scala/com/thoughtworks/dsl/domains/scalaz.scala @@ -2,17 +2,17 @@ package com.thoughtworks.dsl.domains import com.thoughtworks.Extractor._ import com.thoughtworks.dsl.Dsl -import com.thoughtworks.dsl.Dsl.{!!, Keyword} +import com.thoughtworks.dsl.Dsl.!! import scala.language.higherKinds import scala.language.implicitConversions -import _root_.scalaz.{Applicative, Bind, Monad, MonadError, MonadTrans, Unapply} +import _root_.scalaz.{Applicative, Bind, Monad, MonadError, MonadTrans} import com.thoughtworks.dsl.keywords.Catch.CatchDsl -import com.thoughtworks.dsl.keywords.{Catch, Monadic, Return} +import com.thoughtworks.dsl.keywords.{Monadic, Return} import com.thoughtworks.dsl.Dsl.{TryCatch, TryFinally} import scala.util.control.Exception.Catcher -import scala.util.control.{ControlThrowable, NonFatal} +import scala.util.control.NonFatal /** Contains interpreters to enable [[Dsl.Keyword#unary_$bang !-notation]] * for [[keywords.Monadic Monadic]] and other keywords From a132b698e9d7080278e893619f0beda1e83693a1 Mon Sep 17 00:00:00 2001 From: Yang Bo Date: Fri, 21 Jun 2019 02:00:50 -0700 Subject: [PATCH 5/5] Use lambda expression for SAM type classes --- .../main/scala/com/thoughtworks/dsl/Dsl.scala | 318 ++++++++---------- .../com/thoughtworks/dsl/domains/scalaz.scala | 10 +- keywords-Fork/build.sbt | 10 + .../com/thoughtworks/dsl/keywords/Fork.scala | 9 +- 4 files changed, 164 insertions(+), 183 deletions(-) diff --git a/Dsl/src/main/scala/com/thoughtworks/dsl/Dsl.scala b/Dsl/src/main/scala/com/thoughtworks/dsl/Dsl.scala index ebecceb14..8abd4f551 100644 --- a/Dsl/src/main/scala/com/thoughtworks/dsl/Dsl.scala +++ b/Dsl/src/main/scala/com/thoughtworks/dsl/Dsl.scala @@ -277,7 +277,8 @@ object Dsl extends LowPriorityDsl0 { * !-notation is allowed by default for `? !! Throwable` and [[scala.concurrent.Future Future]] domains, * with the help of this type class. */ - @implicitNotFound("The `try` ... `catch` ... `finally` expression cannot contain !-notation inside a function that returns ${OuterDomain}.") + @implicitNotFound( + "The `try` ... `catch` ... `finally` expression cannot contain !-notation inside a function that returns ${OuterDomain}.") trait TryCatchFinally[Value, OuterDomain, BlockDomain, FinalizerDomain] { def tryCatchFinally(block: BlockDomain !! Value, catcher: Catcher[BlockDomain !! Value], @@ -296,26 +297,22 @@ object Dsl extends LowPriorityDsl0 { typeClass.tryCatchFinally(block, catcher, finalizer, outerSuccessHandler) } - def cpsApply[Value, OuterDomain, BlockDomain, FinalizerDomain]( - ops: Ops[Value, OuterDomain, BlockDomain, FinalizerDomain]) = ops - implicit def fromTryCatchTryFinally[Value, OuterDomain, BlockDomain, FinalizerDomain]( implicit tryFinally: TryFinally[Value, OuterDomain, BlockDomain, FinalizerDomain], tryCatch: TryCatch[Value, BlockDomain, BlockDomain] - ): TryCatchFinally[Value, OuterDomain, BlockDomain, FinalizerDomain] = - new TryCatchFinally[Value, OuterDomain, BlockDomain, FinalizerDomain] { - def tryCatchFinally(block: BlockDomain !! Value, - catcher: Catcher[BlockDomain !! Value], - finalizer: FinalizerDomain !! Unit, - outerSuccessHandler: Value => OuterDomain): OuterDomain = { - tryFinally.tryFinally({ - tryCatch.tryCatch(block, catcher, _) - }, finalizer, outerSuccessHandler) - } - } + ): TryCatchFinally[Value, OuterDomain, BlockDomain, FinalizerDomain] = { + (block: BlockDomain !! Value, + catcher: Catcher[BlockDomain !! Value], + finalizer: FinalizerDomain !! Unit, + outerSuccessHandler: Value => OuterDomain) => + tryFinally.tryFinally({ + tryCatch.tryCatch(block, catcher, _) + }, finalizer, outerSuccessHandler) + } } - @implicitNotFound("The `try` ... `catch` expression cannot contain !-notation inside a function that returns ${OuterDomain}.") + @implicitNotFound( + "The `try` ... `catch` expression cannot contain !-notation inside a function that returns ${OuterDomain}.") trait TryCatch[Value, OuterDomain, BlockDomain] { def tryCatch(block: BlockDomain !! Value, catcher: Catcher[BlockDomain !! Value], @@ -325,20 +322,18 @@ object Dsl extends LowPriorityDsl0 { private[dsl] trait LowPriorityTryCatch { implicit def liftFunction1TryCatch[Value, OuterDomain, BlockDomain, State]( implicit restTryCatch: TryCatch[Value, OuterDomain, BlockDomain]) - : TryCatch[Value, State => OuterDomain, State => BlockDomain] = - new TryCatch[Value, State => OuterDomain, State => BlockDomain] { - def tryCatch(block: (State => BlockDomain) !! Value, - catcher: Catcher[(State => BlockDomain) !! Value], - outerSuccessHandler: Value => State => OuterDomain): State => OuterDomain = { state => - def withState(blockContinuation: (State => BlockDomain) !! Value) = { blockHandler: (Value => BlockDomain) => - blockContinuation { value: Value => (state: State) => - blockHandler(value) - }(state) - } - - restTryCatch.tryCatch(withState(block), catcher.andThen(withState _), outerSuccessHandler(_)(state)) + : TryCatch[Value, State => OuterDomain, State => BlockDomain] = { + (block: (State => BlockDomain) !! Value, + catcher: Catcher[(State => BlockDomain) !! Value], + outerSuccessHandler: Value => State => OuterDomain) => (state: State) => + def withState(blockContinuation: (State => BlockDomain) !! Value) = { blockHandler: (Value => BlockDomain) => + blockContinuation { value: Value => (state: State) => + blockHandler(value) + }(state) } - } + + restTryCatch.tryCatch(withState(block), catcher.andThen(withState _), outerSuccessHandler(_)(state)) + } } object TryCatch extends LowPriorityTryCatch { @@ -351,95 +346,87 @@ object Dsl extends LowPriorityDsl0 { } } - def cpsApply[Value, OuterDomain, BlockDomain](ops: Ops[Value, OuterDomain, BlockDomain]) = ops - implicit def futureTryCatch[BlockValue, OuterValue]( - implicit executionContext: ExecutionContext): TryCatch[BlockValue, Future[OuterValue], Future[BlockValue]] = - new TryCatch[BlockValue, Future[OuterValue], Future[BlockValue]] { - def tryCatch(block: Future[BlockValue] !! BlockValue, - catcher: Catcher[Future[BlockValue] !! BlockValue], - outerSuccessHandler: BlockValue => Future[OuterValue]): Future[OuterValue] = { - catchNativeException(block) - .recoverWith { - case e: Throwable => - def recover(): Future[BlockValue] = { - (try { - catcher.lift(e) - } catch { - case NonFatal(extractorException) => - return Future.failed(extractorException) - }) match { - case None => - Future.failed(e) - case Some(recovered) => - catchNativeException(recovered) - } + implicit executionContext: ExecutionContext): TryCatch[BlockValue, Future[OuterValue], Future[BlockValue]] = { + (block: Future[BlockValue] !! BlockValue, + catcher: Catcher[Future[BlockValue] !! BlockValue], + outerSuccessHandler: BlockValue => Future[OuterValue]) => + catchNativeException(block) + .recoverWith { + case e: Throwable => + def recover(): Future[BlockValue] = { + (try { + catcher.lift(e) + } catch { + case NonFatal(extractorException) => + return Future.failed(extractorException) + }) match { + case None => + Future.failed(e) + case Some(recovered) => + catchNativeException(recovered) } - recover() - } - .flatMap(outerSuccessHandler) - } - } + } + recover() + } + .flatMap(outerSuccessHandler) + } implicit def throwableContinuationTryCatch[LeftDomain, Value] : TryCatch[Value, LeftDomain !! Throwable, LeftDomain !! Throwable] = { - new TryCatch[Value, LeftDomain !! Throwable, LeftDomain !! Throwable] { - def tryCatch(block: LeftDomain !! Throwable !! Value, - catcher: Catcher[LeftDomain !! Throwable !! Value], - outerSuccessHandler: Value => LeftDomain !! Throwable): LeftDomain !! Throwable = { - outerFailureHandler => - def innerFailureHandler(e: Throwable): LeftDomain = { - catcher.lift(e) match { - case None => - outerFailureHandler(e) - case Some(recovered) => - @inline - def recoveredHandler(): LeftDomain = { - locally { - try { - recovered(outerSuccessHandler) - } catch { - case NonFatal(nativeThrown) => - return outerFailureHandler(nativeThrown) - } - }(outerFailureHandler) - } - recoveredHandler() + (block: LeftDomain !! Throwable !! Value, + catcher: Catcher[LeftDomain !! Throwable !! Value], + outerSuccessHandler: Value => LeftDomain !! Throwable) => outerFailureHandler => + def innerFailureHandler(e: Throwable): LeftDomain = { + catcher.lift(e) match { + case None => + outerFailureHandler(e) + case Some(recovered) => + @inline + def recoveredHandler(): LeftDomain = { + locally { + try { + recovered(outerSuccessHandler) + } catch { + case NonFatal(nativeThrown) => + return outerFailureHandler(nativeThrown) + } + }(outerFailureHandler) } - } - def runBlock(): LeftDomain = { - (try { - block { a => hookedFailureHandler => - @inline - def successHandler(): LeftDomain = { - locally { - try { - outerSuccessHandler(a) - } catch { - case NonFatal(nativeThrown) => - return outerFailureHandler(nativeThrown) - } - }(outerFailureHandler) + recoveredHandler() + } + } + + def runBlock(): LeftDomain = { + (try { + block { a => hookedFailureHandler => + @inline + def successHandler(): LeftDomain = { + locally { + try { + outerSuccessHandler(a) + } catch { + case NonFatal(nativeThrown) => + return outerFailureHandler(nativeThrown) } + }(outerFailureHandler) + } - successHandler() - } - } catch { - case NonFatal(e) => - return innerFailureHandler(e) - })(innerFailureHandler) + successHandler() } - runBlock() + } catch { + case NonFatal(e) => + return innerFailureHandler(e) + })(innerFailureHandler) } - } - + runBlock() } - } - @implicitNotFound("The `try` ... `finally` expression cannot contain !-notation inside a function that returns ${OuterDomain}.") + @implicitNotFound( + "The `try` ... `finally` expression cannot contain !-notation inside a function that returns ${OuterDomain}.") trait TryFinally[Value, OuterDomain, BlockDomain, FinalizerDomain] { def tryFinally(block: BlockDomain !! Value, finalizer: FinalizerDomain !! Unit, @@ -449,20 +436,18 @@ object Dsl extends LowPriorityDsl0 { private[dsl] trait LowPriorityTryFinally { implicit def liftFunction1TryCatch[Value, OuterDomain, BlockDomain, FinalizerDomain, State]( implicit restTryFinally: TryFinally[Value, OuterDomain, BlockDomain, FinalizerDomain]) - : TryFinally[Value, State => OuterDomain, State => BlockDomain, State => FinalizerDomain] = - new TryFinally[Value, State => OuterDomain, State => BlockDomain, State => FinalizerDomain] { - def tryFinally(block: (State => BlockDomain) !! Value, - finalizer: (State => FinalizerDomain) !! Unit, - outerSuccessHandler: Value => State => OuterDomain): State => OuterDomain = { state => - def withState[Domain, Value](blockContinuation: (State => Domain) !! Value) = { blockHandler: (Value => Domain) => - blockContinuation { value: Value => (state: State) => - blockHandler(value) - }(state) - } - - restTryFinally.tryFinally(withState(block), withState(finalizer), outerSuccessHandler(_)(state)) + : TryFinally[Value, State => OuterDomain, State => BlockDomain, State => FinalizerDomain] = { + (block: (State => BlockDomain) !! Value, + finalizer: (State => FinalizerDomain) !! Unit, + outerSuccessHandler: Value => State => OuterDomain) => state => + def withState[Domain, Value](blockContinuation: (State => Domain) !! Value) = { blockHandler: (Value => Domain) => + blockContinuation { value: Value => (state: State) => + blockHandler(value) + }(state) } - } + + restTryFinally.tryFinally(withState(block), withState(finalizer), outerSuccessHandler(_)(state)) + } } object TryFinally extends LowPriorityTryFinally { @@ -476,75 +461,64 @@ object Dsl extends LowPriorityDsl0 { } } - def cpsApply[Value, OuterDomain, BlockDomain, FinalizerDomain]( - ops: Ops[Value, OuterDomain, BlockDomain, FinalizerDomain]) = ops - implicit def futureTryFinally[BlockValue, OuterValue](implicit executionContext: ExecutionContext) - : TryFinally[BlockValue, Future[OuterValue], Future[BlockValue], Future[Unit]] = - new TryFinally[BlockValue, Future[OuterValue], Future[BlockValue], Future[Unit]] { - def tryFinally(block: Future[BlockValue] !! BlockValue, - finalizer: Future[Unit] !! Unit, - outerSuccessHandler: BlockValue => Future[OuterValue]): Future[OuterValue] = { - @inline - def injectFinalizer[A](f: Unit => Future[A]): Future[A] = { - catchNativeException(finalizer).flatMap(f) - } - catchNativeException(block) - .recoverWith { - case e: Throwable => - injectFinalizer { _: Unit => - Future.failed(e) - } - } - .flatMap { a => + : TryFinally[BlockValue, Future[OuterValue], Future[BlockValue], Future[Unit]] = { + (block: Future[BlockValue] !! BlockValue, + finalizer: Future[Unit] !! Unit, + outerSuccessHandler: BlockValue => Future[OuterValue]) => + @inline + def injectFinalizer[A](f: Unit => Future[A]): Future[A] = { + catchNativeException(finalizer).flatMap(f) + } + + catchNativeException(block) + .recoverWith { + case e: Throwable => injectFinalizer { _: Unit => - outerSuccessHandler(a) + Future.failed(e) } + } + .flatMap { a => + injectFinalizer { _: Unit => + outerSuccessHandler(a) } - } - } + } + } implicit def throwableContinuationTryFinally[LeftDomain, Value] : TryFinally[Value, LeftDomain !! Throwable, LeftDomain !! Throwable, LeftDomain !! Throwable] = { - new TryFinally[Value, LeftDomain !! Throwable, LeftDomain !! Throwable, LeftDomain !! Throwable] { - def tryFinally(block: LeftDomain !! Throwable !! Value, - finalizer: LeftDomain !! Throwable !! Unit, - outerSuccessHandler: Value => LeftDomain !! Throwable): LeftDomain !! Throwable = { - outerFailureHandler => - @inline - def injectFinalizer(finalizerHandler: Unit => LeftDomain !! Throwable): LeftDomain = { - locally { - try { - finalizer(finalizerHandler) - } catch { - case NonFatal(e) => - return outerFailureHandler(e) - } - }(outerFailureHandler) + (block, finalizer, outerSuccessHandler) => outerFailureHandler => + @inline + def injectFinalizer(finalizerHandler: Unit => LeftDomain !! Throwable): LeftDomain = { + locally { + try { + finalizer(finalizerHandler) + } catch { + case NonFatal(e) => + return outerFailureHandler(e) } + }(outerFailureHandler) + } + + @inline + def hookedFailureHandler(e: Throwable) = + injectFinalizer { _: Unit => + _(e) + } - @inline - def hookedFailureHandler(e: Throwable) = + def runBlock(): LeftDomain = { + (try { + block { value => hookedFailureHandler => injectFinalizer { _: Unit => - _(e) + outerSuccessHandler(value) } - - def runBlock(): LeftDomain = { - (try { - block { value => hookedFailureHandler => - injectFinalizer { _: Unit => - outerSuccessHandler(value) - } - } - } catch { - case NonFatal(e) => - return hookedFailureHandler(e) - })(hookedFailureHandler) } - runBlock() + } catch { + case NonFatal(e) => + return hookedFailureHandler(e) + })(hookedFailureHandler) } - } + runBlock() } } - } diff --git a/domains-scalaz/src/main/scala/com/thoughtworks/dsl/domains/scalaz.scala b/domains-scalaz/src/main/scala/com/thoughtworks/dsl/domains/scalaz.scala index 48c94c64e..94d415e89 100644 --- a/domains-scalaz/src/main/scala/com/thoughtworks/dsl/domains/scalaz.scala +++ b/domains-scalaz/src/main/scala/com/thoughtworks/dsl/domains/scalaz.scala @@ -200,12 +200,10 @@ object scalaz { } implicit def scalazReturnDsl[F[_], A, B](implicit applicative: Applicative[F], - restReturnDsl: Dsl[Return[A], B, Nothing]) = - new Dsl[Return[A], F[B], Nothing] { - def cpsApply(keyword: Return[A], handler: Nothing => F[B]): F[B] = { - applicative.pure(restReturnDsl.cpsApply(keyword, identity)) - } - } + restReturnDsl: Dsl[Return[A], B, Nothing]) = { + (keyword: Return[A], handler: Nothing => F[B]) => + applicative.pure(restReturnDsl.cpsApply(keyword, identity)) + } implicit def scalazMonadTransformerDsl1[F[_[_], _], H[_], G[_], A, B]( implicit monadTrans: MonadTrans[F], diff --git a/keywords-Fork/build.sbt b/keywords-Fork/build.sbt index 88f64dff1..4b467d8c3 100644 --- a/keywords-Fork/build.sbt +++ b/keywords-Fork/build.sbt @@ -17,3 +17,13 @@ libraryDependencies ++= { } libraryDependencies += "com.thoughtworks.enableIf" %% "enableif" % "1.1.7" + +scalacOptions ++= { + import Ordering.Implicits._ + if (VersionNumber(scalaVersion.value).numbers < Seq(2L, 12L)) { + // Enable SAM types for Scala 2.11 + Some("-Xexperimental") + } else { + None + } +} diff --git a/keywords-Fork/src/main/scala/com/thoughtworks/dsl/keywords/Fork.scala b/keywords-Fork/src/main/scala/com/thoughtworks/dsl/keywords/Fork.scala index 8ee0adb55..124d7a23b 100644 --- a/keywords-Fork/src/main/scala/com/thoughtworks/dsl/keywords/Fork.scala +++ b/keywords-Fork/src/main/scala/com/thoughtworks/dsl/keywords/Fork.scala @@ -104,10 +104,9 @@ object Fork { canBuildFrom: Factory[WidenElement, RightDomain], continueDsl: Dsl[Continue, LeftDomain, Nothing], tryCatchFinally: TryCatchFinally[Unit, LeftDomain, LeftDomain, LeftDomain] - ): Dsl[Fork[NarrowElement], LeftDomain !! RightDomain, NarrowElement] = - new Dsl[Fork[NarrowElement], LeftDomain !! RightDomain, NarrowElement] { - def cpsApply(fork: Fork[NarrowElement], - mapper: NarrowElement => LeftDomain !! RightDomain): LeftDomain !! RightDomain = _ { + ): Dsl[Fork[NarrowElement], LeftDomain !! RightDomain, NarrowElement] = { + (fork: Fork[NarrowElement], mapper: NarrowElement => LeftDomain !! RightDomain) => + _ { val builder: mutable.Builder[WidenElement, RightDomain] = newBuilder[WidenElement, RightDomain] val exceptionBuilder = Set.newBuilder[Throwable] val counter = new AtomicInteger(1) @@ -148,7 +147,7 @@ object Fork { } } } - } + } @deprecated("[[keywords.Catch]] will be removed in favor of [[Dsl.TryCatch]].", "Dsl.scala 1.4.0") private[Fork] def forkContinuationDsl[NarrowElement, LeftDomain, WidenElement, RightDomain](