From 10f3c8db6163ebe3196173c1d87e69c1fb6a3a65 Mon Sep 17 00:00:00 2001 From: Jason Zaugg Date: Thu, 7 Nov 2013 15:27:40 +0100 Subject: Minimize the public API - Remove the CPS fallback version of async. That was not intended to be part of 1.0. - Lookup the await method beside the macro, rather than requiring all calls to go to AsyncBase.await. - Create a minimal version of Async that just contains await/async and delegates to the macro implementation in internal._ - Add scaladoc. --- src/main/scala/scala/async/Async.scala | 55 +++++++++++ src/main/scala/scala/async/AsyncBase.scala | 23 ----- src/main/scala/scala/async/StateMachine.scala | 2 + .../continuations/AsyncBaseWithCPSFallback.scala | 102 --------------------- .../async/continuations/AsyncWithCPSFallback.scala | 25 ----- .../scala/async/continuations/CPSBasedAsync.scala | 24 ----- .../async/continuations/CPSBasedAsyncBase.scala | 23 ----- .../continuations/ScalaConcurrentCPSFallback.scala | 32 ------- .../scala/scala/async/internal/AsyncAnalysis.scala | 9 +- .../scala/scala/async/internal/AsyncBase.scala | 5 + src/main/scala/scala/async/internal/AsyncId.scala | 2 - .../scala/async/internal/AsyncTransform.scala | 2 +- .../scala/scala/async/internal/FutureSystem.scala | 7 -- .../async/internal/ScalaConcurrentAsync.scala | 18 ++++ .../scala/async/internal/TransformUtils.scala | 3 +- src/test/scala/scala/async/run/cps/CPSSpec.scala | 46 ---------- 16 files changed, 86 insertions(+), 292 deletions(-) create mode 100644 src/main/scala/scala/async/Async.scala delete mode 100644 src/main/scala/scala/async/AsyncBase.scala delete mode 100644 src/main/scala/scala/async/continuations/AsyncBaseWithCPSFallback.scala delete mode 100644 src/main/scala/scala/async/continuations/AsyncWithCPSFallback.scala delete mode 100644 src/main/scala/scala/async/continuations/CPSBasedAsync.scala delete mode 100644 src/main/scala/scala/async/continuations/CPSBasedAsyncBase.scala delete mode 100644 src/main/scala/scala/async/continuations/ScalaConcurrentCPSFallback.scala create mode 100644 src/main/scala/scala/async/internal/ScalaConcurrentAsync.scala delete mode 100644 src/test/scala/scala/async/run/cps/CPSSpec.scala (limited to 'src') diff --git a/src/main/scala/scala/async/Async.scala b/src/main/scala/scala/async/Async.scala new file mode 100644 index 0000000..c45a9c6 --- /dev/null +++ b/src/main/scala/scala/async/Async.scala @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2012 Typesafe Inc. + */ + +package scala.async + +import scala.language.experimental.macros +import scala.concurrent.{Future, ExecutionContext} +import scala.reflect.internal.annotations.compileTimeOnly + +/** + * Async blocks provide a direct means to work with [[scala.concurrent.Future]]. + * + * For example, to use an API to that fetches as web page to fetch + * two pages and add their lengths: + * + * {{{ + * import ExecutionContext.Implicits.global + * import scala.async.Async.{async, await} + * + * def fetchURL(url: URL): Future[String] = ... + * + * val sumLengths: Future[Int] = async { + * val body1 = fetchURL("http://scala-lang.org") + * val body2 = fetchURL("http://docs.scala-lang.org") + * await(body1).length + await(body2).length + * } + * }}} + * + * Note that the in the following program, the second fetch does *not* start + * until after the first. If you need to start tasks in parallel, you must do + * so before `await`-ing a result. + * + * {{{ + * val sumLengths: Future[Int] = async { + * await(fetchURL("http://scala-lang.org")).length + await(fetchURL("http://docs.scala-lang.org")).length + * } + * }}} + */ +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] + + /** + * Non-blocking await the on result of `awaitable`. This may only be used directly within an enclosing `await` block. + * + * Internally, this will register the remainder of the code in enclosing `async` block as a callback + * in the `onComplete` handler of `awaitable`, and will *not* block a thread. + */ + @compileTimeOnly("`await` must be enclosed in an `async` block") + def await[T](awaitable: Future[T]): T = ??? // No implementation here, as calls to this are translated to `onComplete` by the macro. +} diff --git a/src/main/scala/scala/async/AsyncBase.scala b/src/main/scala/scala/async/AsyncBase.scala deleted file mode 100644 index ff04a57..0000000 --- a/src/main/scala/scala/async/AsyncBase.scala +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Copyright (C) 2012 Typesafe Inc. - */ - -package scala.async - -import scala.language.experimental.macros -import scala.reflect.macros.Context -import scala.concurrent.{Future, ExecutionContext} -import scala.async.internal.{AsyncBase, ScalaConcurrentFutureSystem} - -object Async extends AsyncBase { - type FS = ScalaConcurrentFutureSystem.type - val futureSystem: FS = ScalaConcurrentFutureSystem - - def async[T](body: T)(implicit execContext: ExecutionContext): Future[T] = macro asyncImpl[T] - - override def asyncImpl[T: c.WeakTypeTag](c: 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/StateMachine.scala b/src/main/scala/scala/async/StateMachine.scala index 823df71..cae8312 100644 --- a/src/main/scala/scala/async/StateMachine.scala +++ b/src/main/scala/scala/async/StateMachine.scala @@ -5,6 +5,8 @@ package scala.async /** Internal class used by the `async` macro; should not be manually extended by client code */ +// NOTE: this is not in the `internal` package as we must keep this binary compatible as it extended +// by the translated code. abstract class StateMachine[Result, EC] extends (scala.util.Try[Any] => Unit) with (() => Unit) { def result: Result diff --git a/src/main/scala/scala/async/continuations/AsyncBaseWithCPSFallback.scala b/src/main/scala/scala/async/continuations/AsyncBaseWithCPSFallback.scala deleted file mode 100644 index 20f5cce..0000000 --- a/src/main/scala/scala/async/continuations/AsyncBaseWithCPSFallback.scala +++ /dev/null @@ -1,102 +0,0 @@ -/* - * Copyright (C) 2012 Typesafe Inc. - */ - -package scala.async -package continuations - -import scala.language.experimental.macros - -import scala.reflect.macros.Context -import scala.util.continuations._ -import scala.async.internal.{AsyncMacro, AsyncUtils} - -trait AsyncBaseWithCPSFallback extends internal.AsyncBase { - - /* Fall-back for `await` using CPS plugin. - * - * Note: This method is public, but is intended only for internal use. - */ - def awaitFallback[T](awaitable: futureSystem.Fut[T]): T @cps[futureSystem.Fut[Any]] - - override protected[async] def fallbackEnabled = true - - /* Implements `async { ... }` using the CPS plugin. - */ - protected def cpsBasedAsyncImpl[T: c.WeakTypeTag](c: Context) - (body: c.Expr[T]) - (execContext: c.Expr[futureSystem.ExecContext]): c.Expr[futureSystem.Fut[T]] = { - import c.universe._ - - def lookupClassMember(clazz: String, name: String) = { - val asyncTrait = c.mirror.staticClass(clazz) - val tpe = asyncTrait.asType.toType - tpe.member(newTermName(name)).ensuring(_ != NoSymbol, s"$clazz.$name") - } - def lookupObjectMember(clazz: String, name: String) = { - val moduleClass = c.mirror.staticModule(clazz).moduleClass - val tpe = moduleClass.asType.toType - tpe.member(newTermName(name)).ensuring(_ != NoSymbol, s"$clazz.$name") - } - - AsyncUtils.vprintln("AsyncBaseWithCPSFallback.cpsBasedAsyncImpl") - - val symTab = c.universe.asInstanceOf[reflect.internal.SymbolTable] - val futureSystemOps = futureSystem.mkOps(symTab) - val awaitSym = lookupObjectMember("scala.async.Async", "await") - val awaitFallbackSym = lookupClassMember("scala.async.continuations.AsyncBaseWithCPSFallback", "awaitFallback") - - // replace `await` invocations with `awaitFallback` invocations - val awaitReplacer = new Transformer { - override def transform(tree: Tree): Tree = tree match { - case Apply(fun @ TypeApply(_, List(futArgTpt)), args) if fun.symbol == awaitSym => - val typeApp = treeCopy.TypeApply(fun, atPos(tree.pos)(Ident(awaitFallbackSym)), List(atPos(tree.pos)(TypeTree(futArgTpt.tpe)))) - treeCopy.Apply(tree, typeApp, args.map(arg => c.resetAllAttrs(arg.duplicate))) - case _ => - super.transform(tree) - } - } - val bodyWithAwaitFallback = awaitReplacer.transform(body.tree) - - /* generate an expression that looks like this: - reset { - val f = future { ... } - ... - val x = awaitFallback(f) - ... - future { expr } - }.asInstanceOf[Future[T]] - */ - - def spawn(expr: Tree) = futureSystemOps.spawn(expr.asInstanceOf[futureSystemOps.universe.Tree], execContext.tree.asInstanceOf[futureSystemOps.universe.Tree]).asInstanceOf[Tree] - - val bodyWithFuture = { - val tree = bodyWithAwaitFallback match { - case Block(stmts, expr) => Block(stmts, spawn(expr)) - case expr => spawn(expr) - } - c.Expr[futureSystem.Fut[Any]](c.resetAllAttrs(tree.duplicate)) - } - - val bodyWithReset: c.Expr[futureSystem.Fut[Any]] = reify { - reset { bodyWithFuture.splice } - } - val bodyWithCast = futureSystemOps.castTo[T](bodyWithReset.asInstanceOf[futureSystemOps.universe.Expr[futureSystem.Fut[Any]]]).asInstanceOf[c.Expr[futureSystem.Fut[T]]] - - AsyncUtils.vprintln(s"CPS-based async transform expands to:\n${bodyWithCast.tree}") - bodyWithCast - } - - override def asyncImpl[T: c.WeakTypeTag](c: Context) - (body: c.Expr[T]) - (execContext: c.Expr[futureSystem.ExecContext]): c.Expr[futureSystem.Fut[T]] = { - AsyncUtils.vprintln("AsyncBaseWithCPSFallback.asyncImpl") - - val asyncMacro = AsyncMacro(c, this) - - if (!asyncMacro.reportUnsupportedAwaits(body.tree.asInstanceOf[asyncMacro.global.Tree], report = fallbackEnabled)) - super.asyncImpl[T](c)(body)(execContext) // no unsupported awaits - else - cpsBasedAsyncImpl[T](c)(body)(execContext) // fallback to CPS - } -} diff --git a/src/main/scala/scala/async/continuations/AsyncWithCPSFallback.scala b/src/main/scala/scala/async/continuations/AsyncWithCPSFallback.scala deleted file mode 100644 index e0da5aa..0000000 --- a/src/main/scala/scala/async/continuations/AsyncWithCPSFallback.scala +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (C) 2012 Typesafe Inc. - */ - -package scala.async -package continuations - -import scala.language.experimental.macros - -import scala.reflect.macros.Context -import scala.concurrent.Future - -trait AsyncWithCPSFallback extends AsyncBaseWithCPSFallback with ScalaConcurrentCPSFallback - -object AsyncWithCPSFallback extends AsyncWithCPSFallback { - import scala.concurrent.{ExecutionContext, Future} - - def async[T](body: T)(implicit execContext: ExecutionContext): Future[T] = macro asyncImpl[T] - - override def asyncImpl[T: c.WeakTypeTag](c: Context) - (body: c.Expr[T]) - (execContext: c.Expr[ExecutionContext]): c.Expr[Future[T]] = { - super.asyncImpl[T](c)(body)(execContext) - } -} diff --git a/src/main/scala/scala/async/continuations/CPSBasedAsync.scala b/src/main/scala/scala/async/continuations/CPSBasedAsync.scala deleted file mode 100644 index 2003082..0000000 --- a/src/main/scala/scala/async/continuations/CPSBasedAsync.scala +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Copyright (C) 2012 Typesafe Inc. - */ - -package scala.async -package continuations - -import scala.language.experimental.macros - -import scala.reflect.macros.Context -import scala.concurrent.{ExecutionContext, Future} - -trait CPSBasedAsync extends CPSBasedAsyncBase with ScalaConcurrentCPSFallback - -object CPSBasedAsync extends CPSBasedAsync { - - def async[T](body: T)(implicit execContext: ExecutionContext): Future[T] = macro asyncImpl[T] - - override def asyncImpl[T: c.WeakTypeTag](c: Context) - (body: c.Expr[T]) - (execContext: c.Expr[ExecutionContext]): c.Expr[Future[T]] = { - super.asyncImpl[T](c)(body)(execContext) - } -} diff --git a/src/main/scala/scala/async/continuations/CPSBasedAsyncBase.scala b/src/main/scala/scala/async/continuations/CPSBasedAsyncBase.scala deleted file mode 100644 index a350704..0000000 --- a/src/main/scala/scala/async/continuations/CPSBasedAsyncBase.scala +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Copyright (C) 2012 Typesafe Inc. - */ - -package scala.async -package continuations - -import scala.language.experimental.macros - -import scala.reflect.macros.Context -import scala.util.continuations._ - -/* Specializes `AsyncBaseWithCPSFallback` to always fall back to CPS, yielding a purely CPS-based - * implementation of async/await. - */ -trait CPSBasedAsyncBase extends AsyncBaseWithCPSFallback { - - override def asyncImpl[T: c.WeakTypeTag](c: Context) - (body: c.Expr[T]) - (execContext: c.Expr[futureSystem.ExecContext]): c.Expr[futureSystem.Fut[T]] = { - super.cpsBasedAsyncImpl[T](c)(body)(execContext) - } -} diff --git a/src/main/scala/scala/async/continuations/ScalaConcurrentCPSFallback.scala b/src/main/scala/scala/async/continuations/ScalaConcurrentCPSFallback.scala deleted file mode 100644 index f864ad6..0000000 --- a/src/main/scala/scala/async/continuations/ScalaConcurrentCPSFallback.scala +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright (C) 2012 Typesafe Inc. - */ - -package scala.async -package continuations - -import scala.util.continuations._ -import scala.concurrent.{Future, Promise, ExecutionContext} -import scala.async.internal.ScalaConcurrentFutureSystem - -trait ScalaConcurrentCPSFallback { - self: AsyncBaseWithCPSFallback => - - import ExecutionContext.Implicits.global - - lazy val futureSystem = ScalaConcurrentFutureSystem - type FS = ScalaConcurrentFutureSystem.type - - /* Fall-back for `await` when it is called at an unsupported position. - */ - override def awaitFallback[T](awaitable: futureSystem.Fut[T]): T @cps[Future[Any]] = - shift { - (k: (T => Future[Any])) => - val fr = Promise[Any]() - awaitable onComplete { - case tr => fr completeWith k(tr.get) - } - fr.future - } - -} diff --git a/src/main/scala/scala/async/internal/AsyncAnalysis.scala b/src/main/scala/scala/async/internal/AsyncAnalysis.scala index 122109e..69e4c3c 100644 --- a/src/main/scala/scala/async/internal/AsyncAnalysis.scala +++ b/src/main/scala/scala/async/internal/AsyncAnalysis.scala @@ -18,13 +18,13 @@ trait AsyncAnalysis { * * Must be called on the original tree, not on the ANF transformed tree. */ - def reportUnsupportedAwaits(tree: Tree, report: Boolean): Boolean = { - val analyzer = new UnsupportedAwaitAnalyzer(report) + def reportUnsupportedAwaits(tree: Tree): Unit = { + val analyzer = new UnsupportedAwaitAnalyzer analyzer.traverse(tree) analyzer.hasUnsupportedAwaits } - private class UnsupportedAwaitAnalyzer(report: Boolean) extends AsyncTraverser { + private class UnsupportedAwaitAnalyzer extends AsyncTraverser { var hasUnsupportedAwaits = false override def nestedClass(classDef: ClassDef) { @@ -87,8 +87,7 @@ trait AsyncAnalysis { private def reportError(pos: Position, msg: String) { hasUnsupportedAwaits = true - if (report) - abort(pos, msg) + abort(pos, msg) } } } diff --git a/src/main/scala/scala/async/internal/AsyncBase.scala b/src/main/scala/scala/async/internal/AsyncBase.scala index e44c27f..89d5a4d 100644 --- a/src/main/scala/scala/async/internal/AsyncBase.scala +++ b/src/main/scala/scala/async/internal/AsyncBase.scala @@ -61,6 +61,11 @@ abstract class AsyncBase { c.Expr[futureSystem.Fut[T]](code) } + protected[async] def awaitMethod(u: Universe)(asyncMacroSymbol: u.Symbol): u.Symbol = { + import u._ + asyncMacroSymbol.owner.typeSignature.member(newTermName("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 index 7f7807f..c123675 100644 --- a/src/main/scala/scala/async/internal/AsyncId.scala +++ b/src/main/scala/scala/async/internal/AsyncId.scala @@ -78,7 +78,5 @@ object IdentityFutureSystem extends FutureSystem { prom.splice.a = value.splice.get Expr[Unit](Literal(Constant(()))).splice } - - def castTo[A: WeakTypeTag](future: Expr[Fut[Any]]): Expr[Fut[A]] = ??? } } diff --git a/src/main/scala/scala/async/internal/AsyncTransform.scala b/src/main/scala/scala/async/internal/AsyncTransform.scala index 27d95a4..cdae074 100644 --- a/src/main/scala/scala/async/internal/AsyncTransform.scala +++ b/src/main/scala/scala/async/internal/AsyncTransform.scala @@ -15,7 +15,7 @@ trait AsyncTransform { // This implicit propagates the annotated type in the type tag. implicit val uncheckedBoundsResultTag: WeakTypeTag[T] = WeakTypeTag[T](rootMirror, FixedMirrorTypeCreator(rootMirror, uncheckedBounds(resultType.tpe))) - reportUnsupportedAwaits(body, report = !cpsFallbackEnabled) + reportUnsupportedAwaits(body) // Transform to A-normal form: // - no await calls in qualifiers or arguments, diff --git a/src/main/scala/scala/async/internal/FutureSystem.scala b/src/main/scala/scala/async/internal/FutureSystem.scala index f7fdc49..9fe88b4 100644 --- a/src/main/scala/scala/async/internal/FutureSystem.scala +++ b/src/main/scala/scala/async/internal/FutureSystem.scala @@ -51,9 +51,6 @@ trait FutureSystem { def spawn(tree: Tree, execContext: Tree): Tree = future(Expr[Unit](tree))(Expr[ExecContext](execContext)).tree - - // This is only needed in `AsyncBaseWithCPSFallback` and should be removed once CPS fall-back support is dropped. - def castTo[A: WeakTypeTag](future: Expr[Fut[Any]]): Expr[Fut[A]] } def mkOps(c: SymbolTable): Ops { val universe: c.type } @@ -96,9 +93,5 @@ object ScalaConcurrentFutureSystem extends FutureSystem { prom.splice.complete(value.splice) Expr[Unit](Literal(Constant(()))).splice } - - def castTo[A: WeakTypeTag](future: Expr[Fut[Any]]): Expr[Fut[A]] = reify { - future.splice.asInstanceOf[Fut[A]] - } } } diff --git a/src/main/scala/scala/async/internal/ScalaConcurrentAsync.scala b/src/main/scala/scala/async/internal/ScalaConcurrentAsync.scala new file mode 100644 index 0000000..4e1a0af --- /dev/null +++ b/src/main/scala/scala/async/internal/ScalaConcurrentAsync.scala @@ -0,0 +1,18 @@ +package scala +package async +package internal + +import scala.language.experimental.macros +import scala.reflect.macros.Context +import scala.concurrent.Future + +object ScalaConcurrentAsync extends AsyncBase { + type FS = ScalaConcurrentFutureSystem.type + val futureSystem: FS = ScalaConcurrentFutureSystem + + override def asyncImpl[T: c.WeakTypeTag](c: 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/TransformUtils.scala b/src/main/scala/scala/async/internal/TransformUtils.scala index 663ca45..9722610 100644 --- a/src/main/scala/scala/async/internal/TransformUtils.scala +++ b/src/main/scala/scala/async/internal/TransformUtils.scala @@ -89,8 +89,7 @@ private[async] trait TransformUtils { val Try_isFailure = TryClass.typeSignature.member(newTermName("isFailure")).ensuring(_ != NoSymbol) val TryAnyType = appliedType(TryClass.toType, List(definitions.AnyTpe)) val NonFatalClass = rootMirror.staticModule("scala.util.control.NonFatal") - val AsyncClass = rootMirror.staticClass("scala.async.internal.AsyncBase") - val Async_await = AsyncClass.typeSignature.member(newTermName("await")).ensuring(_ != NoSymbol) + val Async_await = asyncBase.awaitMethod(global)(macroApplication.symbol).ensuring(_ != NoSymbol) } def isSafeToInline(tree: Tree) = { diff --git a/src/test/scala/scala/async/run/cps/CPSSpec.scala b/src/test/scala/scala/async/run/cps/CPSSpec.scala deleted file mode 100644 index 9476b22..0000000 --- a/src/test/scala/scala/async/run/cps/CPSSpec.scala +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright (C) 2012 Typesafe Inc. - */ - -package scala.async -package run -package cps - -import scala.concurrent.{Future, Promise, ExecutionContext, future, Await} -import scala.concurrent.duration._ -import scala.async.continuations.CPSBasedAsync._ -import scala.util.continuations._ - -import org.junit.Test - -class CPSSpec { - - import ExecutionContext.Implicits.global - - def m1(y: Int): Future[Int] = async { - val f = future { y + 2 } - val f2 = future { y + 3 } - val x1 = await(f) - val x2 = await(f2) - x1 + x2 - } - - def m2(y: Int): Future[Int] = async { - val f = future { y + 2 } - val res = await(f) - if (y > 0) res + 2 - else res - 2 - } - - @Test - def testCPSFallback() { - val fut1 = m1(10) - val res1 = Await.result(fut1, 2.seconds) - assert(res1 == 25, s"expected 25, got $res1") - - val fut2 = m2(10) - val res2 = Await.result(fut2, 2.seconds) - assert(res2 == 14, s"expected 14, got $res2") - } - -} -- cgit v1.2.3