Skip to content

Commit

Permalink
Drop support for compilers without -Xasync
Browse files Browse the repository at this point in the history
  • Loading branch information
retronym committed Jun 23, 2020
1 parent b9adbac commit 8d7eb09
Show file tree
Hide file tree
Showing 52 changed files with 754 additions and 7,523 deletions.
6 changes: 3 additions & 3 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,16 @@ ScalaModulePlugin.scalaModuleSettingsJVM

name := "scala-async"

scalaVersion := "2.12.12-bin-1732cb0-SNAPSHOT"
resolvers in Global += "scala-integration" at "https://scala-ci.typesafe.com/artifactory/scala-integration/"
scalaVersion := "2.12.12-bin-7599ff2"
libraryDependencies += "org.scala-lang" % "scala-reflect" % scalaVersion.value % "provided"
libraryDependencies += "org.scala-lang" % "scala-compiler" % scalaVersion.value % "test" // for ToolBox
libraryDependencies += "junit" % "junit" % "4.12" % "test"
libraryDependencies += "com.novocode" % "junit-interface" % "0.11" % "test"

ScalaModulePlugin.enableOptimizer
testOptions += Tests.Argument(TestFrameworks.JUnit, "-q", "-v", "-s")
scalacOptions in Test ++= Seq("-Yrangepos")
scalacOptions in Test ++= (if (scalaVersion.value == "2.12.12-bin-1732cb0-SNAPSHOT") List("-Xasync") else Nil)
scalacOptions ++= List("-deprecation" , "-Xasync")

parallelExecution in Global := false

Expand Down
35 changes: 32 additions & 3 deletions src/main/scala/scala/async/Async.scala
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,9 @@
package scala.async

import scala.language.experimental.macros
import scala.concurrent.{Future, ExecutionContext}
import scala.concurrent.{ExecutionContext, Future}
import scala.annotation.compileTimeOnly
import scala.reflect.macros.whitebox

/**
* Async blocks provide a direct means to work with [[scala.concurrent.Future]].
Expand Down Expand Up @@ -50,14 +51,42 @@ object Async {
* Run the block of code `body` asynchronously. `body` may contain calls to `await` when the results of
* a `Future` are needed; this is translated into non-blocking code.
*/
def async[T](body: => T)(implicit execContext: ExecutionContext): Future[T] = macro internal.ScalaConcurrentAsync.asyncImpl[T]
def async[T](body: => T)(implicit execContext: ExecutionContext): Future[T] = macro asyncImpl[T]

/**
* Non-blocking await the on result of `awaitable`. This may only be used directly within an enclosing `async` block.
*
* Internally, this will register the remainder of the code in enclosing `async` block as a callback
* in the `onComplete` handler of `awaitable`, and will *not* block a thread.
*/
@compileTimeOnly("`await` must be enclosed in an `async` block")
@compileTimeOnly("[async] `await` must be enclosed in an `async` block")
def await[T](awaitable: Future[T]): T = ??? // No implementation here, as calls to this are translated to `onComplete` by the macro.

def asyncImpl[T: c.WeakTypeTag](c: whitebox.Context)
(body: c.Tree)
(execContext: c.Tree): c.Tree = {
import c.universe._
if (!c.compilerSettings.contains("-Xasync")) {
c.abort(c.macroApplication.pos, "The async requires the compiler option -Xasync (supported only by Scala 2.12.12+ / 2.13.3+)")
} else try {
val awaitSym = typeOf[Async.type].decl(TermName("await"))
def mark(t: DefDef): Tree = {
import language.reflectiveCalls
c.internal.asInstanceOf[{
def markForAsyncTransform(owner: Symbol, method: DefDef, awaitSymbol: Symbol, config: Map[String, AnyRef]): DefDef
}].markForAsyncTransform(c.internal.enclosingOwner, t, awaitSym, Map.empty)
}
val name = TypeName("stateMachine$async")
q"""
final class $name extends _root_.scala.async.FutureStateMachine(${execContext}) {
// FSM translated method
${mark(q"""override def apply(tr$$async: _root_.scala.util.Try[_root_.scala.AnyRef]) = ${body}""")}
}
new $name().start() : ${c.macroApplication.tpe}
"""
} catch {
case e: ReflectiveOperationException =>
c.abort(c.macroApplication.pos, "-Xasync is provided as a Scala compiler option, but the async macro is unable to call c.internal.markForAsyncTransform. " + e.getClass.getName + " " + e.getMessage)
}
}
}
80 changes: 80 additions & 0 deletions src/main/scala/scala/async/FutureStateMachine.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
/*
* Scala (https://www.scala-lang.org)
*
* Copyright EPFL and Lightbend, Inc.
*
* Licensed under Apache License 2.0
* (http://www.apache.org/licenses/LICENSE-2.0).
*
* See the NOTICE file distributed with this work for
* additional information regarding copyright ownership.
*/
package scala.async

import java.util.Objects

import scala.util.{Failure, Success, Try}
import scala.concurrent.{ExecutionContext, Future, Promise}

/** The base class for state machines generated by the `scala.async.Async.async` macro.
* Not intended to be directly extended in user-written code.
*/
abstract class FutureStateMachine(execContext: ExecutionContext) extends Function1[Try[AnyRef], Unit] {
Objects.requireNonNull(execContext)

type F = scala.concurrent.Future[AnyRef]
type R = scala.util.Try[AnyRef]

private[this] val result$async: Promise[AnyRef] = Promise[AnyRef]();
private[this] var state$async: Int = 0

/** Retrieve the current value of the state variable */
protected def state: Int = state$async

/** Assign `i` to the state variable */
protected def state_=(s: Int): Unit = state$async = s

/** Complete the state machine with the given failure. */
// scala-async accidentally started catching NonFatal exceptions in:
// https://github.com/scala/scala-async/commit/e3ff0382ae4e015fc69da8335450718951714982#diff-136ab0b6ecaee5d240cd109e2b17ccb2R411
// This follows the new behaviour but should we fix the regression?
protected def completeFailure(t: Throwable): Unit = {
result$async.complete(Failure(t))
}

/** Complete the state machine with the given value. */
protected def completeSuccess(value: AnyRef): Unit = {
result$async.complete(Success(value))
}

/** Register the state machine as a completion callback of the given future. */
protected def onComplete(f: F): Unit = {
f.onComplete(this)(execContext)
}

/** Extract the result of the given future if it is complete, or `null` if it is incomplete. */
protected def getCompleted(f: F): Try[AnyRef] = {
if (f.isCompleted) {
f.value.get
} else null
}

/**
* Extract the success value of the given future. If the state machine detects a failure it may
* complete the async block and return `this` as a sentinel value to indicate that the caller
* (the state machine dispatch loop) should immediately exit.
*/
protected def tryGet(tr: R): AnyRef = tr match {
case Success(value) =>
value.asInstanceOf[AnyRef]
case Failure(throwable) =>
completeFailure(throwable)
this // sentinel value to indicate the dispatch loop should exit.
}

def start[T](): Future[T] = {
// This cast is safe because we know that `def apply` does not consult its argument when `state == 0`.
Future.unit.asInstanceOf[Future[AnyRef]].onComplete(this)(execContext)
result$async.future.asInstanceOf[Future[T]]
}
}
Loading

0 comments on commit 8d7eb09

Please sign in to comment.