diff --git a/.travis.yml b/.travis.yml
index 2e0d04fc..79a052a4 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -5,8 +5,8 @@ import: scala/scala-dev:travis/default.yml
language: scala
scala:
- - 2.12.11
- - 2.13.2
+ - 2.12.12
+ - 2.13.3
env:
- ADOPTOPENJDK=8
diff --git a/README.md b/README.md
index d5f08434..afffc79c 100644
--- a/README.md
+++ b/README.md
@@ -1,14 +1,20 @@
# scala-async [![Build Status](https://travis-ci.org/scala/scala-async.svg?branch=master)](https://travis-ci.org/scala/scala-async) [](http://search.maven.org/#search%7Cga%7C1%7Cg%3Aorg.scala-lang.modules%20a%3Ascala-async_2.12) [](http://search.maven.org/#search%7Cga%7C1%7Cg%3Aorg.scala-lang.modules%20a%3Ascala-async_2.13)
-## Supported Scala versions
-
-This branch (version series 0.10.x) targets Scala 2.12 and 2.13. `scala-async` is no longer maintained for older versions.
+A DSL to enable a direct style of programming with when composing values wrapped in Scala `Future`s.
## Quick start
To include scala-async in an existing project use the library published on Maven Central.
For sbt projects add the following to your build definition - build.sbt or project/Build.scala:
+### Use a modern Scala compiler
+
+As of scala-async 1.0, Scala 2.12.12+ or 2.13.3+ are required.
+
+### Add dependency
+
+#### SBT Example
+
```scala
libraryDependencies += "org.scala-lang.modules" %% "scala-async" % "0.10.0"
libraryDependencies += "org.scala-lang" % "scala-reflect" % scalaVersion.value % Provided
@@ -17,28 +23,58 @@ libraryDependencies += "org.scala-lang" % "scala-reflect" % scalaVersion.value %
For Maven projects add the following to your (make sure to use the correct Scala version suffix
to match your project’s Scala binary version):
+#### Maven Example
+
```scala
- org.scala-lang.modules
- scala-async_2.12
- 0.10.0
+ org.scala-lang.modules
+ scala-async_2.13
+ 1.0.0
- org.scala-lang
- scala-reflect
- 2.12.11
- provided
+ org.scala-lang
+ scala-reflect
+ 2.13.3
+ provided
```
-After adding scala-async to your classpath, write your first `async` block:
+### Enable compiler support for `async`
+
+Add the `-Xasync` to the Scala compiler options.
+
+#### SBT Example
+```scala
+scalaOptions += "-Xasync"
+```
+
+#### Maven Example
+
+```xml
+
+ ...
+
+ net.alchim31.maven
+ scala-maven-plugin
+ 4.4.0
+
+
+ -Xasync
+
+
+
+ ...
+
+```
+
+### Start coding
```scala
import scala.concurrent.ExecutionContext.Implicits.global
import scala.async.Async.{async, await}
val future = async {
- val f1 = async { ...; true }
+ val f1: Future[Boolean] = async { ...; true }
val f2 = async { ...; 42 }
if (await(f1)) await(f2) else 0
}
@@ -93,6 +129,22 @@ def combined: Future[Int] = async {
}
```
+## Limitations
+
+### `await` must be directly in the control flow of the async expression
+
+The `await` cannot be nested under a local method, object, class or lambda:
+
+```
+async {
+ List(1).foreach { x => await(f(x) } // invali
+}
+```
+
+### `await` must be not be nested within `try` / `catch` / `finally`.
+
+This implementation restriction may be lifted in future versions.
+
## Comparison with direct use of `Future` API
This computation could also be expressed by directly using the
@@ -119,53 +171,3 @@ The `async` approach has two advantages over the use of
required at each generator (`<-`) in the for-comprehension.
This reduces the size of generated code, and can avoid boxing
of intermediate results.
-
-## Comparison with CPS plugin
-
-The existing continuations (CPS) plugin for Scala can also be used
-to provide a syntactic layer like `async`. This approach has been
-used in Akka's [Dataflow Concurrency](http://doc.akka.io/docs/akka/2.3-M1/scala/dataflow.html)
-(now deprecated in favour of this library).
-
-CPS-based rewriting of asynchronous code also produces a closure
-for each suspension. It can also lead to type errors that are
-difficult to understand.
-
-## How it works
-
- - The `async` macro analyses the block of code, looking for control
- structures and locations of `await` calls. It then breaks the code
- into 'chunks'. Each chunk contains a linear sequence of statements
- that concludes with a branching decision, or with the registration
- of a subsequent state handler as the continuation.
- - Before this analysis and transformation, the program is normalized
- into a form amenable to this manipulation. This is called the
- "A Normal Form" (ANF), and roughly means that:
- - `if` and `match` constructs are only used as statements;
- they cannot be used as an expression.
- - calls to `await` are not allowed in compound expressions.
- - Identify vals, vars and defs that are accessed from multiple
- states. These will be lifted out to fields in the state machine
- object.
- - Synthesize a class that holds:
- - an integer representing the current state ID.
- - the lifted definitions.
- - an `apply(value: Try[Any]): Unit` method that will be
- called on completion of each future. The behavior of
- this method is determined by the current state. It records
- the downcast result of the future in a field, and calls the
- `resume()` method.
- - the `resume(): Unit` method that switches on the current state
- and runs the users code for one 'chunk', and either:
- a) registers the state machine as the handler for the next future
- b) completes the result Promise of the `async` block, if at the terminal state.
- - an `apply(): Unit` method that starts the computation.
-
-## Limitations
-
- - See the [neg](https://github.com/scala/async/tree/master/src/test/scala/scala/async/neg) test cases
- for constructs that are not allowed in an `async` block.
- - See the [issue list](https://github.com/scala/async/issues?state=open) for which of these restrictions are planned
- to be dropped in the future.
- - See [#32](https://github.com/scala/async/issues/32) for why `await` is not possible in closures, and for suggestions on
- ways to structure the code to work around this limitation.
diff --git a/build.sbt b/build.sbt
index 026c76fe..2745bdd4 100644
--- a/build.sbt
+++ b/build.sbt
@@ -4,13 +4,13 @@ ScalaModulePlugin.scalaModuleOsgiSettings
name := "scala-async"
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 ++= List("-deprecation" , "-Xasync")
parallelExecution in Global := false
diff --git a/pending/run/fallback0/MinimalScalaTest.scala b/pending/run/fallback0/MinimalScalaTest.scala
deleted file mode 100644
index e69de29b..00000000
diff --git a/src/main/scala/scala/async/Async.scala b/src/main/scala/scala/async/Async.scala
index e99891be..b4399e15 100644
--- a/src/main/scala/scala/async/Async.scala
+++ b/src/main/scala/scala/async/Async.scala
@@ -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]].
@@ -50,7 +51,7 @@ 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.
@@ -58,6 +59,34 @@ object Async {
* 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)
+ }
+ }
}
diff --git a/src/main/scala/scala/async/FutureStateMachine.scala b/src/main/scala/scala/async/FutureStateMachine.scala
new file mode 100644
index 00000000..48d2692b
--- /dev/null
+++ b/src/main/scala/scala/async/FutureStateMachine.scala
@@ -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]]
+ }
+}
diff --git a/src/main/scala/scala/async/internal/AnfTransform.scala b/src/main/scala/scala/async/internal/AnfTransform.scala
deleted file mode 100644
index 86b347fb..00000000
--- a/src/main/scala/scala/async/internal/AnfTransform.scala
+++ /dev/null
@@ -1,424 +0,0 @@
-/*
- * 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.internal
-
-import scala.Predef._
-import scala.reflect.internal.util.Collections.map2
-
-private[async] trait AnfTransform {
- self: AsyncMacro =>
-
- import c.universe._
- import Flag._
- import c.internal._
- import decorators._
-
- def anfTransform(tree: Tree, owner: Symbol): Block = {
- // Must prepend the () for issue #31.
- val block = c.typecheck(atPos(tree.pos)(newBlock(List(Literal(Constant(()))), tree))).setType(tree.tpe)
-
- sealed abstract class AnfMode
- case object Anf extends AnfMode
- case object Linearizing extends AnfMode
-
- val tree1 = adjustTypeOfTranslatedPatternMatches(block, owner)
-
- var mode: AnfMode = Anf
-
- object trace {
- private var indent = -1
-
- private def indentString = " " * indent
-
- def apply[T](args: Any)(t: => T): T = {
- def prefix = mode.toString.toLowerCase
- indent += 1
- def oneLine(s: Any) = s.toString.replaceAll("""\n""", "\\\\n").take(127)
- try {
- if(AsyncUtils.trace)
- AsyncUtils.trace(s"$indentString$prefix(${oneLine(args)})")
- val result = t
- if(AsyncUtils.trace)
- AsyncUtils.trace(s"$indentString= ${oneLine(result)}")
- result
- } finally {
- indent -= 1
- }
- }
- }
-
- typingTransform(tree1, owner)((tree, api) => {
- def blockToList(tree: Tree): List[Tree] = tree match {
- case Block(stats, expr) => stats :+ expr
- case t => t :: Nil
- }
-
- def listToBlock(trees: List[Tree]): Block = trees match {
- case trees @ (init :+ last) =>
- val pos = trees.map(_.pos).reduceLeft{
- (p, q) =>
- if (!q.isRange) p
- else if (p.isRange) p.withStart(p.start.min(q.start)).withEnd(p.end.max(q.end))
- else q
- }
- newBlock(init, last).setType(last.tpe).setPos(pos)
- }
-
- object linearize {
- def transformToList(tree: Tree): List[Tree] = {
- mode = Linearizing; blockToList(api.recur(tree))
- }
-
- def transformToBlock(tree: Tree): Block = listToBlock(transformToList(tree))
-
- def _transformToList(tree: Tree): List[Tree] = trace(tree) {
- val stats :+ expr = _anf.transformToList(tree)
- def statsExprUnit =
- stats :+ expr :+ api.typecheck(atPos(expr.pos)(Literal(Constant(()))))
- def statsExprThrow =
- stats :+ expr :+ api.typecheck(atPos(expr.pos)(Throw(Apply(Select(New(gen.mkAttributedRef(defn.IllegalStateExceptionClass)), termNames.CONSTRUCTOR), Nil))))
- expr match {
- case Apply(fun, args) if isAwait(fun) =>
- val valDef = defineVal(name.await(), expr, tree.pos)
- val ref = gen.mkAttributedStableRef(valDef.symbol).setType(tree.tpe)
- val ref1 = if (ref.tpe =:= definitions.UnitTpe)
- // https://github.com/scala/async/issues/74
- // Use a cast to hide from "pure expression does nothing" error
- //
- // TODO avoid creating a ValDef for the result of this await to avoid this tree shape altogether.
- // This will require some deeper changes to the later parts of the macro which currently assume regular
- // tree structure around `await` calls.
- api.typecheck(atPos(tree.pos)(gen.mkCast(ref, definitions.UnitTpe)))
- else ref
- stats :+ valDef :+ atPos(tree.pos)(ref1)
-
- case If(cond, thenp, elsep) =>
- // If we run the ANF transform post patmat, deal with trees like `(if (cond) jump1(){String} else jump2(){String}){String}`
- // as though it was typed with `Unit`.
- def isPatMatGeneratedJump(t: Tree): Boolean = t match {
- case Block(_, expr) => isPatMatGeneratedJump(expr)
- case If(_, thenp, elsep) => isPatMatGeneratedJump(thenp) && isPatMatGeneratedJump(elsep)
- case _: Apply if isLabel(t.symbol) => true
- case _ => false
- }
- if (isPatMatGeneratedJump(expr)) {
- internal.setType(expr, definitions.UnitTpe)
- }
- // if type of if-else is Unit don't introduce assignment,
- // but add Unit value to bring it into form expected by async transform
- if (expr.tpe =:= definitions.UnitTpe) {
- statsExprUnit
- } else if (expr.tpe =:= definitions.NothingTpe) {
- statsExprThrow
- } else {
- val varDef = defineVar(name.ifRes(), expr.tpe, tree.pos)
- def typedAssign(lhs: Tree) =
- api.typecheck(atPos(lhs.pos)(Assign(Ident(varDef.symbol), mkAttributedCastPreservingAnnotations(lhs, tpe(varDef.symbol)))))
-
- def branchWithAssign(t: Tree): Tree = {
- t match {
- case MatchEnd(ld) =>
- deriveLabelDef(ld, branchWithAssign)
- case blk @ Block(thenStats, thenExpr) =>
- treeCopy.Block(blk, thenStats, branchWithAssign(thenExpr)).setType(definitions.UnitTpe)
- case _ =>
- typedAssign(t)
- }
- }
- val ifWithAssign = treeCopy.If(tree, cond, branchWithAssign(thenp), branchWithAssign(elsep)).setType(definitions.UnitTpe)
- stats :+ varDef :+ ifWithAssign :+ atPos(tree.pos)(gen.mkAttributedStableRef(varDef.symbol)).setType(tree.tpe)
- }
- case ld @ LabelDef(name, params, rhs) =>
- if (ld.symbol.info.resultType.typeSymbol == definitions.UnitClass)
- statsExprUnit
- else
- stats :+ expr
-
- case Match(scrut, cases) =>
- // if type of match is Unit don't introduce assignment,
- // but add Unit value to bring it into form expected by async transform
- if (expr.tpe =:= definitions.UnitTpe) {
- statsExprUnit
- } else if (expr.tpe =:= definitions.NothingTpe) {
- statsExprThrow
- } else {
- val varDef = defineVar(name.matchRes(), expr.tpe, tree.pos)
- def typedAssign(lhs: Tree) =
- api.typecheck(atPos(lhs.pos)(Assign(Ident(varDef.symbol), mkAttributedCastPreservingAnnotations(lhs, tpe(varDef.symbol)))))
- val casesWithAssign = cases map {
- case cd@CaseDef(pat, guard, body) =>
- def bodyWithAssign(t: Tree): Tree = {
- t match {
- case MatchEnd(ld) => deriveLabelDef(ld, bodyWithAssign)
- case b@Block(caseStats, caseExpr) => treeCopy.Block(b, caseStats, bodyWithAssign(caseExpr)).setType(definitions.UnitTpe)
- case _ => typedAssign(t)
- }
- }
- treeCopy.CaseDef(cd, pat, guard, bodyWithAssign(body)).setType(definitions.UnitTpe)
- }
- val matchWithAssign = treeCopy.Match(tree, scrut, casesWithAssign).setType(definitions.UnitTpe)
- require(matchWithAssign.tpe != null, matchWithAssign)
- stats :+ varDef :+ matchWithAssign :+ atPos(tree.pos)(gen.mkAttributedStableRef(varDef.symbol)).setType(tree.tpe)
- }
- case _ =>
- stats :+ expr
- }
- }
-
- def defineVar(name: TermName, tp: Type, pos: Position): ValDef = {
- val sym = api.currentOwner.newTermSymbol(name, pos, MUTABLE | SYNTHETIC).setInfo(uncheckedBounds(tp))
- valDef(sym, mkZero(uncheckedBounds(tp))).setType(NoType).setPos(pos)
- }
- }
-
- def defineVal(name: TermName, lhs: Tree, pos: Position): ValDef = {
- val sym = api.currentOwner.newTermSymbol(name, pos, SYNTHETIC).setInfo(uncheckedBounds(lhs.tpe))
- internal.valDef(sym, internal.changeOwner(lhs, api.currentOwner, sym)).setType(NoType).setPos(pos)
- }
-
- object _anf {
- def transformToList(tree: Tree): List[Tree] = {
- mode = Anf; blockToList(api.recur(tree))
- }
-
- def _transformToList(tree: Tree): List[Tree] = trace(tree) {
- if (!containsAwait(tree)) {
- tree match {
- case Block(stats, expr) =>
- // avoids nested block in `while(await(false)) ...`.
- // TODO I think `containsAwait` really should return true if the code contains a label jump to an enclosing
- // while/doWhile and there is an await *anywhere* inside that construct.
- stats :+ expr
- case _ => List(tree)
- }
- } else tree match {
- case Select(qual, sel) =>
- val stats :+ expr = linearize.transformToList(qual)
- stats :+ treeCopy.Select(tree, expr, sel)
-
- case Throw(expr) =>
- val stats :+ expr1 = linearize.transformToList(expr)
- stats :+ treeCopy.Throw(tree, expr1)
-
- case Typed(expr, tpt) =>
- val stats :+ expr1 = linearize.transformToList(expr)
- stats :+ treeCopy.Typed(tree, expr1, tpt)
-
- case Applied(fun, targs, argss) if argss.nonEmpty =>
- // we can assume that no await call appears in a by-name argument position,
- // this has already been checked.
- val funStats :+ simpleFun = linearize.transformToList(fun)
- val (argStatss, argExprss): (List[List[List[Tree]]], List[List[Tree]]) =
- mapArgumentss[List[Tree]](fun, argss) {
- case Arg(expr, byName, _) if byName /*|| isPure(expr) TODO */ => (Nil, expr)
- case Arg(expr, _, argName) =>
- linearize.transformToList(expr) match {
- case stats :+ expr1 =>
- val valDef = defineVal(name.freshen(argName), expr1, expr1.pos)
- require(valDef.tpe != null, valDef)
- val stats1 = stats :+ valDef
- (stats1, atPos(tree.pos.makeTransparent)(gen.stabilize(gen.mkAttributedIdent(valDef.symbol))))
- }
- }
-
- def copyApplied(tree: Tree, depth: Int): Tree = {
- tree match {
- case TypeApply(_, targs) => treeCopy.TypeApply(tree, simpleFun, targs)
- case _ if depth == 0 => simpleFun
- case Apply(fun, args) =>
- val newTypedArgs = map2(args.map(_.pos), argExprss(depth - 1))((pos, arg) => api.typecheck(atPos(pos)(arg)))
- treeCopy.Apply(tree, copyApplied(fun, depth - 1), newTypedArgs)
- }
- }
-
- val typedNewApply = copyApplied(tree, argss.length)
-
- funStats ++ argStatss.flatten.flatten :+ typedNewApply
-
- case Block(stats, expr) =>
- val stats1 = stats.flatMap(linearize.transformToList).filterNot(isLiteralUnit)
- val exprs1 = linearize.transformToList(expr)
- val trees = stats1 ::: exprs1
- def groupsEndingWith[T](ts: List[T])(f: T => Boolean): List[List[T]] = if (ts.isEmpty) Nil else {
- ts.indexWhere(f) match {
- case -1 => List(ts)
- case i =>
- val (ts1, ts2) = ts.splitAt(i + 1)
- ts1 :: groupsEndingWith(ts2)(f)
- }
- }
- val matchGroups = groupsEndingWith(trees){ case MatchEnd(_) => true; case _ => false }
- val trees1 = matchGroups.flatMap(eliminateMatchEndLabelParameter)
- val result = trees1 flatMap {
- case Block(stats, expr) => stats :+ expr
- case t => t :: Nil
- }
- result
-
- case ValDef(mods, name, tpt, rhs) =>
- if (containsAwait(rhs)) {
- val stats :+ expr = linearize.transformToList(rhs)
- stats.foreach(_.changeOwner(api.currentOwner, api.currentOwner.owner))
- stats :+ treeCopy.ValDef(tree, mods, name, tpt, expr)
- } else List(tree)
-
- case Assign(lhs, rhs) =>
- val stats :+ expr = linearize.transformToList(rhs)
- stats :+ treeCopy.Assign(tree, lhs, expr)
-
- case If(cond, thenp, elsep) =>
- val condStats :+ condExpr = linearize.transformToList(cond)
- val thenBlock = linearize.transformToBlock(thenp)
- val elseBlock = linearize.transformToBlock(elsep)
- condStats :+ treeCopy.If(tree, condExpr, thenBlock, elseBlock)
-
- case Match(scrut, cases) =>
- val scrutStats :+ scrutExpr = linearize.transformToList(scrut)
- val caseDefs = cases map {
- case CaseDef(pat, guard, body) =>
- // extract local variables for all names bound in `pat`, and rewrite `body`
- // to refer to these.
- // TODO we can move this into ExprBuilder once we get rid of `AsyncDefinitionUseAnalyzer`.
- val block = linearize.transformToBlock(body)
- val (valDefs, mappings) = (pat collect {
- case b@Bind(bindName, _) =>
- val vd = defineVal(name.freshen(bindName.toTermName), gen.mkAttributedStableRef(b.symbol).setPos(b.pos), b.pos)
- vd.symbol.updateAttachment(SyntheticBindVal)
- (vd, (b.symbol, vd.symbol))
- }).unzip
- val (from, to) = mappings.unzip
- val b@Block(stats1, expr1) = block.substituteSymbols(from, to).asInstanceOf[Block]
- val newBlock = treeCopy.Block(b, valDefs ++ stats1, expr1)
- treeCopy.CaseDef(tree, pat, guard, newBlock)
- }
- scrutStats :+ treeCopy.Match(tree, scrutExpr, caseDefs)
-
- case LabelDef(name, params, rhs) =>
- if (tree.symbol.info.typeSymbol == definitions.UnitClass)
- List(treeCopy.LabelDef(tree, name, params, api.typecheck(newBlock(linearize.transformToList(rhs), Literal(Constant(()))))).setSymbol(tree.symbol))
- else
- List(treeCopy.LabelDef(tree, name, params, api.typecheck(listToBlock(linearize.transformToList(rhs)))).setSymbol(tree.symbol))
-
- case TypeApply(fun, targs) =>
- val funStats :+ simpleFun = linearize.transformToList(fun)
- funStats :+ treeCopy.TypeApply(tree, simpleFun, targs)
-
- case _ =>
- List(tree)
- }
- }
- }
-
- // Replace the label parameters on `matchEnd` with use of a `matchRes` temporary variable
- //
- // CaseDefs are translated to labels without parameters. A terminal label, `matchEnd`, accepts
- // a parameter which is the result of the match (this is regular, so even Unit-typed matches have this).
- //
- // For our purposes, it is easier to:
- // - extract a `matchRes` variable
- // - rewrite the terminal label def to take no parameters, and instead read this temp variable
- // - change jumps to the terminal label to an assignment and a no-arg label application
- def eliminateMatchEndLabelParameter(statsExpr: List[Tree]): List[Tree] = {
- import internal.{methodType, setInfo}
- val caseDefToMatchResult = collection.mutable.Map[Symbol, Symbol]()
-
- val matchResults = collection.mutable.Buffer[Tree]()
- def modifyLabelDef(ld: LabelDef): (Tree, Tree) = {
- val param = ld.params.head
- val ld2 = if (ld.params.head.tpe.typeSymbol == definitions.UnitClass) {
- // Unit typed match: eliminate the label def parameter, but don't create a matchres temp variable to
- // store the result for cleaner generated code.
- caseDefToMatchResult(ld.symbol) = NoSymbol
- val rhs2 = substituteTrees(ld.rhs, param.symbol :: Nil, api.typecheck(literalUnit) :: Nil)
- (treeCopy.LabelDef(ld, ld.name, Nil, api.typecheck(literalUnit)), rhs2)
- } else {
- // Otherwise, create the matchres var. We'll callers of the label def below.
- // Remember: we're iterating through the statement sequence in reverse, so we'll get
- // to the LabelDef and mutate `matchResults` before we'll get to its callers.
- val matchResult = linearize.defineVar(name.matchRes(), param.tpe, ld.pos)
- matchResults += matchResult
- caseDefToMatchResult(ld.symbol) = matchResult.symbol
- val rhs2 = ld.rhs.substituteSymbols(param.symbol :: Nil, matchResult.symbol :: Nil)
- (treeCopy.LabelDef(ld, ld.name, Nil, api.typecheck(literalUnit)), rhs2)
- }
- setInfo(ld.symbol, methodType(Nil, definitions.UnitTpe))
- ld2
- }
- val statsExpr0 = statsExpr.reverse.flatMap {
- case ld @ LabelDef(_, param :: Nil, _) =>
- val (ld1, after) = modifyLabelDef(ld)
- List(after, ld1)
- case a @ ValDef(mods, name, tpt, ld @ LabelDef(_, param :: Nil, _)) =>
- val (ld1, after) = modifyLabelDef(ld)
- List(treeCopy.ValDef(a, mods, name, tpt, after), ld1)
- case t =>
- if (caseDefToMatchResult.isEmpty) t :: Nil
- else typingTransform(t)((tree, api) => {
- def typedPos(pos: Position)(t: Tree): Tree =
- api.typecheck(atPos(pos)(t))
- tree match {
- case Apply(fun, arg :: Nil) if isLabel(fun.symbol) && caseDefToMatchResult.contains(fun.symbol) =>
- val temp = caseDefToMatchResult(fun.symbol)
- if (temp == NoSymbol)
- typedPos(tree.pos)(newBlock(api.recur(arg) :: Nil, treeCopy.Apply(tree, fun, Nil)))
- else
- // setType needed for LateExpansion.shadowingRefinedType test case. There seems to be an inconsistency
- // in the trees after pattern matcher.
- // TODO miminize the problem in patmat and fix in scalac.
- typedPos(tree.pos)(newBlock(Assign(Ident(temp), api.recur(internal.setType(arg, fun.tpe.paramLists.head.head.info))) :: Nil, treeCopy.Apply(tree, fun, Nil)))
- case Block(stats, expr: Apply) if isLabel(expr.symbol) =>
- api.default(tree) match {
- case Block(stats0, Block(stats1, expr1)) =>
- // flatten the block returned by `case Apply` above into the enclosing block for
- // cleaner generated code.
- treeCopy.Block(tree, stats0 ::: stats1, expr1)
- case t => t
- }
- case _ =>
- api.default(tree)
- }
- }) :: Nil
- }
- matchResults.toList match {
- case _ if caseDefToMatchResult.isEmpty =>
- statsExpr // return the original trees if nothing changed
- case Nil =>
- statsExpr0.reverse :+ literalUnit // must have been a unit-typed match, no matchRes variable to definne or refer to
- case r1 :: Nil =>
- // { var matchRes = _; ....; matchRes }
- (r1 +: statsExpr0.reverse) :+ atPos(tree.pos)(gen.mkAttributedIdent(r1.symbol))
- case _ => c.error(macroPos, "Internal error: unexpected tree encountered during ANF transform " + statsExpr); statsExpr
- }
- }
-
- def anfLinearize(tree: Tree): Block = {
- val trees: List[Tree] = mode match {
- case Anf => _anf._transformToList(tree)
- case Linearizing => linearize._transformToList(tree)
- }
- listToBlock(trees)
- }
-
- tree match {
- case _: ValDef | _: DefDef | _: Function | _: ClassDef | _: TypeDef =>
- api.atOwner(tree.symbol)(anfLinearize(tree))
- case _: ModuleDef =>
- api.atOwner(tree.symbol.asModule.moduleClass orElse tree.symbol)(anfLinearize(tree))
- case _ =>
- anfLinearize(tree)
- }
- }).asInstanceOf[Block]
- }
-}
-
-object SyntheticBindVal
diff --git a/src/main/scala/scala/async/internal/AsyncAnalysis.scala b/src/main/scala/scala/async/internal/AsyncAnalysis.scala
deleted file mode 100644
index cb5a09fa..00000000
--- a/src/main/scala/scala/async/internal/AsyncAnalysis.scala
+++ /dev/null
@@ -1,110 +0,0 @@
-/*
- * 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.internal
-
-import scala.collection.mutable.ListBuffer
-
-trait AsyncAnalysis {
- self: AsyncMacro =>
-
- import c.universe._
-
- /**
- * Analyze the contents of an `async` block in order to:
- * - Report unsupported `await` calls under nested templates, functions, by-name arguments.
- *
- * Must be called on the original tree, not on the ANF transformed tree.
- */
- def reportUnsupportedAwaits(tree: Tree): Unit = {
- val analyzer = new UnsupportedAwaitAnalyzer
- analyzer.traverse(tree)
- // analyzer.hasUnsupportedAwaits // XB: not used?!
- }
-
- private class UnsupportedAwaitAnalyzer extends AsyncTraverser {
- var hasUnsupportedAwaits = false
-
- override def nestedClass(classDef: ClassDef): Unit = {
- val kind = if (classDef.symbol.asClass.isTrait) "trait" else "class"
- reportUnsupportedAwait(classDef, s"nested $kind")
- }
-
- override def nestedModule(module: ModuleDef): Unit = {
- reportUnsupportedAwait(module, "nested object")
- }
-
- override def nestedMethod(defDef: DefDef): Unit = {
- reportUnsupportedAwait(defDef, "nested method")
- }
-
- override def byNameArgument(arg: Tree): Unit = {
- reportUnsupportedAwait(arg, "by-name argument")
- }
-
- override def function(function: Function): Unit = {
- reportUnsupportedAwait(function, "nested function")
- }
-
- override def patMatFunction(tree: Match): Unit = {
- reportUnsupportedAwait(tree, "nested function")
- }
-
- override def traverse(tree: Tree): Unit = {
- tree match {
- case Try(_, _, _) if containsAwait(tree) =>
- reportUnsupportedAwait(tree, "try/catch")
- super.traverse(tree)
- case Return(_) =>
- c.abort(tree.pos, "return is illegal within a async block")
- case DefDef(mods, _, _, _, _, _) if mods.hasFlag(Flag.LAZY) && containsAwait(tree) =>
- reportUnsupportedAwait(tree, "lazy val initializer")
- case ValDef(mods, _, _, _) if mods.hasFlag(Flag.LAZY) && containsAwait(tree) =>
- reportUnsupportedAwait(tree, "lazy val initializer")
- case CaseDef(_, guard, _) if guard exists isAwait =>
- // TODO lift this restriction
- reportUnsupportedAwait(tree, "pattern guard")
- case _ =>
- super.traverse(tree)
- }
- }
-
- /**
- * @return true, if the tree contained an unsupported await.
- */
- private def reportUnsupportedAwait(tree: Tree, whyUnsupported: String): Boolean = {
- val badAwaits = ListBuffer[Tree]()
- object traverser extends Traverser {
- override def traverse(tree: Tree): Unit = {
- if (!isAsync(tree))
- super.traverse(tree)
- tree match {
- case rt: RefTree if isAwait(rt) =>
- badAwaits += rt
- case _ =>
- }
- }
- }
- traverser(tree)
- badAwaits foreach {
- tree =>
- reportError(tree.pos, s"await must not be used under a $whyUnsupported.")
- }
- badAwaits.nonEmpty
- }
-
- private def reportError(pos: Position, msg: String): Unit = {
- hasUnsupportedAwaits = true
- c.abort(pos, msg)
- }
- }
-}
diff --git a/src/main/scala/scala/async/internal/AsyncBase.scala b/src/main/scala/scala/async/internal/AsyncBase.scala
deleted file mode 100644
index b7de62b5..00000000
--- a/src/main/scala/scala/async/internal/AsyncBase.scala
+++ /dev/null
@@ -1,78 +0,0 @@
-/*
- * 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.internal
-
-import scala.annotation.compileTimeOnly
-import scala.reflect.macros.whitebox
-import scala.reflect.api.Universe
-
-/**
- * A base class for the `async` macro. Subclasses must provide:
- *
- * - Concrete types for a given future system
- * - Tree manipulations to create and complete the equivalent of Future and Promise
- * in that system.
- * - The `async` macro declaration itself, and a forwarder for the macro implementation.
- * (The latter is temporarily needed to workaround bug SI-6650 in the macro system)
- *
- * The default implementation, [[scala.async.Async]], binds the macro to `scala.concurrent._`.
- */
-abstract class AsyncBase {
- self =>
-
- type FS <: FutureSystem
- val futureSystem: FS
-
- /**
- * A call to `await` must be nested in an enclosing `async` block.
- *
- * A call to `await` does not block the current thread, rather it is a delimiter
- * used by the enclosing `async` macro. Code following the `await`
- * call is executed asynchronously, when the argument of `await` has been completed.
- *
- * @param awaitable the future from which a value is awaited.
- * @tparam T the type of that value.
- * @return the value.
- */
- @compileTimeOnly("`await` must be enclosed in an `async` block")
- def await[T](awaitable: futureSystem.Fut[T]): T = ???
-
- def asyncImpl[T: c.WeakTypeTag](c: whitebox.Context)
- (body: c.Expr[T])
- (execContext: c.Expr[futureSystem.ExecContext]): c.Expr[futureSystem.Fut[T]] = {
- import c.internal._, decorators._
- val asyncMacro = AsyncMacro(c, self)(body.tree)
-
- val code = asyncMacro.asyncTransform[T](execContext.tree)(c.weakTypeTag[T])
- AsyncUtils.vprintln(s"async state machine transform expands to:\n $code")
-
- // Mark range positions for synthetic code as transparent to allow some wiggle room for overlapping ranges
- for (t <- code) t.setPos(t.pos.makeTransparent)
- c.Expr[futureSystem.Fut[T]](code)
- }
-
- protected[async] def asyncMethod(u: Universe)(asyncMacroSymbol: u.Symbol): u.Symbol = {
- import u._
- if (asyncMacroSymbol == null) NoSymbol
- else asyncMacroSymbol.owner.typeSignature.member(TermName("async"))
- }
-
- protected[async] def awaitMethod(u: Universe)(asyncMacroSymbol: u.Symbol): u.Symbol = {
- import u._
- if (asyncMacroSymbol == null) NoSymbol
- else asyncMacroSymbol.owner.typeSignature.member(TermName("await"))
- }
-
- protected[async] def nullOut(u: Universe)(name: u.Expr[String], v: u.Expr[Any]): u.Expr[Unit] =
- u.reify { () }
-}
diff --git a/src/main/scala/scala/async/internal/AsyncId.scala b/src/main/scala/scala/async/internal/AsyncId.scala
deleted file mode 100644
index aee3360f..00000000
--- a/src/main/scala/scala/async/internal/AsyncId.scala
+++ /dev/null
@@ -1,107 +0,0 @@
-/*
- * 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.internal
-
-import language.experimental.macros
-import scala.reflect.macros.whitebox
-import scala.reflect.api.Universe
-
-object AsyncId extends AsyncBase {
- lazy val futureSystem = IdentityFutureSystem
- type FS = IdentityFutureSystem.type
-
- def async[T](body: => T): T = macro asyncIdImpl[T]
-
- def asyncIdImpl[T: c.WeakTypeTag](c: whitebox.Context)(body: c.Expr[T]): c.Expr[T] = asyncImpl[T](c)(body)(c.literalUnit)
-}
-
-object AsyncTestLV extends AsyncBase {
- lazy val futureSystem = IdentityFutureSystem
- type FS = IdentityFutureSystem.type
-
- def async[T](body: T): T = macro asyncIdImpl[T]
-
- def asyncIdImpl[T: c.WeakTypeTag](c: whitebox.Context)(body: c.Expr[T]): c.Expr[T] = asyncImpl[T](c)(body)(c.literalUnit)
-
- var log: List[(String, Any)] = Nil
- def assertNulledOut(a: Any): Unit = assert(log.exists(_._2 == a), AsyncTestLV.log)
- def assertNotNulledOut(a: Any): Unit = assert(!log.exists(_._2 == a), AsyncTestLV.log)
- def clear(): Unit = log = Nil
-
- def apply(name: String, v: Any): Unit =
- log ::= (name -> v)
-
- protected[async] override def nullOut(u: Universe)(name: u.Expr[String], v: u.Expr[Any]): u.Expr[Unit] =
- u.reify { scala.async.internal.AsyncTestLV(name.splice, v.splice) }
-}
-
-/**
- * A trivial implementation of [[FutureSystem]] that performs computations
- * on the current thread. Useful for testing.
- */
-class Box[A] {
- var a: A = _
-}
-object IdentityFutureSystem extends FutureSystem {
- type Prom[A] = Box[A]
-
- type Fut[A] = A
- type ExecContext = Unit
- type Tryy[A] = scala.util.Try[A]
-
- def mkOps(c0: whitebox.Context): Ops {val c: c0.type} = new Ops {
- val c: c0.type = c0
- import c.universe._
-
- def execContext: Expr[ExecContext] = c.Expr[Unit](Literal(Constant(())))
-
- def promType[A: WeakTypeTag]: Type = weakTypeOf[Box[A]]
- def tryType[A: WeakTypeTag]: Type = weakTypeOf[scala.util.Try[A]]
- def execContextType: Type = weakTypeOf[Unit]
-
- def createProm[A: WeakTypeTag]: Expr[Prom[A]] = reify {
- new Prom[A]()
- }
-
- def promiseToFuture[A: WeakTypeTag](prom: Expr[Prom[A]]) = reify {
- prom.splice.a
- }
-
- def future[A: WeakTypeTag](t: Expr[A])(execContext: Expr[ExecContext]) = t
-
- def onComplete[A, U](future: Expr[Fut[A]], fun: Expr[Tryy[A] => U],
- execContext: Expr[ExecContext]): Expr[Unit] = reify {
- fun.splice.apply(util.Success(future.splice))
- c.Expr[Unit](Literal(Constant(()))).splice
- }
-
- def completeProm[A](prom: Expr[Prom[A]], value: Expr[Tryy[A]]): Expr[Unit] = reify {
- prom.splice.a = value.splice.get
- c.Expr[Unit](Literal(Constant(()))).splice
- }
-
- def tryyIsFailure[A](tryy: Expr[Tryy[A]]): Expr[Boolean] = reify {
- tryy.splice.isFailure
- }
-
- def tryyGet[A](tryy: Expr[Tryy[A]]): Expr[A] = reify {
- tryy.splice.get
- }
- def tryySuccess[A: WeakTypeTag](a: Expr[A]): Expr[Tryy[A]] = reify {
- scala.util.Success[A](a.splice)
- }
- def tryyFailure[A: WeakTypeTag](a: Expr[Throwable]): Expr[Tryy[A]] = reify {
- scala.util.Failure[A](a.splice)
- }
- }
-}
diff --git a/src/main/scala/scala/async/internal/AsyncMacro.scala b/src/main/scala/scala/async/internal/AsyncMacro.scala
deleted file mode 100644
index 16150c6f..00000000
--- a/src/main/scala/scala/async/internal/AsyncMacro.scala
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
- * 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.internal
-
-object AsyncMacro {
- def apply(c0: reflect.macros.whitebox.Context, base: AsyncBase)(body0: c0.Tree): AsyncMacro { val c: c0.type } = {
- // Use an attachment on RootClass as a sneaky place for a per-Global cache
- val att = c0.internal.attachments(c0.universe.rootMirror.RootClass)
- val names = att.get[AsyncNames[_]].getOrElse {
- val names = new AsyncNames[c0.universe.type](c0.universe)
- att.update(names)
- names
- }
-
- new AsyncMacro { self =>
- val c: c0.type = c0
- val asyncNames: AsyncNames[c.universe.type] = names.asInstanceOf[AsyncNames[c.universe.type]]
- val body: c.Tree = body0
- // This member is required by `AsyncTransform`:
- val asyncBase: AsyncBase = base
- // These members are required by `ExprBuilder`:
- val futureSystem: FutureSystem = base.futureSystem
- val futureSystemOps: futureSystem.Ops {val c: self.c.type} = futureSystem.mkOps(c)
- var containsAwait: c.Tree => Boolean = containsAwaitCached(body0)
- }
- }
-}
-
-private[async] trait AsyncMacro
- extends AnfTransform with TransformUtils with Lifter
- with ExprBuilder with AsyncTransform with AsyncAnalysis with LiveVariables {
-
- val c: scala.reflect.macros.whitebox.Context
- val body: c.Tree
- var containsAwait: c.Tree => Boolean
- val asyncNames: AsyncNames[c.universe.type]
-
- lazy val macroPos: c.universe.Position = c.macroApplication.pos.makeTransparent
- def atMacroPos(t: c.Tree): c.Tree = c.universe.atPos(macroPos)(t)
-
-}
diff --git a/src/main/scala/scala/async/internal/AsyncNames.scala b/src/main/scala/scala/async/internal/AsyncNames.scala
deleted file mode 100644
index 1828aa55..00000000
--- a/src/main/scala/scala/async/internal/AsyncNames.scala
+++ /dev/null
@@ -1,121 +0,0 @@
-/*
- * 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.internal
-
-import java.util.concurrent.atomic.AtomicInteger
-
-import scala.collection.mutable
-import scala.collection.mutable.ArrayBuffer
-import scala.reflect.api.Names
-
-/**
- * A per-global cache of names needed by the Async macro.
- */
-final class AsyncNames[U <: Names with Singleton](val u: U) {
- self =>
- import u._
-
- abstract class NameCache[N <: U#Name](base: String) {
- val cached = new ArrayBuffer[N]()
- protected def newName(s: String): N
- def apply(i: Int): N = {
- if (cached.isDefinedAt(i)) cached(i)
- else {
- assert(cached.length == i)
- val name = newName(freshenString(base, i))
- cached += name
- name
- }
- }
- }
-
- final class TermNameCache(base: String) extends NameCache[U#TermName](base) {
- override protected def newName(s: String): U#TermName = TermName(s)
- }
- final class TypeNameCache(base: String) extends NameCache[U#TypeName](base) {
- override protected def newName(s: String): U#TypeName = TypeName(s)
- }
- private val matchRes: TermNameCache = new TermNameCache("match")
- private val ifRes: TermNameCache = new TermNameCache("if")
- private val await: TermNameCache = new TermNameCache("await")
-
- private val result = TermName("result$async")
- private val completed: TermName = TermName("completed$async")
- private val apply = TermName("apply")
- private val stateMachine = TermName("stateMachine$async")
- private val stateMachineT = stateMachine.toTypeName
- private val state: u.TermName = TermName("state$async")
- private val execContext = TermName("execContext$async")
- private val tr: u.TermName = TermName("tr$async")
- private val t: u.TermName = TermName("throwable$async")
-
- final class NameSource[N <: U#Name](cache: NameCache[N]) {
- private val count = new AtomicInteger(0)
- def apply(): N = cache(count.getAndIncrement())
- }
-
- class AsyncName {
- final val matchRes = new NameSource[U#TermName](self.matchRes)
- final val ifRes = new NameSource[U#TermName](self.matchRes)
- final val await = new NameSource[U#TermName](self.await)
- final val completed = self.completed
- final val result = self.result
- final val apply = self.apply
- final val stateMachine = self.stateMachine
- final val stateMachineT = self.stateMachineT
- final val state: u.TermName = self.state
- final val execContext = self.execContext
- final val tr: u.TermName = self.tr
- final val t: u.TermName = self.t
-
- private val seenPrefixes = mutable.AnyRefMap[Name, AtomicInteger]()
- private val freshened = mutable.HashSet[Name]()
-
- final def freshenIfNeeded(name: TermName): TermName = {
- seenPrefixes.getOrNull(name) match {
- case null =>
- seenPrefixes.put(name, new AtomicInteger())
- name
- case counter =>
- freshen(name, counter)
- }
- }
- final def freshenIfNeeded(name: TypeName): TypeName = {
- seenPrefixes.getOrNull(name) match {
- case null =>
- seenPrefixes.put(name, new AtomicInteger())
- name
- case counter =>
- freshen(name, counter)
- }
- }
- final def freshen(name: TermName): TermName = {
- val counter = seenPrefixes.getOrElseUpdate(name, new AtomicInteger())
- freshen(name, counter)
- }
- final def freshen(name: TypeName): TypeName = {
- val counter = seenPrefixes.getOrElseUpdate(name, new AtomicInteger())
- freshen(name, counter)
- }
- private def freshen(name: TermName, counter: AtomicInteger): TermName = {
- if (freshened.contains(name)) name
- else TermName(freshenString(name.toString, counter.incrementAndGet()))
- }
- private def freshen(name: TypeName, counter: AtomicInteger): TypeName = {
- if (freshened.contains(name)) name
- else TypeName(freshenString(name.toString, counter.incrementAndGet()))
- }
- }
-
- private def freshenString(name: String, counter: Int): String = name.toString + "$async$" + counter
-}
diff --git a/src/main/scala/scala/async/internal/AsyncTransform.scala b/src/main/scala/scala/async/internal/AsyncTransform.scala
deleted file mode 100644
index f60135bd..00000000
--- a/src/main/scala/scala/async/internal/AsyncTransform.scala
+++ /dev/null
@@ -1,257 +0,0 @@
-/*
- * 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.internal
-
-trait AsyncTransform {
- self: AsyncMacro =>
-
- import c.universe._
- import c.internal._
- import decorators._
-
- val asyncBase: AsyncBase
-
- def asyncTransform[T](execContext: Tree)
- (resultType: WeakTypeTag[T]): Tree = {
-
- // We annotate the type of the whole expression as `T @uncheckedBounds` so as not to introduce
- // warnings about non-conformant LUBs. See SI-7694
- // This implicit propagates the annotated type in the type tag.
- implicit val uncheckedBoundsResultTag: WeakTypeTag[T] = c.WeakTypeTag[T](uncheckedBounds(resultType.tpe))
-
- reportUnsupportedAwaits(body)
-
- // Transform to A-normal form:
- // - no await calls in qualifiers or arguments,
- // - if/match only used in statement position.
- val anfTree0: Block = anfTransform(body, c.internal.enclosingOwner)
-
- val anfTree = futureSystemOps.postAnfTransform(anfTree0)
-
- cleanupContainsAwaitAttachments(anfTree)
- containsAwait = containsAwaitCached(anfTree)
-
- val applyDefDefDummyBody: DefDef = {
- val applyVParamss = List(List(ValDef(Modifiers(Flag.PARAM), name.tr, TypeTree(futureSystemOps.tryType[Any]), EmptyTree)))
- DefDef(NoMods, name.apply, Nil, applyVParamss, TypeTree(definitions.UnitTpe), literalUnit)
- }
-
- // Create `ClassDef` of state machine with empty method bodies for `resume` and `apply`.
- val stateMachine: ClassDef = {
- val body: List[Tree] = {
- val stateVar = ValDef(Modifiers(Flag.MUTABLE | Flag.PRIVATE | Flag.LOCAL), name.state, TypeTree(definitions.IntTpe), Literal(Constant(StateAssigner.Initial)))
- val resultAndAccessors = mkMutableField(futureSystemOps.promType[T](uncheckedBoundsResultTag), name.result, futureSystemOps.createProm[T](uncheckedBoundsResultTag).tree)
- val execContextValDef = ValDef(NoMods, name.execContext, TypeTree(), execContext)
-
- val apply0DefDef: DefDef = {
- // We extend () => Unit so we can pass this class as the by-name argument to `Future.apply`.
- // See SI-1247 for the the optimization that avoids creation.
- DefDef(NoMods, name.apply, Nil, List(Nil), TypeTree(definitions.UnitTpe), Apply(Ident(name.apply), literalNull :: Nil))
- }
- List(emptyConstructor, stateVar) ++ resultAndAccessors ++ List(execContextValDef) ++ List(applyDefDefDummyBody, apply0DefDef)
- }
-
- val customParents = futureSystemOps.stateMachineClassParents
- val tycon = if (customParents.forall(_.typeSymbol.asClass.isTrait)) {
- // prefer extending a class to reduce the class file size of the state machine.
- symbolOf[scala.runtime.AbstractFunction1[Any, Any]]
- } else {
- // ... unless a custom future system already extends some class
- symbolOf[scala.Function1[Any, Any]]
- }
- val tryToUnit = appliedType(tycon, futureSystemOps.tryType[Any], typeOf[Unit])
- val template = Template((futureSystemOps.stateMachineClassParents ::: List(tryToUnit, typeOf[() => Unit])).map(TypeTree(_)), noSelfType, body)
-
- val t = ClassDef(NoMods, name.stateMachineT, Nil, template)
- typecheckClassDef(t)
- }
-
- val stateMachineClass = stateMachine.symbol
- val asyncBlock: AsyncBlock = {
- val symLookup = SymLookup(stateMachineClass, applyDefDefDummyBody.vparamss.head.head.symbol)
- buildAsyncBlock(anfTree, symLookup)
- }
-
- val liftedFields: List[Tree] = liftables(asyncBlock.asyncStates)
-
- // live variables analysis
- // the result map indicates in which states a given field should be nulled out
- val assignsOf = fieldsToNullOut(asyncBlock.asyncStates, liftedFields)
-
- for ((state, flds) <- assignsOf) {
- val assigns = flds.map { fld =>
- val fieldSym = fld.symbol
- val assign = Assign(gen.mkAttributedStableRef(thisType(fieldSym.owner), fieldSym), mkZero(fieldSym.info))
- asyncBase.nullOut(c.universe)(c.Expr[String](Literal(Constant(fieldSym.name.toString))), c.Expr[Any](Ident(fieldSym))).tree match {
- case Literal(Constant(value: Unit)) => assign
- case x => Block(x :: Nil, assign)
- }
- }
- val asyncState = asyncBlock.asyncStates.find(_.state == state).get
- asyncState.stats = assigns ++ asyncState.stats
- }
-
- def startStateMachine: Tree = {
- val stateMachineSpliced: Tree = spliceMethodBodies(
- liftedFields,
- stateMachine,
- atMacroPos(asyncBlock.onCompleteHandler[T])
- )
-
- def selectStateMachine(selection: TermName) = Select(Ident(name.stateMachine), selection)
-
- Block(List[Tree](
- stateMachineSpliced,
- ValDef(NoMods, name.stateMachine, TypeTree(), Apply(Select(New(Ident(stateMachine.symbol)), termNames.CONSTRUCTOR), Nil)),
- futureSystemOps.spawn(Apply(selectStateMachine(name.apply), Nil), selectStateMachine(name.execContext))
- ),
- futureSystemOps.promiseToFuture(c.Expr[futureSystem.Prom[T]](selectStateMachine(name.result))).tree)
- }
-
- val isSimple = asyncBlock.asyncStates.size == 1
- val result = if (isSimple)
- futureSystemOps.spawn(body, execContext) // generate lean code for the simple case of `async { 1 + 1 }`
- else
- startStateMachine
-
- if(AsyncUtils.verbose) {
- logDiagnostics(anfTree, asyncBlock, asyncBlock.asyncStates.map(_.toString))
- }
- futureSystemOps.dot(enclosingOwner, body).foreach(f => f(asyncBlock.toDot))
- cleanupContainsAwaitAttachments(result)
- }
-
- def logDiagnostics(anfTree: Tree, block: AsyncBlock, states: Seq[String]): Unit = {
- def location = try {
- macroPos.source.path
- } catch {
- case _: UnsupportedOperationException =>
- macroPos.toString
- }
-
- AsyncUtils.vprintln(s"In file '$location':")
- AsyncUtils.vprintln(s"${c.macroApplication}")
- AsyncUtils.vprintln(s"ANF transform expands to:\n $anfTree")
- states foreach (s => AsyncUtils.vprintln(s))
- AsyncUtils.vprintln("===== DOT =====")
- AsyncUtils.vprintln(block.toDot)
- }
-
- /**
- * Build final `ClassDef` tree of state machine class.
- *
- * @param liftables trees of definitions that are lifted to fields of the state machine class
- * @param tree `ClassDef` tree of the state machine class
- * @param applyBody tree of onComplete handler (`apply` method)
- * @return transformed `ClassDef` tree of the state machine class
- */
- def spliceMethodBodies(liftables: List[Tree], tree: ClassDef, applyBody: Tree): Tree = {
- val liftedSyms = liftables.map(_.symbol).toSet
- val stateMachineClass = tree.symbol
- liftedSyms.foreach {
- sym =>
- if (sym != null) {
- sym.setOwner(stateMachineClass)
- if (sym.isModule)
- sym.asModule.moduleClass.setOwner(stateMachineClass)
- }
- }
-
- def adjustType(tree: Tree): Tree = {
- val resultType = if (tree.tpe eq null) null else tree.tpe.map {
- case TypeRef(pre, sym, args) if liftedSyms.contains(sym) =>
- val tp1 = internal.typeRef(thisType(sym.owner.asClass), sym, args)
- tp1
- case SingleType(pre, sym) if liftedSyms.contains(sym) =>
- val tp1 = internal.singleType(thisType(sym.owner.asClass), sym)
- tp1
- case tp => tp
- }
- setType(tree, resultType)
- }
-
- // Replace the ValDefs in the splicee with Assigns to the corresponding lifted
- // fields. Similarly, replace references to them with references to the field.
- //
- // This transform will only be run on the RHS of `def foo`.
- val useFields: (Tree, TypingTransformApi) => Tree = (tree, api) => tree match {
- case _ if api.currentOwner == stateMachineClass =>
- api.default(tree)
- case ValDef(_, _, _, rhs) if liftedSyms(tree.symbol) =>
- api.atOwner(api.currentOwner) {
- val fieldSym = tree.symbol
- if (fieldSym.asTerm.isLazy) Literal(Constant(()))
- else {
- val lhs = atPos(tree.pos) {
- gen.mkAttributedStableRef(thisType(fieldSym.owner.asClass), fieldSym)
- }
- treeCopy.Assign(tree, lhs, api.recur(rhs)).setType(definitions.UnitTpe).changeOwner(fieldSym, api.currentOwner)
- }
- }
- case _: DefTree if liftedSyms(tree.symbol) =>
- EmptyTree
- case Ident(name) if liftedSyms(tree.symbol) =>
- val fieldSym = tree.symbol
- atPos(tree.pos) {
- gen.mkAttributedStableRef(thisType(fieldSym.owner.asClass), fieldSym).setType(tree.tpe)
- }
- case sel @ Select(n@New(tt: TypeTree), termNamesCONSTRUCTOR) =>
- adjustType(sel)
- adjustType(n)
- adjustType(tt)
- sel
- case _ =>
- api.default(tree)
- }
-
- val liftablesUseFields = liftables.map {
- case vd: ValDef if !vd.symbol.asTerm.isLazy => vd
- case x => typingTransform(x, stateMachineClass)(useFields)
- }
-
- tree.children.foreach(_.changeOwner(enclosingOwner, tree.symbol))
- val treeSubst = tree
-
- /* Fixes up DefDef: use lifted fields in `body` */
- def fixup(dd: DefDef, body: Tree, api: TypingTransformApi): Tree = {
- val spliceeAnfFixedOwnerSyms = body
- val newRhs = typingTransform(spliceeAnfFixedOwnerSyms, dd.symbol)(useFields)
- val newRhsTyped = api.atOwner(dd, dd.symbol)(api.typecheck(newRhs))
- treeCopy.DefDef(dd, dd.mods, dd.name, dd.tparams, dd.vparamss, dd.tpt, newRhsTyped)
- }
-
- liftablesUseFields.foreach(t => if (t.symbol != null) stateMachineClass.info.decls.enter(t.symbol))
-
- val result0 = transformAt(treeSubst) {
- case t@Template(parents, self, stats) =>
- (api: TypingTransformApi) => {
- treeCopy.Template(t, parents, self, liftablesUseFields ++ stats)
- }
- }
- val result = transformAt(result0) {
- case dd@DefDef(_, name.apply, _, List(List(_)), _, _) if dd.symbol.owner == stateMachineClass =>
- (api: TypingTransformApi) =>
- val typedTree = fixup(dd, applyBody.changeOwner(enclosingOwner, dd.symbol), api)
- typedTree
- }
- result
- }
-
- def typecheckClassDef(cd: ClassDef): ClassDef = {
- val Block(cd1 :: Nil, _) = typingTransform(atPos(macroPos)(Block(cd :: Nil, Literal(Constant(())))))(
- (tree, api) =>
- api.typecheck(tree)
- )
- cd1.asInstanceOf[ClassDef]
- }
-}
diff --git a/src/main/scala/scala/async/internal/AsyncUtils.scala b/src/main/scala/scala/async/internal/AsyncUtils.scala
deleted file mode 100644
index 81b296ca..00000000
--- a/src/main/scala/scala/async/internal/AsyncUtils.scala
+++ /dev/null
@@ -1,26 +0,0 @@
-/*
- * 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.internal
-
-object AsyncUtils {
-
-
- private def enabled(level: String) = sys.props.getOrElse(s"scala.async.$level", "false").equalsIgnoreCase("true")
-
- private[async] val verbose = enabled("debug")
- private[async] val trace = enabled("trace")
-
- @inline private[async] def vprintln(s: => Any): Unit = if (verbose) println(s"[async] $s")
-
- @inline private[async] def trace(s: => Any): Unit = if (trace) println(s"[async] $s")
-}
diff --git a/src/main/scala/scala/async/internal/ExprBuilder.scala b/src/main/scala/scala/async/internal/ExprBuilder.scala
deleted file mode 100644
index 9570af99..00000000
--- a/src/main/scala/scala/async/internal/ExprBuilder.scala
+++ /dev/null
@@ -1,650 +0,0 @@
-/*
- * 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.internal
-
-import java.util.function.IntUnaryOperator
-
-import scala.collection.mutable
-import scala.collection.mutable.ListBuffer
-
-trait ExprBuilder {
- builder: AsyncMacro =>
-
- import c.universe._
- import c.internal._
-
- val futureSystem: FutureSystem
- val futureSystemOps: futureSystem.Ops { val c: builder.c.type }
-
- val stateAssigner = new StateAssigner
- val labelDefStates = collection.mutable.Map[Symbol, Int]()
-
- trait AsyncState {
- def state: Int
-
- def nextStates: Array[Int]
-
- def mkHandlerCaseForState[T: WeakTypeTag]: CaseDef
-
- def mkOnCompleteHandler[T: WeakTypeTag]: Option[CaseDef] = None
-
- var stats: List[Tree]
-
- def treesThenStats(trees: List[Tree]): List[Tree] = {
- (stats match {
- case init :+ last if tpeOf(last) =:= definitions.NothingTpe =>
- adaptToUnit((trees ::: init) :+ Typed(last, TypeTree(definitions.AnyTpe)))
- case _ =>
- adaptToUnit(trees ::: stats)
- }) :: Nil
- }
-
- final def allStats: List[Tree] = this match {
- case a: AsyncStateWithAwait => treesThenStats(a.awaitable.resultValDef :: Nil)
- case _ => stats
- }
-
- final def body: Tree = stats match {
- case stat :: Nil => stat
- case init :+ last => Block(init, last)
- }
- }
-
- /** A sequence of statements that concludes with a unconditional transition to `nextState` */
- final class SimpleAsyncState(var stats: List[Tree], val state: Int, nextState: Int, symLookup: SymLookup)
- extends AsyncState {
-
- val nextStates: Array[Int] =
- Array(nextState)
-
- def mkHandlerCaseForState[T: WeakTypeTag]: CaseDef = {
- mkHandlerCase(state, treesThenStats(mkStateTree(nextState, symLookup) :: Nil))
- }
-
- override val toString: String =
- s"AsyncState #$state, next = $nextState"
- }
-
- /** A sequence of statements with a conditional transition to the next state, which will represent
- * a branch of an `if` or a `match`.
- */
- final class AsyncStateWithoutAwait(var stats: List[Tree], val state: Int, val nextStates: Array[Int]) extends AsyncState {
- override def mkHandlerCaseForState[T: WeakTypeTag]: CaseDef =
- mkHandlerCase(state, stats)
-
- override val toString: String =
- s"AsyncStateWithoutAwait #$state, nextStates = ${nextStates.toList}"
- }
-
- /** A sequence of statements that concludes with an `await` call. The `onComplete`
- * handler will unconditionally transition to `nextState`.
- */
- final class AsyncStateWithAwait(var stats: List[Tree], val state: Int, val onCompleteState: Int, nextState: Int,
- val awaitable: Awaitable, symLookup: SymLookup)
- extends AsyncState {
-
- val nextStates: Array[Int] =
- Array(nextState)
-
- override def mkHandlerCaseForState[T: WeakTypeTag]: CaseDef = {
- val fun = This(typeNames.EMPTY)
- val callOnComplete = futureSystemOps.onComplete[Any, Unit](c.Expr[futureSystem.Fut[Any]](awaitable.expr),
- c.Expr[futureSystem.Tryy[Any] => Unit](fun), c.Expr[futureSystem.ExecContext](Ident(name.execContext))).tree
- val tryGetOrCallOnComplete: List[Tree] =
- if (futureSystemOps.continueCompletedFutureOnSameThread) {
- val tempName = name.completed
- val initTemp = ValDef(NoMods, tempName, TypeTree(futureSystemOps.tryType[Any]), futureSystemOps.getCompleted[Any](c.Expr[futureSystem.Fut[Any]](awaitable.expr)).tree)
- val ifTree = If(Apply(Select(Literal(Constant(null)), TermName("ne")), Ident(tempName) :: Nil),
- adaptToUnit(ifIsFailureTree[T](Ident(tempName)) :: Nil),
- Block(toList(callOnComplete), Return(literalUnit)))
- initTemp :: ifTree :: Nil
- } else
- toList(callOnComplete) ::: Return(literalUnit) :: Nil
- mkHandlerCase(state, stats ++ List(mkStateTree(onCompleteState, symLookup)) ++ tryGetOrCallOnComplete)
- }
-
- private def tryGetTree(tryReference: => Tree) =
- Assign(
- Ident(awaitable.resultName),
- TypeApply(Select(futureSystemOps.tryyGet[Any](c.Expr[futureSystem.Tryy[Any]](tryReference)).tree, TermName("asInstanceOf")), List(TypeTree(awaitable.resultType)))
- )
-
- /* if (tr.isFailure)
- * result.complete(tr.asInstanceOf[Try[T]])
- * else {
- * = tr.get.asInstanceOf[]
- *
- *
- * }
- */
- def ifIsFailureTree[T: WeakTypeTag](tryReference: => Tree) = {
- val getAndUpdateState = Block(List(tryGetTree(tryReference)), mkStateTree(nextState, symLookup))
- if (asyncBase.futureSystem.emitTryCatch) {
- If(futureSystemOps.tryyIsFailure(c.Expr[futureSystem.Tryy[T]](tryReference)).tree,
- Block(toList(futureSystemOps.completeProm[T](
- c.Expr[futureSystem.Prom[T]](symLookup.memberRef(name.result)),
- c.Expr[futureSystem.Tryy[T]](
- TypeApply(Select(tryReference, TermName("asInstanceOf")),
- List(TypeTree(futureSystemOps.tryType[T]))))).tree),
- Return(literalUnit)),
- getAndUpdateState
- )
- } else {
- getAndUpdateState
- }
- }
-
- override def mkOnCompleteHandler[T: WeakTypeTag]: Option[CaseDef] = {
- Some(mkHandlerCase(onCompleteState, List(ifIsFailureTree[T](Ident(symLookup.applyTrParam)))))
- }
-
- override val toString: String =
- s"AsyncStateWithAwait #$state, next = $nextState"
- }
-
- /*
- * Builder for a single state of an async expression.
- */
- final class AsyncStateBuilder(state: Int, private val symLookup: SymLookup) {
- /* Statements preceding an await call. */
- private val stats = ListBuffer[Tree]()
- /** The state of the target of a LabelDef application (while loop jump) */
- private var nextJumpState: Option[Int] = None
- private var nextJumpSymbol: Symbol = NoSymbol
- def effectiveNextState(nextState: Int) = nextJumpState.orElse(if (nextJumpSymbol == NoSymbol) None else Some(stateIdForLabel(nextJumpSymbol))).getOrElse(nextState)
-
- def +=(stat: Tree): this.type = {
- stat match {
- case Literal(Constant(())) => // This case occurs in do/while
- case _ =>
- assert(nextJumpState.isEmpty, s"statement appeared after a label jump: $stat")
- }
- def addStat() = stats += stat
- stat match {
- case Apply(fun, args) if isLabel(fun.symbol) =>
- // labelDefStates belongs to the current ExprBuilder
- labelDefStates get fun.symbol match {
- case opt@Some(nextState) =>
- // A backward jump
- nextJumpState = opt // re-use object
- nextJumpSymbol = fun.symbol
- case None =>
- // We haven't the corresponding LabelDef, this is a forward jump
- nextJumpSymbol = fun.symbol
- }
- case _ => addStat()
- }
- this
- }
-
- def resultWithAwait(awaitable: Awaitable,
- onCompleteState: Int,
- nextState: Int): AsyncState = {
- new AsyncStateWithAwait(stats.toList, state, onCompleteState, effectiveNextState(nextState), awaitable, symLookup)
- }
-
- def resultSimple(nextState: Int): AsyncState = {
- new SimpleAsyncState(stats.toList, state, effectiveNextState(nextState), symLookup)
- }
-
- def resultWithIf(condTree: Tree, thenState: Int, elseState: Int): AsyncState = {
- def mkBranch(state: Int) = mkStateTree(state, symLookup)
- this += If(condTree, mkBranch(thenState), mkBranch(elseState))
- new AsyncStateWithoutAwait(stats.toList, state, Array(thenState, elseState))
- }
-
- /**
- * Build `AsyncState` ending with a match expression.
- *
- * The cases of the match simply resume at the state of their corresponding right-hand side.
- *
- * @param scrutTree tree of the scrutinee
- * @param cases list of case definitions
- * @param caseStates starting state of the right-hand side of the each case
- * @return an `AsyncState` representing the match expression
- */
- def resultWithMatch(scrutTree: Tree, cases: List[CaseDef], caseStates: Array[Int], symLookup: SymLookup): AsyncState = {
- // 1. build list of changed cases
- val newCases = for ((cas, num) <- cases.zipWithIndex) yield cas match {
- case CaseDef(pat, guard, rhs) =>
- val bindAssigns = rhs.children.takeWhile(isSyntheticBindVal)
- CaseDef(pat, guard, Block(bindAssigns, mkStateTree(caseStates(num), symLookup)))
- }
- // 2. insert changed match tree at the end of the current state
- this += Match(scrutTree, newCases)
- new AsyncStateWithoutAwait(stats.toList, state, caseStates)
- }
-
- def resultWithLabel(startLabelState: Int, symLookup: SymLookup): AsyncState = {
- this += mkStateTree(startLabelState, symLookup)
- new AsyncStateWithoutAwait(stats.toList, state, Array(startLabelState))
- }
-
- override def toString: String = {
- val statsBeforeAwait = stats.mkString("\n")
- s"ASYNC STATE:\n$statsBeforeAwait"
- }
- }
-
- /**
- * An `AsyncBlockBuilder` builds a `ListBuffer[AsyncState]` based on the expressions of a `Block(stats, expr)` (see `Async.asyncImpl`).
- *
- * @param stats a list of expressions
- * @param expr the last expression of the block
- * @param startState the start state
- * @param endState the state to continue with
- */
- final private class AsyncBlockBuilder(stats: List[Tree], expr: Tree, startState: Int, endState: Int,
- private val symLookup: SymLookup) {
- val asyncStates = ListBuffer[AsyncState]()
-
- var stateBuilder = new AsyncStateBuilder(startState, symLookup)
- var currState = startState
-
- def checkForUnsupportedAwait(tree: Tree) = if (containsAwait(tree))
- c.abort(tree.pos, "await must not be used in this position")
-
- def nestedBlockBuilder(nestedTree: Tree, startState: Int, endState: Int) = {
- val (nestedStats, nestedExpr) = statsAndExpr(nestedTree)
- new AsyncBlockBuilder(nestedStats, nestedExpr, startState, endState, symLookup)
- }
-
- import stateAssigner.nextState
- def directlyAdjacentLabelDefs(t: Tree): List[Tree] = {
- def isPatternCaseLabelDef(t: Tree) = t match {
- case LabelDef(name, _, _) => name.toString.startsWith("case")
- case _ => false
- }
- val span = (stats :+ expr).filterNot(isLiteralUnit).span(_ ne t)
- span match {
- case (before, _ :: after) =>
- before.reverse.takeWhile(isPatternCaseLabelDef) ::: after.takeWhile(isPatternCaseLabelDef)
- case _ =>
- stats :+ expr
- }
- }
-
- // populate asyncStates
- def add(stat: Tree, afterState: Option[Int] = None): Unit = stat match {
- // the val name = await(..) pattern
- case vd @ ValDef(mods, name, tpt, Apply(fun, arg :: Nil)) if isAwait(fun) =>
- val onCompleteState = nextState()
- val afterAwaitState = afterState.getOrElse(nextState())
- val awaitable = Awaitable(arg, stat.symbol, tpt.tpe, vd)
- asyncStates += stateBuilder.resultWithAwait(awaitable, onCompleteState, afterAwaitState) // complete with await
- currState = afterAwaitState
- stateBuilder = new AsyncStateBuilder(currState, symLookup)
-
- case If(cond, thenp, elsep) if containsAwait(stat) || containsForiegnLabelJump(stat) =>
- checkForUnsupportedAwait(cond)
-
- val thenStartState = nextState()
- val elseStartState = nextState()
- val afterIfState = afterState.getOrElse(nextState())
-
- asyncStates +=
- // the two Int arguments are the start state of the then branch and the else branch, respectively
- stateBuilder.resultWithIf(cond, thenStartState, elseStartState)
-
- List((thenp, thenStartState), (elsep, elseStartState)) foreach {
- case (branchTree, state) =>
- val builder = nestedBlockBuilder(branchTree, state, afterIfState)
- asyncStates ++= builder.asyncStates
- }
-
- currState = afterIfState
- stateBuilder = new AsyncStateBuilder(currState, symLookup)
-
- case Match(scrutinee, cases) if containsAwait(stat) =>
- checkForUnsupportedAwait(scrutinee)
-
- val caseStates = new Array[Int](cases.length)
- java.util.Arrays.setAll(caseStates, new IntUnaryOperator {
- override def applyAsInt(operand: Int): Int = nextState()
- })
- val afterMatchState = afterState.getOrElse(nextState())
-
- asyncStates +=
- stateBuilder.resultWithMatch(scrutinee, cases, caseStates, symLookup)
-
- for ((cas, num) <- cases.zipWithIndex) {
- val (stats, expr) = statsAndExpr(cas.body)
- val stats1 = stats.dropWhile(isSyntheticBindVal)
- val builder = nestedBlockBuilder(Block(stats1, expr), caseStates(num), afterMatchState)
- asyncStates ++= builder.asyncStates
- }
-
- currState = afterMatchState
- stateBuilder = new AsyncStateBuilder(currState, symLookup)
- case ld @ LabelDef(name, params, rhs)
- if containsAwait(rhs) || directlyAdjacentLabelDefs(ld).exists(containsAwait) =>
-
- val startLabelState = stateIdForLabel(ld.symbol)
- val afterLabelState = afterState.getOrElse(nextState())
- asyncStates += stateBuilder.resultWithLabel(startLabelState, symLookup)
- labelDefStates(ld.symbol) = startLabelState
- val builder = nestedBlockBuilder(rhs, startLabelState, afterLabelState)
- asyncStates ++= builder.asyncStates
- currState = afterLabelState
- stateBuilder = new AsyncStateBuilder(currState, symLookup)
- case b @ Block(stats, expr) =>
- for (stat <- stats) add(stat)
- add(expr, afterState = Some(endState))
- case _ =>
- checkForUnsupportedAwait(stat)
- stateBuilder += stat
- }
- for (stat <- (stats :+ expr)) add(stat)
- val lastState = stateBuilder.resultSimple(endState)
- asyncStates += lastState
- }
-
- trait AsyncBlock {
- def asyncStates: List[AsyncState]
-
- def onCompleteHandler[T: WeakTypeTag]: Tree
-
- def toDot: String
- }
-
- case class SymLookup(stateMachineClass: Symbol, applyTrParam: Symbol) {
- def stateMachineMember(name: TermName): Symbol =
- stateMachineClass.info.member(name)
- def memberRef(name: TermName): Tree =
- gen.mkAttributedRef(stateMachineMember(name))
- }
-
- /**
- * Uses `AsyncBlockBuilder` to create an instance of `AsyncBlock`.
- *
- * @param block a `Block` tree in ANF
- * @param symLookup helper for looking up members of the state machine class
- * @return an `AsyncBlock`
- */
- def buildAsyncBlock(block: Block, symLookup: SymLookup): AsyncBlock = {
- val Block(stats, expr) = block
- val startState = stateAssigner.nextState()
- val endState = Int.MaxValue
-
- val blockBuilder = new AsyncBlockBuilder(stats, expr, startState, endState, symLookup)
-
- new AsyncBlock {
- val switchIds = mutable.AnyRefMap[Integer, Integer]()
-
- // render with http://graphviz.it/#/new
- def toDot: String = {
- val states = asyncStates
- def toHtmlLabel(label: String, preText: String, builder: StringBuilder): Unit = {
- val br = "
"
- builder.append("").append(label).append("").append("
")
- builder.append("")
- preText.split("\n").foreach {
- (line: String) =>
- builder.append(br)
- builder.append(line.replaceAllLiterally("\"", """).replaceAllLiterally("<", "<").replaceAllLiterally(">", ">").replaceAllLiterally(" ", " "))
- }
- builder.append(br)
- builder.append("")
- }
- val dotBuilder = new StringBuilder()
- dotBuilder.append("digraph {\n")
- def stateLabel(s: Int) = {
- if (s == 0) "INITIAL" else if (s == Int.MaxValue) "TERMINAL" else switchIds.get(s).map(_.toString).getOrElse(s.toString)
- }
- val length = states.size
- for ((state, i) <- asyncStates.zipWithIndex) {
- dotBuilder.append(s"""${stateLabel(state.state)} [label=""").append("<")
- def show(t: Tree): String = {
- (t match {
- case Block(stats, expr) => stats ::: expr :: Nil
- case t => t :: Nil
- }).iterator.map(t => showCode(t)).mkString("\n")
- }
- if (i != length - 1) {
- val CaseDef(_, _, body) = state.mkHandlerCaseForState
- toHtmlLabel(stateLabel(state.state), show(compactStateTransform.transform(body)), dotBuilder)
- } else {
- toHtmlLabel(stateLabel(state.state), state.allStats.map(show(_)).mkString("\n"), dotBuilder)
- }
- dotBuilder.append("> ]\n")
- state match {
- case s: AsyncStateWithAwait =>
- val CaseDef(_, _, body) = s.mkOnCompleteHandler.get
- dotBuilder.append(s"""${stateLabel(s.onCompleteState)} [label=""").append("<")
- toHtmlLabel(stateLabel(s.onCompleteState), show(compactStateTransform.transform(body)), dotBuilder)
- dotBuilder.append("> ]\n")
- case _ =>
- }
- }
- for (state <- states) {
- state match {
- case s: AsyncStateWithAwait =>
- dotBuilder.append(s"""${stateLabel(state.state)} -> ${stateLabel(s.onCompleteState)} [style=dashed color=red]""")
- dotBuilder.append("\n")
- for (succ <- state.nextStates) {
- dotBuilder.append(s"""${stateLabel(s.onCompleteState)} -> ${stateLabel(succ)}""")
- dotBuilder.append("\n")
- }
- case _ =>
- for (succ <- state.nextStates) {
- dotBuilder.append(s"""${stateLabel(state.state)} -> ${stateLabel(succ)}""")
- dotBuilder.append("\n")
- }
- }
- }
- dotBuilder.append("}\n")
- dotBuilder.toString
- }
-
- lazy val asyncStates: List[AsyncState] = filterStates
-
- def filterStates = {
- val all = blockBuilder.asyncStates.toList
- val (initial :: rest) = all
- val map = all.iterator.map(x => (x.state, x)).toMap
- var seen = mutable.HashSet[Int]()
- def loop(state: AsyncState): Unit = {
- seen.add(state.state)
- for (i <- state.nextStates) {
- if (i != Int.MaxValue && !seen.contains(i)) {
- loop(map(i))
- }
- }
- }
- loop(initial)
- val live = rest.filter(state => seen(state.state))
- var nextSwitchId = 0
- (initial :: live).foreach { state =>
- val switchId = nextSwitchId
- switchIds(state.state) = switchId
- nextSwitchId += 1
- state match {
- case state: AsyncStateWithAwait =>
- val switchId = nextSwitchId
- switchIds(state.onCompleteState) = switchId
- nextSwitchId += 1
- case _ =>
- }
- }
- initial :: live
-
- }
-
- def mkCombinedHandlerCases[T: WeakTypeTag]: List[CaseDef] = {
- val caseForLastState: CaseDef = {
- val lastState = asyncStates.last
- val lastStateBody = c.Expr[T](lastState.body)
- val rhs = futureSystemOps.completeWithSuccess(
- c.Expr[futureSystem.Prom[T]](symLookup.memberRef(name.result)), lastStateBody)
- mkHandlerCase(lastState.state, Block(rhs.tree, Return(literalUnit)))
- }
- asyncStates match {
- case s :: Nil =>
- List(caseForLastState)
- case _ =>
- val initCases = for (state <- asyncStates.init) yield state.mkHandlerCaseForState[T]
- initCases :+ caseForLastState
- }
- }
-
- val initStates = asyncStates.init
-
- /**
- * Builds the definition of the `resume` method.
- *
- * The resulting tree has the following shape:
- *
- * def resume(): Unit = {
- * try {
- * state match {
- * case 0 => {
- * f11 = exprReturningFuture
- * f11.onComplete(onCompleteHandler)(context)
- * }
- * ...
- * }
- * } catch {
- * case NonFatal(t) => result.failure(t)
- * }
- * }
- */
- private def resumeFunTree[T: WeakTypeTag]: Tree = {
- val stateMemberRef = symLookup.memberRef(name.state)
- val body = Match(stateMemberRef, mkCombinedHandlerCases[T] ++ initStates.flatMap(_.mkOnCompleteHandler[T]) ++ List(CaseDef(Ident(termNames.WILDCARD), EmptyTree, Throw(Apply(Select(New(Ident(defn.IllegalStateExceptionClass)), termNames.CONSTRUCTOR), List())))))
- val body1 = compactStates(body)
-
- maybeTry(
- body1,
- List(
- CaseDef(
- Bind(name.t, Typed(Ident(termNames.WILDCARD), Ident(defn.ThrowableClass))),
- EmptyTree, {
- val thenn = {
- val t = c.Expr[Throwable](Ident(name.t))
- val complete = futureSystemOps.completeProm[T](
- c.Expr[futureSystem.Prom[T]](symLookup.memberRef(name.result)), futureSystemOps.tryyFailure[T](t)).tree
- Block(toList(complete), Return(literalUnit))
- }
- If(Apply(Ident(defn.NonFatalClass), List(Ident(name.t))), thenn, Throw(Ident(name.t)))
- thenn
- })), EmptyTree)
- }
-
- private lazy val stateMemberSymbol = symLookup.stateMachineMember(name.state)
- private val compactStateTransform = new Transformer {
- override def transform(tree: Tree): Tree = tree match {
- case as @ Assign(lhs, Literal(Constant(i: Integer))) if lhs.symbol == stateMemberSymbol =>
- val replacement = switchIds(i)
- treeCopy.Assign(tree, lhs, Literal(Constant(replacement)))
- case _: Match | _: CaseDef | _: Block | _: If =>
- super.transform(tree)
- case _ => tree
- }
- }
-
- private def compactStates(m: Match): Tree = {
- val cases1 = m.cases.flatMap {
- case cd @ CaseDef(Literal(Constant(i: Integer)), EmptyTree, rhs) =>
- val replacement = switchIds(i)
- val rhs1 = compactStateTransform.transform(rhs)
- treeCopy.CaseDef(cd, Literal(Constant(replacement)), EmptyTree, rhs1) :: Nil
- case x => x :: Nil
- }
- treeCopy.Match(m, m.selector, cases1)
- }
-
- def forever(t: Tree): Tree = {
- val termName = TermName(name.fresh("while$"))
- LabelDef(termName, Nil, Block(toList(t), Apply(Ident(termName), Nil)))
- }
-
- /**
- * Builds a `match` expression used as an onComplete handler.
- *
- * Assumes `tr: Try[Any]` is in scope. The resulting tree has the following shape:
- *
- * state match {
- * case 0 =>
- * x11 = tr.get.asInstanceOf[Double]
- * state = 1
- * resume()
- * }
- */
- def onCompleteHandler[T: WeakTypeTag]: Tree = {
- initStates.flatMap(_.mkOnCompleteHandler[T])
- forever {
- adaptToUnit(toList(resumeFunTree))
- }
- }
- }
- }
-
- private def isSyntheticBindVal(tree: Tree) = tree match {
- case vd@ValDef(_, lname, _, Ident(rname)) => attachments(vd.symbol).contains[SyntheticBindVal.type]
- case _ => false
- }
-
- case class Awaitable(expr: Tree, resultName: Symbol, resultType: Type, resultValDef: ValDef)
-
- private def mkStateTree(nextState: Int, symLookup: SymLookup): Tree =
- Assign(symLookup.memberRef(name.state), Literal(Constant(nextState)))
-
- private def mkHandlerCase(num: Int, rhs: List[Tree]): CaseDef =
- mkHandlerCase(num, adaptToUnit(rhs))
-
- // We use the convention that the state machine's ID for a state corresponding to
- // a labeldef will a negative number be based on the symbol ID. This allows us
- // to translate a forward jump to the label as a state transition to a known state
- // ID, even though the state machine transform hasn't yet processed the target label
- // def. Negative numbers are used so as as not to clash with regular state IDs, which
- // are allocated in ascending order from 0.
- private def stateIdForLabel(sym: Symbol): Int = -symId(sym)
-
- private def tpeOf(t: Tree): Type = t match {
- case _ if t.tpe != null => t.tpe
- case Try(body, Nil, _) => tpeOf(body)
- case Block(_, expr) => tpeOf(expr)
- case Literal(Constant(value)) if value == (()) => definitions.UnitTpe
- case Return(_) => definitions.NothingTpe
- case _ => NoType
- }
-
- private def adaptToUnit(rhs: List[Tree]): c.universe.Block = {
- rhs match {
- case (rhs: Block) :: Nil if tpeOf(rhs) <:< definitions.UnitTpe =>
- rhs
- case init :+ last if tpeOf(last) <:< definitions.UnitTpe =>
- Block(init, last)
- case init :+ (last @ Literal(Constant(()))) =>
- Block(init, last)
- case init :+ (last @ Block(_, Return(_) | Literal(Constant(())))) =>
- Block(init, last)
- case init :+ (Block(stats, expr)) =>
- Block(init, Block(stats :+ expr, literalUnit))
- case _ =>
- Block(rhs, literalUnit)
- }
- }
-
- private def mkHandlerCase(num: Int, rhs: Tree): CaseDef =
- CaseDef(Literal(Constant(num)), EmptyTree, rhs)
-
- def literalUnit = Literal(Constant(())) // a def to avoid sharing trees
-
- def toList(tree: Tree): List[Tree] = tree match {
- case Block(stats, Literal(Constant(value))) if value == (()) => stats
- case _ => tree :: Nil
- }
-
- def literalNull = Literal(Constant(null))
-}
diff --git a/src/main/scala/scala/async/internal/FutureSystem.scala b/src/main/scala/scala/async/internal/FutureSystem.scala
deleted file mode 100644
index 11c57ef4..00000000
--- a/src/main/scala/scala/async/internal/FutureSystem.scala
+++ /dev/null
@@ -1,156 +0,0 @@
-/*
- * 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.internal
-
-import scala.language.higherKinds
-import scala.reflect.macros.whitebox
-
-/**
- * An abstraction over a future system.
- *
- * Used by the macro implementations in [[scala.async.internal.AsyncBase]] to
- * customize the code generation.
- *
- * The API mirrors that of `scala.concurrent.Future`, see the instance
- * [[ScalaConcurrentFutureSystem]] for an example of how
- * to implement this.
- */
-trait FutureSystem {
- /** A container to receive the final value of the computation */
- type Prom[A]
- /** A (potentially in-progress) computation */
- type Fut[A]
- /** An execution context, required to create or register an on completion callback on a Future. */
- type ExecContext
- /** Any data type isomorphic to scala.util.Try. */
- type Tryy[T]
-
- trait Ops {
- val c: whitebox.Context
- import c.universe._
-
- def promType[A: WeakTypeTag]: Type
- def tryType[A: WeakTypeTag]: Type
- def execContextType: Type
- def stateMachineClassParents: List[Type] = Nil
-
- /** Create an empty promise */
- def createProm[A: WeakTypeTag]: Expr[Prom[A]]
-
- /** Extract a future from the given promise. */
- def promiseToFuture[A: WeakTypeTag](prom: Expr[Prom[A]]): Expr[Fut[A]]
-
- /** Construct a future to asynchronously compute the given expression */
- def future[A: WeakTypeTag](a: Expr[A])(execContext: Expr[ExecContext]): Expr[Fut[A]]
-
- /** Register an call back to run on completion of the given future */
- def onComplete[A, U](future: Expr[Fut[A]], fun: Expr[Tryy[A] => U],
- execContext: Expr[ExecContext]): Expr[Unit]
-
- def continueCompletedFutureOnSameThread = false
-
- /** Return `null` if this future is not yet completed, or `Tryy[A]` with the completed result
- * otherwise
- */
- def getCompleted[A: WeakTypeTag](future: Expr[Fut[A]]): Expr[Tryy[A]] =
- throw new UnsupportedOperationException("getCompleted not supported by this FutureSystem")
-
- /** Complete a promise with a value */
- def completeProm[A](prom: Expr[Prom[A]], value: Expr[Tryy[A]]): Expr[Unit]
- def completeWithSuccess[A: WeakTypeTag](prom: Expr[Prom[A]], value: Expr[A]): Expr[Unit] = completeProm(prom, tryySuccess(value))
-
- def spawn(tree: Tree, execContext: Tree): Tree =
- future(c.Expr[Unit](tree))(c.Expr[ExecContext](execContext)).tree
-
- def tryyIsFailure[A](tryy: Expr[Tryy[A]]): Expr[Boolean]
-
- def tryyGet[A](tryy: Expr[Tryy[A]]): Expr[A]
- def tryySuccess[A: WeakTypeTag](a: Expr[A]): Expr[Tryy[A]]
- def tryyFailure[A: WeakTypeTag](a: Expr[Throwable]): Expr[Tryy[A]]
-
- /** A hook for custom macros to transform the tree post-ANF transform */
- def postAnfTransform(tree: Block): Block = tree
-
- /** A hook for custom macros to selectively generate and process a Graphviz visualization of the transformed state machine */
- def dot(enclosingOwner: Symbol, macroApplication: Tree): Option[(String => Unit)] = None
- }
-
- def mkOps(c0: whitebox.Context): Ops { val c: c0.type }
-
- @deprecated("No longer honoured by the macro, all generated names now contain $async to avoid accidental clashes with lambda lifted names", "0.9.7")
- def freshenAllNames: Boolean = false
- def emitTryCatch: Boolean = true
- @deprecated("No longer honoured by the macro, all generated names now contain $async to avoid accidental clashes with lambda lifted names", "0.9.7")
- def resultFieldName: String = "result"
-}
-
-object ScalaConcurrentFutureSystem extends FutureSystem {
-
- import scala.concurrent._
-
- type Prom[A] = Promise[A]
- type Fut[A] = Future[A]
- type ExecContext = ExecutionContext
- type Tryy[A] = scala.util.Try[A]
-
- def mkOps(c0: whitebox.Context): Ops {val c: c0.type} = new Ops {
- val c: c0.type = c0
- import c.universe._
-
- def promType[A: WeakTypeTag]: Type = weakTypeOf[Promise[A]]
- def tryType[A: WeakTypeTag]: Type = weakTypeOf[scala.util.Try[A]]
- def execContextType: Type = weakTypeOf[ExecutionContext]
-
- def createProm[A: WeakTypeTag]: Expr[Prom[A]] = reify {
- Promise[A]()
- }
-
- def promiseToFuture[A: WeakTypeTag](prom: Expr[Prom[A]]) = reify {
- prom.splice.future
- }
-
- def future[A: WeakTypeTag](a: Expr[A])(execContext: Expr[ExecContext]) = reify {
- Future(a.splice)(execContext.splice)
- }
-
- def onComplete[A, U](future: Expr[Fut[A]], fun: Expr[scala.util.Try[A] => U],
- execContext: Expr[ExecContext]): Expr[Unit] = reify {
- future.splice.onComplete(fun.splice)(execContext.splice)
- }
-
- override def continueCompletedFutureOnSameThread: Boolean = true
-
- override def getCompleted[A: WeakTypeTag](future: Expr[Fut[A]]): Expr[Tryy[A]] = reify {
- if (future.splice.isCompleted) future.splice.value.get else null
- }
-
- def completeProm[A](prom: Expr[Prom[A]], value: Expr[scala.util.Try[A]]): Expr[Unit] = reify {
- prom.splice.complete(value.splice)
- c.Expr[Unit](Literal(Constant(()))).splice
- }
-
- def tryyIsFailure[A](tryy: Expr[scala.util.Try[A]]): Expr[Boolean] = reify {
- tryy.splice.isFailure
- }
-
- def tryyGet[A](tryy: Expr[Tryy[A]]): Expr[A] = reify {
- tryy.splice.get
- }
- def tryySuccess[A: WeakTypeTag](a: Expr[A]): Expr[Tryy[A]] = reify {
- scala.util.Success[A](a.splice)
- }
- def tryyFailure[A: WeakTypeTag](a: Expr[Throwable]): Expr[Tryy[A]] = reify {
- scala.util.Failure[A](a.splice)
- }
- }
-}
diff --git a/src/main/scala/scala/async/internal/Lifter.scala b/src/main/scala/scala/async/internal/Lifter.scala
deleted file mode 100644
index 57fefa20..00000000
--- a/src/main/scala/scala/async/internal/Lifter.scala
+++ /dev/null
@@ -1,179 +0,0 @@
-/*
- * 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.internal
-
-import scala.collection.mutable
-import scala.collection.mutable.ListBuffer
-
-trait Lifter {
- self: AsyncMacro =>
- import c.universe._
- import Flag._
- import c.internal._
- import decorators._
-
- /**
- * Identify which DefTrees are used (including transitively) which are declared
- * in some state but used (including transitively) in another state.
- *
- * These will need to be lifted to class members of the state machine.
- */
- def liftables(asyncStates: List[AsyncState]): List[Tree] = {
- object companionship {
- private val companions = collection.mutable.Map[Symbol, Symbol]()
- private val companionsInverse = collection.mutable.Map[Symbol, Symbol]()
- private def record(sym1: Symbol, sym2: Symbol): Unit = {
- companions(sym1) = sym2
- companions(sym2) = sym1
- }
-
- def record(defs: List[Tree]): Unit = {
- // Keep note of local companions so we rename them consistently
- // when lifting.
- for {
- cd@ClassDef(_, _, _, _) <- defs
- md@ModuleDef(_, _, _) <- defs
- if (cd.name.toTermName == md.name)
- } record(cd.symbol, md.symbol)
- }
- def companionOf(sym: Symbol): Symbol = {
- companions.get(sym).orElse(companionsInverse.get(sym)).getOrElse(NoSymbol)
- }
- }
-
-
- val defs: mutable.LinkedHashMap[Tree, Int] = {
- /** Collect the DefTrees directly enclosed within `t` that have the same owner */
- def collectDirectlyEnclosedDefs(t: Tree): List[DefTree] = t match {
- case ld: LabelDef => Nil
- case dt: DefTree => dt :: Nil
- case _: Function => Nil
- case t =>
- val childDefs = t.children.flatMap(collectDirectlyEnclosedDefs(_))
- companionship.record(childDefs)
- childDefs
- }
- mutable.LinkedHashMap(asyncStates.flatMap {
- asyncState =>
- val defs = collectDirectlyEnclosedDefs(Block(asyncState.allStats: _*))
- defs.map((_, asyncState.state))
- }: _*)
- }
-
- // In which block are these symbols defined?
- val symToDefiningState: mutable.LinkedHashMap[Symbol, Int] = defs.map {
- case (k, v) => (k.symbol, v)
- }
-
- // The definitions trees
- val symToTree: mutable.LinkedHashMap[Symbol, Tree] = defs.map {
- case (k, v) => (k.symbol, k)
- }
-
- // The direct references of each definition tree
- val defSymToReferenced: mutable.LinkedHashMap[Symbol, List[Symbol]] = defs.map {
- case (tree, _) => (tree.symbol, tree.collect {
- case rt: RefTree if symToDefiningState.contains(rt.symbol) => rt.symbol
- })
- }
-
- // The direct references of each block, excluding references of `DefTree`-s which
- // are already accounted for.
- val stateIdToDirectlyReferenced: mutable.LinkedHashMap[Int, List[Symbol]] = {
- val result = new mutable.LinkedHashMap[Int, ListBuffer[Symbol]]()
- asyncStates.foreach(
- asyncState => asyncState.stats.filterNot(t => t.isDef && !isLabel(t.symbol)).foreach(_.foreach {
- case rt: RefTree
- if symToDefiningState.contains(rt.symbol) =>
- result.getOrElseUpdate(asyncState.state, new ListBuffer) += rt.symbol
- case tt: TypeTree =>
- tt.tpe.foreach { tp =>
- val termSym = tp.termSymbol
- if (symToDefiningState.contains(termSym))
- result.getOrElseUpdate(asyncState.state, new ListBuffer) += termSym
- val typeSym = tp.typeSymbol
- if (symToDefiningState.contains(typeSym))
- result.getOrElseUpdate(asyncState.state, new ListBuffer) += typeSym
- }
- case _ =>
- })
- )
- result.map { case (a, b) => (a, b.result())}
- }
-
- def liftableSyms: mutable.LinkedHashSet[Symbol] = {
- val liftableMutableSet = mutable.LinkedHashSet[Symbol]()
- def markForLift(sym: Symbol): Unit = {
- if (!liftableMutableSet(sym)) {
- liftableMutableSet += sym
-
- // Only mark transitive references of defs, modules and classes. The RHS of lifted vals/vars
- // stays in its original location, so things that it refers to need not be lifted.
- if (!(sym.isTerm && !sym.asTerm.isLazy && (sym.asTerm.isVal || sym.asTerm.isVar)))
- defSymToReferenced(sym).foreach(sym2 => markForLift(sym2))
- }
- }
- // Start things with DefTrees directly referenced from statements from other states...
- val liftableStatementRefs: List[Symbol] = stateIdToDirectlyReferenced.iterator.flatMap {
- case (i, syms) => syms.filter(sym => symToDefiningState(sym) != i)
- }.toList
- // .. and likewise for DefTrees directly referenced by other DefTrees from other states
- val liftableRefsOfDefTrees = defSymToReferenced.toList.flatMap {
- case (referee, referents) => referents.filter(sym => symToDefiningState(sym) != symToDefiningState(referee))
- }
- // Mark these for lifting, which will follow transitive references.
- (liftableStatementRefs ++ liftableRefsOfDefTrees).foreach(markForLift)
- liftableMutableSet
- }
-
- liftableSyms.iterator.map(symToTree).map {
- t =>
- val sym = t.symbol
- val treeLifted = t match {
- case vd@ValDef(_, _, tpt, rhs) =>
- sym.setFlag(MUTABLE | STABLE | PRIVATE | LOCAL)
- sym.setName(name.fresh(sym.name.toTermName))
- sym.setInfo(deconst(sym.info))
- val rhs1 = if (sym.asTerm.isLazy) rhs else EmptyTree
- treeCopy.ValDef(vd, Modifiers(sym.flags), sym.name, TypeTree(tpe(sym)).setPos(t.pos), rhs1)
- case dd@DefDef(_, _, tparams, vparamss, tpt, rhs) =>
- sym.setName(this.name.freshen(sym.name.toTermName))
- sym.setFlag(PRIVATE | LOCAL)
- // Was `DefDef(sym, rhs)`, but this ran afoul of `ToughTypeSpec.nestedMethodWithInconsistencyTreeAndInfoParamSymbols`
- // due to the handling of type parameter skolems in `thisMethodType` in `Namers`
- treeCopy.DefDef(dd, Modifiers(sym.flags), sym.name, tparams, vparamss, tpt, rhs)
- case cd@ClassDef(_, _, tparams, impl) =>
- sym.setName(name.freshen(sym.name.toTypeName))
- companionship.companionOf(cd.symbol) match {
- case NoSymbol =>
- case moduleSymbol =>
- moduleSymbol.setName(sym.name.toTermName)
- moduleSymbol.asModule.moduleClass.setName(moduleSymbol.name.toTypeName)
- }
- treeCopy.ClassDef(cd, Modifiers(sym.flags), sym.name, tparams, impl)
- case md@ModuleDef(_, _, impl) =>
- companionship.companionOf(md.symbol) match {
- case NoSymbol =>
- sym.setName(name.freshen(sym.name.toTermName))
- sym.asModule.moduleClass.setName(sym.name.toTypeName)
- case classSymbol => // will be renamed by `case ClassDef` above.
- }
- treeCopy.ModuleDef(md, Modifiers(sym.flags), sym.name, impl)
- case td@TypeDef(_, _, tparams, rhs) =>
- sym.setName(name.freshen(sym.name.toTypeName))
- treeCopy.TypeDef(td, Modifiers(sym.flags), sym.name, tparams, rhs)
- }
- atPos(t.pos)(treeLifted)
- }.toList
- }
-}
diff --git a/src/main/scala/scala/async/internal/LiveVariables.scala b/src/main/scala/scala/async/internal/LiveVariables.scala
deleted file mode 100644
index 2f7ecc29..00000000
--- a/src/main/scala/scala/async/internal/LiveVariables.scala
+++ /dev/null
@@ -1,313 +0,0 @@
-/*
- * 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.internal
-
-import scala.collection.mutable
-
-import java.util.function.IntConsumer
-
-import scala.collection.immutable.IntMap
-
-trait LiveVariables {
- self: AsyncMacro =>
- import c.universe._
- import Flag._
-
- /**
- * Returns for a given state a list of fields (as trees) that should be nulled out
- * upon resuming that state (at the beginning of `resume`).
- *
- * @param asyncStates the states of an `async` block
- * @param liftables the lifted fields
- * @return a map mapping a state to the fields that should be nulled out
- * upon resuming that state
- */
- def fieldsToNullOut(asyncStates: List[AsyncState], liftables: List[Tree]): mutable.LinkedHashMap[Int, List[Tree]] = {
- // live variables analysis:
- // the result map indicates in which states a given field should be nulled out
- val liveVarsMap: mutable.LinkedHashMap[Tree, StateSet] = liveVars(asyncStates, liftables)
-
- var assignsOf = mutable.LinkedHashMap[Int, List[Tree]]()
-
- for ((fld, where) <- liveVarsMap) {
- where.foreach { new IntConsumer { def accept(state: Int): Unit = {
- assignsOf get state match {
- case None =>
- assignsOf += (state -> List(fld))
- case Some(trees) if !trees.exists(_.symbol == fld.symbol) =>
- assignsOf += (state -> (fld +: trees))
- case _ =>
- // do nothing
- }
- }}}
- }
-
- assignsOf
- }
-
- /**
- * Live variables data-flow analysis.
- *
- * The goal is to find, for each lifted field, the last state where the field is used.
- * In all direct successor states which are not (indirect) predecessors of that last state
- * (possible through loops), the corresponding field should be nulled out (at the beginning of
- * `resume`).
- *
- * @param asyncStates the states of an `async` block
- * @param liftables the lifted fields
- * @return a map which indicates for a given field (the key) the states in which it should be nulled out
- */
- def liveVars(asyncStates: List[AsyncState], liftables: List[Tree]): mutable.LinkedHashMap[Tree, StateSet] = {
- val liftedSyms: Set[Symbol] = // include only vars
- liftables.iterator.filter {
- case ValDef(mods, _, _, _) => mods.hasFlag(MUTABLE)
- case _ => false
- }.map(_.symbol).toSet
-
- // determine which fields should be live also at the end (will not be nulled out)
- val noNull: Set[Symbol] = liftedSyms.filter { sym =>
- val typeSym = tpe(sym).typeSymbol
- (typeSym.isClass && (typeSym.asClass.isPrimitive || typeSym == definitions.NothingClass)) || liftables.exists { tree =>
- !liftedSyms.contains(tree.symbol) && tree.exists(_.symbol == sym)
- }
- }
- AsyncUtils.vprintln(s"fields never zero-ed out: ${noNull.mkString(", ")}")
-
- /**
- * Traverse statements of an `AsyncState`, collect `Ident`-s referring to lifted fields.
- *
- * @param as a state of an `async` expression
- * @return a set of lifted fields that are used within state `as`
- */
- def fieldsUsedIn(as: AsyncState): ReferencedFields = {
- class FindUseTraverser extends AsyncTraverser {
- var usedFields: Set[Symbol] = Set[Symbol]()
- var capturedFields: Set[Symbol] = Set[Symbol]()
- private def capturing[A](body: => A): A = {
- val saved = capturing
- try {
- capturing = true
- body
- } finally capturing = saved
- }
- private def capturingCheck(tree: Tree) = capturing(tree foreach check)
- private var capturing: Boolean = false
- private def check(tree: Tree): Unit = {
- tree match {
- case Ident(_) if liftedSyms(tree.symbol) =>
- if (capturing)
- capturedFields += tree.symbol
- else
- usedFields += tree.symbol
- case _ =>
- }
- }
- override def traverse(tree: Tree) = {
- check(tree)
- super.traverse(tree)
- }
-
- override def nestedClass(classDef: ClassDef): Unit = capturingCheck(classDef)
-
- override def nestedModule(module: ModuleDef): Unit = capturingCheck(module)
-
- override def nestedMethod(defdef: DefDef): Unit = capturingCheck(defdef)
-
- override def byNameArgument(arg: Tree): Unit = capturingCheck(arg)
-
- override def function(function: Function): Unit = capturingCheck(function)
-
- override def patMatFunction(tree: Match): Unit = capturingCheck(tree)
- }
-
- val findUses = new FindUseTraverser
- findUses.traverse(Block(as.stats: _*))
- ReferencedFields(findUses.usedFields, findUses.capturedFields)
- }
- case class ReferencedFields(used: Set[Symbol], captured: Set[Symbol]) {
- override def toString = s"used: ${used.mkString(",")}\ncaptured: ${captured.mkString(",")}"
- }
-
- /* Build the control-flow graph.
- *
- * A state `i` is contained in the list that is the value to which
- * key `j` maps iff control can flow from state `j` to state `i`.
- */
- val cfg: IntMap[Array[Int]] = {
- var res = IntMap.empty[Array[Int]]
-
- for (as <- asyncStates) res = res.updated(as.state, as.nextStates)
- res
- }
-
- /** Tests if `state1` is a predecessor of `state2`.
- */
- def isPred(state1: Int, state2: Int): Boolean = {
- val seen = new StateSet()
-
- def isPred0(state1: Int, state2: Int): Boolean =
- if(state1 == state2) false
- else if (seen.contains(state1)) false // breaks cycles in the CFG
- else cfg getOrElse(state1, null) match {
- case null => false
- case nextStates =>
- seen += state1
- var i = 0
- while (i < nextStates.length) {
- if (nextStates(i) == state2 || isPred0(nextStates(i), state2)) return true
- i += 1
- }
- false
- }
-
- isPred0(state1, state2)
- }
-
- val finalState = asyncStates.find(as => !asyncStates.exists(other => isPred(as.state, other.state))).get
-
- if(AsyncUtils.verbose) {
- for (as <- asyncStates)
- AsyncUtils.vprintln(s"fields used in state #${as.state}: ${fieldsUsedIn(as)}")
- }
-
- /* Backwards data-flow analysis. Computes live variables information at entry and exit
- * of each async state.
- *
- * Compute using a simple fixed point iteration:
- *
- * 1. currStates = List(finalState)
- * 2. for each cs \in currStates, compute LVentry(cs) from LVexit(cs) and used fields information for cs
- * 3. record if LVentry(cs) has changed for some cs.
- * 4. obtain predecessors pred of each cs \in currStates
- * 5. for each p \in pred, compute LVexit(p) as union of the LVentry of its successors
- * 6. currStates = pred
- * 7. repeat if something has changed
- */
-
- var LVentry = IntMap[Set[Symbol]]() withDefaultValue Set[Symbol]()
- var LVexit: Map[Int, Set[Symbol]] = IntMap[Set[Symbol]]() withDefaultValue Set[Symbol]()
-
- // All fields are declared to be dead at the exit of the final async state, except for the ones
- // that cannot be nulled out at all (those in noNull), because they have been captured by a nested def.
- LVexit = LVexit + (finalState.state -> noNull)
-
- var currStates = List(finalState) // start at final state
- var captured: Set[Symbol] = Set()
-
- def contains(as: Array[Int], a: Int): Boolean = {
- var i = 0
- while (i < as.length) {
- if (as(i) == a) return true
- i += 1
- }
- false
- }
- while (!currStates.isEmpty) {
- var entryChanged: List[AsyncState] = Nil
-
- for (cs <- currStates) {
- val LVentryOld = LVentry(cs.state)
- val referenced = fieldsUsedIn(cs)
- captured ++= referenced.captured
- val LVentryNew = LVexit(cs.state) ++ referenced.used
- if (!LVentryNew.sameElements(LVentryOld)) {
- LVentry = LVentry.updated(cs.state, LVentryNew)
- entryChanged ::= cs
- }
- }
-
- val pred = entryChanged.flatMap(cs => asyncStates.filter(state => contains(state.nextStates, cs.state)))
- var exitChanged: List[AsyncState] = Nil
-
- for (p <- pred) {
- val LVexitOld = LVexit(p.state)
- val LVexitNew = p.nextStates.flatMap(succ => LVentry(succ)).toSet
- if (!LVexitNew.sameElements(LVexitOld)) {
- LVexit = LVexit.updated(p.state, LVexitNew)
- exitChanged ::= p
- }
- }
-
- currStates = exitChanged
- }
-
- if(AsyncUtils.verbose) {
- for (as <- asyncStates) {
- AsyncUtils.vprintln(s"LVentry at state #${as.state}: ${LVentry(as.state).mkString(", ")}")
- AsyncUtils.vprintln(s"LVexit at state #${as.state}: ${LVexit(as.state).mkString(", ")}")
- }
- }
-
- def lastUsagesOf(field: Tree, at: AsyncState): StateSet = {
- val avoid = scala.collection.mutable.HashSet[AsyncState]()
-
- val result = new StateSet
- def lastUsagesOf0(field: Tree, at: AsyncState): Unit = {
- if (avoid(at)) ()
- else if (captured(field.symbol)) {
- ()
- }
- else LVentry get at.state match {
- case Some(fields) if fields.contains(field.symbol) =>
- result += at.state
- case _ =>
- avoid += at
- for (state <- asyncStates) {
- if (contains(state.nextStates, at.state)) {
- lastUsagesOf0(field, state)
- }
- }
- }
- }
-
- lastUsagesOf0(field, at)
- result
- }
-
- val lastUsages: mutable.LinkedHashMap[Tree, StateSet] =
- mutable.LinkedHashMap(liftables.map(fld => fld -> lastUsagesOf(fld, finalState)): _*)
-
- if(AsyncUtils.verbose) {
- for ((fld, lastStates) <- lastUsages)
- AsyncUtils.vprintln(s"field ${fld.symbol.name} is last used in states ${lastStates.iterator.mkString(", ")}")
- }
-
- val nullOutAt: mutable.LinkedHashMap[Tree, StateSet] =
- for ((fld, lastStates) <- lastUsages) yield {
- var result = new StateSet
- lastStates.foreach(new IntConsumer { def accept(s: Int): Unit = {
- if (s != finalState.state) {
- val lastAsyncState = asyncStates.find(_.state == s).get
- val succNums = lastAsyncState.nextStates
- // all successor states that are not indirect predecessors
- // filter out successor states where the field is live at the entry
- var i = 0
- while (i < succNums.length) {
- val num = succNums(i)
- if (!isPred(num, s) && !LVentry(num).contains(fld.symbol))
- result += num
- i += 1
- }
- }
- }})
- (fld, result)
- }
-
- if(AsyncUtils.verbose) {
- for ((fld, killAt) <- nullOutAt)
- AsyncUtils.vprintln(s"field ${fld.symbol.name} should be nulled out in states ${killAt.iterator.mkString(", ")}")
- }
-
- nullOutAt
- }
-}
diff --git a/src/main/scala/scala/async/internal/ScalaConcurrentAsync.scala b/src/main/scala/scala/async/internal/ScalaConcurrentAsync.scala
deleted file mode 100644
index 0b2b3711..00000000
--- a/src/main/scala/scala/async/internal/ScalaConcurrentAsync.scala
+++ /dev/null
@@ -1,29 +0,0 @@
-/*
- * 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
-package async
-package internal
-
-import scala.reflect.macros.whitebox
-import scala.concurrent.Future
-
-object ScalaConcurrentAsync extends AsyncBase {
- type FS = ScalaConcurrentFutureSystem.type
- val futureSystem: FS = ScalaConcurrentFutureSystem
-
- override def asyncImpl[T: c.WeakTypeTag](c: whitebox.Context)
- (body: c.Expr[T])
- (execContext: c.Expr[futureSystem.ExecContext]): c.Expr[Future[T]] = {
- super.asyncImpl[T](c)(body)(execContext)
- }
-}
diff --git a/src/main/scala/scala/async/internal/StateAssigner.scala b/src/main/scala/scala/async/internal/StateAssigner.scala
deleted file mode 100644
index 5e6c45e7..00000000
--- a/src/main/scala/scala/async/internal/StateAssigner.scala
+++ /dev/null
@@ -1,23 +0,0 @@
-/*
- * 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.internal
-
-private[async] final class StateAssigner {
- private var current = StateAssigner.Initial
-
- def nextState(): Int = try current finally current += 1
-}
-
-object StateAssigner {
- final val Initial = 0
-}
diff --git a/src/main/scala/scala/async/internal/StateSet.scala b/src/main/scala/scala/async/internal/StateSet.scala
deleted file mode 100644
index 7b7c8124..00000000
--- a/src/main/scala/scala/async/internal/StateSet.scala
+++ /dev/null
@@ -1,38 +0,0 @@
-/*
- * 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.internal
-
-import java.util
-import java.util.function.{Consumer, IntConsumer}
-
-import scala.collection.JavaConverters.{asScalaIteratorConverter, iterableAsScalaIterableConverter}
-
-// Set for StateIds, which are either small positive integers or -symbolID.
-final class StateSet {
- private val bitSet = new java.util.BitSet()
- private val caseSet = new util.HashSet[Integer]()
- def +=(stateId: Int): Unit = if (storeInBitSet(stateId)) bitSet.set(stateId) else caseSet.add(stateId)
- def contains(stateId: Int): Boolean = if (storeInBitSet(stateId)) bitSet.get(stateId) else caseSet.contains(stateId)
- private def storeInBitSet(stateId: Int) = {
- stateId > 0 && stateId < 1024
- }
- def iterator: Iterator[Integer] = {
- bitSet.stream().iterator().asScala ++ caseSet.asScala.iterator
- }
- def foreach(f: IntConsumer): Unit = {
- bitSet.stream().forEach(f)
- caseSet.stream().forEach(new Consumer[Integer] {
- override def accept(value: Integer): Unit = f.accept(value)
- })
- }
-}
diff --git a/src/main/scala/scala/async/internal/TransformUtils.scala b/src/main/scala/scala/async/internal/TransformUtils.scala
deleted file mode 100644
index 1c1dd17a..00000000
--- a/src/main/scala/scala/async/internal/TransformUtils.scala
+++ /dev/null
@@ -1,590 +0,0 @@
-/*
- * 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.internal
-
-import scala.collection.immutable.ListMap
-import scala.collection.mutable
-import scala.collection.mutable.ListBuffer
-
-/**
- * Utilities used in both `ExprBuilder` and `AnfTransform`.
- */
-private[async] trait TransformUtils {
- self: AsyncMacro =>
-
- import c.universe._
- import c.internal._
- import decorators._
-
- object name extends asyncNames.AsyncName {
- def fresh(name: TermName): TermName = freshenIfNeeded(name)
- def fresh(name: String): String = c.freshName(name)
- }
-
- def maybeTry(block: Tree, catches: List[CaseDef], finalizer: Tree) = if (asyncBase.futureSystem.emitTryCatch) Try(block, catches, finalizer) else block
-
- def isAsync(fun: Tree) =
- fun.symbol == defn.Async_async
-
- def isAwait(fun: Tree) =
- fun.symbol == defn.Async_await
-
- def newBlock(stats: List[Tree], expr: Tree): Block = {
- Block(stats, expr)
- }
-
- def isLiteralUnit(t: Tree) = t match {
- case Literal(Constant(())) =>
- true
- case _ => false
- }
-
- def isPastTyper =
- c.universe.asInstanceOf[scala.reflect.internal.SymbolTable].isPastTyper
-
- // Copy pasted from TreeInfo in the compiler.
- // Using a quasiquote pattern like `case q"$fun[..$targs](...$args)" => is not
- // sufficient since https://github.com/scala/scala/pull/3656 as it doesn't match
- // constructor invocations.
- class Applied(val tree: Tree) {
- /** The tree stripped of the possibly nested applications.
- * The original tree if it's not an application.
- */
- def callee: Tree = {
- def loop(tree: Tree): Tree = tree match {
- case Apply(fn, _) => loop(fn)
- case tree => tree
- }
- loop(tree)
- }
-
- /** The `callee` unwrapped from type applications.
- * The original `callee` if it's not a type application.
- */
- def core: Tree = callee match {
- case TypeApply(fn, _) => fn
- case AppliedTypeTree(fn, _) => fn
- case tree => tree
- }
-
- /** The type arguments of the `callee`.
- * `Nil` if the `callee` is not a type application.
- */
- def targs: List[Tree] = callee match {
- case TypeApply(_, args) => args
- case AppliedTypeTree(_, args) => args
- case _ => Nil
- }
-
- /** (Possibly multiple lists of) value arguments of an application.
- * `Nil` if the `callee` is not an application.
- */
- def argss: List[List[Tree]] = {
- def loop(tree: Tree): List[List[Tree]] = tree match {
- case Apply(fn, args) => loop(fn) :+ args
- case _ => Nil
- }
- loop(tree)
- }
- }
-
- /** Returns a wrapper that knows how to destructure and analyze applications.
- */
- def dissectApplied(tree: Tree) = new Applied(tree)
-
- /** Destructures applications into important subparts described in `Applied` class,
- * namely into: core, targs and argss (in the specified order).
- *
- * Trees which are not applications are also accepted. Their callee and core will
- * be equal to the input, while targs and argss will be Nil.
- *
- * The provided extractors don't expose all the API of the `Applied` class.
- * For advanced use, call `dissectApplied` explicitly and use its methods instead of pattern matching.
- */
- object Applied {
- def apply(tree: Tree): Applied = new Applied(tree)
-
- def unapply(applied: Applied): Option[(Tree, List[Tree], List[List[Tree]])] =
- Some((applied.core, applied.targs, applied.argss))
-
- def unapply(tree: Tree): Option[(Tree, List[Tree], List[List[Tree]])] =
- unapply(dissectApplied(tree))
- }
- private lazy val Boolean_ShortCircuits: Set[Symbol] = {
- import definitions.BooleanClass
- def BooleanTermMember(name: String) = BooleanClass.typeSignature.member(TermName(name).encodedName)
- val Boolean_&& = BooleanTermMember("&&")
- val Boolean_|| = BooleanTermMember("||")
- Set(Boolean_&&, Boolean_||)
- }
-
- private def isByName(fun: Tree): ((Int, Int) => Boolean) = {
- if (Boolean_ShortCircuits contains fun.symbol) (i, j) => true
- else if (fun.tpe == null) (x, y) => false
- else {
- val paramLists = fun.tpe.paramLists
- val byNamess = paramLists.map(_.map(_.asTerm.isByNameParam))
- (i, j) => util.Try(byNamess(i)(j)).getOrElse(false)
- }
- }
- private def argName(fun: Tree): ((Int, Int) => TermName) = {
- val paramLists = fun.tpe.paramLists
- val namess = paramLists.map(_.map(_.name.toTermName))
- (i, j) => util.Try(namess(i)(j)).getOrElse(TermName(s"arg_${i}_${j}"))
- }
-
- object defn {
- def mkList_apply[A](args: List[Expr[A]]): Expr[List[A]] = {
- c.Expr(Apply(Ident(definitions.List_apply), args.map(_.tree)))
- }
-
- def mkList_contains[A](self: Expr[List[A]])(elem: Expr[Any]) = reify {
- self.splice.contains(elem.splice)
- }
-
- def mkAny_==(self: Expr[Any])(other: Expr[Any]) = reify {
- self.splice == other.splice
- }
-
- def mkTry_get[A](self: Expr[util.Try[A]]) = reify {
- self.splice.get
- }
-
- val NonFatalClass = rootMirror.staticModule("scala.util.control.NonFatal")
- val ThrowableClass = rootMirror.staticClass("java.lang.Throwable")
- lazy val Async_async = asyncBase.asyncMethod(c.universe)(c.macroApplication.symbol)
- lazy val Async_await = asyncBase.awaitMethod(c.universe)(c.macroApplication.symbol)
- val IllegalStateExceptionClass = rootMirror.staticClass("java.lang.IllegalStateException")
- }
-
- // `while(await(x))` ... or `do { await(x); ... } while(...)` contain an `If` that loops;
- // we must break that `If` into states so that it convert the label jump into a state machine
- // transition
- final def containsForiegnLabelJump(t: Tree): Boolean = {
- val labelDefs = t.collect {
- case ld: LabelDef => ld.symbol
- }.toSet
- val result = t.exists {
- case rt: RefTree => rt.symbol != null && isLabel(rt.symbol) && !(labelDefs contains rt.symbol)
- case _ => false
- }
- result
- }
-
- def isLabel(sym: Symbol): Boolean = {
- val LABEL = 1L << 17 // not in the public reflection API.
- (internal.flags(sym).asInstanceOf[Long] & LABEL) != 0L
- }
- def isSynth(sym: Symbol): Boolean = {
- val SYNTHETIC = 1 << 21 // not in the public reflection API.
- (internal.flags(sym).asInstanceOf[Long] & SYNTHETIC) != 0L
- }
- def symId(sym: Symbol): Int = {
- val symtab = this.c.universe.asInstanceOf[reflect.internal.SymbolTable]
- sym.asInstanceOf[symtab.Symbol].id
- }
- def substituteTrees(t: Tree, from: List[Symbol], to: List[Tree]): Tree = {
- val symtab = this.c.universe.asInstanceOf[reflect.internal.SymbolTable]
- val subst = new symtab.TreeSubstituter(from.asInstanceOf[List[symtab.Symbol]], to.asInstanceOf[List[symtab.Tree]])
- subst.transform(t.asInstanceOf[symtab.Tree]).asInstanceOf[Tree]
- }
-
-
- /** Map a list of arguments to:
- * - A list of argument Trees
- * - A list of auxillary results.
- *
- * The function unwraps and rewraps the `arg :_*` construct.
- *
- * @param args The original argument trees
- * @param f A function from argument (with '_*' unwrapped) and argument index to argument.
- * @tparam A The type of the auxillary result
- */
- private def mapArguments[A](args: List[Tree])(f: (Tree, Int) => (A, Tree)): (List[A], List[Tree]) = {
- args match {
- case args :+ Typed(tree, Ident(typeNames.WILDCARD_STAR)) =>
- val (a, argExprs :+ lastArgExpr) = (args :+ tree).zipWithIndex.map(f.tupled).unzip
- val exprs = argExprs :+ atPos(lastArgExpr.pos.makeTransparent)(Typed(lastArgExpr, Ident(typeNames.WILDCARD_STAR)))
- (a, exprs)
- case args =>
- args.zipWithIndex.map(f.tupled).unzip
- }
- }
-
- case class Arg(expr: Tree, isByName: Boolean, argName: TermName)
-
- /**
- * Transform a list of argument lists, producing the transformed lists, and lists of auxillary
- * results.
- *
- * The function `f` need not concern itself with varargs arguments e.g (`xs : _*`). It will
- * receive `xs`, and it's result will be re-wrapped as `f(xs) : _*`.
- *
- * @param fun The function being applied
- * @param argss The argument lists
- * @return (auxillary results, mapped argument trees)
- */
- def mapArgumentss[A](fun: Tree, argss: List[List[Tree]])(f: Arg => (A, Tree)): (List[List[A]], List[List[Tree]]) = {
- val isByNamess: (Int, Int) => Boolean = isByName(fun)
- val argNamess: (Int, Int) => TermName = argName(fun)
- argss.zipWithIndex.map { case (args, i) =>
- mapArguments[A](args) {
- (tree, j) => f(Arg(tree, isByNamess(i, j), argNamess(i, j)))
- }
- }.unzip
- }
-
-
- def statsAndExpr(tree: Tree): (List[Tree], Tree) = tree match {
- case Block(stats, expr) => (stats, expr)
- case _ => (List(tree), Literal(Constant(())))
- }
-
- def emptyConstructor: DefDef = {
- val emptySuperCall = Apply(Select(Super(This(typeNames.EMPTY), typeNames.EMPTY), termNames.CONSTRUCTOR), Nil)
- DefDef(NoMods, termNames.CONSTRUCTOR, List(), List(List()), TypeTree(), Block(List(emptySuperCall), Literal(Constant(()))))
- }
-
- def applied(className: String, types: List[Type]): AppliedTypeTree =
- AppliedTypeTree(Ident(rootMirror.staticClass(className)), types.map(TypeTree(_)))
-
- /** Descends into the regions of the tree that are subject to the
- * translation to a state machine by `async`. When a nested template,
- * function, or by-name argument is encountered, the descent stops,
- * and `nestedClass` etc are invoked.
- */
- trait AsyncTraverser extends Traverser {
- def nestedClass(classDef: ClassDef): Unit = {
- }
-
- def nestedModule(module: ModuleDef): Unit = {
- }
-
- def nestedMethod(defdef: DefDef): Unit = {
- }
-
- def byNameArgument(arg: Tree): Unit = {
- }
-
- def function(function: Function): Unit = {
- }
-
- def patMatFunction(tree: Match): Unit = {
- }
-
- override def traverse(tree: Tree): Unit = {
- tree match {
- case _ if isAsync(tree) =>
- // Under -Ymacro-expand:discard, used in the IDE, nested async blocks will be visible to the outer blocks
- case cd: ClassDef => nestedClass(cd)
- case md: ModuleDef => nestedModule(md)
- case dd: DefDef => nestedMethod(dd)
- case fun: Function => function(fun)
- case m@Match(EmptyTree, _) => patMatFunction(m) // Pattern matching anonymous function under -Xoldpatmat of after `restorePatternMatchingFunctions`
- case q"$fun[..$targs](...$argss)" if argss.nonEmpty =>
- val isInByName = isByName(fun)
- for ((args, i) <- argss.zipWithIndex) {
- for ((arg, j) <- args.zipWithIndex) {
- if (!isInByName(i, j)) traverse(arg)
- else byNameArgument(arg)
- }
- }
- traverse(fun)
- case _ => super.traverse(tree)
- }
- }
- }
-
- def transformAt(tree: Tree)(f: PartialFunction[Tree, (TypingTransformApi => Tree)]) = {
- typingTransform(tree)((tree, api) => {
- if (f.isDefinedAt(tree)) f(tree)(api)
- else api.default(tree)
- })
- }
-
- def toMultiMap[A, B](abs: Iterable[(A, B)]): mutable.LinkedHashMap[A, List[B]] = {
- // LinkedHashMap for stable order of results.
- val result = new mutable.LinkedHashMap[A, ListBuffer[B]]()
- for ((a, b) <- abs) {
- val buffer = result.getOrElseUpdate(a, new ListBuffer[B])
- buffer += b
- }
- result.map { case (a, b) => (a, b.toList) }
- }
-
- // Attributed version of `TreeGen#mkCastPreservingAnnotations`
- def mkAttributedCastPreservingAnnotations(tree: Tree, tp: Type): Tree = {
- atPos(tree.pos) {
- val casted = c.typecheck(gen.mkCast(tree, uncheckedBounds(withoutAnnotations(tp)).dealias))
- Typed(casted, TypeTree(tp)).setType(tp)
- }
- }
-
- def deconst(tp: Type): Type = tp match {
- case AnnotatedType(anns, underlying) => annotatedType(anns, deconst(underlying))
- case ExistentialType(quants, underlying) => existentialType(quants, deconst(underlying))
- case ConstantType(value) => deconst(value.tpe)
- case _ => tp
- }
-
- def withAnnotation(tp: Type, ann: Annotation): Type = withAnnotations(tp, List(ann))
-
- def withAnnotations(tp: Type, anns: List[Annotation]): Type = tp match {
- case AnnotatedType(existingAnns, underlying) => annotatedType(anns ::: existingAnns, underlying)
- case ExistentialType(quants, underlying) => existentialType(quants, withAnnotations(underlying, anns))
- case _ => annotatedType(anns, tp)
- }
-
- def withoutAnnotations(tp: Type): Type = tp match {
- case AnnotatedType(anns, underlying) => withoutAnnotations(underlying)
- case ExistentialType(quants, underlying) => existentialType(quants, withoutAnnotations(underlying))
- case _ => tp
- }
-
- def tpe(sym: Symbol): Type = {
- if (sym.isType) sym.asType.toType
- else sym.info
- }
-
- def thisType(sym: Symbol): Type = {
- if (sym.isClass) sym.asClass.thisPrefix
- else NoPrefix
- }
-
- private def derivedValueClassUnbox(cls: Symbol) =
- (cls.info.decls.find(sym => sym.isMethod && sym.asTerm.isParamAccessor) getOrElse NoSymbol)
-
- def mkZero(tp: Type): Tree = {
- val tpSym = tp.typeSymbol
- if (tpSym.isClass && tpSym.asClass.isDerivedValueClass) {
- val argZero = mkZero(derivedValueClassUnbox(tpSym).infoIn(tp).resultType)
- val baseType = tp.baseType(tpSym) // use base type here to dealias / strip phantom "tagged types" etc.
-
- // By explicitly attributing the types and symbols here, we subvert privacy.
- // Otherwise, ticket86PrivateValueClass would fail.
-
- // Approximately:
- // q"new ${valueClass}[$..targs](argZero)"
- val target: Tree = gen.mkAttributedSelect(
- c.typecheck(atMacroPos(
- New(TypeTree(baseType)))), tpSym.asClass.primaryConstructor)
-
- val zero = gen.mkMethodCall(target, argZero :: Nil)
- // restore the original type which we might otherwise have weakened with `baseType` above
- c.typecheck(atMacroPos(gen.mkCast(zero, tp)))
- } else {
- gen.mkZero(tp)
- }
- }
-
- // =====================================
- // Copy/Pasted from Scala 2.10.3. See scala/bug#7694
- private lazy val UncheckedBoundsClass =
- c.mirror.staticClass("scala.reflect.internal.annotations.uncheckedBounds")
- final def uncheckedBounds(tp: Type): Type =
- if ((tp.typeArgs.isEmpty && (tp match { case _: TypeRef => true; case _ => false}))) tp
- else withAnnotation(tp, Annotation(UncheckedBoundsClass.asType.toType, Nil, ListMap()))
- // =====================================
-
- /**
- * Efficiently decorate each subtree within `t` with the result of `t exists isAwait`,
- * and return a function that can be used on derived trees to efficiently test the
- * same condition.
- *
- * If the derived tree contains synthetic wrapper trees, these will be recursed into
- * in search of a sub tree that was decorated with the cached answer.
- */
- final def containsAwaitCached(t: Tree): Tree => Boolean = {
- if (c.macroApplication.symbol == null) return (t => false)
-
- def treeCannotContainAwait(t: Tree) = t match {
- case _: Ident | _: TypeTree | _: Literal => true
- case _ => isAsync(t)
- }
- def shouldAttach(t: Tree) = !treeCannotContainAwait(t)
- val symtab = c.universe.asInstanceOf[scala.reflect.internal.SymbolTable]
- def attachContainsAwait(t: Tree): Unit = if (shouldAttach(t)) {
- val t1 = t.asInstanceOf[symtab.Tree]
- t1.updateAttachment(ContainsAwait)
- t1.removeAttachment[NoAwait.type]
- }
- def attachNoAwait(t: Tree): Unit = if (shouldAttach(t)) {
- val t1 = t.asInstanceOf[symtab.Tree]
- t1.updateAttachment(NoAwait)
- }
- object markContainsAwaitTraverser extends Traverser {
- var stack: List[Tree] = Nil
-
- override def traverse(tree: Tree): Unit = {
- stack ::= tree
- try {
- if (isAsync(tree)) {
- ;
- } else {
- if (isAwait(tree))
- stack.foreach(attachContainsAwait)
- else
- attachNoAwait(tree)
- super.traverse(tree)
- }
- } finally stack = stack.tail
- }
- }
- markContainsAwaitTraverser.traverse(t)
-
- (t: Tree) => {
- object traverser extends Traverser {
- var containsAwait = false
- override def traverse(tree: Tree): Unit = {
- def castTree = tree.asInstanceOf[symtab.Tree]
- if (!castTree.hasAttachment[NoAwait.type]) {
- if (castTree.hasAttachment[ContainsAwait.type])
- containsAwait = true
- else if (!treeCannotContainAwait(t))
- super.traverse(tree)
- }
- }
- }
- traverser.traverse(t)
- traverser.containsAwait
- }
- }
-
- final def cleanupContainsAwaitAttachments(t: Tree): t.type = {
- val symtab = c.universe.asInstanceOf[scala.reflect.internal.SymbolTable]
- t.foreach {t =>
- t.asInstanceOf[symtab.Tree].removeAttachment[ContainsAwait.type]
- t.asInstanceOf[symtab.Tree].removeAttachment[NoAwait.type]
- }
- t
- }
-
- // First modification to translated patterns:
- // - Set the type of label jumps to `Unit`
- // - Propagate this change to trees known to directly enclose them:
- // ``If` / `Block`) adjust types of enclosing
- final def adjustTypeOfTranslatedPatternMatches(t: Tree, owner: Symbol): Tree = {
- import definitions.UnitTpe
- typingTransform(t, owner) {
- (tree, api) =>
- tree match {
- case LabelDef(name, params, rhs) =>
- val rhs1 = api.recur(rhs)
- if (rhs1.tpe =:= UnitTpe) {
- internal.setInfo(tree.symbol, internal.methodType(tree.symbol.info.paramLists.head, UnitTpe))
- treeCopy.LabelDef(tree, name, params, rhs1)
- } else {
- treeCopy.LabelDef(tree, name, params, rhs1)
- }
- case Block(stats, expr) =>
- val stats1 = stats map api.recur
- val expr1 = api.recur(expr)
- if (expr1.tpe =:= UnitTpe)
- internal.setType(treeCopy.Block(tree, stats1, expr1), UnitTpe)
- else
- treeCopy.Block(tree, stats1, expr1)
- case If(cond, thenp, elsep) =>
- val cond1 = api.recur(cond)
- val thenp1 = api.recur(thenp)
- val elsep1 = api.recur(elsep)
- if (thenp1.tpe =:= definitions.UnitTpe && elsep.tpe =:= UnitTpe)
- internal.setType(treeCopy.If(tree, cond1, thenp1, elsep1), UnitTpe)
- else
- treeCopy.If(tree, cond1, thenp1, elsep1)
- case Apply(fun, args) if isLabel(fun.symbol) =>
- internal.setType(treeCopy.Apply(tree, api.recur(fun), args map api.recur), UnitTpe)
- case vd @ ValDef(mods, name, tpt, rhs) if isCaseTempVal(vd.symbol) =>
- def addUncheckedBounds(t: Tree) = {
- typingTransform(t, owner) {
- (tree, api) =>
- if (tree.tpe == null) tree else internal.setType(api.default(tree), uncheckedBoundsIfNeeded(tree.tpe))
- }
-
- }
- val uncheckedRhs = addUncheckedBounds(api.recur(rhs))
- val uncheckedTpt = addUncheckedBounds(tpt)
- internal.setInfo(vd.symbol, uncheckedBoundsIfNeeded(vd.symbol.info))
- treeCopy.ValDef(vd, mods, name, uncheckedTpt, uncheckedRhs)
- case t => api.default(t)
- }
- }
- }
-
- private def isExistentialSkolem(s: Symbol) = {
- val EXISTENTIAL: Long = 1L << 35
- internal.isSkolem(s) && (internal.flags(s).asInstanceOf[Long] & EXISTENTIAL) != 0
- }
- private def isCaseTempVal(s: Symbol) = {
- s.isTerm && s.asTerm.isVal && s.isSynthetic && s.name.toString.startsWith("x")
- }
-
- def uncheckedBoundsIfNeeded(t: Type): Type = {
- var quantified: List[Symbol] = Nil
- var badSkolemRefs: List[Symbol] = Nil
- t.foreach {
- case et: ExistentialType =>
- quantified :::= et.quantified
- case TypeRef(pre, sym, args) =>
- val illScopedSkolems = args.map(_.typeSymbol).filter(arg => isExistentialSkolem(arg) && !quantified.contains(arg))
- badSkolemRefs :::= illScopedSkolems
- case _ =>
- }
- if (badSkolemRefs.isEmpty) t
- else t.map {
- case tp @ TypeRef(pre, sym, args) if args.exists(a => badSkolemRefs.contains(a.typeSymbol)) =>
- uncheckedBounds(tp)
- case t => t
- }
- }
-
-
- final def mkMutableField(tpt: Type, name: TermName, init: Tree): List[Tree] = {
- if (isPastTyper) {
- // If we are running after the typer phase (ie being called from a compiler plugin)
- // we have to create the trio of members manually.
- val ACCESSOR = (1L << 27).asInstanceOf[FlagSet]
- val STABLE = (1L << 22).asInstanceOf[FlagSet]
- val field = ValDef(Modifiers(Flag.MUTABLE | Flag.PRIVATE | Flag.LOCAL), TermName(name.toString + " "), TypeTree(tpt), init)
- val getter = DefDef(Modifiers(ACCESSOR | STABLE), name, Nil, Nil, TypeTree(tpt), Select(This(typeNames.EMPTY), field.name))
- val setter = DefDef(Modifiers(ACCESSOR), TermName(name.toString + "_="), Nil, List(List(ValDef(NoMods, TermName("x"), TypeTree(tpt), EmptyTree))), TypeTree(definitions.UnitTpe), Assign(Select(This(typeNames.EMPTY), field.name), Ident(TermName("x"))))
- field :: getter :: setter :: Nil
- } else {
- val result = ValDef(NoMods, name, TypeTree(tpt), init)
- result :: Nil
- }
- }
-
- def deriveLabelDef(ld: LabelDef, applyToRhs: Tree => Tree): LabelDef = {
- val rhs2 = applyToRhs(ld.rhs)
- val ld2 = treeCopy.LabelDef(ld, ld.name, ld.params, rhs2)
- if (ld eq ld2) ld
- else {
- val info2 = ld2.symbol.info match {
- case MethodType(params, p) => internal.methodType(params, rhs2.tpe)
- case t => t
- }
- internal.setInfo(ld2.symbol, info2)
- ld2
- }
- }
- object MatchEnd {
- def unapply(t: Tree): Option[LabelDef] = t match {
- case ValDef(_, _, _, t) => unapply(t)
- case ld: LabelDef if ld.name.toString.startsWith("matchEnd") => Some(ld)
- case _ => None
- }
- }
-}
-
-case object ContainsAwait
-case object NoAwait
diff --git a/src/test/scala/scala/async/FutureSpec.scala b/src/test/scala/scala/async/FutureSpec.scala
new file mode 100644
index 00000000..d692f621
--- /dev/null
+++ b/src/test/scala/scala/async/FutureSpec.scala
@@ -0,0 +1,541 @@
+/*
+ * 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.concurrent.ConcurrentHashMap
+
+import org.junit.Test
+
+import scala.async.Async.{async, await}
+import scala.async.TestUtil._
+import scala.concurrent.duration.Duration.Inf
+import scala.concurrent.duration._
+import scala.concurrent.{ExecutionContext, Future, Promise, _}
+import scala.language.postfixOps
+import scala.util.{Failure, Success}
+
+class FutureSpec {
+
+ def testAsync(s: String)(implicit ec: ExecutionContext): Future[String] = s match {
+ case "Hello" => Future { "World" }
+ case "Failure" => Future.failed(new RuntimeException("Expected exception; to test fault-tolerance"))
+ case "NoReply" => Promise[String]().future
+ }
+
+ val defaultTimeout = 5 seconds
+
+ /* future specification */
+
+ @Test def `A future with custom ExecutionContext should handle Throwables`(): Unit = {
+ val ms = new ConcurrentHashMap[Throwable, Unit]
+ implicit val ec = scala.concurrent.ExecutionContext.fromExecutor(new java.util.concurrent.ForkJoinPool(), {
+ t =>
+ ms.put(t, ())
+ })
+
+ class ThrowableTest(m: String) extends Throwable(m)
+
+ val f1 = Future[Any] {
+ throw new ThrowableTest("test")
+ }
+
+ intercept[ThrowableTest] {
+ Await.result(f1, defaultTimeout)
+ }
+
+ val latch = new TestLatch
+ val f2 = Future {
+ Await.ready(latch, 5 seconds)
+ "success"
+ }
+ val f3 = async {
+ val s = await(f2)
+ s.toUpperCase
+ }
+
+ f2 foreach { _ => throw new ThrowableTest("dispatcher foreach") }
+ f2 onComplete { case Success(_) => throw new ThrowableTest("dispatcher receive") case _ => }
+
+ latch.open()
+
+ Await.result(f2, defaultTimeout) mustBe ("success")
+
+ f2 foreach { _ => throw new ThrowableTest("current thread foreach") }
+ f2 onComplete { case Success(_) => throw new ThrowableTest("current thread receive"); case _ => }
+
+ Await.result(f3, defaultTimeout) mustBe ("SUCCESS")
+
+ val waiting = Future {
+ Thread.sleep(1000)
+ }
+ Await.ready(waiting, 2000 millis)
+
+ ms.size mustBe (4)
+ }
+
+ import ExecutionContext.Implicits._
+
+ @Test def `A future with global ExecutionContext should compose with for-comprehensions`(): Unit = {
+
+ def asyncInt(x: Int) = Future { (x * 2).toString }
+ val future0 = Future[Any] {
+ "five!".length
+ }
+
+ val future1 = async {
+ val a = await(future0.mapTo[Int]) // returns 5
+ val b = await(asyncInt(a)) // returns "10"
+ val c = await(asyncInt(7)) // returns "14"
+ b + "-" + c
+ }
+
+ val future2 = async {
+ val a = await(future0.mapTo[Int])
+ val b = await((Future { (a * 2).toString }).mapTo[Int])
+ val c = await(Future { (7 * 2).toString })
+ b + "-" + c
+ }
+
+ Await.result(future1, defaultTimeout) mustBe ("10-14")
+ //assert(checkType(future1, manifest[String]))
+ intercept[ClassCastException] { Await.result(future2, defaultTimeout) }
+ }
+
+ //TODO this is not yet supported by Async
+ @Test def `support pattern matching within a for-comprehension`(): Unit = {
+ case class Req[T](req: T)
+ case class Res[T](res: T)
+ def asyncReq[T](req: Req[T]) = (req: @unchecked) match {
+ case Req(s: String) => Future { Res(s.length) }
+ case Req(i: Int) => Future { Res((i * 2).toString) }
+ }
+
+ val future1 = for {
+ Res(a: Int) <- asyncReq(Req("Hello"))
+ Res(b: String) <- asyncReq(Req(a))
+ Res(c: String) <- asyncReq(Req(7))
+ } yield b + "-" + c
+
+ val future2 = for {
+ Res(a: Int) <- asyncReq(Req("Hello"))
+ Res(b: Int) <- asyncReq(Req(a))
+ Res(c: Int) <- asyncReq(Req(7))
+ } yield b + "-" + c
+
+ Await.result(future1, defaultTimeout) mustBe ("10-14")
+ intercept[NoSuchElementException] { Await.result(future2, defaultTimeout) }
+ }
+
+ @Test def mini(): Unit = {
+ val future4 = async {
+ await(Future.successful(0)).toString
+ }
+ Await.result(future4, defaultTimeout)
+ }
+
+ @Test def `recover from exceptions`(): Unit = {
+ val future1 = Future(5)
+ val future2 = async { await(future1) / 0 }
+ val future3 = async { await(future2).toString }
+
+ val future1Recovered = future1 recover {
+ case e: ArithmeticException => 0
+ }
+ val future4 = async { await(future1Recovered).toString }
+
+ val future2Recovered = future2 recover {
+ case e: ArithmeticException => 0
+ }
+ val future5 = async { await(future2Recovered).toString }
+
+ val future2Recovered2 = future2 recover {
+ case e: MatchError => 0
+ }
+ val future6 = async { await(future2Recovered2).toString }
+
+ val future7 = future3 recover {
+ case e: ArithmeticException => "You got ERROR"
+ }
+
+ val future8 = testAsync("Failure")
+ val future9 = testAsync("Failure") recover {
+ case e: RuntimeException => "FAIL!"
+ }
+ val future10 = testAsync("Hello") recover {
+ case e: RuntimeException => "FAIL!"
+ }
+ val future11 = testAsync("Failure") recover {
+ case _ => "Oops!"
+ }
+
+ Await.result(future1, defaultTimeout) mustBe (5)
+ intercept[ArithmeticException] { Await.result(future2, defaultTimeout) }
+ intercept[ArithmeticException] { Await.result(future3, defaultTimeout) }
+ Await.result(future4, defaultTimeout) mustBe ("5")
+ Await.result(future5, defaultTimeout) mustBe ("0")
+ intercept[ArithmeticException] { Await.result(future6, defaultTimeout) }
+ Await.result(future7, defaultTimeout) mustBe ("You got ERROR")
+ intercept[RuntimeException] { Await.result(future8, defaultTimeout) }
+ Await.result(future9, defaultTimeout) mustBe ("FAIL!")
+ Await.result(future10, defaultTimeout) mustBe ("World")
+ Await.result(future11, defaultTimeout) mustBe ("Oops!")
+ }
+
+ @Test def `recoverWith from exceptions`(): Unit = {
+ val o = new IllegalStateException("original")
+ val r = new IllegalStateException("recovered")
+
+ intercept[IllegalStateException] {
+ val failed = Future.failed[String](o) recoverWith {
+ case _ if false == true => Future.successful("yay!")
+ }
+ Await.result(failed, defaultTimeout)
+ } mustBe (o)
+
+ val recovered = Future.failed[String](o) recoverWith {
+ case _ => Future.successful("yay!")
+ }
+ Await.result(recovered, defaultTimeout) mustBe ("yay!")
+
+ intercept[IllegalStateException] {
+ val refailed = Future.failed[String](o) recoverWith {
+ case _ => Future.failed[String](r)
+ }
+ Await.result(refailed, defaultTimeout)
+ } mustBe (r)
+ }
+
+ @Test def `andThen like a boss`(): Unit = {
+ val q = new java.util.concurrent.LinkedBlockingQueue[Int]
+ for (i <- 1 to 1000) {
+ val chained = Future {
+ q.add(1); 3
+ } andThen {
+ case _ => q.add(2)
+ } andThen {
+ case Success(0) => q.add(Int.MaxValue)
+ } andThen {
+ case _ => q.add(3);
+ }
+ Await.result(chained, defaultTimeout) mustBe (3)
+ q.poll() mustBe (1)
+ q.poll() mustBe (2)
+ q.poll() mustBe (3)
+ q.clear()
+ }
+ }
+
+ @Test def `firstCompletedOf`(): Unit = {
+ def futures = Vector.fill[Future[Int]](10) {
+ Promise[Int]().future
+ } :+ Future.successful[Int](5)
+
+ Await.result(Future.firstCompletedOf(futures), defaultTimeout) mustBe (5)
+ Await.result(Future.firstCompletedOf(futures.iterator), defaultTimeout) mustBe (5)
+ }
+
+ @Test def `find`(): Unit = {
+ val futures = for (i <- 1 to 10) yield Future {
+ i
+ }
+
+ val result = Future.find[Int](futures)(_ == 3)
+ Await.result(result, defaultTimeout) mustBe (Some(3))
+
+ val notFound = Future.find[Int](futures)(_ == 11)
+ Await.result(notFound, defaultTimeout) mustBe (None)
+ }
+
+ @Test def `zip`(): Unit = {
+ val timeout = 10000 millis
+ val f = new IllegalStateException("test")
+ intercept[IllegalStateException] {
+ val failed = Future.failed[String](f) zip Future.successful("foo")
+ Await.result(failed, timeout)
+ } mustBe (f)
+
+ intercept[IllegalStateException] {
+ val failed = Future.successful("foo") zip Future.failed[String](f)
+ Await.result(failed, timeout)
+ } mustBe (f)
+
+ intercept[IllegalStateException] {
+ val failed = Future.failed[String](f) zip Future.failed[String](f)
+ Await.result(failed, timeout)
+ } mustBe (f)
+
+ val successful = Future.successful("foo") zip Future.successful("foo")
+ Await.result(successful, timeout) mustBe (("foo", "foo"))
+ }
+
+ @Test def `fold`(): Unit = {
+ val timeout = 10000 millis
+ def async(add: Int, wait: Int) = Future {
+ Thread.sleep(wait)
+ add
+ }
+
+ val futures = (0 to 9) map {
+ idx => async(idx, idx * 20)
+ }
+ val folded = Future.foldLeft(futures)(0)(_ + _)
+ Await.result(folded, timeout) mustBe (45)
+
+ val futuresit = (0 to 9) map {
+ idx => async(idx, idx * 20)
+ }
+ val foldedit = Future.foldLeft(futures)(0)(_ + _)
+ Await.result(foldedit, timeout) mustBe (45)
+ }
+
+ @Test def `fold by composing`(): Unit = {
+ val timeout = 10000 millis
+ def async(add: Int, wait: Int) = Future {
+ Thread.sleep(wait)
+ add
+ }
+ def futures = (0 to 9) map {
+ idx => async(idx, idx * 20)
+ }
+ val folded = futures.foldLeft(Future(0)) {
+ case (fr, fa) => for (r <- fr; a <- fa) yield (r + a)
+ }
+ Await.result(folded, timeout) mustBe (45)
+ }
+
+ @Test def `fold with an exception`(): Unit = {
+ val timeout = 10000 millis
+ def async(add: Int, wait: Int) = Future {
+ Thread.sleep(wait)
+ if (add == 6) throw new IllegalArgumentException("shouldFoldResultsWithException: expected")
+ add
+ }
+ def futures = (0 to 9) map {
+ idx => async(idx, idx * 10)
+ }
+ val folded = Future.foldLeft(futures)(0)(_ + _)
+ intercept[IllegalArgumentException] {
+ Await.result(folded, timeout)
+ }.getMessage mustBe ("shouldFoldResultsWithException: expected")
+ }
+
+ @Test def `fold mutable zeroes safely`(): Unit = {
+ import scala.collection.mutable.ArrayBuffer
+ def test(testNumber: Int): Unit = {
+ val fs = (0 to 1000) map (i => Future(i))
+ val f = Future.foldLeft(fs)(ArrayBuffer.empty[AnyRef]) {
+ case (l, i) if i % 2 == 0 => l += i.asInstanceOf[AnyRef]
+ case (l, _) => l
+ }
+ val result = Await.result(f.mapTo[ArrayBuffer[Int]], 10000 millis).sum
+
+ assert(result == 250500)
+ }
+
+ (1 to 100) foreach test //Make sure it tries to provoke the problem
+ }
+
+ @Test def `return zero value if folding empty list`(): Unit = {
+ val zero = Future.foldLeft(List[Future[Int]]())(0)(_ + _)
+ Await.result(zero, defaultTimeout) mustBe (0)
+ }
+
+ @Test def `shouldReduceResults`(): Unit = {
+ def async(idx: Int) = Future {
+ Thread.sleep(idx * 20)
+ idx
+ }
+ val timeout = 10000 millis
+
+ val futures = (0 to 9) map { async }
+ val reduced = Future.reduceLeft(futures)(_ + _)
+ Await.result(reduced, timeout) mustBe (45)
+
+ val futuresit = (0 to 9) map { async }
+ val reducedit = Future.reduceLeft(futuresit)(_ + _)
+ Await.result(reducedit, timeout) mustBe (45)
+ }
+
+ @Test def `shouldReduceResultsWithException`(): Unit = {
+ def async(add: Int, wait: Int) = Future {
+ Thread.sleep(wait)
+ if (add == 6) throw new IllegalArgumentException("shouldFoldResultsWithException: expected")
+ else add
+ }
+ val timeout = 10000 millis
+ def futures = (1 to 10) map {
+ idx => async(idx, idx * 10)
+ }
+ val failed = Future.reduceLeft(futures)(_ + _)
+ intercept[IllegalArgumentException] {
+ Await.result(failed, timeout)
+ }.getMessage mustBe ("shouldFoldResultsWithException: expected")
+ }
+
+ @Test def `shouldReduceThrowNSEEOnEmptyInput`(): Unit = {
+ intercept[java.util.NoSuchElementException] {
+ val emptyreduced = Future.reduceLeft(List[Future[Int]]())(_ + _)
+ Await.result(emptyreduced, defaultTimeout)
+ }
+ }
+
+ @Test def `shouldTraverseFutures`(): Unit = {
+ object counter {
+ var count = -1
+ def incAndGet() = counter.synchronized {
+ count += 2
+ count
+ }
+ }
+
+ val oddFutures = List.fill(100)(Future { counter.incAndGet() }).iterator
+ val traversed = Future.sequence(oddFutures)
+ Await.result(traversed, defaultTimeout).sum mustBe (10000)
+
+ val list = (1 to 100).toList
+ val traversedList = Future.traverse(list)(x => Future(x * 2 - 1))
+ Await.result(traversedList, defaultTimeout).sum mustBe (10000)
+
+ val iterator = (1 to 100).toList.iterator
+ val traversedIterator = Future.traverse(iterator)(x => Future(x * 2 - 1))
+ Await.result(traversedIterator, defaultTimeout).sum mustBe (10000)
+ }
+
+ @Test def `shouldBlockUntilResult`(): Unit = {
+ val latch = new TestLatch
+
+ val f = Future {
+ Await.ready(latch, 5 seconds)
+ 5
+ }
+ val f2 = Future {
+ val res = Await.result(f, Inf)
+ res + 9
+ }
+
+ intercept[TimeoutException] {
+ Await.ready(f2, 100 millis)
+ }
+
+ latch.open()
+
+ Await.result(f2, defaultTimeout) mustBe (14)
+
+ val f3 = Future {
+ Thread.sleep(100)
+ 5
+ }
+
+ intercept[TimeoutException] {
+ Await.ready(f3, 0 millis)
+ }
+ }
+
+ @Test def `run callbacks async`(): Unit = {
+ val latch = Vector.fill(10)(new TestLatch)
+
+ val f1 = Future {
+ latch(0).open()
+ Await.ready(latch(1), TestLatch.DefaultTimeout)
+ "Hello"
+ }
+ val f2 = async {
+ val s = await(f1)
+ latch(2).open()
+ Await.ready(latch(3), TestLatch.DefaultTimeout)
+ s.length
+ }
+ for (_ <- f2) latch(4).open()
+
+ Await.ready(latch(0), TestLatch.DefaultTimeout)
+
+ f1.isCompleted mustBe (false)
+ f2.isCompleted mustBe (false)
+
+ latch(1).open()
+ Await.ready(latch(2), TestLatch.DefaultTimeout)
+
+ f1.isCompleted mustBe (true)
+ f2.isCompleted mustBe (false)
+
+ val f3 = async {
+ val s = await(f1)
+ latch(5).open()
+ Await.ready(latch(6), TestLatch.DefaultTimeout)
+ s.length * 2
+ }
+ for (_ <- f3) latch(3).open()
+
+ Await.ready(latch(5), TestLatch.DefaultTimeout)
+
+ f3.isCompleted mustBe (false)
+
+ latch(6).open()
+ Await.ready(latch(4), TestLatch.DefaultTimeout)
+
+ f2.isCompleted mustBe (true)
+ f3.isCompleted mustBe (true)
+
+ val p1 = Promise[String]()
+ val f4 = async {
+ val s = await(p1.future)
+ latch(7).open()
+ Await.ready(latch(8), TestLatch.DefaultTimeout)
+ s.length
+ }
+ for (_ <- f4) latch(9).open()
+
+ p1.future.isCompleted mustBe (false)
+ f4.isCompleted mustBe (false)
+
+ p1 complete Success("Hello")
+
+ Await.ready(latch(7), TestLatch.DefaultTimeout)
+
+ p1.future.isCompleted mustBe (true)
+ f4.isCompleted mustBe (false)
+
+ latch(8).open()
+ Await.ready(latch(9), TestLatch.DefaultTimeout)
+
+ Await.ready(f4, defaultTimeout).isCompleted mustBe (true)
+ }
+
+ @Test def `should not deadlock with nested await (ticket 1313)`(): Unit = {
+ val simple = async {
+ await { Future { } }
+ val unit = Future(())
+ val umap = unit map { _ => () }
+ Await.result(umap, Inf)
+ }
+ Await.ready(simple, Inf).isCompleted mustBe (true)
+
+ val l1, l2 = new TestLatch
+ val complex = async {
+ await{ Future { } }
+ blocking {
+ val nested = Future(())
+ for (_ <- nested) l1.open()
+ Await.ready(l1, TestLatch.DefaultTimeout) // make sure nested is completed
+ for (_ <- nested) l2.open()
+ Await.ready(l2, TestLatch.DefaultTimeout)
+ }
+ }
+ Await.ready(complex, defaultTimeout).isCompleted mustBe (true)
+ }
+
+ @Test def `should not throw when Await.ready`(): Unit = {
+ val expected = try Success(5 / 0) catch { case a: ArithmeticException => Failure(a) }
+ val f = async { await(Future(5)) / 0 }
+ Await.ready(f, defaultTimeout).value.get.toString mustBe expected.toString
+ }
+}
diff --git a/src/test/scala/scala/async/SmokeTest.scala b/src/test/scala/scala/async/SmokeTest.scala
new file mode 100644
index 00000000..204481d1
--- /dev/null
+++ b/src/test/scala/scala/async/SmokeTest.scala
@@ -0,0 +1,32 @@
+/*
+ * 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 org.junit.{Assert, Test}
+
+import scala.async.Async._
+import scala.concurrent._
+import scala.concurrent.ExecutionContext.Implicits.global
+import scala.concurrent.Future.{successful => f}
+import scala.concurrent.duration.Duration
+
+class SmokeTest {
+ def block[T](f: Future[T]): T = Await.result(f, Duration.Inf)
+
+ @Test def testBasic(): Unit = {
+ val result = async {
+ await(f(1)) + await(f(2))
+ }
+ Assert.assertEquals(3, block(result))
+ }
+
+}
diff --git a/src/test/scala/scala/async/TestLatch.scala b/src/test/scala/scala/async/TestLatch.scala
deleted file mode 100644
index 011a8323..00000000
--- a/src/test/scala/scala/async/TestLatch.scala
+++ /dev/null
@@ -1,48 +0,0 @@
-/*
- * 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 concurrent.{CanAwait, Awaitable}
-import concurrent.duration.Duration
-import java.util.concurrent.{TimeoutException, CountDownLatch, TimeUnit}
-
-object TestLatch {
- val DefaultTimeout = Duration(5, TimeUnit.SECONDS)
-
- def apply(count: Int = 1) = new TestLatch(count)
-}
-
-
-class TestLatch(count: Int = 1) extends Awaitable[Unit] {
- private var latch = new CountDownLatch(count)
-
- def countDown() = latch.countDown()
-
- def isOpen: Boolean = latch.getCount == 0
-
- def open() = while (!isOpen) countDown()
-
- def reset() = latch = new CountDownLatch(count)
-
- @throws(classOf[TimeoutException])
- def ready(atMost: Duration)(implicit permit: CanAwait) = {
- val opened = latch.await(atMost.toNanos, TimeUnit.NANOSECONDS)
- if (!opened) throw new TimeoutException(s"Timeout of ${(atMost.toString)}.")
- this
- }
-
- @throws(classOf[Exception])
- def result(atMost: Duration)(implicit permit: CanAwait): Unit = {
- ready(atMost)
- }
-}
diff --git a/src/test/scala/scala/async/TestUtil.scala b/src/test/scala/scala/async/TestUtil.scala
new file mode 100644
index 00000000..ac44de96
--- /dev/null
+++ b/src/test/scala/scala/async/TestUtil.scala
@@ -0,0 +1,66 @@
+/*
+ * 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.concurrent.{CountDownLatch, TimeUnit}
+
+import scala.concurrent.{Awaitable, CanAwait, TimeoutException}
+import scala.concurrent.duration.{Duration, FiniteDuration}
+import scala.reflect.{ClassTag, classTag}
+
+object TestUtil {
+ object TestLatch {
+ val DefaultTimeout: FiniteDuration = Duration(5, TimeUnit.SECONDS)
+
+ def apply(count: Int = 1) = new TestLatch(count)
+ }
+
+ class TestLatch(count: Int = 1) extends Awaitable[Unit] {
+ private var latch = new CountDownLatch(count)
+
+ def countDown(): Unit = latch.countDown()
+
+ def isOpen: Boolean = latch.getCount == 0
+
+ def open(): Unit = while (!isOpen) countDown()
+
+ def reset(): Unit = latch = new CountDownLatch(count)
+
+ @throws(classOf[TimeoutException])
+ def ready(atMost: Duration)(implicit permit: CanAwait): TestLatch.this.type = {
+ val opened = latch.await(atMost.toNanos, TimeUnit.NANOSECONDS)
+ if (!opened) throw new TimeoutException(s"Timeout of ${(atMost.toString)}.")
+ this
+ }
+
+ @throws(classOf[Exception])
+ def result(atMost: Duration)(implicit permit: CanAwait): Unit = {
+ ready(atMost)
+ }
+ }
+ def intercept[T <: Throwable : ClassTag](body: => Any): T = {
+ try {
+ body
+ throw new Exception(s"Exception of type ${classTag[T]} was not thrown")
+ } catch {
+ case t: Throwable =>
+ if (!classTag[T].runtimeClass.isAssignableFrom(t.getClass)) throw t
+ else t.asInstanceOf[T]
+ }
+ }
+
+ implicit class objectops(obj: Any) {
+ def mustBe(other: Any): Unit = assert(obj == other, obj + " is not " + other)
+
+ def mustEqual(other: Any): Unit = mustBe(other)
+ }
+}
diff --git a/src/test/scala/scala/async/TreeInterrogation.scala b/src/test/scala/scala/async/TreeInterrogation.scala
deleted file mode 100644
index 2317d088..00000000
--- a/src/test/scala/scala/async/TreeInterrogation.scala
+++ /dev/null
@@ -1,112 +0,0 @@
-/*
- * 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 org.junit.Test
-import scala.async.internal.AsyncId
-import AsyncId._
-import tools.reflect.ToolBox
-
-class TreeInterrogation {
- @Test
- def `a minimal set of vals are lifted to vars`(): Unit = {
- val cm = reflect.runtime.currentMirror
- val tb = mkToolbox(s"-cp $toolboxClasspath")
- val tree = tb.parse(
- """| import _root_.scala.async.internal.AsyncId._
- | async {
- | val x = await(1)
- | val y = x * 2
- | def foo(a: Int) = { def nested = 0; a } // don't lift `nested`.
- | val z = await(x * 3)
- | foo(z)
- | z
- | }""".stripMargin)
- val tree1 = tb.typeCheck(tree)
-
- //println(cm.universe.show(tree1))
-
- import tb.u._
- val functions = tree1.collect {
- case f: Function => f
- case t: Template => t
- }
- functions.size mustBe 1
-
- val varDefs = tree1.collect {
- case vd @ ValDef(mods, name, _, _) if mods.hasFlag(Flag.MUTABLE) && vd.symbol.owner.isClass => name
- }
- varDefs.map(_.decoded.trim).toSet.toList.sorted mustStartWith (List("await$async$", "await$async", "state$async"))
-
- val defDefs = tree1.collect {
- case t: Template =>
- val stats: List[Tree] = t.body
- stats.collect {
- case dd : DefDef
- if !dd.symbol.isImplementationArtifact
- && !dd.symbol.asTerm.isAccessor && !dd.symbol.asTerm.isSetter => dd.name
- }
- }.flatten
- defDefs.map(_.decoded.trim) mustStartWith List("foo$async$", "", "apply", "apply")
- }
-}
-
-object TreeInterrogationApp extends App {
- def withDebug[T](t: => T): T = {
- def set(level: String, value: Boolean) = System.setProperty(s"scala.async.$level", value.toString)
- val levels = Seq("trace", "debug")
- def setAll(value: Boolean) = levels.foreach(set(_, value))
-
- setAll(value = true)
- try t finally setAll(value = false)
- }
-
- withDebug {
- val cm = reflect.runtime.currentMirror
- val tb = mkToolbox(s"-cp ${toolboxClasspath} -Xprint:typer")
- import scala.async.internal.AsyncId._
- val tree = tb.parse(
- """
- | import scala.async.internal.AsyncId._
- | trait QBound { type D; trait ResultType { case class Inner() }; def toResult: ResultType = ??? }
- | trait QD[Q <: QBound] {
- | val operation: Q
- | type D = operation.D
- | }
- |
- | async {
- | if (!"".isEmpty) {
- | val treeResult = null.asInstanceOf[QD[QBound]]
- | await(0)
- | val y = treeResult.operation
- | type RD = treeResult.operation.D
- | (null: Object) match {
- | case (_, _: RD) => ???
- | case _ => val x = y.toResult; x.Inner()
- | }
- | await(1)
- | (y, null.asInstanceOf[RD])
- | ""
- | }
- |
- | }
- |
- | """.stripMargin)
- println(tree)
- val tree1 = tb.typeCheck(tree.duplicate)
- println(cm.universe.show(tree1))
-
- println(tb.eval(tree))
- }
-
-}
diff --git a/src/test/scala/scala/async/neg/LocalClasses0Spec.scala b/src/test/scala/scala/async/neg/LocalClasses0Spec.scala
deleted file mode 100644
index bbf3c11e..00000000
--- a/src/test/scala/scala/async/neg/LocalClasses0Spec.scala
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
- * 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
-package neg
-
-import org.junit.Test
-import scala.async.internal.AsyncId
-
-class LocalClasses0Spec {
- @Test
- def localClassCrashIssue16(): Unit = {
- import AsyncId.{async, await}
- async {
- class B { def f = 1 }
- await(new B()).f
- } mustBe 1
- }
-
- @Test
- def nestedCaseClassAndModuleAllowed(): Unit = {
- import AsyncId.{await, async}
- async {
- trait Base { def base = 0}
- await(0)
- case class Person(name: String) extends Base
- val fut = async { "bob" }
- val x = Person(await(fut))
- x.base
- x.name
- } mustBe "bob"
- }
-}
diff --git a/src/test/scala/scala/async/neg/NakedAwait.scala b/src/test/scala/scala/async/neg/NakedAwait.scala
deleted file mode 100644
index 4dbd0fa1..00000000
--- a/src/test/scala/scala/async/neg/NakedAwait.scala
+++ /dev/null
@@ -1,183 +0,0 @@
-/*
- * 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
-package neg
-
-import org.junit.Test
-
-class NakedAwait {
- @Test
- def `await only allowed in async neg`(): Unit = {
- expectError("`await` must be enclosed in an `async` block") {
- """
- | import _root_.scala.async.Async._
- | await[Any](null)
- """.stripMargin
- }
- }
-
- @Test
- def `await not allowed in by-name argument`(): Unit = {
- expectError("await must not be used under a by-name argument.") {
- """
- | import _root_.scala.async.internal.AsyncId._
- | def foo(a: Int)(b: => Int) = 0
- | async { foo(0)(await(0)) }
- """.stripMargin
- }
- }
-
- @Test
- def `await not allowed in boolean short circuit argument 1`(): Unit = {
- expectError("await must not be used under a by-name argument.") {
- """
- | import _root_.scala.async.internal.AsyncId._
- | async { true && await(false) }
- """.stripMargin
- }
- }
-
- @Test
- def `await not allowed in boolean short circuit argument 2`(): Unit = {
- expectError("await must not be used under a by-name argument.") {
- """
- | import _root_.scala.async.internal.AsyncId._
- | async { true || await(false) }
- """.stripMargin
- }
- }
-
- @Test
- def nestedObject(): Unit = {
- expectError("await must not be used under a nested object.") {
- """
- | import _root_.scala.async.internal.AsyncId._
- | async { object Nested { await(false) } }
- """.stripMargin
- }
- }
-
- @Test
- def nestedTrait(): Unit = {
- expectError("await must not be used under a nested trait.") {
- """
- | import _root_.scala.async.internal.AsyncId._
- | async { trait Nested { await(false) } }
- """.stripMargin
- }
- }
-
- @Test
- def nestedClass(): Unit = {
- expectError("await must not be used under a nested class.") {
- """
- | import _root_.scala.async.internal.AsyncId._
- | async { class Nested { await(false) } }
- """.stripMargin
- }
- }
-
- @Test
- def nestedFunction(): Unit = {
- expectError("await must not be used under a nested function.") {
- """
- | import _root_.scala.async.internal.AsyncId._
- | async { () => { await(false) } }
- """.stripMargin
- }
- }
-
- @Test
- def nestedPatMatFunction(): Unit = {
- expectError("await must not be used under a nested class.") { // TODO more specific error message
- """
- | import _root_.scala.async.internal.AsyncId._
- | async { { case x => { await(false) } } : PartialFunction[Any, Any] }
- """.stripMargin
- }
- }
-
- @Test
- def tryBody(): Unit = {
- expectError("await must not be used under a try/catch.") {
- """
- | import _root_.scala.async.internal.AsyncId._
- | async { try { await(false) } catch { case _ => } }
- """.stripMargin
- }
- }
-
- @Test
- def catchBody(): Unit = {
- expectError("await must not be used under a try/catch.") {
- """
- | import _root_.scala.async.internal.AsyncId._
- | async { try { () } catch { case _ => await(false) } }
- """.stripMargin
- }
- }
-
- @Test
- def finallyBody(): Unit = {
- expectError("await must not be used under a try/catch.") {
- """
- | import _root_.scala.async.internal.AsyncId._
- | async { try { () } finally { await(false) } }
- """.stripMargin
- }
- }
-
- @Test
- def guard(): Unit = {
- expectError("await must not be used under a pattern guard.") {
- """
- | import _root_.scala.async.internal.AsyncId._
- | async { 1 match { case _ if await(true) => } }
- """.stripMargin
- }
- }
-
- @Test
- def nestedMethod(): Unit = {
- expectError("await must not be used under a nested method.") {
- """
- | import _root_.scala.async.internal.AsyncId._
- | async { def foo = await(false) }
- """.stripMargin
- }
- }
-
- @Test
- def returnIllegal(): Unit = {
- expectError("return is illegal") {
- """
- | import _root_.scala.async.internal.AsyncId._
- | def foo(): Any = async { return false }
- | ()
- |
- |""".stripMargin
- }
- }
-
- @Test
- def lazyValIllegal(): Unit = {
- expectError("await must not be used under a lazy val initializer") {
- """
- | import _root_.scala.async.internal.AsyncId._
- | def foo(): Any = async { val x = { lazy val y = await(0); y } }
- | ()
- |
- |""".stripMargin
- }
- }
-}
diff --git a/src/test/scala/scala/async/neg/SampleNegSpec.scala b/src/test/scala/scala/async/neg/SampleNegSpec.scala
deleted file mode 100644
index cf2c8394..00000000
--- a/src/test/scala/scala/async/neg/SampleNegSpec.scala
+++ /dev/null
@@ -1,27 +0,0 @@
-/*
- * 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
-package neg
-
-import org.junit.Test
-
-class SampleNegSpec {
- @Test
- def `missing symbol`(): Unit = {
- expectError("not found: value kaboom") {
- """
- | kaboom
- """.stripMargin
- }
- }
-}
diff --git a/src/test/scala/scala/async/package.scala b/src/test/scala/scala/async/package.scala
deleted file mode 100644
index e27a3cf5..00000000
--- a/src/test/scala/scala/async/package.scala
+++ /dev/null
@@ -1,90 +0,0 @@
-/*
- * 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
-
-import reflect._
-import tools.reflect.{ToolBox, ToolBoxError}
-
-package object async {
-
-
- implicit class objectops(obj: Any) {
- def mustBe(other: Any) = assert(obj == other, obj + " is not " + other)
-
- def mustEqual(other: Any) = mustBe(other)
- }
-
- implicit class stringops(text: String) {
- def mustContain(substring: String) = assert(text contains substring, text)
-
- def mustStartWith(prefix: String) = assert(text startsWith prefix, text)
- }
-
- implicit class listops(list: List[String]) {
- def mustStartWith(prefixes: List[String]) = {
- assert(list.length == prefixes.size, ("expected = " + prefixes.length + ", actual = " + list.length, list))
- list.zip(prefixes).foreach{ case (el, prefix) => el mustStartWith prefix }
- }
- }
-
- def intercept[T <: Throwable : ClassTag](body: => Any): T = {
- try {
- body
- throw new Exception(s"Exception of type ${classTag[T]} was not thrown")
- } catch {
- case t: Throwable =>
- if (!classTag[T].runtimeClass.isAssignableFrom(t.getClass)) throw t
- else t.asInstanceOf[T]
- }
- }
-
- def eval(code: String, compileOptions: String = ""): Any = {
- val tb = mkToolbox(compileOptions)
- tb.eval(tb.parse(code))
- }
-
- def mkToolbox(compileOptions: String = ""): ToolBox[_ <: scala.reflect.api.Universe] = {
- val m = scala.reflect.runtime.currentMirror
- import scala.tools.reflect.ToolBox
- m.mkToolBox(options = compileOptions)
- }
-
- import scala.tools.nsc._, reporters._
- def mkGlobal(compileOptions: String = ""): Global = {
- val settings = new Settings()
- settings.processArgumentString(compileOptions)
- val initClassPath = settings.classpath.value
- settings.embeddedDefaults(getClass.getClassLoader)
- if (initClassPath == settings.classpath.value)
- settings.usejavacp.value = true // not running under SBT, try to use the Java claspath instead
- val reporter = new StoreReporter
- new Global(settings, reporter)
- }
-
- // returns e.g. target/scala-2.12/classes
- // implementation is kludgy, but it's just test code. Scala version number formats and their
- // relation to Scala binary versions are too diverse to attempt to do that mapping ourselves here,
- // as we learned from experience. and we could use sbt-buildinfo to have sbt tell us, but that
- // complicates the build since it does source generation (which may e.g. confuse IntelliJ).
- // so this is, uh, fine? (crosses fingers)
- def toolboxClasspath =
- new java.io.File(this.getClass.getProtectionDomain.getCodeSource.getLocation.toURI)
- .getParentFile.getParentFile
-
- def expectError(errorSnippet: String, compileOptions: String = "",
- baseCompileOptions: String = s"-cp ${toolboxClasspath}")(code: String): Unit = {
- intercept[ToolBoxError] {
- eval(code, compileOptions + " " + baseCompileOptions)
- }.getMessage mustContain errorSnippet
- }
-}
diff --git a/src/test/scala/scala/async/run/SyncOptimizationSpec.scala b/src/test/scala/scala/async/run/SyncOptimizationSpec.scala
deleted file mode 100644
index b5cd6539..00000000
--- a/src/test/scala/scala/async/run/SyncOptimizationSpec.scala
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
- * 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.run
-
-import org.junit.Test
-import scala.async.Async._
-import scala.concurrent._
-import scala.concurrent.duration._
-import ExecutionContext.Implicits._
-
-class SyncOptimizationSpec {
- @Test
- def awaitOnCompletedFutureRunsOnSameThread: Unit = {
-
- def stackDepth = Thread.currentThread().getStackTrace.length
-
- val future = async {
- val thread1 = Thread.currentThread
- val stackDepth1 = stackDepth
-
- val f = await(Future.successful(1))
- val thread2 = Thread.currentThread
- val stackDepth2 = stackDepth
- assert(thread1 == thread2)
- assert(stackDepth1 == stackDepth2)
- }
- Await.result(future, 10.seconds)
- }
-
-}
diff --git a/src/test/scala/scala/async/run/WarningsSpec.scala b/src/test/scala/scala/async/run/WarningsSpec.scala
deleted file mode 100644
index 155794d3..00000000
--- a/src/test/scala/scala/async/run/WarningsSpec.scala
+++ /dev/null
@@ -1,105 +0,0 @@
-/*
- * 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
-package run
-
-import org.junit.Test
-
-import scala.language.{postfixOps, reflectiveCalls}
-import scala.tools.nsc.reporters.StoreReporter
-
-
-class WarningsSpec {
-
- @Test
- // https://github.com/scala/async/issues/74
- def noPureExpressionInStatementPositionWarning_t74(): Unit = {
- val tb = mkToolbox(s"-cp ${toolboxClasspath} -Xfatal-warnings")
- // was: "a pure expression does nothing in statement position; you may be omitting necessary parentheses"
- tb.eval(tb.parse {
- """
- | import scala.async.internal.AsyncId._
- | async {
- | if ("".isEmpty) {
- | await(println("hello"))
- | ()
- | } else 42
- | }
- """.stripMargin
- })
- }
-
- @Test
- // https://github.com/scala/async/issues/74
- def noDeadCodeWarningForAsyncThrow(): Unit = {
- val global = mkGlobal("-cp ${toolboxClasspath} -Yrangepos -Ywarn-dead-code -Xfatal-warnings -Ystop-after:refchecks")
- // was: "a pure expression does nothing in statement position; you may be omitting necessary parentheses"
- val source =
- """
- | class Test {
- | import scala.async.Async._
- | import scala.concurrent.ExecutionContext.Implicits.global
- | async { throw new Error() }
- | }
- """.stripMargin
- val run = new global.Run
- val sourceFile = global.newSourceFile(source)
- run.compileSources(sourceFile :: Nil)
- assert(!global.reporter.hasErrors, global.reporter.asInstanceOf[StoreReporter].infos)
- }
-
- @Test
- def noDeadCodeWarningInMacroExpansion(): Unit = {
- val global = mkGlobal("-cp ${toolboxClasspath} -Yrangepos -Ywarn-dead-code -Xfatal-warnings -Ystop-after:refchecks")
- val source = """
- | class Test {
- | def test = {
- | import scala.async.Async._, scala.concurrent._, ExecutionContext.Implicits.global
- | async {
- | val opt = await(async(Option.empty[String => Future[Unit]]))
- | opt match {
- | case None =>
- | throw new RuntimeException("case a")
- | case Some(f) =>
- | await(f("case b"))
- | }
- | }
- | }
- |}
- """.stripMargin
- val run = new global.Run
- val sourceFile = global.newSourceFile(source)
- run.compileSources(sourceFile :: Nil)
- assert(!global.reporter.hasErrors, global.reporter.asInstanceOf[StoreReporter].infos)
- }
-
- @Test
- def ignoreNestedAwaitsInIDE_t1002561(): Unit = {
- // https://www.assembla.com/spaces/scala-ide/tickets/1002561
- val global = mkGlobal("-cp ${toolboxClasspath} -Yrangepos -Ystop-after:typer ")
- val source = """
- | class Test {
- | def test = {
- | import scala.async.Async._, scala.concurrent._, ExecutionContext.Implicits.global
- | async {
- | 1 + await({def foo = (async(await(async(2)))); foo})
- | }
- | }
- |}
- """.stripMargin
- val run = new global.Run
- val sourceFile = global.newSourceFile(source)
- run.compileSources(sourceFile :: Nil)
- assert(!global.reporter.hasErrors, global.reporter.asInstanceOf[StoreReporter].infos)
- }
-}
diff --git a/src/test/scala/scala/async/run/anf/AnfTransformSpec.scala b/src/test/scala/scala/async/run/anf/AnfTransformSpec.scala
deleted file mode 100644
index 2d133b02..00000000
--- a/src/test/scala/scala/async/run/anf/AnfTransformSpec.scala
+++ /dev/null
@@ -1,459 +0,0 @@
-/*
- * 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
-package run
-package anf
-
-import language.{reflectiveCalls, postfixOps}
-import scala.concurrent.{Future, ExecutionContext, Await}
-import scala.concurrent.duration._
-import scala.async.Async.{async, await}
-import org.junit.Test
-import scala.async.internal.AsyncId
-
-
-class AnfTestClass {
-
- import ExecutionContext.Implicits.global
-
- def base(x: Int): Future[Int] = Future {
- x + 2
- }
-
- def m(y: Int): Future[Int] = async {
- val blerg = base(y)
- await(blerg)
- }
-
- def m2(y: Int): Future[Int] = async {
- val f = base(y)
- val f2 = base(y + 1)
- await(f) + await(f2)
- }
-
- def m3(y: Int): Future[Int] = async {
- val f = base(y)
- var z = 0
- if (y > 0) {
- z = await(f) + 2
- } else {
- z = await(f) - 2
- }
- z
- }
-
- def m4(y: Int): Future[Int] = async {
- val f = base(y)
- val z = if (y > 0) {
- await(f) + 2
- } else {
- await(f) - 2
- }
- z + 1
- }
-
- def futureUnitIfElse(y: Int): Future[Unit] = async {
- val f = base(y)
- if (y > 0) {
- State.result = await(f) + 2
- } else {
- State.result = await(f) - 2
- }
- }
-}
-
-object State {
- @volatile var result: Int = 0
-}
-
-class AnfTransformSpec {
-
- @Test
- def `simple ANF transform`(): Unit = {
- val o = new AnfTestClass
- val fut = o.m(10)
- val res = Await.result(fut, 2 seconds)
- res mustBe (12)
- }
-
- @Test
- def `simple ANF transform 2`(): Unit = {
- val o = new AnfTestClass
- val fut = o.m2(10)
- val res = Await.result(fut, 2 seconds)
- res mustBe (25)
- }
-
- @Test
- def `simple ANF transform 3`(): Unit = {
- val o = new AnfTestClass
- val fut = o.m3(10)
- val res = Await.result(fut, 2 seconds)
- res mustBe (14)
- }
-
- @Test
- def `ANF transform of assigning the result of an if-else`(): Unit = {
- val o = new AnfTestClass
- val fut = o.m4(10)
- val res = Await.result(fut, 2 seconds)
- res mustBe (15)
- }
-
- @Test
- def `Unit-typed if-else in tail position`(): Unit = {
- val o = new AnfTestClass
- val fut = o.futureUnitIfElse(10)
- Await.result(fut, 2 seconds)
- State.result mustBe (14)
- }
-
- @Test
- def `inlining block does not produce duplicate definition`(): Unit = {
- AsyncId.async {
- val f = 12
- val x = AsyncId.await(f)
-
- {
- type X = Int
- val x: X = 42
- println(x)
- }
- type X = Int
- x: X
- }
- }
-
- @Test
- def `inlining block in tail position does not produce duplicate definition`(): Unit = {
- AsyncId.async {
- val f = 12
- val x = AsyncId.await(f)
-
- {
- val x = 42
- x
- }
- } mustBe (42)
- }
-
- @Test
- def `match as expression 1`(): Unit = {
- import ExecutionContext.Implicits.global
- val result = AsyncId.async {
- val x = "" match {
- case _ => AsyncId.await(1) + 1
- }
- x
- }
- result mustBe (2)
- }
-
- @Test
- def `match as expression 2`(): Unit = {
- import ExecutionContext.Implicits.global
- val result = AsyncId.async {
- val x = "" match {
- case "" if false => AsyncId.await(1) + 1
- case _ => 2 + AsyncId.await(1)
- }
- val y = x
- "" match {
- case _ => AsyncId.await(y) + 100
- }
- }
- result mustBe (103)
- }
-
- @Test
- def nestedAwaitAsBareExpression(): Unit = {
- import ExecutionContext.Implicits.global
- import AsyncId.{async, await}
- val result = async {
- await(await("").isEmpty)
- }
- result mustBe (true)
- }
-
- @Test
- def nestedAwaitInBlock(): Unit = {
- import ExecutionContext.Implicits.global
- import AsyncId.{async, await}
- val result = async {
- ()
- await(await("").isEmpty)
- }
- result mustBe (true)
- }
-
- @Test
- def nestedAwaitInIf(): Unit = {
- import ExecutionContext.Implicits.global
- import AsyncId.{async, await}
- val result = async {
- if ("".isEmpty)
- await(await("").isEmpty)
- else 0
- }
- result mustBe (true)
- }
-
- @Test
- def byNameExpressionsArentLifted(): Unit = {
- import AsyncId.{async, await}
- def foo(ignored: => Any, b: Int) = b
- val result = async {
- foo(???, await(1))
- }
- result mustBe (1)
- }
-
- @Test
- def evaluationOrderRespected(): Unit = {
- import AsyncId.{async, await}
- def foo(a: Int, b: Int) = (a, b)
- val result = async {
- var i = 0
- def next() = {
- i += 1
- i
- }
- foo(next(), await(next()))
- }
- result mustBe ((1, 2))
- }
-
- @Test
- def awaitInNonPrimaryParamSection1(): Unit = {
- import AsyncId.{async, await}
- def foo(a0: Int)(b0: Int) = s"a0 = $a0, b0 = $b0"
- val res = async {
- var i = 0
- def get = {i += 1; i}
- foo(get)(await(get))
- }
- res mustBe "a0 = 1, b0 = 2"
- }
-
- @Test
- def awaitInNonPrimaryParamSection2(): Unit = {
- import AsyncId.{async, await}
- def foo[T](a0: Int)(b0: Int*) = s"a0 = $a0, b0 = ${b0.head}"
- val res = async {
- var i = 0
- def get = async {i += 1; i}
- foo[Int](await(get))(await(get) :: await(async(Nil)) : _*)
- }
- res mustBe "a0 = 1, b0 = 2"
- }
-
- @Test
- def awaitInNonPrimaryParamSectionWithLazy1(): Unit = {
- import AsyncId.{async, await}
- def foo[T](a: => Int)(b: Int) = b
- val res = async {
- def get = async {0}
- foo[Int](???)(await(get))
- }
- res mustBe 0
- }
-
- @Test
- def awaitInNonPrimaryParamSectionWithLazy2(): Unit = {
- import AsyncId.{async, await}
- def foo[T](a: Int)(b: => Int) = a
- val res = async {
- def get = async {0}
- foo[Int](await(get))(???)
- }
- res mustBe 0
- }
-
- @Test
- def awaitWithLazy(): Unit = {
- import AsyncId.{async, await}
- def foo[T](a: Int, b: => Int) = a
- val res = async {
- def get = async {0}
- foo[Int](await(get), ???)
- }
- res mustBe 0
- }
-
- @Test
- def awaitOkInReciever(): Unit = {
- import AsyncId.{async, await}
- class Foo { def bar(a: Int)(b: Int) = a + b }
- async {
- await(async(new Foo)).bar(1)(2)
- }
- }
-
- @Test
- def namedArgumentsRespectEvaluationOrder(): Unit = {
- import AsyncId.{async, await}
- def foo(a: Int, b: Int) = (a, b)
- val result = async {
- var i = 0
- def next() = {
- i += 1
- i
- }
- foo(b = next(), a = await(next()))
- }
- result mustBe ((2, 1))
- }
-
- @Test
- def namedAndDefaultArgumentsRespectEvaluationOrder(): Unit = {
- import AsyncId.{async, await}
- var i = 0
- def next() = {
- i += 1
- i
- }
- def foo(a: Int = next(), b: Int = next()) = (a, b)
- async {
- foo(b = await(next()))
- } mustBe ((2, 1))
- i = 0
- async {
- foo(a = await(next()))
- } mustBe ((1, 2))
- }
-
- @Test
- def repeatedParams1(): Unit = {
- import AsyncId.{async, await}
- var i = 0
- def foo(a: Int, b: Int*) = b.toList
- def id(i: Int) = i
- async {
- foo(await(0), id(1), id(2), id(3), await(4))
- } mustBe (List(1, 2, 3, 4))
- }
-
- @Test
- def repeatedParams2(): Unit = {
- import AsyncId.{async, await}
- var i = 0
- def foo(a: Int, b: Int*) = b.toList
- def id(i: Int) = i
- async {
- foo(await(0), List(id(1), id(2), id(3)): _*)
- } mustBe (List(1, 2, 3))
- }
-
- @Test
- def awaitInThrow(): Unit = {
- import _root_.scala.async.internal.AsyncId.{async, await}
- intercept[Exception](
- async {
- throw new Exception("msg: " + await(0))
- }
- ).getMessage mustBe "msg: 0"
- }
-
- @Test
- def awaitInTyped(): Unit = {
- import _root_.scala.async.internal.AsyncId.{async, await}
- async {
- (("msg: " + await(0)): String).toString
- } mustBe "msg: 0"
- }
-
-
- @Test
- def awaitInAssign(): Unit = {
- import _root_.scala.async.internal.AsyncId.{async, await}
- async {
- var x = 0
- x = await(1)
- x
- } mustBe 1
- }
-
- @Test
- def caseBodyMustBeTypedAsUnit(): Unit = {
- import _root_.scala.async.internal.AsyncId.{async, await}
- val Up = 1
- val Down = 2
- val sign = async {
- await(1) match {
- case Up => 1.0
- case Down => -1.0
- }
- }
- sign mustBe 1.0
- }
-
- @Test
- def awaitInImplicitApply(): Unit = {
- val tb = mkToolbox(s"-cp ${toolboxClasspath}")
- val tree = tb.typeCheck(tb.parse {
- """
- | import language.implicitConversions
- | import _root_.scala.async.internal.AsyncId.{async, await}
- | implicit def view(a: Int): String = ""
- | async {
- | await(0).length
- | }
- """.stripMargin
- })
- val applyImplicitView = tree.collect { case x if x.getClass.getName.endsWith("ApplyImplicitView") => x }
- println(applyImplicitView)
- applyImplicitView.map(_.toString) mustStartWith List("view(")
- }
-
- @Test
- def nothingTypedIf(): Unit = {
- import scala.async.internal.AsyncId.{async, await}
- val result = util.Try(async {
- if (true) {
- val n = await(1)
- if (n < 2) {
- throw new RuntimeException("case a")
- }
- else {
- throw new RuntimeException("case b")
- }
- }
- else {
- "case c"
- }
- })
-
- assert(result.asInstanceOf[util.Failure[_]].exception.getMessage == "case a")
- }
-
- @Test
- def nothingTypedMatch(): Unit = {
- import scala.async.internal.AsyncId.{async, await}
- val result = util.Try(async {
- 0 match {
- case _ if "".isEmpty =>
- val n = await(1)
- n match {
- case _ if n < 2 =>
- throw new RuntimeException("case a")
- case _ =>
- throw new RuntimeException("case b")
- }
- case _ =>
- "case c"
- }
- })
-
- assert(result.asInstanceOf[util.Failure[_]].exception.getMessage == "case a")
- }
-}
diff --git a/src/test/scala/scala/async/run/await0/Await0Spec.scala b/src/test/scala/scala/async/run/await0/Await0Spec.scala
deleted file mode 100644
index e70a811e..00000000
--- a/src/test/scala/scala/async/run/await0/Await0Spec.scala
+++ /dev/null
@@ -1,81 +0,0 @@
-/*
- * 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
-package run
-package await0
-
-/**
- * Copyright (C) 2012-2014 Lightbend Inc.
- */
-
-import language.{reflectiveCalls, postfixOps}
-
-import scala.concurrent.{Future, ExecutionContext, Await}
-import scala.concurrent.duration._
-import scala.async.Async.{async, await}
-import org.junit.Test
-
-class Await0Class {
-
- import ExecutionContext.Implicits.global
-
- def m1(x: Double): Future[Double] = Future {
- x + 2.0
- }
-
- def m2(x: Float): Future[Float] = Future {
- x + 2.0f
- }
-
- def m3(x: Char): Future[Char] = Future {
- (x.toInt + 2).toChar
- }
-
- def m4(x: Short): Future[Short] = Future {
- (x + 2).toShort
- }
-
- def m5(x: Byte): Future[Byte] = Future {
- (x + 2).toByte
- }
-
- def m0(y: Int): Future[Double] = async {
- val f1 = m1(y.toDouble)
- val x1: Double = await(f1)
-
- val f2 = m2(y.toFloat)
- val x2: Float = await(f2)
-
- val f3 = m3(y.toChar)
- val x3: Char = await(f3)
-
- val f4 = m4(y.toShort)
- val x4: Short = await(f4)
-
- val f5 = m5(y.toByte)
- val x5: Byte = await(f5)
-
- x1 + x2 + 2.0
- }
-}
-
-class Await0Spec {
-
- @Test
- def `An async method support a simple await`(): Unit = {
- val o = new Await0Class
- val fut = o.m0(10)
- val res = Await.result(fut, 10 seconds)
- res mustBe (26.0)
- }
-}
diff --git a/src/test/scala/scala/async/run/block0/AsyncSpec.scala b/src/test/scala/scala/async/run/block0/AsyncSpec.scala
deleted file mode 100644
index 6284dbdb..00000000
--- a/src/test/scala/scala/async/run/block0/AsyncSpec.scala
+++ /dev/null
@@ -1,65 +0,0 @@
-/*
- * 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
-package run
-package block0
-
-import language.{reflectiveCalls, postfixOps}
-import scala.concurrent.{Future, ExecutionContext, Await}
-import scala.concurrent.duration._
-import scala.async.Async.{async, await}
-import org.junit.Test
-
-
-class Test1Class {
-
- import ExecutionContext.Implicits.global
-
- def m1(x: Int): Future[Int] = Future {
- x + 2
- }
-
- def m2(y: Int): Future[Int] = async {
- val f = m1(y)
- val x = await(f)
- x + 2
- }
-
- def m3(y: Int): Future[Int] = async {
- val f1 = m1(y)
- val x1 = await(f1)
- val f2 = m1(y + 2)
- val x2 = await(f2)
- x1 + x2
- }
-}
-
-
-class AsyncSpec {
-
- @Test
- def `simple await`(): Unit = {
- val o = new Test1Class
- val fut = o.m2(10)
- val res = Await.result(fut, 2 seconds)
- res mustBe (14)
- }
-
- @Test
- def `several awaits in sequence`(): Unit = {
- val o = new Test1Class
- val fut = o.m3(10)
- val res = Await.result(fut, 4 seconds)
- res mustBe (26)
- }
-}
diff --git a/src/test/scala/scala/async/run/block1/block1.scala b/src/test/scala/scala/async/run/block1/block1.scala
deleted file mode 100644
index 7247c244..00000000
--- a/src/test/scala/scala/async/run/block1/block1.scala
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
- * 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
-package run
-package block1
-
-import language.{reflectiveCalls, postfixOps}
-import scala.concurrent.{Future, ExecutionContext, Await}
-import scala.concurrent.duration._
-import scala.async.Async.{async, await}
-import org.junit.Test
-
-
-class Test1Class {
-
- import ExecutionContext.Implicits.global
-
- def m1(x: Int): Future[Int] = Future {
- x + 2
- }
-
- def m4(y: Int): Future[Int] = async {
- val f1 = m1(y)
- val f2 = m1(y + 2)
- val x1 = await(f1)
- val x2 = await(f2)
- x1 + x2
- }
-}
-
-class Block1Spec {
-
- @Test def `support a simple await`(): Unit = {
- val o = new Test1Class
- val fut = o.m4(10)
- val res = Await.result(fut, 2 seconds)
- res mustBe (26)
- }
-}
diff --git a/src/test/scala/scala/async/run/exceptions/ExceptionsSpec.scala b/src/test/scala/scala/async/run/exceptions/ExceptionsSpec.scala
deleted file mode 100644
index e75594ab..00000000
--- a/src/test/scala/scala/async/run/exceptions/ExceptionsSpec.scala
+++ /dev/null
@@ -1,66 +0,0 @@
-/*
- * 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
-package run
-package exceptions
-
-import scala.async.Async.{async, await}
-
-import scala.concurrent.{Future, ExecutionContext, Await}
-import ExecutionContext.Implicits._
-import scala.concurrent.duration._
-import scala.reflect.ClassTag
-
-import org.junit.Test
-
-class ExceptionsSpec {
-
- @Test
- def `uncaught exception within async`(): Unit = {
- val fut = async { throw new Exception("problem") }
- intercept[Exception] { Await.result(fut, 2.seconds) }
- }
-
- @Test
- def `uncaught exception within async after await`(): Unit = {
- val base = Future { "five!".length }
- val fut = async {
- val len = await(base)
- throw new Exception(s"illegal length: $len")
- }
- intercept[Exception] { Await.result(fut, 2.seconds) }
- }
-
- @Test
- def `await failing future within async`(): Unit = {
- val base = Future[Int] { throw new Exception("problem") }
- val fut = async {
- val x = await(base)
- x * 2
- }
- intercept[Exception] { Await.result(fut, 2.seconds) }
- }
-
- @Test
- def `await failing future within async after await`(): Unit = {
- val base = Future[Any] { "five!".length }
- val fut = async {
- val a = await(base.mapTo[Int]) // result: 5
- val b = await((Future { (a * 2).toString }).mapTo[Int]) // result: ClassCastException
- val c = await(Future { (7 * 2).toString }) // result: "14"
- b + "-" + c
- }
- intercept[ClassCastException] { Await.result(fut, 2.seconds) }
- }
-
-}
diff --git a/src/test/scala/scala/async/run/futures/FutureSpec.scala b/src/test/scala/scala/async/run/futures/FutureSpec.scala
deleted file mode 100644
index 52566894..00000000
--- a/src/test/scala/scala/async/run/futures/FutureSpec.scala
+++ /dev/null
@@ -1,560 +0,0 @@
-/*
- * 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
-package run
-package futures
-
-import java.util.concurrent.ConcurrentHashMap
-
-import scala.language.postfixOps
-import scala.concurrent._
-import scala.concurrent.duration._
-import scala.concurrent.duration.Duration.Inf
-import scala.collection._
-import scala.runtime.NonLocalReturnControl
-import scala.util.{Try,Success,Failure}
-
-import scala.async.Async.{async, await}
-
-import org.junit.Test
-
-class FutureSpec {
-
- /* some utils */
-
- def testAsync(s: String)(implicit ec: ExecutionContext): Future[String] = s match {
- case "Hello" => Future { "World" }
- case "Failure" => Future.failed(new RuntimeException("Expected exception; to test fault-tolerance"))
- case "NoReply" => Promise[String]().future
- }
-
- val defaultTimeout = 5 seconds
-
- /* future specification */
-
- @Test def `A future with custom ExecutionContext should handle Throwables`(): Unit = {
- val ms = new ConcurrentHashMap[Throwable, Unit]
- implicit val ec = scala.concurrent.ExecutionContext.fromExecutor(new java.util.concurrent.ForkJoinPool(), {
- t =>
- ms.put(t, ())
- })
-
- class ThrowableTest(m: String) extends Throwable(m)
-
- val f1 = Future[Any] {
- throw new ThrowableTest("test")
- }
-
- intercept[ThrowableTest] {
- Await.result(f1, defaultTimeout)
- }
-
- val latch = new TestLatch
- val f2 = Future {
- Await.ready(latch, 5 seconds)
- "success"
- }
- val f3 = async {
- val s = await(f2)
- s.toUpperCase
- }
-
- f2 foreach { _ => throw new ThrowableTest("dispatcher foreach") }
- f2 onComplete { case Success(_) => throw new ThrowableTest("dispatcher receive") }
-
- latch.open()
-
- Await.result(f2, defaultTimeout) mustBe ("success")
-
- f2 foreach { _ => throw new ThrowableTest("current thread foreach") }
- f2 onComplete { case Success(_) => throw new ThrowableTest("current thread receive") }
-
- Await.result(f3, defaultTimeout) mustBe ("SUCCESS")
-
- val waiting = Future {
- Thread.sleep(1000)
- }
- Await.ready(waiting, 2000 millis)
-
- ms.size mustBe (4)
- }
-
- import ExecutionContext.Implicits._
-
- @Test def `A future with global ExecutionContext should compose with for-comprehensions`(): Unit = {
- import scala.reflect.ClassTag
-
- def asyncInt(x: Int) = Future { (x * 2).toString }
- val future0 = Future[Any] {
- "five!".length
- }
-
- val future1 = async {
- val a = await(future0.mapTo[Int]) // returns 5
- val b = await(asyncInt(a)) // returns "10"
- val c = await(asyncInt(7)) // returns "14"
- b + "-" + c
- }
-
- val future2 = async {
- val a = await(future0.mapTo[Int])
- val b = await((Future { (a * 2).toString }).mapTo[Int])
- val c = await(Future { (7 * 2).toString })
- b + "-" + c
- }
-
- Await.result(future1, defaultTimeout) mustBe ("10-14")
- //assert(checkType(future1, manifest[String]))
- intercept[ClassCastException] { Await.result(future2, defaultTimeout) }
- }
-
- //TODO this is not yet supported by Async
- @Test def `support pattern matching within a for-comprehension`(): Unit = {
- case class Req[T](req: T)
- case class Res[T](res: T)
- def asyncReq[T](req: Req[T]) = req match {
- case Req(s: String) => Future { Res(s.length) }
- case Req(i: Int) => Future { Res((i * 2).toString) }
- }
-
- val future1 = for {
- Res(a: Int) <- asyncReq(Req("Hello"))
- Res(b: String) <- asyncReq(Req(a))
- Res(c: String) <- asyncReq(Req(7))
- } yield b + "-" + c
-
- val future2 = for {
- Res(a: Int) <- asyncReq(Req("Hello"))
- Res(b: Int) <- asyncReq(Req(a))
- Res(c: Int) <- asyncReq(Req(7))
- } yield b + "-" + c
-
- Await.result(future1, defaultTimeout) mustBe ("10-14")
- intercept[NoSuchElementException] { Await.result(future2, defaultTimeout) }
- }
-
- @Test def mini(): Unit = {
- val future4 = async {
- await(Future.successful(0)).toString
- }
- Await.result(future4, defaultTimeout)
- }
-
- @Test def `recover from exceptions`(): Unit = {
- val future1 = Future(5)
- val future2 = async { await(future1) / 0 }
- val future3 = async { await(future2).toString }
-
- val future1Recovered = future1 recover {
- case e: ArithmeticException => 0
- }
- val future4 = async { await(future1Recovered).toString }
-
- val future2Recovered = future2 recover {
- case e: ArithmeticException => 0
- }
- val future5 = async { await(future2Recovered).toString }
-
- val future2Recovered2 = future2 recover {
- case e: MatchError => 0
- }
- val future6 = async { await(future2Recovered2).toString }
-
- val future7 = future3 recover {
- case e: ArithmeticException => "You got ERROR"
- }
-
- val future8 = testAsync("Failure")
- val future9 = testAsync("Failure") recover {
- case e: RuntimeException => "FAIL!"
- }
- val future10 = testAsync("Hello") recover {
- case e: RuntimeException => "FAIL!"
- }
- val future11 = testAsync("Failure") recover {
- case _ => "Oops!"
- }
-
- Await.result(future1, defaultTimeout) mustBe (5)
- intercept[ArithmeticException] { Await.result(future2, defaultTimeout) }
- intercept[ArithmeticException] { Await.result(future3, defaultTimeout) }
- Await.result(future4, defaultTimeout) mustBe ("5")
- Await.result(future5, defaultTimeout) mustBe ("0")
- intercept[ArithmeticException] { Await.result(future6, defaultTimeout) }
- Await.result(future7, defaultTimeout) mustBe ("You got ERROR")
- intercept[RuntimeException] { Await.result(future8, defaultTimeout) }
- Await.result(future9, defaultTimeout) mustBe ("FAIL!")
- Await.result(future10, defaultTimeout) mustBe ("World")
- Await.result(future11, defaultTimeout) mustBe ("Oops!")
- }
-
- @Test def `recoverWith from exceptions`(): Unit = {
- val o = new IllegalStateException("original")
- val r = new IllegalStateException("recovered")
-
- intercept[IllegalStateException] {
- val failed = Future.failed[String](o) recoverWith {
- case _ if false == true => Future.successful("yay!")
- }
- Await.result(failed, defaultTimeout)
- } mustBe (o)
-
- val recovered = Future.failed[String](o) recoverWith {
- case _ => Future.successful("yay!")
- }
- Await.result(recovered, defaultTimeout) mustBe ("yay!")
-
- intercept[IllegalStateException] {
- val refailed = Future.failed[String](o) recoverWith {
- case _ => Future.failed[String](r)
- }
- Await.result(refailed, defaultTimeout)
- } mustBe (r)
- }
-
- @Test def `andThen like a boss`(): Unit = {
- val q = new java.util.concurrent.LinkedBlockingQueue[Int]
- for (i <- 1 to 1000) {
- val chained = Future {
- q.add(1); 3
- } andThen {
- case _ => q.add(2)
- } andThen {
- case Success(0) => q.add(Int.MaxValue)
- } andThen {
- case _ => q.add(3);
- }
- Await.result(chained, defaultTimeout) mustBe (3)
- q.poll() mustBe (1)
- q.poll() mustBe (2)
- q.poll() mustBe (3)
- q.clear()
- }
- }
-
- @Test def `firstCompletedOf`(): Unit = {
- def futures = Vector.fill[Future[Int]](10) {
- Promise[Int]().future
- } :+ Future.successful[Int](5)
-
- Await.result(Future.firstCompletedOf(futures), defaultTimeout) mustBe (5)
- Await.result(Future.firstCompletedOf(futures.iterator), defaultTimeout) mustBe (5)
- }
-
- @Test def `find`(): Unit = {
- val futures = for (i <- 1 to 10) yield Future {
- i
- }
-
- val result = Future.find[Int](futures)(_ == 3)
- Await.result(result, defaultTimeout) mustBe (Some(3))
-
- val notFound = Future.find[Int](futures)(_ == 11)
- Await.result(notFound, defaultTimeout) mustBe (None)
- }
-
- @Test def `zip`(): Unit = {
- val timeout = 10000 millis
- val f = new IllegalStateException("test")
- intercept[IllegalStateException] {
- val failed = Future.failed[String](f) zip Future.successful("foo")
- Await.result(failed, timeout)
- } mustBe (f)
-
- intercept[IllegalStateException] {
- val failed = Future.successful("foo") zip Future.failed[String](f)
- Await.result(failed, timeout)
- } mustBe (f)
-
- intercept[IllegalStateException] {
- val failed = Future.failed[String](f) zip Future.failed[String](f)
- Await.result(failed, timeout)
- } mustBe (f)
-
- val successful = Future.successful("foo") zip Future.successful("foo")
- Await.result(successful, timeout) mustBe (("foo", "foo"))
- }
-
- @Test def `fold`(): Unit = {
- val timeout = 10000 millis
- def async(add: Int, wait: Int) = Future {
- Thread.sleep(wait)
- add
- }
-
- val futures = (0 to 9) map {
- idx => async(idx, idx * 20)
- }
- // TODO: change to `foldLeft` after support for 2.11 is dropped
- val folded = Future.fold(futures)(0)(_ + _)
- Await.result(folded, timeout) mustBe (45)
-
- val futuresit = (0 to 9) map {
- idx => async(idx, idx * 20)
- }
- // TODO: change to `foldLeft` after support for 2.11 is dropped
- val foldedit = Future.fold(futures)(0)(_ + _)
- Await.result(foldedit, timeout) mustBe (45)
- }
-
- @Test def `fold by composing`(): Unit = {
- val timeout = 10000 millis
- def async(add: Int, wait: Int) = Future {
- Thread.sleep(wait)
- add
- }
- def futures = (0 to 9) map {
- idx => async(idx, idx * 20)
- }
- val folded = futures.foldLeft(Future(0)) {
- case (fr, fa) => for (r <- fr; a <- fa) yield (r + a)
- }
- Await.result(folded, timeout) mustBe (45)
- }
-
- @Test def `fold with an exception`(): Unit = {
- val timeout = 10000 millis
- def async(add: Int, wait: Int) = Future {
- Thread.sleep(wait)
- if (add == 6) throw new IllegalArgumentException("shouldFoldResultsWithException: expected")
- add
- }
- def futures = (0 to 9) map {
- idx => async(idx, idx * 10)
- }
- // TODO: change to `foldLeft` after support for 2.11 is dropped
- val folded = Future.fold(futures)(0)(_ + _)
- intercept[IllegalArgumentException] {
- Await.result(folded, timeout)
- }.getMessage mustBe ("shouldFoldResultsWithException: expected")
- }
-
- @Test def `fold mutable zeroes safely`(): Unit = {
- import scala.collection.mutable.ArrayBuffer
- def test(testNumber: Int): Unit = {
- val fs = (0 to 1000) map (i => Future(i))
- // TODO: change to `foldLeft` after support for 2.11 is dropped
- val f = Future.fold(fs)(ArrayBuffer.empty[AnyRef]) {
- case (l, i) if i % 2 == 0 => l += i.asInstanceOf[AnyRef]
- case (l, _) => l
- }
- val result = Await.result(f.mapTo[ArrayBuffer[Int]], 10000 millis).sum
-
- assert(result == 250500)
- }
-
- (1 to 100) foreach test //Make sure it tries to provoke the problem
- }
-
- @Test def `return zero value if folding empty list`(): Unit = {
- // TODO: change to `foldLeft` after support for 2.11 is dropped
- val zero = Future.fold(List[Future[Int]]())(0)(_ + _)
- Await.result(zero, defaultTimeout) mustBe (0)
- }
-
- @Test def `shouldReduceResults`(): Unit = {
- def async(idx: Int) = Future {
- Thread.sleep(idx * 20)
- idx
- }
- val timeout = 10000 millis
-
- val futures = (0 to 9) map { async }
- // TODO: change to `reduceLeft` after support for 2.11 is dropped
- val reduced = Future.reduce(futures)(_ + _)
- Await.result(reduced, timeout) mustBe (45)
-
- val futuresit = (0 to 9) map { async }
- // TODO: change to `reduceLeft` after support for 2.11 is dropped
- val reducedit = Future.reduce(futuresit)(_ + _)
- Await.result(reducedit, timeout) mustBe (45)
- }
-
- @Test def `shouldReduceResultsWithException`(): Unit = {
- def async(add: Int, wait: Int) = Future {
- Thread.sleep(wait)
- if (add == 6) throw new IllegalArgumentException("shouldFoldResultsWithException: expected")
- else add
- }
- val timeout = 10000 millis
- def futures = (1 to 10) map {
- idx => async(idx, idx * 10)
- }
- // TODO: change to `reduceLeft` after support for 2.11 is dropped
- val failed = Future.reduce(futures)(_ + _)
- intercept[IllegalArgumentException] {
- Await.result(failed, timeout)
- }.getMessage mustBe ("shouldFoldResultsWithException: expected")
- }
-
- @Test def `shouldReduceThrowNSEEOnEmptyInput`(): Unit = {
- intercept[java.util.NoSuchElementException] {
- // TODO: change to `reduceLeft` after support for 2.11 is dropped
- val emptyreduced = Future.reduce(List[Future[Int]]())(_ + _)
- Await.result(emptyreduced, defaultTimeout)
- }
- }
-
- @Test def `shouldTraverseFutures`(): Unit = {
- object counter {
- var count = -1
- def incAndGet() = counter.synchronized {
- count += 2
- count
- }
- }
-
- val oddFutures = List.fill(100)(Future { counter.incAndGet() }).iterator
- val traversed = Future.sequence(oddFutures)
- Await.result(traversed, defaultTimeout).sum mustBe (10000)
-
- val list = (1 to 100).toList
- val traversedList = Future.traverse(list)(x => Future(x * 2 - 1))
- Await.result(traversedList, defaultTimeout).sum mustBe (10000)
-
- val iterator = (1 to 100).toList.iterator
- val traversedIterator = Future.traverse(iterator)(x => Future(x * 2 - 1))
- Await.result(traversedIterator, defaultTimeout).sum mustBe (10000)
- }
-
- @Test def `shouldBlockUntilResult`(): Unit = {
- val latch = new TestLatch
-
- val f = Future {
- Await.ready(latch, 5 seconds)
- 5
- }
- val f2 = Future {
- val res = Await.result(f, Inf)
- res + 9
- }
-
- intercept[TimeoutException] {
- Await.ready(f2, 100 millis)
- }
-
- latch.open()
-
- Await.result(f2, defaultTimeout) mustBe (14)
-
- val f3 = Future {
- Thread.sleep(100)
- 5
- }
-
- intercept[TimeoutException] {
- Await.ready(f3, 0 millis)
- }
- }
-
- @Test def `run callbacks async`(): Unit = {
- val latch = Vector.fill(10)(new TestLatch)
-
- val f1 = Future {
- latch(0).open()
- Await.ready(latch(1), TestLatch.DefaultTimeout)
- "Hello"
- }
- val f2 = async {
- val s = await(f1)
- latch(2).open()
- Await.ready(latch(3), TestLatch.DefaultTimeout)
- s.length
- }
- for (_ <- f2) latch(4).open()
-
- Await.ready(latch(0), TestLatch.DefaultTimeout)
-
- f1.isCompleted mustBe (false)
- f2.isCompleted mustBe (false)
-
- latch(1).open()
- Await.ready(latch(2), TestLatch.DefaultTimeout)
-
- f1.isCompleted mustBe (true)
- f2.isCompleted mustBe (false)
-
- val f3 = async {
- val s = await(f1)
- latch(5).open()
- Await.ready(latch(6), TestLatch.DefaultTimeout)
- s.length * 2
- }
- for (_ <- f3) latch(3).open()
-
- Await.ready(latch(5), TestLatch.DefaultTimeout)
-
- f3.isCompleted mustBe (false)
-
- latch(6).open()
- Await.ready(latch(4), TestLatch.DefaultTimeout)
-
- f2.isCompleted mustBe (true)
- f3.isCompleted mustBe (true)
-
- val p1 = Promise[String]()
- val f4 = async {
- val s = await(p1.future)
- latch(7).open()
- Await.ready(latch(8), TestLatch.DefaultTimeout)
- s.length
- }
- for (_ <- f4) latch(9).open()
-
- p1.future.isCompleted mustBe (false)
- f4.isCompleted mustBe (false)
-
- p1 complete Success("Hello")
-
- Await.ready(latch(7), TestLatch.DefaultTimeout)
-
- p1.future.isCompleted mustBe (true)
- f4.isCompleted mustBe (false)
-
- latch(8).open()
- Await.ready(latch(9), TestLatch.DefaultTimeout)
-
- Await.ready(f4, defaultTimeout).isCompleted mustBe (true)
- }
-
- @Test def `should not deadlock with nested await (ticket 1313)`(): Unit = {
- val simple = async {
- await { Future { } }
- val unit = Future(())
- val umap = unit map { _ => () }
- Await.result(umap, Inf)
- }
- Await.ready(simple, Inf).isCompleted mustBe (true)
-
- val l1, l2 = new TestLatch
- val complex = async {
- await{ Future { } }
- blocking {
- val nested = Future(())
- for (_ <- nested) l1.open()
- Await.ready(l1, TestLatch.DefaultTimeout) // make sure nested is completed
- for (_ <- nested) l2.open()
- Await.ready(l2, TestLatch.DefaultTimeout)
- }
- }
- Await.ready(complex, defaultTimeout).isCompleted mustBe (true)
- }
-
- @Test def `should not throw when Await.ready`(): Unit = {
- val expected = try Success(5 / 0) catch { case a: ArithmeticException => Failure(a) }
- val f = async { await(Future(5)) / 0 }
- Await.ready(f, defaultTimeout).value.get.toString mustBe expected.toString
- }
-}
-
-
diff --git a/src/test/scala/scala/async/run/hygiene/Hygiene.scala b/src/test/scala/scala/async/run/hygiene/Hygiene.scala
deleted file mode 100644
index 78afecaf..00000000
--- a/src/test/scala/scala/async/run/hygiene/Hygiene.scala
+++ /dev/null
@@ -1,92 +0,0 @@
-/*
- * 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
-package run
-package hygiene
-
-import org.junit.Test
-import scala.async.internal.AsyncId
-
-class HygieneSpec {
-
- import AsyncId.{async, await}
-
- @Test
- def `is hygenic`(): Unit = {
- val state = 23
- val result: Any = "result"
- def resume(): Any = "resume"
- val res = async {
- val f1 = state + 2
- val x = await(f1)
- val y = await(result)
- val z = await(resume())
- (x, y, z)
- }
- res mustBe ((25, "result", "resume"))
- }
-
- @Test
- def `external var as result of await`(): Unit = {
- var ext = 0
- async {
- ext = await(12)
- }
- ext mustBe (12)
- }
-
- @Test
- def `external var as result of await 2`(): Unit = {
- var ext = 0
- val inp = 10
- async {
- if (inp > 0)
- ext = await(12)
- else
- ext = await(10)
- }
- ext mustBe (12)
- }
-
- @Test
- def `external var as result of await 3`(): Unit = {
- var ext = 0
- val inp = 10
- async {
- val x = if (inp > 0)
- await(12)
- else
- await(10)
- ext = x + await(2)
- }
- ext mustBe (14)
- }
-
- @Test
- def `is hygenic nested`(): Unit = {
- val state = 23
- val result: Any = "result"
- def resume(): Any = "resume"
- import AsyncId.{await, async}
- val res = async {
- val f1 = async { state + 2 }
- val x = await(f1)
- val y = await(async { result })
- val z = await(async(await(async { resume() })))
- (x, y, z)
- }
- res._1 mustBe (25)
- res._2 mustBe ("result")
- res._3 mustBe ("resume")
- }
-}
diff --git a/src/test/scala/scala/async/run/ifelse0/IfElse0.scala b/src/test/scala/scala/async/run/ifelse0/IfElse0.scala
deleted file mode 100644
index 7603f3a3..00000000
--- a/src/test/scala/scala/async/run/ifelse0/IfElse0.scala
+++ /dev/null
@@ -1,64 +0,0 @@
-/*
- * 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
-package run
-package ifelse0
-
-import language.{reflectiveCalls, postfixOps}
-import scala.concurrent.{Future, ExecutionContext, Await}
-import scala.concurrent.duration._
-import scala.async.Async.{async, await}
-import org.junit.Test
-import scala.async.internal.AsyncId
-
-
-class TestIfElseClass {
-
- import ExecutionContext.Implicits.global
-
- def m1(x: Int): Future[Int] = Future {
- x + 2
- }
-
- def m2(y: Int): Future[Int] = async {
- val f = m1(y)
- var z = 0
- if (y > 0) {
- val x1 = await(f)
- z = x1 + 2
- } else {
- val x2 = await(f)
- z = x2 - 2
- }
- z
- }
-}
-
-
-class IfElseSpec {
-
- @Test def `support await in a simple if-else expression`(): Unit = {
- val o = new TestIfElseClass
- val fut = o.m2(10)
- val res = Await.result(fut, 2 seconds)
- res mustBe (14)
- }
-
- @Test def `await in condition`(): Unit = {
- import AsyncId.{async, await}
- val result = async {
- if ({await(true); await(true)}) await(1) else ???
- }
- result mustBe (1)
- }
-}
diff --git a/src/test/scala/scala/async/run/ifelse0/WhileSpec.scala b/src/test/scala/scala/async/run/ifelse0/WhileSpec.scala
deleted file mode 100644
index cfd08d7e..00000000
--- a/src/test/scala/scala/async/run/ifelse0/WhileSpec.scala
+++ /dev/null
@@ -1,127 +0,0 @@
-/*
- * 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
-package run
-package ifelse0
-
-import org.junit.Test
-import scala.async.internal.AsyncId
-
-class WhileSpec {
-
- @Test
- def whiling1(): Unit = {
- import AsyncId._
-
- val result = async {
- var xxx: Int = 0
- var y = 0
- while (xxx < 3) {
- y = await(xxx)
- xxx = xxx + 1
- }
- y
- }
- result mustBe (2)
- }
-
- @Test
- def whiling2(): Unit = {
- import AsyncId._
-
- val result = async {
- var xxx: Int = 0
- var y = 0
- while (false) {
- y = await(xxx)
- xxx = xxx + 1
- }
- y
- }
- result mustBe (0)
- }
-
- @Test
- def nestedWhile(): Unit = {
- import AsyncId._
-
- val result = async {
- var sum = 0
- var i = 0
- while (i < 5) {
- var j = 0
- while (j < 5) {
- sum += await(i) * await(j)
- j += 1
- }
- i += 1
- }
- sum
- }
- result mustBe (100)
- }
-
- @Test
- def whileExpr(): Unit = {
- import AsyncId._
-
- val result = async {
- var cond = true
- while (cond) {
- cond = false
- await { 22 }
- }
- }
- result mustBe ()
- }
-
- @Test def doWhile(): Unit = {
- import AsyncId._
- val result = async {
- var b = 0
- var x = ""
- await(do {
- x += "1"
- x += await("2")
- x += "3"
- b += await(1)
- } while (b < 2))
- await(x)
- }
- result mustBe "123123"
- }
-
- @Test def whileAwaitCondition(): Unit = {
- import AsyncId._
- val result = async {
- var b = true
- while(await(b)) {
- b = false
- }
- await(b)
- }
- result mustBe false
- }
-
- @Test def doWhileAwaitCondition(): Unit = {
- import AsyncId._
- val result = async {
- var b = true
- do {
- b = false
- } while(await(b))
- b
- }
- result mustBe false
- }
-}
diff --git a/src/test/scala/scala/async/run/ifelse1/IfElse1.scala b/src/test/scala/scala/async/run/ifelse1/IfElse1.scala
deleted file mode 100644
index 28b850b0..00000000
--- a/src/test/scala/scala/async/run/ifelse1/IfElse1.scala
+++ /dev/null
@@ -1,212 +0,0 @@
-/*
- * 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
-package run
-package ifelse1
-
-import language.{reflectiveCalls, postfixOps}
-import scala.concurrent.{Future, ExecutionContext, Await}
-import scala.concurrent.duration._
-import scala.async.Async.{async, await}
-import org.junit.Test
-
-
-class TestIfElse1Class {
-
- import ExecutionContext.Implicits.global
-
- def base(x: Int): Future[Int] = Future {
- x + 2
- }
-
- def m1(y: Int): Future[Int] = async {
- val f = base(y)
- var z = 0
- if (y > 0) {
- if (y > 100)
- 5
- else {
- val x1 = await(f)
- z = x1 + 2
- }
- } else {
- val x2 = await(f)
- z = x2 - 2
- }
- z
- }
-
- def m2(y: Int): Future[Int] = async {
- val f = base(y)
- var z = 0
- if (y > 0) {
- if (y < 100) {
- val x1 = await(f)
- z = x1 + 2
- }
- else
- 5
- } else {
- val x2 = await(f)
- z = x2 - 2
- }
- z
- }
-
- def m3(y: Int): Future[Int] = async {
- val f = base(y)
- var z = 0
- if (y < 0) {
- val x2 = await(f)
- z = x2 - 2
- } else {
- if (y > 100)
- 5
- else {
- val x1 = await(f)
- z = x1 + 2
- }
- }
- z
- }
-
- def m4(y: Int): Future[Int] = async {
- val f = base(y)
- var z = 0
- if (y < 0) {
- val x2 = await(f)
- z = x2 - 2
- } else {
- if (y < 100) {
- val x1 = await(f)
- z = x1 + 2
- } else
- 5
- }
- z
- }
-
- def pred: Future[Boolean] = async(true)
-
- def m5: Future[Boolean] = async {
- if(if(if(if(if(if(if(if(if(if(if(if(if(if(if(if(if(if(if(if(if(await(pred))
- await(pred)
- else
- false)
- await(pred)
- else
- false)
- await(pred)
- else
- false)
- await(pred)
- else
- false)
- await(pred)
- else
- false)
- await(pred)
- else
- false)
- await(pred)
- else
- false)
- await(pred)
- else
- false)
- await(pred)
- else
- false)
- await(pred)
- else
- false)
- await(pred)
- else
- false)
- await(pred)
- else
- false)
- await(pred)
- else
- false)
- await(pred)
- else
- false)
- await(pred)
- else
- false)
- await(pred)
- else
- false)
- await(pred)
- else
- false)
- await(pred)
- else
- false)
- await(pred)
- else
- false)
- await(pred)
- else
- false)
- await(pred)
- else
- false
- }
-}
-
-class IfElse1Spec {
-
- @Test
- def `await in a nested if-else expression`(): Unit = {
- val o = new TestIfElse1Class
- val fut = o.m1(10)
- val res = Await.result(fut, 2 seconds)
- res mustBe (14)
- }
-
- @Test
- def `await in a nested if-else expression 2`(): Unit = {
- val o = new TestIfElse1Class
- val fut = o.m2(10)
- val res = Await.result(fut, 2 seconds)
- res mustBe (14)
- }
-
-
- @Test
- def `await in a nested if-else expression 3`(): Unit = {
- val o = new TestIfElse1Class
- val fut = o.m3(10)
- val res = Await.result(fut, 2 seconds)
- res mustBe (14)
- }
-
-
- @Test
- def `await in a nested if-else expression 4`(): Unit = {
- val o = new TestIfElse1Class
- val fut = o.m4(10)
- val res = Await.result(fut, 2 seconds)
- res mustBe (14)
- }
-
- @Test
- def `await in deeply-nested if-else conditions`(): Unit = {
- val o = new TestIfElse1Class
- val fut = o.m5
- val res = Await.result(fut, 2 seconds)
- res mustBe true
- }
-}
diff --git a/src/test/scala/scala/async/run/ifelse2/ifelse2.scala b/src/test/scala/scala/async/run/ifelse2/ifelse2.scala
deleted file mode 100644
index 4527d0d2..00000000
--- a/src/test/scala/scala/async/run/ifelse2/ifelse2.scala
+++ /dev/null
@@ -1,55 +0,0 @@
-/*
- * 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
-package run
-package ifelse2
-
-import language.{reflectiveCalls, postfixOps}
-import scala.concurrent.{Future, ExecutionContext, Await}
-import scala.concurrent.duration._
-import scala.async.Async.{async, await}
-import org.junit.Test
-
-
-class TestIfElse2Class {
-
- import ExecutionContext.Implicits.global
-
- def base(x: Int): Future[Int] = Future {
- x + 2
- }
-
- def m(y: Int): Future[Int] = async {
- val f = base(y)
- var z = 0
- if (y > 0) {
- val x = await(f)
- z = x + 2
- } else {
- val x = await(f)
- z = x - 2
- }
- z
- }
-}
-
-class IfElse2Spec {
-
- @Test
- def `variables of the same name in different blocks`(): Unit = {
- val o = new TestIfElse2Class
- val fut = o.m(10)
- val res = Await.result(fut, 2 seconds)
- res mustBe (14)
- }
-}
diff --git a/src/test/scala/scala/async/run/ifelse3/IfElse3.scala b/src/test/scala/scala/async/run/ifelse3/IfElse3.scala
deleted file mode 100644
index 805d95d6..00000000
--- a/src/test/scala/scala/async/run/ifelse3/IfElse3.scala
+++ /dev/null
@@ -1,58 +0,0 @@
-/*
- * 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
-package run
-package ifelse3
-
-import language.{reflectiveCalls, postfixOps}
-import scala.concurrent.{Future, ExecutionContext, Await}
-import scala.concurrent.duration._
-import scala.async.Async.{async, await}
-import org.junit.Test
-
-
-class TestIfElse3Class {
-
- import ExecutionContext.Implicits.global
-
- def base(x: Int): Future[Int] = Future {
- x + 2
- }
-
- def m(y: Int): Future[Int] = async {
- val f = base(y)
- var z = 0
- if (y > 0) {
- val x1 = await(f)
- var w = x1 + 2
- z = w + 2
- } else {
- val x2 = await(f)
- var w = x2 + 2
- z = w - 2
- }
- z
- }
-}
-
-
-class IfElse3Spec {
-
- @Test
- def `variables of the same name in different blocks`(): Unit = {
- val o = new TestIfElse3Class
- val fut = o.m(10)
- val res = Await.result(fut, 2 seconds)
- res mustBe (16)
- }
-}
diff --git a/src/test/scala/scala/async/run/ifelse4/IfElse4.scala b/src/test/scala/scala/async/run/ifelse4/IfElse4.scala
deleted file mode 100644
index a71b62eb..00000000
--- a/src/test/scala/scala/async/run/ifelse4/IfElse4.scala
+++ /dev/null
@@ -1,71 +0,0 @@
-/*
- * 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
-package run
-package ifelse4
-
-import language.{reflectiveCalls, postfixOps}
-import scala.concurrent.{Future, ExecutionContext, Await}
-import scala.concurrent.duration._
-import scala.async.Async.{async, await}
-import org.junit.Test
-
-
-class TestIfElse4Class {
-
- import ExecutionContext.Implicits.global
-
- class F[A]
- class S[A](val id: String)
- trait P
-
- case class K(f: F[_])
-
- def result[A](f: F[A]) = async {
- new S[A with P]("foo")
- }
-
- def run(k: K) = async {
- val res = await(result(k.f))
- // these triggered a crash with mismatched existential skolems
- // found : S#10272[_$1#10308 with String#137] where type _$1#10308
- // required: S#10272[_$1#10311 with String#137] forSome { type _$1#10311 }
-
- // This variation of the crash could be avoided by fixing the over-eager
- // generation of states in `If` nodes, which was caused by a bug in label
- // detection code.
- if(true) {
- identity(res)
- }
-
- // This variation remained after the aforementioned fix, however.
- // It was fixed by manually typing the `Assign(liftedField, rhs)` AST,
- // which is how we avoid these problems through the rest of the ANF transform.
- if(true) {
- identity(res)
- await(result(k.f))
- }
- res
- }
-}
-
-class IfElse4Spec {
-
- @Test
- def `await result with complex type containing skolem`(): Unit = {
- val o = new TestIfElse4Class
- val fut = o.run(o.K(null))
- val res = Await.result(fut, 2 seconds)
- res.id mustBe ("foo")
- }
-}
diff --git a/src/test/scala/scala/async/run/late/LateExpansion.scala b/src/test/scala/scala/async/run/late/LateExpansion.scala
deleted file mode 100644
index 51dbdb28..00000000
--- a/src/test/scala/scala/async/run/late/LateExpansion.scala
+++ /dev/null
@@ -1,612 +0,0 @@
-/*
- * 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.run.late
-
-import java.io.File
-
-import junit.framework.Assert.assertEquals
-import org.junit.{Assert, Ignore, Test}
-
-import scala.annotation.StaticAnnotation
-import scala.annotation.meta.{field, getter}
-import scala.async.internal.AsyncId
-import scala.reflect.internal.util.ScalaClassLoader.URLClassLoader
-import scala.tools.nsc._
-import scala.tools.nsc.plugins.{Plugin, PluginComponent}
-import scala.tools.nsc.reporters.StoreReporter
-import scala.tools.nsc.transform.TypingTransformers
-
-// Tests for customized use of the async transform from a compiler plugin, which
-// calls it from a new phase that runs after patmat.
-class LateExpansion {
-
- @Test def testRewrittenApply(): Unit = {
- val result = wrapAndRun(
- """
- | object O {
- | case class Foo(a: Any)
- | }
- | @autoawait def id(a: String) = a
- | O.Foo
- | id("foo") + id("bar")
- | O.Foo(1)
- | """.stripMargin)
- assertEquals("Foo(1)", result.toString)
- }
-
- @Ignore("Need to use adjustType more pervasively in AsyncTransform, but that exposes bugs in {Type, ... }Symbol's cache invalidation")
- @Test def testIsInstanceOfType(): Unit = {
- val result = wrapAndRun(
- """
- | class Outer
- | @autoawait def id(a: String) = a
- | val o = new Outer
- | id("foo") + id("bar")
- | ("": Object).isInstanceOf[o.type]
- | """.stripMargin)
- assertEquals(false, result)
- }
-
- @Test def testIsInstanceOfTerm(): Unit = {
- val result = wrapAndRun(
- """
- | class Outer
- | @autoawait def id(a: String) = a
- | val o = new Outer
- | id("foo") + id("bar")
- | o.isInstanceOf[Outer]
- | """.stripMargin)
- assertEquals(true, result)
- }
-
- @Test def testArrayLocalModule(): Unit = {
- val result = wrapAndRun(
- """
- | class Outer
- | @autoawait def id(a: String) = a
- | val O = ""
- | id("foo") + id("bar")
- | new Array[O.type](0)
- | """.stripMargin)
- assertEquals(classOf[Array[String]], result.getClass)
- }
-
- @Test def test0(): Unit = {
- val result = wrapAndRun(
- """
- | @autoawait def id(a: String) = a
- | id("foo") + id("bar")
- | """.stripMargin)
- assertEquals("foobar", result)
- }
-
- @Test def testGuard(): Unit = {
- val result = wrapAndRun(
- """
- | @autoawait def id[A](a: A) = a
- | "" match { case _ if id(false) => ???; case _ => "okay" }
- | """.stripMargin)
- assertEquals("okay", result)
- }
-
- @Test def testExtractor(): Unit = {
- val result = wrapAndRun(
- """
- | object Extractor { @autoawait def unapply(a: String) = Some((a, a)) }
- | "" match { case Extractor(a, b) if "".isEmpty => a == b }
- | """.stripMargin)
- assertEquals(true, result)
- }
-
- @Test def testNestedMatchExtractor(): Unit = {
- val result = wrapAndRun(
- """
- | object Extractor { @autoawait def unapply(a: String) = Some((a, a)) }
- | "" match {
- | case _ if "".isEmpty =>
- | "" match { case Extractor(a, b) => a == b }
- | }
- | """.stripMargin)
- assertEquals(true, result)
- }
-
- @Test def testCombo(): Unit = {
- val result = wrapAndRun(
- """
- | object Extractor1 { @autoawait def unapply(a: String) = Some((a + 1, a + 2)) }
- | object Extractor2 { @autoawait def unapply(a: String) = Some(a + 3) }
- | @autoawait def id(a: String) = a
- | println("Test.test")
- | val r1 = Predef.identity("blerg") match {
- | case x if " ".isEmpty => "case 2: " + x
- | case Extractor1(Extractor2(x), y: String) if x == "xxx" => "case 1: " + x + ":" + y
- | x match {
- | case Extractor1(Extractor2(x), y: String) =>
- | case _ =>
- | }
- | case Extractor2(x) => "case 3: " + x
- | }
- | r1
- | """.stripMargin)
- assertEquals("case 3: blerg3", result)
- }
-
- @Test def polymorphicMethod(): Unit = {
- val result = run(
- """
- |import scala.async.run.late.{autoawait,lateasync}
- |object Test {
- | class C { override def toString = "C" }
- | @autoawait def foo[A <: C](a: A): A = a
- | @lateasync
- | def test1[CC <: C](c: CC): (CC, CC) = {
- | val x: (CC, CC) = 0 match { case _ if false => ???; case _ => (foo(c), foo(c)) }
- | x
- | }
- | def test(): (C, C) = test1(new C)
- |}
- | """.stripMargin)
- assertEquals("(C,C)", result.toString)
- }
-
- @Test def shadowing(): Unit = {
- val result = run(
- """
- |import scala.async.run.late.{autoawait,lateasync}
- |object Test {
- | trait Foo
- | trait Bar extends Foo
- | @autoawait def boundary = ""
- | @lateasync
- | def test: Unit = {
- | (new Bar {}: Any) match {
- | case foo: Bar =>
- | boundary
- | 0 match {
- | case _ => foo; ()
- | }
- | ()
- | }
- | ()
- | }
- |}
- | """.stripMargin)
- }
-
- @Test def shadowing0(): Unit = {
- val result = run(
- """
- |import scala.async.run.late.{autoawait,lateasync}
- |object Test {
- | trait Foo
- | trait Bar
- | def test: Any = test(new C)
- | @autoawait def asyncBoundary: String = ""
- | @lateasync
- | def test(foo: Foo): Foo = foo match {
- | case foo: Bar =>
- | val foo2: Foo with Bar = new Foo with Bar {}
- | asyncBoundary
- | null match {
- | case _ => foo2
- | }
- | case other => foo
- | }
- | class C extends Foo with Bar
- |}
- | """.stripMargin)
- }
-
- @Test def shadowing2(): Unit = {
- val result = run(
- """
- |import scala.async.run.late.{autoawait,lateasync}
- |object Test {
- | trait Base; trait Foo[T <: Base] { @autoawait def func: Option[Foo[T]] = None }
- | class Sub extends Base
- | trait Bar extends Foo[Sub]
- | def test: Any = test(new Bar {})
- | @lateasync
- | def test[T <: Base](foo: Foo[T]): Foo[T] = foo match {
- | case foo: Bar =>
- | val res = foo.func
- | res match {
- | case _ =>
- | }
- | foo
- | case other => foo
- | }
- | test(new Bar {})
- |}
- | """.stripMargin)
- }
-
- @Test def patternAlternative(): Unit = {
- val result = wrapAndRun(
- """
- | @autoawait def one = 1
- |
- | @lateasync def test = {
- | Option(true) match {
- | case null | None => false
- | case Some(v) => one; v
- | }
- | }
- | """.stripMargin)
- }
-
- @Test def patternAlternativeBothAnnotations(): Unit = {
- val result = wrapAndRun(
- """
- |import scala.async.run.late.{autoawait,lateasync}
- |object Test {
- | @autoawait def func1() = "hello"
- | @lateasync def func(a: Option[Boolean]) = a match {
- | case null | None => func1 + " world"
- | case _ => "okay"
- | }
- | def test: Any = func(None)
- |}
- | """.stripMargin)
- }
-
- @Test def shadowingRefinedTypes(): Unit = {
- val result = run(
- s"""
- |import scala.async.run.late.{autoawait,lateasync}
- |trait Base
- |class Sub extends Base
- |trait Foo[T <: Base] {
- | @autoawait def func: Option[Foo[T]] = None
- |}
- |trait Bar extends Foo[Sub]
- |object Test {
- | @lateasync def func[T <: Base](foo: Foo[T]): Foo[T] = foo match { // the whole pattern match will be wrapped with async{ }
- | case foo: Bar =>
- | val res = foo.func // will be rewritten into: await(foo.func)
- | res match {
- | case Some(v) => v // this will report type mismtach
- | case other => foo
- | }
- | case other => foo
- | }
- | def test: Any = { val b = new Bar{}; func(b) == b }
- |}""".stripMargin)
- assertEquals(true, result)
- }
-
- @Test def testMatchEndIssue(): Unit = {
- val result = run(
- """
- |import scala.async.run.late.{autoawait,lateasync}
- |sealed trait Subject
- |final class Principal(val name: String) extends Subject
- |object Principal {
- | def unapply(p: Principal): Option[String] = Some(p.name)
- |}
- |object Test {
- | @autoawait @lateasync
- | def containsPrincipal(search: String, value: Subject): Boolean = value match {
- | case Principal(name) if name == search => true
- | case Principal(name) => containsPrincipal(search, value)
- | case other => false
- | }
- |
- | @lateasync
- | def test = containsPrincipal("test", new Principal("test"))
- |}
- | """.stripMargin)
- }
-
- @Test def testGenericTypeBoundaryIssue(): Unit = {
- val result = run(
- """
-
- import scala.async.run.late.{autoawait,lateasync}
- trait InstrumentOfValue
- trait Security[T <: InstrumentOfValue] extends InstrumentOfValue
- class Bound extends Security[Bound]
- class Futures extends Security[Futures]
- object TestGenericTypeBoundIssue {
- @autoawait @lateasync def processBound(bound: Bound): Unit = { println("process Bound") }
- @autoawait @lateasync def processFutures(futures: Futures): Unit = { println("process Futures") }
- @autoawait @lateasync def doStuff(sec: Security[_]): Unit = {
- sec match {
- case bound: Bound => processBound(bound)
- case futures: Futures => processFutures(futures)
- case _ => throw new Exception("Unknown Security type: " + sec)
- }
- }
- }
- object Test { @lateasync def test: Unit = TestGenericTypeBoundIssue.doStuff(new Bound) }
- """.stripMargin)
- }
-
- @Test def testReturnTupleIssue(): Unit = {
- val result = run(
- """
- import scala.async.run.late.{autoawait,lateasync}
- class TestReturnExprIssue(str: String) {
- @autoawait @lateasync def getTestValue = Some(42)
- @autoawait @lateasync def doStuff: Int = {
- val opt: Option[Int] = getTestValue // here we have an async method invoke
- opt match {
- case Some(li) => li // use the result somehow
- case None =>
- }
- 42 // type mismatch; found : AnyVal required: Int
- }
- }
- object Test { @lateasync def test: Unit = new TestReturnExprIssue("").doStuff }
- """.stripMargin)
- }
-
-
- @Test def testAfterRefchecksIssue(): Unit = {
- val result = run(
- """
- import scala.async.run.late.{autoawait,lateasync}
- trait Factory[T] { def create: T }
- sealed trait TimePoint
- class TimeLine[TP <: TimePoint](val tpInitial: Factory[TP]) {
- @autoawait @lateasync private[TimeLine] val tp: TP = tpInitial.create
- @autoawait @lateasync def timePoint: TP = tp
- }
- object Test {
- def test: Unit = ()
- }
- """)
- }
-
- @Test def testArrayIndexOutOfBoundIssue(): Unit = {
- val result = run(
- """
- import scala.async.run.late.{autoawait,lateasync}
-
- sealed trait Result
- case object A extends Result
- case object B extends Result
- case object C extends Result
-
- object Test {
- protected def doStuff(res: Result) = {
- class C {
- @autoawait def needCheck = false
-
- @lateasync def m = {
- if (needCheck) "NO"
- else {
- res match {
- case A => 1
- case _ => 2
- }
- }
- }
- }
- }
-
-
- @lateasync
- def test() = doStuff(B)
- }
- """)
- }
-
- def wrapAndRun(code: String): Any = {
- run(
- s"""
- |import scala.async.run.late.{autoawait,lateasync}
- |object Test {
- | @lateasync
- | def test: Any = {
- | $code
- | }
- |}
- | """.stripMargin)
- }
-
-
- @Test def testNegativeArraySizeException(): Unit = {
- val result = run(
- """
- import scala.async.run.late.{autoawait,lateasync}
-
- object Test {
- def foo(foo: Any, bar: Any) = ()
- @autoawait def getValue = 4.2
- @lateasync def func(f: Any) = {
- foo(f match { case _ if "".isEmpty => 2 }, getValue);
- }
-
- @lateasync
- def test() = func(4)
- }
- """)
- }
-
- @Test def testNegativeArraySizeExceptionFine1(): Unit = {
- val result = run(
- """
- import scala.async.run.late.{autoawait,lateasync}
- case class FixedFoo(foo: Int)
- class Foobar(val foo: Int, val bar: Double) {
- @autoawait @lateasync def getValue = 4.2
- @autoawait @lateasync def func(f: Any) = {
- new Foobar(foo = f match {
- case FixedFoo(x) => x
- case _ => 2
- },
- bar = getValue)
- }
- }
- object Test {
- @lateasync def test() = new Foobar(0, 0).func(4)
- }
- """)
- }
-
- @Test def testByNameOwner(): Unit = {
- val result = run(
- """
- import scala.async.run.late.{autoawait,lateasync}
- object Bleh {
- @autoawait @lateasync def asyncCall(): Int = 0
- def byName[T](fn: => T): T = fn
- }
- object Boffo {
- @autoawait @lateasync def jerk(): Unit = {
- val pointlessSymbolOwner = 1 match {
- case _ =>
- Bleh.asyncCall()
- Bleh.byName {
- val whyDoHateMe = 1
- whyDoHateMe
- }
- }
- }
- }
- object Test {
- @lateasync def test() = Boffo.jerk()
- }
- """)
- }
-
- @Test def testByNameOwner2(): Unit = {
- val result = run(
- """
- import scala.async.run.late.{autoawait,lateasync}
- object Bleh {
- @autoawait @lateasync def bleh = Bleh
- def byName[T](fn: => T): T = fn
- }
- object Boffo {
- @autoawait @lateasync def slob(): Unit = {
- val pointlessSymbolOwner = {
- Bleh.bleh.byName {
- val whyDoHateMeToo = 1
- whyDoHateMeToo
- }
- }
- }
- }
- object Test {
- @lateasync def test() = Boffo.slob()
- }
- """)
- }
-
- private def createTempDir(): File = {
- val f = File.createTempFile("output", "")
- f.delete()
- f.mkdirs()
- f
- }
-
- def run(code: String): Any = {
- val out = createTempDir()
- try {
- val reporter = new StoreReporter
- val settings = new Settings(println(_))
- settings.outdir.value = out.getAbsolutePath
- settings.embeddedDefaults(getClass.getClassLoader)
- // settings.processArgumentString("-Xprint:patmat,postpatmat,jvm -nowarn")
- val isInSBT = !settings.classpath.isSetByUser
- if (isInSBT) settings.usejavacp.value = true
- val global = new Global(settings, reporter) {
- self =>
-
- object late extends {
- val global: self.type = self
- } with LatePlugin
-
- override protected def loadPlugins(): List[Plugin] = late :: Nil
- }
- import global._
-
- val run = new Run
- val source = newSourceFile(code)
- // TreeInterrogation.withDebug {
- run.compileSources(source :: Nil)
- // }
- Assert.assertTrue(reporter.infos.mkString("\n"), !reporter.hasErrors)
- val loader = new URLClassLoader(Seq(new File(settings.outdir.value).toURI.toURL), global.getClass.getClassLoader)
- val cls = loader.loadClass("Test")
- cls.getMethod("test").invoke(null)
- } finally {
- scala.reflect.io.Path.apply(out).deleteRecursively()
- }
- }
-}
-
-abstract class LatePlugin extends Plugin {
-
- import global._
-
- override val components: List[PluginComponent] = List(new PluginComponent with TypingTransformers {
- val global: LatePlugin.this.global.type = LatePlugin.this.global
-
- lazy val asyncIdSym = symbolOf[AsyncId.type]
- lazy val asyncSym = asyncIdSym.info.member(TermName("async"))
- lazy val awaitSym = asyncIdSym.info.member(TermName("await"))
- lazy val autoAwaitSym = symbolOf[autoawait]
- lazy val lateAsyncSym = symbolOf[lateasync]
-
- def newTransformer(unit: CompilationUnit) = new TypingTransformer(unit) {
- override def transform(tree: Tree): Tree = {
- super.transform(tree) match {
- case ap@Apply(fun, args) if fun.symbol.hasAnnotation(autoAwaitSym) =>
- localTyper.typed(Apply(TypeApply(gen.mkAttributedRef(asyncIdSym.typeOfThis, awaitSym), TypeTree(ap.tpe) :: Nil), ap :: Nil))
- case sel@Select(fun, _) if sel.symbol.hasAnnotation(autoAwaitSym) && !(tree.tpe.isInstanceOf[MethodTypeApi] || tree.tpe.isInstanceOf[PolyTypeApi]) =>
- localTyper.typed(Apply(TypeApply(gen.mkAttributedRef(asyncIdSym.typeOfThis, awaitSym), TypeTree(sel.tpe) :: Nil), sel :: Nil))
- case dd: DefDef if dd.symbol.hasAnnotation(lateAsyncSym) => atOwner(dd.symbol) {
- deriveDefDef(dd) { rhs: Tree =>
- val invoke = Apply(TypeApply(gen.mkAttributedRef(asyncIdSym.typeOfThis, asyncSym), TypeTree(rhs.tpe) :: Nil), List(rhs))
- localTyper.typed(atPos(dd.pos)(invoke))
- }
- }
- case vd: ValDef if vd.symbol.hasAnnotation(lateAsyncSym) => atOwner(vd.symbol) {
- deriveValDef(vd) { rhs: Tree =>
- val invoke = Apply(TypeApply(gen.mkAttributedRef(asyncIdSym.typeOfThis, asyncSym), TypeTree(rhs.tpe) :: Nil), List(rhs))
- localTyper.typed(atPos(vd.pos)(invoke))
- }
- }
- case vd: ValDef =>
- vd
- case x => x
- }
- }
- }
-
- override def newPhase(prev: Phase): Phase = new StdPhase(prev) {
- override def apply(unit: CompilationUnit): Unit = {
- val translated = newTransformer(unit).transformUnit(unit)
- //println(show(unit.body))
- translated
- }
- }
-
- override val runsAfter: List[String] = "refchecks" :: Nil
- override val phaseName: String = "postpatmat"
-
- })
- override val description: String = "postpatmat"
- override val name: String = "postpatmat"
-}
-
-// Methods with this annotation are translated to having the RHS wrapped in `AsyncId.async { }`
-@field
-final class lateasync extends StaticAnnotation
-
-// Calls to methods with this annotation are translated to `AsyncId.await()`
-@getter
-final class autoawait extends StaticAnnotation
diff --git a/src/test/scala/scala/async/run/lazyval/LazyValSpec.scala b/src/test/scala/scala/async/run/lazyval/LazyValSpec.scala
deleted file mode 100644
index 6805d28c..00000000
--- a/src/test/scala/scala/async/run/lazyval/LazyValSpec.scala
+++ /dev/null
@@ -1,37 +0,0 @@
-/*
- * 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
-package run
-package lazyval
-
-import org.junit.Test
-import scala.async.internal.AsyncId._
-
-class LazyValSpec {
- @Test
- def lazyValAllowed(): Unit = {
- val result = async {
- var x = 0
- lazy val y = { x += 1; 42 }
- assert(x == 0, x)
- val z = await(1)
- val result = y + x
- assert(x == 1, x)
- identity(y)
- assert(x == 1, x)
- result
- }
- result mustBe 43
- }
-}
-
diff --git a/src/test/scala/scala/async/run/live/LiveVariablesSpec.scala b/src/test/scala/scala/async/run/live/LiveVariablesSpec.scala
deleted file mode 100644
index f4268a73..00000000
--- a/src/test/scala/scala/async/run/live/LiveVariablesSpec.scala
+++ /dev/null
@@ -1,299 +0,0 @@
-/*
- * 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
-package run
-package live
-
-import org.junit.Test
-
-import internal.AsyncTestLV
-import AsyncTestLV._
-
-case class Cell[T](v: T)
-
-class Meter(val len: Long) extends AnyVal
-
-case class MCell[T](var v: T)
-
-
-class LiveVariablesSpec {
- AsyncTestLV.clear()
-
- @Test
- def `zero out fields of reference type`(): Unit = {
- val f = async { Cell(1) }
-
- def m1(x: Cell[Int]): Cell[Int] =
- async { Cell(x.v + 1) }
-
- def m2(x: Cell[Int]): String =
- async { x.v.toString }
-
- def m3() = async {
- val a: Cell[Int] = await(f) // await$1$1
- // a == Cell(1)
- val b: Cell[Int] = await(m1(a)) // await$2$1
- // b == Cell(2)
- assert(AsyncTestLV.log.exists(_._2 == Cell(1)), AsyncTestLV.log)
- val res = await(m2(b)) // await$3$1
- assert(AsyncTestLV.log.exists(_._2 == Cell(2)))
- res
- }
-
- assert(m3() == "2")
- }
-
- @Test
- def `zero out fields of type Any`(): Unit = {
- val f = async { Cell(1) }
-
- def m1(x: Cell[Int]): Cell[Int] =
- async { Cell(x.v + 1) }
-
- def m2(x: Any): String =
- async { x.toString }
-
- def m3() = async {
- val a: Cell[Int] = await(f) // await$4$1
- // a == Cell(1)
- val b: Any = await(m1(a)) // await$5$1
- // b == Cell(2)
- assert(AsyncTestLV.log.exists(_._2 == Cell(1)))
- val res = await(m2(b)) // await$6$1
- assert(AsyncTestLV.log.exists(_._2 == Cell(2)))
- res
- }
-
- assert(m3() == "Cell(2)")
- }
-
- @Test
- def `do not zero out fields of primitive type`(): Unit = {
- val f = async { 1 }
-
- def m1(x: Int): Cell[Int] =
- async { Cell(x + 1) }
-
- def m2(x: Any): String =
- async { x.toString }
-
- def m3() = async {
- val a: Int = await(f) // await$7$1
- // a == 1
- val b: Any = await(m1(a)) // await$8$1
- // b == Cell(2)
- // assert(!AsyncTestLV.log.exists(p => p._1 == "await$7$1"))
- val res = await(m2(b)) // await$9$1
- assert(AsyncTestLV.log.exists(_._2 == Cell(2)))
- res
- }
-
- assert(m3() == "Cell(2)")
- }
-
- @Test
- def `zero out fields of value class type`(): Unit = {
- val f = async { Cell(1) }
-
- def m1(x: Cell[Int]): Meter =
- async { new Meter(x.v + 1) }
-
- def m2(x: Any): String =
- async { x.toString }
-
- def m3() = async {
- val a: Cell[Int] = await(f) // await$10$1
- // a == Cell(1)
- val b: Meter = await(m1(a)) // await$11$1
- // b == Meter(2)
- assert(AsyncTestLV.log.exists(_._2 == Cell(1)))
- val res = await(m2(b.len)) // await$12$1
- assert(AsyncTestLV.log.exists(_._2.asInstanceOf[Meter].len == 2L))
- res
- }
-
- assert(m3() == "2")
- }
-
- @Test
- def `zero out fields after use in loop`(): Unit = {
- val f = async { MCell(1) }
-
- def m1(x: MCell[Int], y: Int): Int =
- async { x.v + y }
-
- def m3() = async {
- // state #1
- val a: MCell[Int] = await(f) // await$13$1
- // state #2
- var y = MCell(0)
-
- while (a.v < 10) {
- // state #4
- a.v = a.v + 1
- y = MCell(await(a).v + 1) // await$14$1
- // state #7
- }
-
- // state #3
- // assert(AsyncTestLV.log.exists(entry => entry._1 == "await$14$1"))
-
- val b = await(m1(a, y.v)) // await$15$1
- // state #8
- assert(AsyncTestLV.log.exists(_._2 == MCell(10)), AsyncTestLV.log)
- assert(AsyncTestLV.log.exists(_._2 == MCell(11)))
- b
- }
-
- assert(m3() == 21, m3())
- }
-
- @Test
- def `don't zero captured fields captured lambda`(): Unit = {
- val f = async {
- val x = "x"
- val y = "y"
- await(0)
- y.reverse
- val f = () => assert(x != null)
- await(0)
- f
- }
- AsyncTestLV.assertNotNulledOut("x")
- AsyncTestLV.assertNulledOut("y")
- f()
- }
-
- @Test
- def `don't zero captured fields captured by-name`(): Unit = {
- def func0[A](a: => A): () => A = () => a
- val f = async {
- val x = "x"
- val y = "y"
- await(0)
- y.reverse
- val f = func0(assert(x != null))
- await(0)
- f
- }
- AsyncTestLV.assertNotNulledOut("x")
- AsyncTestLV.assertNulledOut("y")
- f()
- }
-
- @Test
- def `don't zero captured fields nested class`(): Unit = {
- def func0[A](a: => A): () => A = () => a
- val f = async {
- val x = "x"
- val y = "y"
- await(0)
- y.reverse
- val f = new Function0[Unit] {
- def apply = assert(x != null)
- }
- await(0)
- f
- }
- AsyncTestLV.assertNotNulledOut("x")
- AsyncTestLV.assertNulledOut("y")
- f()
- }
-
- @Test
- def `don't zero captured fields nested object`(): Unit = {
- def func0[A](a: => A): () => A = () => a
- val f = async {
- val x = "x"
- val y = "y"
- await(0)
- y.reverse
- object f extends Function0[Unit] {
- def apply = assert(x != null)
- }
- await(0)
- f
- }
- AsyncTestLV.assertNotNulledOut("x")
- AsyncTestLV.assertNulledOut("y")
- f()
- }
-
- @Test
- def `don't zero captured fields nested def`(): Unit = {
- val f = async {
- val x = "x"
- val y = "y"
- await(0)
- y.reverse
- def xx = x
- val f = xx _
- await(0)
- f
- }
- AsyncTestLV.assertNotNulledOut("x")
- AsyncTestLV.assertNulledOut("y")
- f()
- }
-
- @Test
- def `capture bug`(): Unit = {
- sealed trait Base
- case class B1() extends Base
- case class B2() extends Base
- val outer = List[(Base, Int)]((B1(), 8))
-
- def getMore(b: Base) = 4
-
- def baz = async {
- outer.head match {
- case (a @ B1(), r) => {
- val ents = await(getMore(a))
-
- { () =>
- println(a)
- assert(a ne null)
- }
- }
- case (b @ B2(), x) =>
- () => ???
- }
- }
- baz()
- }
-
- // https://github.com/scala/async/issues/104
- @Test def dontNullOutVarsOfTypeNothing_t104(): Unit = {
- import scala.async.Async._
- import scala.concurrent.duration.Duration
- import scala.concurrent.{Await, Future}
- import scala.concurrent.ExecutionContext.Implicits.global
- def errorGenerator(randomNum: Double) = {
- Future {
- if (randomNum < 0) {
- throw new IllegalStateException("Random number was too low!")
- } else {
- throw new IllegalStateException("Random number was too high!")
- }
- }
- }
- def randomTimesTwo = async {
- val num = _root_.scala.math.random
- if (num < 0 || num > 1) {
- await(errorGenerator(num))
- }
- num * 2
- }
- Await.result(randomTimesTwo, TestLatch.DefaultTimeout) // was: NotImplementedError
- }
-}
diff --git a/src/test/scala/scala/async/run/match0/Match0.scala b/src/test/scala/scala/async/run/match0/Match0.scala
deleted file mode 100644
index d8c136b9..00000000
--- a/src/test/scala/scala/async/run/match0/Match0.scala
+++ /dev/null
@@ -1,154 +0,0 @@
-/*
- * 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
-package run
-package match0
-
-import language.{reflectiveCalls, postfixOps}
-import scala.concurrent.{Future, ExecutionContext, Await}
-import scala.concurrent.duration._
-import scala.async.Async.{async, await}
-import org.junit.Test
-import scala.async.internal.AsyncId
-
-
-class TestMatchClass {
-
- import ExecutionContext.Implicits.global
-
- def m1(x: Int): Future[Int] = Future {
- x + 2
- }
-
- def m2(y: Int): Future[Int] = async {
- val f = m1(y)
- var z = 0
- y match {
- case 10 =>
- val x1 = await(f)
- z = x1 + 2
- case 20 =>
- val x2 = await(f)
- z = x2 - 2
- }
- z
- }
-
- def m3(y: Int): Future[Int] = async {
- val f = m1(y)
- var z = 0
- y match {
- case 0 =>
- val x2 = await(f)
- z = x2 - 2
- case 1 =>
- val x1 = await(f)
- z = x1 + 2
- }
- z
- }
-}
-
-
-class MatchSpec {
-
- @Test def `support await in a simple match expression`(): Unit = {
- val o = new TestMatchClass
- val fut = o.m2(10) // matches first case
- val res = Await.result(fut, 2 seconds)
- res mustBe (14)
- }
-
- @Test def `support await in a simple match expression 2`(): Unit = {
- val o = new TestMatchClass
- val fut = o.m3(1) // matches second case
- val res = Await.result(fut, 2 seconds)
- res mustBe (5)
- }
-
- @Test def `support await in a match expression with binds`(): Unit = {
- val result = AsyncId.async {
- val x = 1
- Option(x) match {
- case op @ Some(x) =>
- assert(op.contains(1))
- x + AsyncId.await(x)
- case None => AsyncId.await(0)
- }
- }
- result mustBe (2)
- }
-
- @Test def `support await referring to pattern matching vals`(): Unit = {
- import AsyncId.{async, await}
- val result = async {
- val x = 1
- val opt = Some("")
- await(0)
- val o @ Some(y) = opt
-
- {
- val o @ Some(y) = Some(".")
- }
-
- await(0)
- await((o, y.isEmpty))
- }
- result mustBe ((Some(""), true))
- }
-
- @Test def `await in scrutinee`(): Unit = {
- import AsyncId.{async, await}
- val result = async {
- await(if ("".isEmpty) await(1) else ???) match {
- case x if x < 0 => ???
- case y: Int => y * await(3)
- }
- }
- result mustBe (3)
- }
-
- @Test def duplicateBindName(): Unit = {
- import AsyncId.{async, await}
- def m4(m: Any) = async {
- m match {
- case buf: String =>
- await(0)
- case buf: Double =>
- await(2)
- }
- }
- m4("") mustBe 0
- }
-
- @Test def bugCastBoxedUnitToStringMatch(): Unit = {
- import scala.async.internal.AsyncId.{async, await}
- def foo = async {
- val p2 = await(5)
- "foo" match {
- case p3: String =>
- p2.toString
- }
- }
- foo mustBe "5"
- }
-
- @Test def bugCastBoxedUnitToStringIf(): Unit = {
- import scala.async.internal.AsyncId.{async, await}
- def foo = async {
- val p2 = await(5)
- if (true) p2.toString else p2.toString
- }
- foo mustBe "5"
- }
-}
diff --git a/src/test/scala/scala/async/run/nesteddef/NestedDef.scala b/src/test/scala/scala/async/run/nesteddef/NestedDef.scala
deleted file mode 100644
index 9e2d3c83..00000000
--- a/src/test/scala/scala/async/run/nesteddef/NestedDef.scala
+++ /dev/null
@@ -1,106 +0,0 @@
-/*
- * 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
-package run
-package nesteddef
-
-import org.junit.Test
-import scala.async.internal.AsyncId
-
-class NestedDef {
-
- @Test
- def nestedDef(): Unit = {
- import AsyncId._
- val result = async {
- val a = 0
- val x = await(a) - 1
- val local = 43
- def bar(d: Double) = -d + a + local
- def foo(z: Any) = (a.toDouble, bar(x).toDouble, z)
- foo(await(2))
- }
- result mustBe ((0d, 44d, 2))
- }
-
-
- @Test
- def nestedFunction(): Unit = {
- import AsyncId._
- val result = async {
- val a = 0
- val x = await(a) - 1
- val local = 43
- val bar = (d: Double) => -d + a + local
- val foo = (z: Any) => (a.toDouble, bar(x).toDouble, z)
- foo(await(2))
- }
- result mustBe ((0d, 44d, 2))
- }
-
- // We must lift `foo` and `bar` in the next two tests.
- @Test
- def nestedDefTransitive1(): Unit = {
- import AsyncId._
- val result = async {
- val a = 0
- val x = await(a) - 1
- def bar = a
- def foo = bar
- foo
- }
- result mustBe 0
- }
-
- @Test
- def nestedDefTransitive2(): Unit = {
- import AsyncId._
- val result = async {
- val a = 0
- val x = await(a) - 1
- def bar = a
- def foo = bar
- 0
- }
- result mustBe 0
- }
-
-
- // checking that our use/definition analysis doesn't cycle.
- @Test
- def mutuallyRecursive1(): Unit = {
- import AsyncId._
- val result = async {
- val a = 0
- val x = await(a) - 1
- def foo: Int = if (true) 0 else bar
- def bar: Int = if (true) 0 else foo
- bar
- }
- result mustBe 0
- }
-
- // checking that our use/definition analysis doesn't cycle.
- @Test
- def mutuallyRecursive2(): Unit = {
- import AsyncId._
- val result = async {
- val a = 0
- def foo: Int = if (true) 0 else bar
- def bar: Int = if (true) 0 else foo
- val x = await(a) - 1
- bar
- }
- result mustBe 0
- }
-}
diff --git a/src/test/scala/scala/async/run/noawait/NoAwaitSpec.scala b/src/test/scala/scala/async/run/noawait/NoAwaitSpec.scala
deleted file mode 100644
index f6f6afb0..00000000
--- a/src/test/scala/scala/async/run/noawait/NoAwaitSpec.scala
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
- * 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
-package run
-package noawait
-
-import scala.async.internal.AsyncId
-import AsyncId._
-import org.junit.Test
-
-class NoAwaitSpec {
- @Test
- def `async block without await`(): Unit = {
- def foo = 1
- async {
- foo
- foo
- } mustBe (foo)
- }
-
- @Test
- def `async block without await 2`(): Unit = {
- async {
- def x = 0
- if (x > 0) 0 else 1
- } mustBe (1)
- }
-
- @Test
- def `async expr without await`(): Unit = {
- def foo = 1
- async(foo) mustBe (foo)
- }
-}
diff --git a/src/test/scala/scala/async/run/stackoverflow/StackOverflowSpec.scala b/src/test/scala/scala/async/run/stackoverflow/StackOverflowSpec.scala
deleted file mode 100644
index 8e3127a0..00000000
--- a/src/test/scala/scala/async/run/stackoverflow/StackOverflowSpec.scala
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * 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
-package run
-package stackoverflow
-
-import org.junit.Test
-import scala.async.internal.AsyncId
-
-
-class StackOverflowSpec {
-
- @Test
- def stackSafety(): Unit = {
- import AsyncId._
- async {
- var i = 100000000
- while (i > 0) {
- if (false) {
- await(())
- }
- i -= 1
- }
- }
- }
-}
diff --git a/src/test/scala/scala/async/run/toughtype/ToughType.scala b/src/test/scala/scala/async/run/toughtype/ToughType.scala
deleted file mode 100644
index f7002b57..00000000
--- a/src/test/scala/scala/async/run/toughtype/ToughType.scala
+++ /dev/null
@@ -1,362 +0,0 @@
-/*
- * 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
-package run
-package toughtype
-
-import language.{reflectiveCalls, postfixOps}
-import scala.concurrent._
-import scala.concurrent.duration._
-import scala.async.Async._
-import org.junit.{Assert, Test}
-import scala.async.internal.AsyncId
-
-
-object ToughTypeObject {
-
- import ExecutionContext.Implicits.global
-
- class Inner
-
- def m2 = async[(List[_], ToughTypeObject.Inner)] {
- val y = await(Future[List[_]](Nil))
- val z = await(Future[Inner](new Inner))
- (y, z)
- }
-}
-
-class ToughTypeSpec {
-
- @Test def `propogates tough types`(): Unit = {
- val fut = ToughTypeObject.m2
- val res: (List[_], scala.async.run.toughtype.ToughTypeObject.Inner) = Await.result(fut, 2 seconds)
- res._1 mustBe (Nil)
- }
-
- @Test def patternMatchingPartialFunction(): Unit = {
- import AsyncId.{await, async}
- async {
- await(1)
- val a = await(1)
- val f = { case x => x + a }: PartialFunction[Int, Int]
- await(f(2))
- } mustBe 3
- }
-
- @Test def patternMatchingPartialFunctionNested(): Unit = {
- import AsyncId.{await, async}
- async {
- await(1)
- val neg1 = -1
- val a = await(1)
- val f = { case x => ({case x => neg1 * x}: PartialFunction[Int, Int])(x + a) }: PartialFunction[Int, Int]
- await(f(2))
- } mustBe -3
- }
-
- @Test def patternMatchingFunction(): Unit = {
- import AsyncId.{await, async}
- async {
- await(1)
- val a = await(1)
- val f = { case x => x + a }: Function[Int, Int]
- await(f(2))
- } mustBe 3
- }
-
- @Test def existentialBindIssue19(): Unit = {
- import AsyncId.{await, async}
- def m7(a: Any) = async {
- a match {
- case s: Seq[_] =>
- val x = s.size
- var ss = s
- ss = s
- await(x)
- }
- }
- m7(Nil) mustBe 0
- }
-
- @Test def existentialBind2Issue19(): Unit = {
- import scala.async.Async._, scala.concurrent.ExecutionContext.Implicits.global
- def conjure[T]: T = null.asInstanceOf[T]
-
- def m3 = async {
- val p: List[Option[_]] = conjure[List[Option[_]]]
- await(Future(1))
- }
-
- def m4 = async {
- await(Future[List[_]](Nil))
- }
- }
-
- @Test def singletonTypeIssue17(): Unit = {
- import AsyncId.{async, await}
- class A { class B }
- async {
- val a = new A
- def foo(b: a.B) = 0
- await(foo(new a.B))
- }
- }
-
- @Test def existentialMatch(): Unit = {
- import AsyncId.{async, await}
- trait Container[+A]
- case class ContainerImpl[A](value: A) extends Container[A]
- def foo: Container[_] = async {
- val a: Any = List(1)
- a match {
- case buf: Seq[_] =>
- val foo = await(5)
- val e0 = buf(0)
- ContainerImpl(e0)
- }
- }
- foo
- }
-
- @Test def existentialIfElse0(): Unit = {
- import AsyncId.{async, await}
- trait Container[+A]
- case class ContainerImpl[A](value: A) extends Container[A]
- def foo: Container[_] = async {
- val a: Any = List(1)
- if (true) {
- val buf: Seq[_] = List(1)
- val foo = await(5)
- val e0 = buf(0)
- ContainerImpl(e0)
- } else ???
- }
- foo
- }
-
- // This test was failing when lifting `def r` with:
- // symbol value m#10864 does not exist in r$1
- //
- // We generated:
- //
- // private[this] def r$1#5727[A#5728 >: Nothing#157 <: Any#156](m#5731: Foo#2349[A#5728]): Unit#208 = Bippy#2352.this.bar#5532({
- // m#5730;
- // ()
- // });
- //
- // Notice the incorrect reference to `m`.
- //
- // We compensated in `Lifter` by copying `ValDef` parameter symbols directly across.
- //
- // Turns out the behaviour stems from `thisMethodType` in `Namers`, which treats type parameter skolem symbols.
- @Test def nestedMethodWithInconsistencyTreeAndInfoParamSymbols(): Unit = {
- import language.{reflectiveCalls, postfixOps}
- import scala.concurrent.{Future, ExecutionContext, Await}
- import scala.concurrent.duration._
- import scala.async.Async.{async, await}
- import scala.async.internal.AsyncId
-
- class Foo[A]
-
- object Bippy {
-
- import ExecutionContext.Implicits.global
-
- def bar(f: => Unit): Unit = f
-
- def quux: Future[String] = ???
-
- def foo = async {
- def r[A](m: Foo[A])(n: A) = {
- bar {
- locally(m)
- locally(n)
- identity[A] _
- }
- }
-
- await(quux)
-
- r(new Foo[String])("")
- }
- }
- Bippy
- }
-
- @Test
- def ticket63(): Unit = {
- import scala.async.Async._
- import scala.concurrent.{ ExecutionContext, Future }
-
- object SomeExecutionContext extends ExecutionContext {
- def reportFailure(t: Throwable): Unit = ???
- def execute(runnable: Runnable): Unit = ???
- }
-
- trait FunDep[W, S, R] {
- def method(w: W, s: S): Future[R]
- }
-
- object FunDep {
- implicit def `Something to do with List`[W, S, R](implicit funDep: FunDep[W, S, R]) =
- new FunDep[W, List[S], W] {
- def method(w: W, l: List[S]) = async {
- val it = l.iterator
- while (it.hasNext) {
- await(funDep.method(w, it.next()))
- }
- w
- }(SomeExecutionContext)
- }
- }
-
- }
-
- @Test def ticket66Nothing(): Unit = {
- import scala.concurrent.Future
- import scala.concurrent.ExecutionContext.Implicits.global
- val e = new Exception()
- val f: Future[Nothing] = Future.failed(e)
- val f1 = async {
- await(f)
- }
- try {
- Await.result(f1, 5.seconds)
- } catch {
- case `e` =>
- }
- }
-
- @Test def ticket83ValueClass(): Unit = {
- import scala.async.Async._
- import scala.concurrent._, duration._, ExecutionContext.Implicits.global
- val f = async {
- val uid = new IntWrapper("foo")
- await(Future(uid))
- }
- val result = Await.result(f, 5.seconds)
- result mustEqual (new IntWrapper("foo"))
- }
-
- @Test def ticket86NestedValueClass(): Unit = {
- import ExecutionContext.Implicits.global
-
- val f = async {
- val a = Future.successful(new IntWrapper("42"))
- await(await(a).plusStr)
- }
- val result = Await.result(f, 5.seconds)
- result mustEqual "42!"
- }
-
- @Test def ticket86MatchedValueClass(): Unit = {
- import ExecutionContext.Implicits.global
-
- def doAThing(param: IntWrapper) = Future(None)
-
- val fut = async {
- Option(new IntWrapper("value!")) match {
- case Some(valueHolder) =>
- await(doAThing(valueHolder))
- case None =>
- None
- }
- }
-
- val result = Await.result(fut, 5.seconds)
- result mustBe None
- }
-
- @Test def ticket86MatchedParameterizedValueClass(): Unit = {
- import ExecutionContext.Implicits.global
-
- def doAThing(param: ParamWrapper[String]) = Future(None)
-
- val fut = async {
- Option(new ParamWrapper("value!")) match {
- case Some(valueHolder) =>
- await(doAThing(valueHolder))
- case None =>
- None
- }
- }
-
- val result = Await.result(fut, 5.seconds)
- result mustBe None
- }
-
- @Test def ticket86PrivateValueClass(): Unit = {
- import ExecutionContext.Implicits.global
-
- def doAThing(param: PrivateWrapper) = Future(None)
-
- val fut = async {
- Option(PrivateWrapper.Instance) match {
- case Some(valueHolder) =>
- await(doAThing(valueHolder))
- case None =>
- None
- }
- }
-
- val result = Await.result(fut, 5.seconds)
- result mustBe None
- }
-
- @Test def awaitOfAbstractType(): Unit = {
- import ExecutionContext.Implicits.global
-
- def combine[A](a1: A, a2: A): A = a1
-
- def combineAsync[A](a1: Future[A], a2: Future[A]) = async {
- combine(await(a1), await(a2))
- }
-
- val fut = combineAsync(Future(1), Future(2))
-
- val result = Await.result(fut, 5.seconds)
- result mustEqual 1
- }
-
- // https://github.com/scala/async/issues/106
- @Test def valueClassT106(): Unit = {
- import scala.async.internal.AsyncId._
- async {
- "whatever value" match {
- case _ =>
- await("whatever return type")
- new IntWrapper("value class matters")
- }
- "whatever return type"
- }
- }
-}
-
-class IntWrapper(val value: String) extends AnyVal {
- def plusStr = Future.successful(value + "!")
-}
-class ParamWrapper[T](val value: T) extends AnyVal
-
-class PrivateWrapper private (private val value: String) extends AnyVal
-object PrivateWrapper {
- def Instance = new PrivateWrapper("")
-}
-
-
-trait A
-
-trait B
-
-trait L[A2, B2 <: A2] {
- def bar(a: Any, b: Any) = 0
-}
diff --git a/src/test/scala/scala/async/run/uncheckedBounds/UncheckedBoundsSpec.scala b/src/test/scala/scala/async/run/uncheckedBounds/UncheckedBoundsSpec.scala
deleted file mode 100644
index 435a14be..00000000
--- a/src/test/scala/scala/async/run/uncheckedBounds/UncheckedBoundsSpec.scala
+++ /dev/null
@@ -1,47 +0,0 @@
-/*
- * 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
-package run
-package uncheckedBounds
-
-import org.junit.{Test, Assert}
-import scala.async.TreeInterrogation
-
-class UncheckedBoundsSpec {
- @Test def insufficientLub_SI_7694(): Unit = {
- eval( s"""
- object Test {
- import _root_.scala.async.run.toughtype._
- import _root_.scala.async.internal.AsyncId.{async, await}
- async {
- (if (true) await(null: L[A, A]) else await(null: L[B, B]))
- }
- }
- """, compileOptions = s"-cp ${toolboxClasspath} ")
- }
-
- @Test def insufficientLub_SI_7694_ScalaConcurrent(): Unit = {
- eval( s"""
- object Test {
- import _root_.scala.async.run.toughtype._
- import _root_.scala.async.Async.{async, await}
- import scala.concurrent._
- import scala.concurrent.ExecutionContext.Implicits.global
- async {
- (if (true) await(null: Future[L[A, A]]) else await(null: Future[L[B, B]]))
- }
- }
- """, compileOptions = s"-cp ${toolboxClasspath} ")
- }
-
-}