Skip to content

Commit

Permalink
Merge pull request #272 from Atry/exceptionhandling
Browse files Browse the repository at this point in the history
Create standalone TryCatch and TryFinally type classes instead of the Catch keyword
  • Loading branch information
Atry authored Jun 21, 2019
2 parents 4e2e1d8 + a132b69 commit 25823e1
Show file tree
Hide file tree
Showing 11 changed files with 560 additions and 87 deletions.
261 changes: 260 additions & 1 deletion Dsl/src/main/scala/com/thoughtworks/dsl/Dsl.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -262,4 +263,262 @@ 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)
}

implicit def fromTryCatchTryFinally[Value, OuterDomain, BlockDomain, FinalizerDomain](
implicit tryFinally: TryFinally[Value, OuterDomain, BlockDomain, FinalizerDomain],
tryCatch: TryCatch[Value, BlockDomain, BlockDomain]
): 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}.")
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] = {
(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 {

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)
}
}

implicit def futureTryCatch[BlockValue, OuterValue](
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)
}

implicit def throwableContinuationTryCatch[LeftDomain, Value]
: TryCatch[Value, LeftDomain !! Throwable, LeftDomain !! Throwable] = {

(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)
}

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] = {
(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 {

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)
}
}

implicit def futureTryFinally[BlockValue, OuterValue](implicit executionContext: ExecutionContext)
: 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 =>
Future.failed(e)
}
}
.flatMap { a =>
injectFinalizer { _: Unit =>
outerSuccessHandler(a)
}
}
}

implicit def throwableContinuationTryFinally[LeftDomain, Value]
: TryFinally[Value, LeftDomain !! Throwable, LeftDomain !! Throwable, LeftDomain !! Throwable] = {
(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)
}

def runBlock(): LeftDomain = {
(try {
block { value => hookedFailureHandler =>
injectFinalizer { _: Unit =>
outerSuccessHandler(value)
}
}
} catch {
case NonFatal(e) =>
return hookedFailureHandler(e)
})(hookedFailureHandler)
}
runBlock()
}
}
}
47 changes: 36 additions & 11 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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

Expand All @@ -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)
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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 ++= {
Expand Down
Loading

0 comments on commit 25823e1

Please sign in to comment.