From d434c20cfb8623a243cd30f187907bb4b199dc99 Mon Sep 17 00:00:00 2001 From: Jason Zaugg Date: Wed, 7 Nov 2012 20:08:33 +0100 Subject: Abstract over the future implementation. - Refactor the base macro implementation to be parameterized by a FutureSystem, which is defines the triple of types (Future, Promise, ExecutionContext) and the operations on those types (at the AST level) - Cleanup generation of ASTs, in particular, use reify more widely. --- src/main/scala/scala/async/Async.scala | 213 ++++++++++++++------------ src/main/scala/scala/async/ExprBuilder.scala | 157 +++++++++++-------- src/main/scala/scala/async/FutureSystem.scala | 138 +++++++++++++++++ 3 files changed, 341 insertions(+), 167 deletions(-) create mode 100644 src/main/scala/scala/async/FutureSystem.scala diff --git a/src/main/scala/scala/async/Async.scala b/src/main/scala/scala/async/Async.scala index d4b950e..d64e04a 100644 --- a/src/main/scala/scala/async/Async.scala +++ b/src/main/scala/scala/async/Async.scala @@ -7,131 +7,140 @@ import language.experimental.macros import scala.reflect.macros.Context import scala.collection.mutable.ListBuffer -import scala.concurrent.{ Future, Promise, ExecutionContext, future } +import scala.concurrent.{Future, Promise, ExecutionContext, future} import ExecutionContext.Implicits.global import scala.util.control.NonFatal -import scala.util.continuations.{ shift, reset, cpsParam } +import scala.util.continuations.{shift, reset, cpsParam} -/* Extending `ControlThrowable`, by default, also avoids filling in the stack trace. */ -class FallbackToCpsException extends scala.util.control.ControlThrowable /* * @author Philipp Haller */ -object Async extends AsyncUtils { +object Async extends AsyncBase { + lazy val futureSystem = ScalaConcurrentFutureSystem + type FS = ScalaConcurrentFutureSystem.type - def async[T](body: T): Future[T] = macro asyncImpl[T] + def async[T](body: T) = macro asyncImpl[T] + override def asyncImpl[T: c.WeakTypeTag](c: Context)(body: c.Expr[T]): c.Expr[Future[T]] = super.asyncImpl[T](c)(body) +} + +object AsyncId extends AsyncBase { + lazy val futureSystem = IdentityFutureSystem + type FS = IdentityFutureSystem.type + + def async[T](body: T) = macro asyncImpl[T] + + override def asyncImpl[T: c.WeakTypeTag](c: Context)(body: c.Expr[T]): c.Expr[T] = super.asyncImpl[T](c)(body) +} + +/** + * 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 a bug in the macro system) + * + * The default implementation, [[scala.async.Async]], binds the macro to `scala.concurrent._`. + */ +abstract class AsyncBase extends AsyncUtils { + 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 thread, rather it is a delimiter + * used by the enclosing `async` macro. Code following the `await` + * call + @ @param awaitable The future from which a value is awaited + * @tparam T The type of that value + * @return The value + */ // TODO Replace with `@compileTimeOnly when this is implemented SI-6539 @deprecated("`await` must be enclosed in an `async` block", "0.1") - def await[T](awaitable: Future[T]): T = ??? - - /* Fall back for `await` when it is called at an unsupported position. - */ - def awaitCps[T, U](awaitable: Future[T], p: Promise[U]): T @cpsParam[U, Unit] = - shift { - (k: (T => U)) => - awaitable onComplete { - case tr => p.success(k(tr.get)) - } - } - - def asyncImpl[T: c.WeakTypeTag](c: Context)(body: c.Expr[T]): c.Expr[Future[T]] = { + def await[T](awaitable: futureSystem.Fut[T]): T = ??? + + def asyncImpl[T: c.WeakTypeTag](c: Context)(body: c.Expr[T]) = { import c.universe._ import Flag._ - - val builder = new ExprBuilder[c.type](c) - val awaitMethod = awaitSym(c) - - try { - body.tree match { - case Block(stats, expr) => - val asyncBlockBuilder = new builder.AsyncBlockBuilder(stats, expr, 0, 1000, 1000, Map()) - vprintln(s"states of current method:") - asyncBlockBuilder.asyncStates foreach vprintln + val builder = new ExprBuilder[c.type, self.FS](c, self.futureSystem) - val handlerExpr = asyncBlockBuilder.mkCombinedHandlerExpr() + import builder.defn._ + import builder.futureSystemOps - vprintln(s"GENERATED handler expr:") - vprintln(handlerExpr) + val awaitMethod = awaitSym(c) - val handlerForLastState: c.Expr[PartialFunction[Int, Unit]] = { - val tree = Apply(Select(Ident("result"), newTermName("success")), - List(asyncBlockBuilder.asyncStates.last.body)) - builder.mkHandler(asyncBlockBuilder.asyncStates.last.state, c.Expr[Unit](tree)) - } + body.tree match { + case Block(stats, expr) => + val asyncBlockBuilder = new builder.AsyncBlockBuilder(stats, expr, 0, 1000, 1000, Map()) - vprintln("GENERATED handler for last state:") - vprintln(handlerForLastState) - - val localVarTrees = asyncBlockBuilder.asyncStates.init.flatMap(_.allVarDefs).toList - - /* - def resume(): Unit = { - try { - (handlerExpr.splice orElse handlerForLastState.splice)(state) - } catch { - case NonFatal(t) => result.failure(t) - } - } - */ - val nonFatalModule = c.mirror.staticModule("scala.util.control.NonFatal") - val resumeFunTree: c.Tree = DefDef(Modifiers(), newTermName("resume"), List(), List(List()), Ident(definitions.UnitClass), - Try(Apply(Select( - Apply(Select(handlerExpr.tree, newTermName("orElse")), List(handlerForLastState.tree)), - newTermName("apply")), List(Ident(newTermName("state")))), - List( - CaseDef( - Apply(Ident(nonFatalModule), List(Bind(newTermName("t"), Ident(nme.WILDCARD)))), - EmptyTree, - Block(List( - Apply(Select(Ident(newTermName("result")), newTermName("failure")), List(Ident(newTermName("t"))))), - Literal(Constant(()))))), EmptyTree)) - - reify { - val result = Promise[T]() - var state = 0 - future { - c.Expr(Block( - localVarTrees :+ resumeFunTree, - Apply(Ident(newTermName("resume")), List()))).splice - } - result.future - } + vprintln(s"states of current method:") + asyncBlockBuilder.asyncStates foreach vprintln - case _ => - // issue error message - reify { - sys.error("expression not supported by async") - } - } - } catch { - case _: FallbackToCpsException => - // replace `await` invocations with `awaitCps` invocations - val awaitReplacer = new Transformer { - val awaitCpsMethod = awaitCpsSym(c) - override def transform(tree: Tree): Tree = tree match { - case Apply(fun @ TypeApply(_, List(futArgTpt)), args) if fun.symbol == awaitMethod => - val typeApp = treeCopy.TypeApply(fun, Ident(awaitCpsMethod), List(TypeTree(futArgTpt.tpe), TypeTree(body.tree.tpe))) - treeCopy.Apply(tree, typeApp, args.map(arg => c.resetAllAttrs(arg.duplicate)) :+ Ident(newTermName("p"))) - - case _ => - super.transform(tree) - } + val handlerExpr = asyncBlockBuilder.mkCombinedHandlerExpr() + + vprintln(s"GENERATED handler expr:") + vprintln(handlerExpr) + + val handlerForLastState: c.Expr[PartialFunction[Int, Unit]] = { + val lastState = asyncBlockBuilder.asyncStates.last + val lastStateBody = c.Expr[T](lastState.body) + builder.mkHandler(lastState.state, futureSystemOps.completeProm(c.Expr[futureSystem.Prom[T]](Ident("result")), reify(scala.util.Success(lastStateBody.splice)))) } - - val newBody = awaitReplacer.transform(body.tree) - - reify { - val p = Promise[T]() - future { - reset { - c.Expr(c.resetAllAttrs(newBody.duplicate)).asInstanceOf[c.Expr[T]].splice + + vprintln("GENERATED handler for last state:") + vprintln(handlerForLastState) + + val localVarTrees = asyncBlockBuilder.asyncStates.init.flatMap(_.allVarDefs).toList + + /* + def resume(): Unit = { + try { + (handlerExpr.splice orElse handlerForLastState.splice)(state) + } catch { + case NonFatal(t) => result.failure(t) } } - p.future + */ + val nonFatalModule = c.mirror.staticModule("scala.util.control.NonFatal") + val resumeFunTree: c.Tree = DefDef(Modifiers(), newTermName("resume"), List(), List(List()), Ident(definitions.UnitClass), + Try( + reify { + val combinedHandler = mkPartialFunction_orElse(handlerExpr)(handlerForLastState).splice + combinedHandler.apply(c.Expr[Int](Ident(newTermName("state"))).splice) + }.tree + , + List( + CaseDef( + Apply(Ident(nonFatalModule), List(Bind(newTermName("t"), Ident(nme.WILDCARD)))), + EmptyTree, + Block(List({ + val t = c.Expr[Throwable](Ident(newTermName("t"))) + futureSystemOps.completeProm[T](c.Expr[futureSystem.Prom[T]](Ident(newTermName("result"))), reify(scala.util.Failure(t.splice))).tree + }), c.literalUnit.tree))), EmptyTree)) + + val prom: Expr[futureSystem.Prom[T]] = reify { + val result = futureSystemOps.createProm[T].splice + var state = 0 + futureSystemOps.future[Unit] { + c.Expr[Unit](Block( + localVarTrees :+ resumeFunTree, + Apply(Ident(newTermName("resume")), List()))) + }(futureSystemOps.execContext).splice + result } + val result = futureSystemOps.promiseToFuture(prom) + // println(s"${c.macroApplication} \nexpands to:\n ${result.tree}") + result + + case tree => + c.abort(c.macroApplication.pos, s"expression not supported by async: ${tree}") } } } diff --git a/src/main/scala/scala/async/ExprBuilder.scala b/src/main/scala/scala/async/ExprBuilder.scala index c5c192d..4beaa34 100644 --- a/src/main/scala/scala/async/ExprBuilder.scala +++ b/src/main/scala/scala/async/ExprBuilder.scala @@ -5,15 +5,22 @@ package scala.async import scala.reflect.macros.Context import scala.collection.mutable.{ListBuffer, Builder} +import concurrent.Future /* * @author Philipp Haller */ -class ExprBuilder[C <: Context with Singleton](val c: C) extends AsyncUtils { +final class ExprBuilder[C <: Context, FS <: FutureSystem](val c: C, val futureSystem: FS) extends AsyncUtils { builder => + lazy val futureSystemOps = futureSystem.mkOps(c) + import c.universe._ import Flag._ + import defn._ + + val execContextType = c.weakTypeOf[futureSystem.ExecContext] + val execContext = futureSystemOps.execContext private val awaitMethod = awaitSym(c) @@ -23,7 +30,7 @@ class ExprBuilder[C <: Context with Singleton](val c: C) extends AsyncUtils { * case any if any == num => rhs * } */ - def mkHandler(num: Int, rhs: c.Expr[Unit]): c.Expr[PartialFunction[Int, Unit]] = { + def mkHandler(num: Int, rhs: c.Expr[Any]): c.Expr[PartialFunction[Int, Unit]] = { /* val numLiteral = c.Expr[Int](Literal(Constant(num))) @@ -44,7 +51,8 @@ class ExprBuilder[C <: Context with Singleton](val c: C) extends AsyncUtils { def mkIncrStateTree(): c.Tree = { Assign( Ident(newTermName("state")), - Apply(Select(Ident(newTermName("state")), newTermName("$plus")), List(Literal(Constant(1))))) + mkInt_+(c.Expr[Int](Ident(newTermName("state"))))(c.literal(1)).tree + ) } def mkStateTree(nextState: Int): c.Tree = @@ -69,7 +77,7 @@ class ExprBuilder[C <: Context with Singleton](val c: C) extends AsyncUtils { // pattern Bind(newTermName("any"), Typed(Ident(nme.WILDCARD), Ident(definitions.IntClass))), // guard - Apply(Select(Ident(newTermName("any")), newTermName("$eq$eq")), List(Literal(Constant(num)))), + mkAny_==(c.Expr(Ident(newTermName("any"))))(c.literal(num)).tree, rhs ) @@ -79,8 +87,7 @@ class ExprBuilder[C <: Context with Singleton](val c: C) extends AsyncUtils { val unitIdent = Ident(definitions.UnitClass) val caseCheck = - Apply(Select(Apply(Ident(definitions.List_apply), - cases.map(p => Literal(Constant(p._2)))), newTermName("contains")), List(Ident(newTermName("x$1")))) + defn.mkList_contains(defn.mkList_apply(cases.map(p => c.literal(p._2))))(c.Expr(Ident(newTermName("x$1")))) Block(List( // anonymous subclass of PartialFunction[Int, Unit] @@ -91,7 +98,7 @@ class ExprBuilder[C <: Context with Singleton](val c: C) extends AsyncUtils { Block(List(Apply(Select(Super(This(tpnme.EMPTY), tpnme.EMPTY), nme.CONSTRUCTOR), List())), Literal(Constant(())))), DefDef(Modifiers(), newTermName("isDefinedAt"), List(), List(List(ValDef(Modifiers(PARAM), newTermName("x$1"), intIdent, EmptyTree))), TypeTree(), - caseCheck), + caseCheck.tree), DefDef(Modifiers(), newTermName("apply"), List(), List(List(ValDef(Modifiers(PARAM), newTermName("x$1"), intIdent, EmptyTree))), TypeTree(), Match(Ident(newTermName("x$1")), cases.map(_._1)) // combine all cases into a single match @@ -168,7 +175,7 @@ class ExprBuilder[C <: Context with Singleton](val c: C) extends AsyncUtils { val assignTree = Assign( Ident(resultName.toString), - Select(Ident("tr"), newTermName("get")) + mkTry_get(c.Expr(Ident("tr"))).tree ) val handlerTree = Match( @@ -179,10 +186,7 @@ class ExprBuilder[C <: Context with Singleton](val c: C) extends AsyncUtils { ) ) ) - Apply( - Select(awaitable, newTermName("onComplete")), - List(handlerTree) - ) + futureSystemOps.onComplete(c.Expr(awaitable), c.Expr(handlerTree), execContext).tree } /* Make an `onComplete` invocation which increments the state upon resuming: @@ -198,23 +202,17 @@ class ExprBuilder[C <: Context with Singleton](val c: C) extends AsyncUtils { val tryGetTree = Assign( Ident(resultName.toString), - Select(Ident("tr"), newTermName("get")) + Select(Ident("tr"), Try_get) ) + val handlerTree = - Match( - EmptyTree, - List( - CaseDef(Bind(newTermName("tr"), Ident("_")), EmptyTree, - Block(tryGetTree, mkIncrStateTree(), Apply(Ident("resume"), List())) // rhs of case - ) - ) - ) - Apply( - Select(awaitable, newTermName("onComplete")), - List(handlerTree) - ) + Function(List(ValDef(Modifiers(PARAM), newTermName("tr"), TypeTree(tryType), EmptyTree)), Block(tryGetTree, mkIncrStateTree(), Apply(Ident("resume"), List()))) + + futureSystemOps.onComplete(c.Expr(awaitable), c.Expr(handlerTree), execContext).tree } + def tryType = appliedType(c.mirror.staticClass("scala.util.Try").toType, List(resultType)) + /* Make an `onComplete` invocation which sets the state to `nextState` upon resuming: * * awaitable.onComplete { @@ -228,21 +226,12 @@ class ExprBuilder[C <: Context with Singleton](val c: C) extends AsyncUtils { val tryGetTree = Assign( Ident(resultName.toString), - Select(Ident("tr"), newTermName("get")) + Select(Ident("tr"), Try_get) ) val handlerTree = - Match( - EmptyTree, - List( - CaseDef(Bind(newTermName("tr"), Ident("_")), EmptyTree, - Block(tryGetTree, mkStateTree(nextState), Apply(Ident("resume"), List())) // rhs of case - ) - ) - ) - Apply( - Select(awaitable, newTermName("onComplete")), - List(handlerTree) - ) + Function(List(ValDef(Modifiers(PARAM), newTermName("tr"), TypeTree(tryType), EmptyTree)), Block(tryGetTree, mkStateTree(nextState), Apply(Ident("resume"), List()))) + + futureSystemOps.onComplete(c.Expr(awaitable), c.Expr(handlerTree), execContext).tree } /* Make a partial function literal handling case #num: @@ -391,12 +380,12 @@ class ExprBuilder[C <: Context with Singleton](val c: C) extends AsyncUtils { override val varDefs = self.varDefs.toList } } - + /** * 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 stateFirstCase state of the right-hand side of the first case @@ -414,7 +403,7 @@ class ExprBuilder[C <: Context with Singleton](val c: C) extends AsyncUtils { override val varDefs = self.varDefs.toList } } - + override def toString: String = { val statsBeforeAwait = stats.mkString("\n") s"ASYNC STATE:\n$statsBeforeAwait \nawaitable: $awaitable \nresult name: $resultName" @@ -423,7 +412,7 @@ class ExprBuilder[C <: Context with Singleton](val c: C) extends AsyncUtils { /** * 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 @@ -441,20 +430,20 @@ class ExprBuilder[C <: Context with Singleton](val c: C) extends AsyncUtils { private var remainingBudget = budget - /* Fall back to CPS plug-in if tree contains an `await` call. */ + /* TODO Fall back to CPS plug-in if tree contains an `await` call. */ def checkForUnsupportedAwait(tree: c.Tree) = if (tree exists { case Apply(fun, _) if fun.symbol == awaitMethod => true case _ => false - }) throw new FallbackToCpsException - + }) c.abort(tree.pos, "await unsupported in this position") //throw new FallbackToCpsException + def builderForBranch(tree: c.Tree, state: Int, nextState: Int, budget: Int, nameMap: Map[c.Symbol, c.Name]): AsyncBlockBuilder = { val (branchStats, branchExpr) = tree match { case Block(s, e) => (s, e) - case _ => (List(tree), Literal(Constant(()))) + case _ => (List(tree), Literal(Constant(()))) } new AsyncBlockBuilder(branchStats, branchExpr, state, nextState, budget, nameMap) } - + // populate asyncStates for (stat <- stats) stat match { // the val name = await(..) pattern @@ -491,44 +480,45 @@ class ExprBuilder[C <: Context with Singleton](val c: C) extends AsyncUtils { asyncStates += // the two Int arguments are the start state of the then branch and the else branch, respectively stateBuilder.resultWithIf(cond, currState + 1, currState + thenBudget) - - List((thenp, currState + 1, thenBudget), (elsep, currState + thenBudget, elseBudget)) foreach { case (tree, state, branchBudget) => - val builder = builderForBranch(tree, state, currState + ifBudget, branchBudget, toRename) - asyncStates ++= builder.asyncStates - toRename ++= builder.toRename + + List((thenp, currState + 1, thenBudget), (elsep, currState + thenBudget, elseBudget)) foreach { + case (tree, state, branchBudget) => + val builder = builderForBranch(tree, state, currState + ifBudget, branchBudget, toRename) + asyncStates ++= builder.asyncStates + toRename ++= builder.toRename } - + // create new state builder for state `currState + ifBudget` currState = currState + ifBudget stateBuilder = new builder.AsyncStateBuilder(currState, toRename) - + case Match(scrutinee, cases) => vprintln("transforming match expr: " + stat) checkForUnsupportedAwait(scrutinee) - + val matchBudget: Int = remainingBudget / 2 remainingBudget -= matchBudget //TODO test if budget > 0 // state that we continue with after match: currState + matchBudget - + val perCaseBudget: Int = matchBudget / cases.size asyncStates += // the two Int arguments are the start state of the first case and the per-case state budget, respectively stateBuilder.resultWithMatch(scrutinee, cases, currState + 1, perCaseBudget) - + for ((cas, num) <- cases.zipWithIndex) { val (casStats, casExpr) = cas match { case CaseDef(_, _, Block(s, e)) => (s, e) - case CaseDef(_, _, rhs) => (List(rhs), Literal(Constant(()))) + case CaseDef(_, _, rhs) => (List(rhs), Literal(Constant(()))) } val builder = new AsyncBlockBuilder(casStats, casExpr, currState + (num * perCaseBudget) + 1, currState + matchBudget, perCaseBudget, toRename) asyncStates ++= builder.asyncStates toRename ++= builder.toRename } - + // create new state builder for state `currState + matchBudget` currState = currState + matchBudget stateBuilder = new builder.AsyncStateBuilder(currState, toRename) - + case _ => checkForUnsupportedAwait(stat) stateBuilder += stat @@ -542,7 +532,9 @@ class ExprBuilder[C <: Context with Singleton](val c: C) extends AsyncUtils { assert(asyncStates.size > 1) val cases = for (state <- asyncStates.toList) yield state.mkHandlerCaseForState() - c.Expr(mkHandlerTreeFor(cases zip asyncStates.init.map(_.state))).asInstanceOf[c.Expr[PartialFunction[Int, Unit]]] + reify { + c.Expr[PartialFunction[Int, Unit]](mkHandlerTreeFor(cases zip asyncStates.init.map(_.state))).splice: PartialFunction[Int, Unit] + } } /* Builds the handler expression for a sequence of async states. @@ -560,14 +552,49 @@ class ExprBuilder[C <: Context with Singleton](val c: C) extends AsyncUtils { // do not traverse first or last state val handlerTreeForNextState = asyncState.mkHandlerTreeForState() val currentHandlerTreeNaked = c.resetAllAttrs(handlerExpr.tree.duplicate) - handlerExpr = c.Expr( - Apply(Select(currentHandlerTreeNaked, newTermName("orElse")), - List(handlerTreeForNextState))).asInstanceOf[c.Expr[PartialFunction[Int, Unit]]] + handlerExpr = mkPartialFunction_orElse(c.Expr(currentHandlerTreeNaked))(c.Expr(handlerTreeForNextState)) } handlerExpr } } + } + + /** `termSym( (_: Foo).bar(null: A, null: B)` will return the symbol of `bar`, after overload resolution. */ + def methodSym(apply: c.Expr[Any]): Symbol = { + val tree2: Tree = c.typeCheck(apply.tree) // TODO why is this needed? + tree2.collect { + case s: SymTree if s.symbol.isMethod => s.symbol + }.headOption.getOrElse(sys.error(s"Unable to find a method symbol in ${apply.tree}")) + } + + 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 mkPartialFunction_orElse[A, B](self: Expr[PartialFunction[A, B]])(other: Expr[PartialFunction[A, B]]) = reify { + self.splice.orElse(other.splice) + } + + def mkFunction_apply[A, B](self: Expr[Function1[A, B]])(arg: Expr[A]) = reify { + self.splice.apply(arg.splice) + } + + def mkInt_+(self: Expr[Int])(other: Expr[Int]) = reify { + self.splice + other.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 Try_get = methodSym(reify((null.asInstanceOf[scala.util.Try[Any]]).get)) } } diff --git a/src/main/scala/scala/async/FutureSystem.scala b/src/main/scala/scala/async/FutureSystem.scala new file mode 100644 index 0000000..64c5a66 --- /dev/null +++ b/src/main/scala/scala/async/FutureSystem.scala @@ -0,0 +1,138 @@ +/** + * Copyright (C) 2012 Typesafe Inc. + */ +package scala.async + +import reflect.macros.Context + +/** + * An abstraction over a future system. + * + * Used by the macro implementations in [[scala.async.AsyncBase]] to + * customize the code generation. + * + * The API mirrors that of `scala.concurrent.Future`, see the instance + * [[scala.async.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 + + trait Ops { + val context: reflect.macros.Context + + import context.universe._ + + /** Lookup the execution context, typically with an implicit search */ + def execContext: Expr[ExecContext] + + /** 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 asynchrously 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[scala.util.Try[A] => U], + execContext: Expr[ExecContext]): Expr[Unit] + + /** Complete a promise with a value */ + def completeProm[A](prom: Expr[Prom[A]], value: Expr[scala.util.Try[A]]): Expr[Unit] + } + + def mkOps(c: Context): Ops {val context: c.type} +} + +object ScalaConcurrentFutureSystem extends FutureSystem { + + import scala.concurrent._ + + type Prom[A] = Promise[A] + type Fut[A] = Future[A] + type ExecContext = ExecutionContext + + def mkOps(c: Context): Ops {val context: c.type} = new Ops { + val context: c.type = c + + import context.universe._ + + def execContext: Expr[ExecContext] = c.Expr(c.inferImplicitValue(c.weakTypeOf[ExecutionContext]) match { + case EmptyTree => c.abort(c.macroApplication.pos, "Unable to resolve implicit ExecutionContext") + case context => context + }) + + 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) + context.literalUnit.splice + } + } + + def completeProm[A](prom: Expr[Prom[A]], value: Expr[scala.util.Try[A]]): Expr[Unit] = reify { + prom.splice.complete(value.splice) + context.literalUnit.splice + } + } +} + +/** + * A trivial implentation of [[scala.async.FutureSystem]] that performs computations + * on the current thread. Useful for testing. + */ +object IdentityFutureSystem extends FutureSystem { + + class Prom[A](var a: A) + + type Fut[A] = A + type ExecContext = Unit + + def mkOps(c: Context): Ops {val context: c.type} = new Ops { + val context: c.type = c + + import context.universe._ + + def execContext: Expr[ExecContext] = c.literalUnit + + def createProm[A: WeakTypeTag]: Expr[Prom[A]] = reify { + new Prom(null.asInstanceOf[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[scala.util.Try[A] => U], + execContext: Expr[ExecContext]): Expr[Unit] = reify { + fun.splice.apply(util.Success(future.splice)) + context.literalUnit.splice + } + + def completeProm[A](prom: Expr[Prom[A]], value: Expr[scala.util.Try[A]]): Expr[Unit] = reify { + prom.splice.a = value.splice.get + context.literalUnit.splice + } + } +} -- cgit v1.2.3 From 370be9ea41c582f033a2eeef05157e77a5077144 Mon Sep 17 00:00:00 2001 From: Jason Zaugg Date: Fri, 9 Nov 2012 14:29:35 +0100 Subject: Further cleanup in AST generation - centralize names - centralize more module/class lookup - reduce duplication - centralize use of resetAllAttrs - remove uses of asInstanceOf --- src/main/scala/scala/async/Async.scala | 22 ++-- src/main/scala/scala/async/ExprBuilder.scala | 166 +++++++++++++++------------ 2 files changed, 104 insertions(+), 84 deletions(-) diff --git a/src/main/scala/scala/async/Async.scala b/src/main/scala/scala/async/Async.scala index d64e04a..acd5128 100644 --- a/src/main/scala/scala/async/Async.scala +++ b/src/main/scala/scala/async/Async.scala @@ -65,13 +65,14 @@ abstract class AsyncBase extends AsyncUtils { @deprecated("`await` must be enclosed in an `async` block", "0.1") def await[T](awaitable: futureSystem.Fut[T]): T = ??? - def asyncImpl[T: c.WeakTypeTag](c: Context)(body: c.Expr[T]) = { + def asyncImpl[T: c.WeakTypeTag](c: Context)(body: c.Expr[T]): c.Expr[futureSystem.Fut[T]] = { import c.universe._ import Flag._ - val builder = new ExprBuilder[c.type, self.FS](c, self.futureSystem) + val builder = new ExprBuilder[c.type, futureSystem.type](c, self.futureSystem) import builder.defn._ + import builder.name import builder.futureSystemOps val awaitMethod = awaitSym(c) @@ -91,7 +92,7 @@ abstract class AsyncBase extends AsyncUtils { val handlerForLastState: c.Expr[PartialFunction[Int, Unit]] = { val lastState = asyncBlockBuilder.asyncStates.last val lastStateBody = c.Expr[T](lastState.body) - builder.mkHandler(lastState.state, futureSystemOps.completeProm(c.Expr[futureSystem.Prom[T]](Ident("result")), reify(scala.util.Success(lastStateBody.splice)))) + builder.mkHandler(lastState.state, futureSystemOps.completeProm(c.Expr[futureSystem.Prom[T]](Ident(name.result)), reify(scala.util.Success(lastStateBody.splice)))) } vprintln("GENERATED handler for last state:") @@ -108,30 +109,31 @@ abstract class AsyncBase extends AsyncUtils { } } */ - val nonFatalModule = c.mirror.staticModule("scala.util.control.NonFatal") - val resumeFunTree: c.Tree = DefDef(Modifiers(), newTermName("resume"), List(), List(List()), Ident(definitions.UnitClass), + val nonFatalModule = builder.defn.NonFatalClass + val resumeFunTree: c.Tree = DefDef(Modifiers(), name.resume, List(), List(List()), Ident(definitions.UnitClass), Try( reify { val combinedHandler = mkPartialFunction_orElse(handlerExpr)(handlerForLastState).splice - combinedHandler.apply(c.Expr[Int](Ident(newTermName("state"))).splice) + combinedHandler.apply(c.Expr[Int](Ident(name.state)).splice) }.tree , List( CaseDef( - Apply(Ident(nonFatalModule), List(Bind(newTermName("t"), Ident(nme.WILDCARD)))), + Apply(Ident(nonFatalModule), List(Bind(name.tr, Ident(nme.WILDCARD)))), EmptyTree, Block(List({ - val t = c.Expr[Throwable](Ident(newTermName("t"))) - futureSystemOps.completeProm[T](c.Expr[futureSystem.Prom[T]](Ident(newTermName("result"))), reify(scala.util.Failure(t.splice))).tree + val t = c.Expr[Throwable](Ident(name.tr)) + futureSystemOps.completeProm[T](c.Expr[futureSystem.Prom[T]](Ident(name.result)), reify(scala.util.Failure(t.splice))).tree }), c.literalUnit.tree))), EmptyTree)) + val prom: Expr[futureSystem.Prom[T]] = reify { val result = futureSystemOps.createProm[T].splice var state = 0 futureSystemOps.future[Unit] { c.Expr[Unit](Block( localVarTrees :+ resumeFunTree, - Apply(Ident(newTermName("resume")), List()))) + Apply(Ident(name.resume), List()))) }(futureSystemOps.execContext).splice result } diff --git a/src/main/scala/scala/async/ExprBuilder.scala b/src/main/scala/scala/async/ExprBuilder.scala index 4beaa34..65e98e0 100644 --- a/src/main/scala/scala/async/ExprBuilder.scala +++ b/src/main/scala/scala/async/ExprBuilder.scala @@ -19,11 +19,28 @@ final class ExprBuilder[C <: Context, FS <: FutureSystem](val c: C, val futureSy import Flag._ import defn._ - val execContextType = c.weakTypeOf[futureSystem.ExecContext] - val execContext = futureSystemOps.execContext + object name { + // TODO do we need to freshen any of these? + val resume = newTermName("resume") + val state = newTermName("state") + val result = newTermName("result") + val tr = newTermName("tr") + val any = newTermName("any") + val x1 = newTermName("x$1") + val apply = newTermName("apply") + val isDefinedAt = newTermName("isDefinedAt") + + val anon = newTypeName("$anon") + } + + private val execContext = futureSystemOps.execContext + + private def resetDuplicate(tree: Tree) = c.resetAllAttrs(tree.duplicate) private val awaitMethod = awaitSym(c) + private def mkResumeApply = Apply(Ident(name.resume), List()) + /* Make a partial function literal handling case #num: * * { @@ -43,22 +60,18 @@ final class ExprBuilder[C <: Context, FS <: FutureSystem](val c: C, val futureSy } }) */ - val rhsTree = c.resetAllAttrs(rhs.tree.duplicate) + val rhsTree = resetDuplicate(rhs.tree) val handlerTree = mkHandlerTree(num, rhsTree) - c.Expr(handlerTree).asInstanceOf[c.Expr[PartialFunction[Int, Unit]]] - } - - def mkIncrStateTree(): c.Tree = { - Assign( - Ident(newTermName("state")), - mkInt_+(c.Expr[Int](Ident(newTermName("state"))))(c.literal(1)).tree - ) + c.Expr(handlerTree) } def mkStateTree(nextState: Int): c.Tree = + mkStateTree(c.literal(nextState).tree) + + def mkStateTree(nextState: Tree): c.Tree = Assign( - Ident(newTermName("state")), - Literal(Constant(nextState))) + Ident(name.state), + nextState) def defaultValue(tpe: Type): Literal = { val defaultValue: Any = @@ -72,61 +85,68 @@ final class ExprBuilder[C <: Context, FS <: FutureSystem](val c: C, val futureSy ValDef(Modifiers(Flag.MUTABLE), resultName, TypeTree(resultType), defaultValue(resultType)) } + def mkHandlerCase(num: Int, rhs: List[c.Tree]): CaseDef = + mkHandlerCase(num, Block(rhs: _*)) + def mkHandlerCase(num: Int, rhs: c.Tree): CaseDef = CaseDef( // pattern - Bind(newTermName("any"), Typed(Ident(nme.WILDCARD), Ident(definitions.IntClass))), + Bind(name.any, Typed(Ident(nme.WILDCARD), Ident(definitions.IntClass))), // guard - mkAny_==(c.Expr(Ident(newTermName("any"))))(c.literal(num)).tree, + mkAny_==(c.Expr(Ident(name.any)))(c.literal(num)).tree, rhs ) def mkHandlerTreeFor(cases: List[(CaseDef, Int)]): c.Tree = { - val partFunIdent = Ident(c.mirror.staticClass("scala.PartialFunction")) + val partFunIdent = Ident(defn.PartialFunctionClass) val intIdent = Ident(definitions.IntClass) val unitIdent = Ident(definitions.UnitClass) val caseCheck = - defn.mkList_contains(defn.mkList_apply(cases.map(p => c.literal(p._2))))(c.Expr(Ident(newTermName("x$1")))) + defn.mkList_contains(defn.mkList_apply(cases.map(p => c.literal(p._2))))(c.Expr(Ident(name.x1))) Block(List( // anonymous subclass of PartialFunction[Int, Unit] // TODO subclass AbstractPartialFunction - ClassDef(Modifiers(FINAL), newTypeName("$anon"), List(), Template(List(AppliedTypeTree(partFunIdent, List(intIdent, unitIdent))), + ClassDef(Modifiers(FINAL), name.anon, List(), Template(List(AppliedTypeTree(partFunIdent, List(intIdent, unitIdent))), emptyValDef, List( DefDef(Modifiers(), nme.CONSTRUCTOR, List(), List(List()), TypeTree(), - Block(List(Apply(Select(Super(This(tpnme.EMPTY), tpnme.EMPTY), nme.CONSTRUCTOR), List())), Literal(Constant(())))), + Block(List(Apply(Select(Super(This(tpnme.EMPTY), tpnme.EMPTY), nme.CONSTRUCTOR), List())), c.literalUnit.tree)), - DefDef(Modifiers(), newTermName("isDefinedAt"), List(), List(List(ValDef(Modifiers(PARAM), newTermName("x$1"), intIdent, EmptyTree))), TypeTree(), + DefDef(Modifiers(), name.isDefinedAt, List(), List(List(ValDef(Modifiers(PARAM), name.x1, intIdent, EmptyTree))), TypeTree(), caseCheck.tree), - DefDef(Modifiers(), newTermName("apply"), List(), List(List(ValDef(Modifiers(PARAM), newTermName("x$1"), intIdent, EmptyTree))), TypeTree(), - Match(Ident(newTermName("x$1")), cases.map(_._1)) // combine all cases into a single match + DefDef(Modifiers(), name.apply, List(), List(List(ValDef(Modifiers(PARAM), name.x1, intIdent, EmptyTree))), TypeTree(), + Match(Ident(name.x1), cases.map(_._1)) // combine all cases into a single match ) )) )), - Apply(Select(New(Ident(newTypeName("$anon"))), nme.CONSTRUCTOR), List()) + Apply(Select(New(Ident(name.anon)), nme.CONSTRUCTOR), List()) ) } def mkHandlerTree(num: Int, rhs: c.Tree): c.Tree = mkHandlerTreeFor(List(mkHandlerCase(num, rhs) -> num)) + def mkHandlerTree(num: Int, rhs: List[c.Tree]): c.Tree = + mkHandlerTree(num, Block(rhs: _*)) + class AsyncState(stats: List[c.Tree], val state: Int, val nextState: Int) { - val body: c.Tree = - if (stats.size == 1) stats.head - else Block(stats: _*) + val body: c.Tree = stats match { + case stat :: Nil => stat + case _ => Block(stats: _*) + } val varDefs: List[(TermName, Type)] = List() def mkHandlerCaseForState(): CaseDef = - mkHandlerCase(state, Block((stats :+ mkStateTree(nextState) :+ Apply(Ident("resume"), List())): _*)) + mkHandlerCase(state, stats :+ mkStateTree(nextState) :+ mkResumeApply) def mkHandlerTreeForState(): c.Tree = - mkHandlerTree(state, Block((stats :+ mkStateTree(nextState) :+ Apply(Ident("resume"), List())): _*)) + mkHandlerTree(state, stats :+ mkStateTree(nextState) :+ mkResumeApply) def mkHandlerTreeForState(nextState: Int): c.Tree = - mkHandlerTree(state, Block((stats :+ mkStateTree(nextState) :+ Apply(Ident("resume"), List())): _*)) + mkHandlerTree(state, stats :+ mkStateTree(nextState) :+ mkResumeApply) def varDefForResult: Option[c.Tree] = None @@ -143,12 +163,12 @@ final class ExprBuilder[C <: Context, FS <: FutureSystem](val c: C, val futureSy // nextState unused, since encoded in then and else branches override def mkHandlerTreeForState(): c.Tree = - mkHandlerTree(state, Block(stats: _*)) + mkHandlerTree(state, stats) //TODO mkHandlerTreeForState(nextState: Int) override def mkHandlerCaseForState(): CaseDef = - mkHandlerCase(state, Block(stats: _*)) + mkHandlerCase(state, stats) override val toString: String = s"AsyncStateWithIf #$state, next = $nextState" @@ -160,6 +180,8 @@ final class ExprBuilder[C <: Context, FS <: FutureSystem](val c: C, val futureSy val resultName: TermName val resultType: Type + protected def tryType = appliedType(TryClass.toType, List(resultType)) + override val toString: String = s"AsyncStateWithAwait #$state, next = $nextState" @@ -174,15 +196,15 @@ final class ExprBuilder[C <: Context, FS <: FutureSystem](val c: C, val futureSy def mkOnCompleteTree: c.Tree = { val assignTree = Assign( - Ident(resultName.toString), - mkTry_get(c.Expr(Ident("tr"))).tree + Ident(resultName), + mkTry_get(c.Expr(Ident(name.tr))).tree ) val handlerTree = Match( EmptyTree, List( - CaseDef(Bind(newTermName("tr"), Ident("_")), EmptyTree, - Block(assignTree, Apply(Ident("resume"), List())) // rhs of case + CaseDef(Bind(name.tr, Ident("_")), EmptyTree, + Block(assignTree, mkResumeApply) // rhs of case ) ) ) @@ -198,20 +220,8 @@ final class ExprBuilder[C <: Context, FS <: FutureSystem](val c: C, val futureSy * resume() * } */ - def mkOnCompleteIncrStateTree: c.Tree = { - val tryGetTree = - Assign( - Ident(resultName.toString), - Select(Ident("tr"), Try_get) - ) - - val handlerTree = - Function(List(ValDef(Modifiers(PARAM), newTermName("tr"), TypeTree(tryType), EmptyTree)), Block(tryGetTree, mkIncrStateTree(), Apply(Ident("resume"), List()))) - - futureSystemOps.onComplete(c.Expr(awaitable), c.Expr(handlerTree), execContext).tree - } - - def tryType = appliedType(c.mirror.staticClass("scala.util.Try").toType, List(resultType)) + def mkOnCompleteIncrStateTree: c.Tree = + mkOnCompleteTree(mkInt_+(c.Expr[Int](Ident(name.state)))(c.literal(1)).tree) /* Make an `onComplete` invocation which sets the state to `nextState` upon resuming: * @@ -222,14 +232,20 @@ final class ExprBuilder[C <: Context, FS <: FutureSystem](val c: C, val futureSy * resume() * } */ - def mkOnCompleteStateTree(nextState: Int): c.Tree = { + def mkOnCompleteStateTree(nextState: Int): c.Tree = + mkOnCompleteTree(c.literal(nextState).tree) + + private def mkOnCompleteTree(nextState: Tree): c.Tree = { val tryGetTree = Assign( - Ident(resultName.toString), - Select(Ident("tr"), Try_get) + Ident(resultName), + Select(Ident(name.tr), Try_get) ) + + val updateState = mkStateTree(nextState) + val handlerTree = - Function(List(ValDef(Modifiers(PARAM), newTermName("tr"), TypeTree(tryType), EmptyTree)), Block(tryGetTree, mkStateTree(nextState), Apply(Ident("resume"), List()))) + Function(List(ValDef(Modifiers(PARAM), name.tr, TypeTree(tryType), EmptyTree)), Block(tryGetTree, updateState, mkResumeApply)) futureSystemOps.onComplete(c.Expr(awaitable), c.Expr(handlerTree), execContext).tree } @@ -240,7 +256,7 @@ final class ExprBuilder[C <: Context, FS <: FutureSystem](val c: C, val futureSy * case any if any == num => * stats * awaitable.onComplete { - * case tr => + * (try: Try[A]) => * resultName = tr.get * resume() * } @@ -266,17 +282,17 @@ final class ExprBuilder[C <: Context, FS <: FutureSystem](val c: C, val futureSy */ override def mkHandlerTreeForState(): c.Tree = { assert(awaitable != null) - mkHandlerTree(state, Block((stats :+ mkOnCompleteIncrStateTree): _*)) + mkHandlerTree(state, stats :+ mkOnCompleteIncrStateTree) } override def mkHandlerTreeForState(nextState: Int): c.Tree = { assert(awaitable != null) - mkHandlerTree(state, Block((stats :+ mkOnCompleteStateTree(nextState)): _*)) + mkHandlerTree(state, stats :+ mkOnCompleteStateTree(nextState)) } override def mkHandlerCaseForState(): CaseDef = { assert(awaitable != null) - mkHandlerCase(state, Block((stats :+ mkOnCompleteIncrStateTree): _*)) + mkHandlerCase(state, stats :+ mkOnCompleteIncrStateTree) } override def varDefForResult: Option[c.Tree] = @@ -315,7 +331,7 @@ final class ExprBuilder[C <: Context, FS <: FutureSystem](val c: C, val futureSy } def +=(stat: c.Tree): this.type = { - stats += c.resetAllAttrs(renamer.transform(stat).duplicate) + stats += resetDuplicate(renamer.transform(stat)) this } @@ -323,7 +339,7 @@ final class ExprBuilder[C <: Context, FS <: FutureSystem](val c: C, val futureSy def addVarDef(mods: Any, name: TermName, tpt: c.Tree, rhs: c.Tree, extNameMap: Map[c.Symbol, c.Name]): this.type = { varDefs += (name -> tpt.tpe) nameMap ++= extNameMap // update name map - this += Assign(Ident(name), c.resetAllAttrs(renamer.transform(rhs).duplicate)) + this += Assign(Ident(name), rhs) this } @@ -357,7 +373,7 @@ final class ExprBuilder[C <: Context, FS <: FutureSystem](val c: C, val futureSy def complete(awaitArg: c.Tree, awaitResultName: TermName, awaitResultType: Tree, extNameMap: Map[c.Symbol, c.Name], nextState: Int = state + 1): this.type = { nameMap ++= extNameMap - awaitable = c.resetAllAttrs(renamer.transform(awaitArg).duplicate) + awaitable = resetDuplicate(renamer.transform(awaitArg)) resultName = awaitResultName resultType = awaitResultType.tpe this.nextState = nextState @@ -372,10 +388,10 @@ final class ExprBuilder[C <: Context, FS <: FutureSystem](val c: C, val futureSy def resultWithIf(condTree: c.Tree, thenState: Int, elseState: Int): AsyncState = { // 1. build changed if-else tree // 2. insert that tree at the end of the current state - val cond = c.resetAllAttrs(condTree.duplicate) + val cond = resetDuplicate(condTree) this += If(cond, - Block(mkStateTree(thenState), Apply(Ident("resume"), List())), - Block(mkStateTree(elseState), Apply(Ident("resume"), List()))) + Block(mkStateTree(thenState), mkResumeApply), + Block(mkStateTree(elseState), mkResumeApply)) new AsyncStateWithoutAwait(stats.toList, state) { override val varDefs = self.varDefs.toList } @@ -395,10 +411,10 @@ final class ExprBuilder[C <: Context, FS <: FutureSystem](val c: C, val futureSy def resultWithMatch(scrutTree: c.Tree, cases: List[CaseDef], stateFirstCase: Int, perCasebudget: Int): AsyncState = { // 1. build list of changed cases val newCases = for ((cas, num) <- cases.zipWithIndex) yield cas match { - case CaseDef(pat, guard, rhs) => CaseDef(pat, guard, Block(mkStateTree(num * perCasebudget + stateFirstCase), Apply(Ident("resume"), List()))) + case CaseDef(pat, guard, rhs) => CaseDef(pat, guard, Block(mkStateTree(num * perCasebudget + stateFirstCase), mkResumeApply)) } // 2. insert changed match tree at the end of the current state - this += Match(c.resetAllAttrs(scrutTree.duplicate), newCases) + this += Match(resetDuplicate(scrutTree), newCases) new AsyncStateWithoutAwait(stats.toList, state) { override val varDefs = self.varDefs.toList } @@ -439,7 +455,7 @@ final class ExprBuilder[C <: Context, FS <: FutureSystem](val c: C, val futureSy def builderForBranch(tree: c.Tree, state: Int, nextState: Int, budget: Int, nameMap: Map[c.Symbol, c.Name]): AsyncBlockBuilder = { val (branchStats, branchExpr) = tree match { case Block(s, e) => (s, e) - case _ => (List(tree), Literal(Constant(()))) + case _ => (List(tree), c.literalUnit.tree) } new AsyncBlockBuilder(branchStats, branchExpr, state, nextState, budget, nameMap) } @@ -451,7 +467,7 @@ final class ExprBuilder[C <: Context, FS <: FutureSystem](val c: C, val futureSy val newName = c.fresh(name) toRename += (stat.symbol -> newName) - asyncStates += stateBuilder.complete(args(0), newName, tpt, toRename).result // complete with await + asyncStates += stateBuilder.complete(args.head, newName, tpt, toRename).result // complete with await if (remainingBudget > 0) remainingBudget -= 1 else @@ -508,7 +524,7 @@ final class ExprBuilder[C <: Context, FS <: FutureSystem](val c: C, val futureSy for ((cas, num) <- cases.zipWithIndex) { val (casStats, casExpr) = cas match { case CaseDef(_, _, Block(s, e)) => (s, e) - case CaseDef(_, _, rhs) => (List(rhs), Literal(Constant(()))) + case CaseDef(_, _, rhs) => (List(rhs), c.literalUnit.tree) } val builder = new AsyncBlockBuilder(casStats, casExpr, currState + (num * perCaseBudget) + 1, currState + matchBudget, perCaseBudget, toRename) asyncStates ++= builder.asyncStates @@ -532,9 +548,7 @@ final class ExprBuilder[C <: Context, FS <: FutureSystem](val c: C, val futureSy assert(asyncStates.size > 1) val cases = for (state <- asyncStates.toList) yield state.mkHandlerCaseForState() - reify { - c.Expr[PartialFunction[Int, Unit]](mkHandlerTreeFor(cases zip asyncStates.init.map(_.state))).splice: PartialFunction[Int, Unit] - } + c.Expr(mkHandlerTreeFor(cases zip asyncStates.init.map(_.state))) } /* Builds the handler expression for a sequence of async states. @@ -543,7 +557,7 @@ final class ExprBuilder[C <: Context, FS <: FutureSystem](val c: C, val futureSy assert(asyncStates.size > 1) var handlerExpr = - c.Expr(asyncStates(0).mkHandlerTreeForState()).asInstanceOf[c.Expr[PartialFunction[Int, Unit]]] + c.Expr[PartialFunction[Int, Unit]](asyncStates.head.mkHandlerTreeForState()) if (asyncStates.size == 2) handlerExpr @@ -551,7 +565,7 @@ final class ExprBuilder[C <: Context, FS <: FutureSystem](val c: C, val futureSy for (asyncState <- asyncStates.tail.init) { // do not traverse first or last state val handlerTreeForNextState = asyncState.mkHandlerTreeForState() - val currentHandlerTreeNaked = c.resetAllAttrs(handlerExpr.tree.duplicate) + val currentHandlerTreeNaked = resetDuplicate(handlerExpr.tree) handlerExpr = mkPartialFunction_orElse(c.Expr(currentHandlerTreeNaked))(c.Expr(handlerTreeForNextState)) } handlerExpr @@ -594,7 +608,11 @@ final class ExprBuilder[C <: Context, FS <: FutureSystem](val c: C, val futureSy self.splice.get } - val Try_get = methodSym(reify((null.asInstanceOf[scala.util.Try[Any]]).get)) + val Try_get = methodSym(reify((null: scala.util.Try[Any]).get)) + + val PartialFunctionClass = c.mirror.staticClass("scala.PartialFunction") + val TryClass = c.mirror.staticClass("scala.util.Try") + val NonFatalClass = c.mirror.staticModule("scala.util.control.NonFatal") } } -- cgit v1.2.3 From cce4c3778b3f33885d340d53d1986289788b79b2 Mon Sep 17 00:00:00 2001 From: Jason Zaugg Date: Fri, 9 Nov 2012 15:40:23 +0100 Subject: Remove sleeps from the tests. --- pending/run/fallback0/fallback0-manual.scala | 1 - pending/run/fallback0/fallback0.scala | 1 - src/test/scala/scala/async/run/await0/Await0Spec.scala | 5 ----- src/test/scala/scala/async/run/block0/AsyncSpec.scala | 1 - src/test/scala/scala/async/run/block1/block1.scala | 1 - src/test/scala/scala/async/run/ifelse0/IfElse0.scala | 1 - src/test/scala/scala/async/run/ifelse1/IfElse1.scala | 1 - src/test/scala/scala/async/run/ifelse2/ifelse2.scala | 1 - src/test/scala/scala/async/run/ifelse3/IfElse3.scala | 1 - src/test/scala/scala/async/run/match0/Match0.scala | 1 - 10 files changed, 14 deletions(-) diff --git a/pending/run/fallback0/fallback0-manual.scala b/pending/run/fallback0/fallback0-manual.scala index 611d09d..9bc570d 100644 --- a/pending/run/fallback0/fallback0-manual.scala +++ b/pending/run/fallback0/fallback0-manual.scala @@ -19,7 +19,6 @@ class TestFallback0ManualClass { import ExecutionContext.Implicits.global def m1(x: Int): Future[Int] = future { - Thread.sleep(1000) x + 2 } diff --git a/pending/run/fallback0/fallback0.scala b/pending/run/fallback0/fallback0.scala index 75b0739..fdb5568 100644 --- a/pending/run/fallback0/fallback0.scala +++ b/pending/run/fallback0/fallback0.scala @@ -17,7 +17,6 @@ class TestFallback0Class { import ExecutionContext.Implicits.global def m1(x: Int): Future[Int] = future { - Thread.sleep(1000) x + 2 } diff --git a/src/test/scala/scala/async/run/await0/Await0Spec.scala b/src/test/scala/scala/async/run/await0/Await0Spec.scala index e7740e0..50c8fa2 100644 --- a/src/test/scala/scala/async/run/await0/Await0Spec.scala +++ b/src/test/scala/scala/async/run/await0/Await0Spec.scala @@ -20,27 +20,22 @@ class Await0Class { import ExecutionContext.Implicits.global def m1(x: Double): Future[Double] = future { - Thread.sleep(200) x + 2.0 } def m2(x: Float): Future[Float] = future { - Thread.sleep(200) x + 2.0f } def m3(x: Char): Future[Char] = future { - Thread.sleep(200) (x.toInt + 2).toChar } def m4(x: Short): Future[Short] = future { - Thread.sleep(200) (x + 2).toShort } def m5(x: Byte): Future[Byte] = future { - Thread.sleep(200) (x + 2).toByte } diff --git a/src/test/scala/scala/async/run/block0/AsyncSpec.scala b/src/test/scala/scala/async/run/block0/AsyncSpec.scala index f56e394..5a7247c 100644 --- a/src/test/scala/scala/async/run/block0/AsyncSpec.scala +++ b/src/test/scala/scala/async/run/block0/AsyncSpec.scala @@ -20,7 +20,6 @@ class Test1Class { import ExecutionContext.Implicits.global def m1(x: Int): Future[Int] = future { - Thread.sleep(1000) x + 2 } diff --git a/src/test/scala/scala/async/run/block1/block1.scala b/src/test/scala/scala/async/run/block1/block1.scala index 8f21688..a449805 100644 --- a/src/test/scala/scala/async/run/block1/block1.scala +++ b/src/test/scala/scala/async/run/block1/block1.scala @@ -20,7 +20,6 @@ class Test1Class { import ExecutionContext.Implicits.global def m1(x: Int): Future[Int] = future { - Thread.sleep(1000) x + 2 } diff --git a/src/test/scala/scala/async/run/ifelse0/IfElse0.scala b/src/test/scala/scala/async/run/ifelse0/IfElse0.scala index eca3acd..0363a75 100644 --- a/src/test/scala/scala/async/run/ifelse0/IfElse0.scala +++ b/src/test/scala/scala/async/run/ifelse0/IfElse0.scala @@ -20,7 +20,6 @@ class TestIfElseClass { import ExecutionContext.Implicits.global def m1(x: Int): Future[Int] = future { - Thread.sleep(1000) x + 2 } diff --git a/src/test/scala/scala/async/run/ifelse1/IfElse1.scala b/src/test/scala/scala/async/run/ifelse1/IfElse1.scala index 128f02a..3ca3a94 100644 --- a/src/test/scala/scala/async/run/ifelse1/IfElse1.scala +++ b/src/test/scala/scala/async/run/ifelse1/IfElse1.scala @@ -20,7 +20,6 @@ class TestIfElse1Class { import ExecutionContext.Implicits.global def base(x: Int): Future[Int] = future { - Thread.sleep(1000) x + 2 } diff --git a/src/test/scala/scala/async/run/ifelse2/ifelse2.scala b/src/test/scala/scala/async/run/ifelse2/ifelse2.scala index f894923..84974b6 100644 --- a/src/test/scala/scala/async/run/ifelse2/ifelse2.scala +++ b/src/test/scala/scala/async/run/ifelse2/ifelse2.scala @@ -20,7 +20,6 @@ class TestIfElse2Class { import ExecutionContext.Implicits.global def base(x: Int): Future[Int] = future { - Thread.sleep(1000) x + 2 } diff --git a/src/test/scala/scala/async/run/ifelse3/IfElse3.scala b/src/test/scala/scala/async/run/ifelse3/IfElse3.scala index 0c0dbfe..d475a0c 100644 --- a/src/test/scala/scala/async/run/ifelse3/IfElse3.scala +++ b/src/test/scala/scala/async/run/ifelse3/IfElse3.scala @@ -20,7 +20,6 @@ class TestIfElse3Class { import ExecutionContext.Implicits.global def base(x: Int): Future[Int] = future { - Thread.sleep(1000) x + 2 } diff --git a/src/test/scala/scala/async/run/match0/Match0.scala b/src/test/scala/scala/async/run/match0/Match0.scala index 3c7e297..6a17e2b 100644 --- a/src/test/scala/scala/async/run/match0/Match0.scala +++ b/src/test/scala/scala/async/run/match0/Match0.scala @@ -20,7 +20,6 @@ class TestMatchClass { import ExecutionContext.Implicits.global def m1(x: Int): Future[Int] = future { - Thread.sleep(1000) x + 2 } -- cgit v1.2.3 From 566102b23cd08f2b6af60121feb28013288a0951 Mon Sep 17 00:00:00 2001 From: Jason Zaugg Date: Fri, 9 Nov 2012 15:53:21 +0100 Subject: Doc fix. --- src/main/scala/scala/async/Async.scala | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/main/scala/scala/async/Async.scala b/src/main/scala/scala/async/Async.scala index acd5128..e8f5263 100644 --- a/src/main/scala/scala/async/Async.scala +++ b/src/main/scala/scala/async/Async.scala @@ -56,8 +56,9 @@ abstract class AsyncBase extends AsyncUtils { * * A call to await does not block the thread, rather it is a delimiter * used by the enclosing `async` macro. Code following the `await` - * call - @ @param awaitable The future from which a value is awaited + * call. + * + * @param awaitable The future from which a value is awaited * @tparam T The type of that value * @return The value */ -- cgit v1.2.3 From 6226c3583b742e0576e5c598fba4e64622c99165 Mon Sep 17 00:00:00 2001 From: Jason Zaugg Date: Fri, 9 Nov 2012 18:33:13 +0100 Subject: Avoid hygiene problems by suffixing result/result/state. Perhaps we should freshen them, but that will be a little awkward in our reify block. --- src/main/scala/scala/async/Async.scala | 14 ++++-- src/main/scala/scala/async/ExprBuilder.scala | 9 ++-- .../scala/scala/async/run/hygiene/Hygiene.scala | 52 ++++++++++++++++++++++ 3 files changed, 67 insertions(+), 8 deletions(-) create mode 100644 src/test/scala/scala/async/run/hygiene/Hygiene.scala diff --git a/src/main/scala/scala/async/Async.scala b/src/main/scala/scala/async/Async.scala index e8f5263..0a3bbbf 100644 --- a/src/main/scala/scala/async/Async.scala +++ b/src/main/scala/scala/async/Async.scala @@ -129,19 +129,25 @@ abstract class AsyncBase extends AsyncUtils { val prom: Expr[futureSystem.Prom[T]] = reify { - val result = futureSystemOps.createProm[T].splice - var state = 0 + val result$async = futureSystemOps.createProm[T].splice + var state$async = 0 futureSystemOps.future[Unit] { c.Expr[Unit](Block( localVarTrees :+ resumeFunTree, Apply(Ident(name.resume), List()))) }(futureSystemOps.execContext).splice - result + result$async } val result = futureSystemOps.promiseToFuture(prom) - // println(s"${c.macroApplication} \nexpands to:\n ${result.tree}") +// println(s"${c.macroApplication} \nexpands to:\n ${result.tree}") +// val positions = result.tree.collect { +// case t => (t.toString.take(10).replaceAll("\n", "\\n"), t.pos) +// } +// println(positions.mkString("\n")) + result + case tree => c.abort(c.macroApplication.pos, s"expression not supported by async: ${tree}") } diff --git a/src/main/scala/scala/async/ExprBuilder.scala b/src/main/scala/scala/async/ExprBuilder.scala index 65e98e0..b563869 100644 --- a/src/main/scala/scala/async/ExprBuilder.scala +++ b/src/main/scala/scala/async/ExprBuilder.scala @@ -21,9 +21,10 @@ final class ExprBuilder[C <: Context, FS <: FutureSystem](val c: C, val futureSy object name { // TODO do we need to freshen any of these? - val resume = newTermName("resume") - val state = newTermName("state") - val result = newTermName("result") + def expanded(prefix: String) = newTermName(prefix + "$async") + val resume = expanded("resume") + val state = expanded("state") + val result = expanded("result") val tr = newTermName("tr") val any = newTermName("any") val x1 = newTermName("x$1") @@ -256,7 +257,7 @@ final class ExprBuilder[C <: Context, FS <: FutureSystem](val c: C, val futureSy * case any if any == num => * stats * awaitable.onComplete { - * (try: Try[A]) => + * (tr: Try[A]) => * resultName = tr.get * resume() * } diff --git a/src/test/scala/scala/async/run/hygiene/Hygiene.scala b/src/test/scala/scala/async/run/hygiene/Hygiene.scala new file mode 100644 index 0000000..0cc68a4 --- /dev/null +++ b/src/test/scala/scala/async/run/hygiene/Hygiene.scala @@ -0,0 +1,52 @@ +/** + * Copyright (C) 2012 Typesafe Inc. + */ + +package scala.async +package run +package hygiene + +import language.{reflectiveCalls, postfixOps} +import concurrent._ +import scala.concurrent.{Future, ExecutionContext, future, Await} +import scala.concurrent.duration._ +import scala.async.Async.{async, await} +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 + + +class HygieneClass { + + import ExecutionContext.Implicits.global + + def m1(x: Int): Future[Int] = future { + x + 2 + } + + def m2(y: Int) = { + val state = 23 + val result: Any = "result" + def resume(): Any = "resume" + async { + val f1 = m1(state) + val x = await(f1) + val y = await(future(result)) + val z = await(future(resume())) + (x, y, z) + } + } +} + +@RunWith(classOf[JUnit4]) +class HygieneSpec { + + @Test def `is hygenic`() { + val o = new HygieneClass + val fut = o.m2(10) + val res = Await.result(fut, 2 seconds) + res._1 mustBe (25) + res._2 mustBe ("result") + res._3 mustBe ("resume") + } +} -- cgit v1.2.3 From 23209e2f10a123aa7ccd512454cd18ace81a2476 Mon Sep 17 00:00:00 2001 From: Jason Zaugg Date: Fri, 9 Nov 2012 18:41:16 +0100 Subject: Test with less trivial types. Works like it says on the box. --- .../scala/async/run/toughtype/ToughType.scala | 39 ++++++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 src/test/scala/scala/async/run/toughtype/ToughType.scala diff --git a/src/test/scala/scala/async/run/toughtype/ToughType.scala b/src/test/scala/scala/async/run/toughtype/ToughType.scala new file mode 100644 index 0000000..f576ddc --- /dev/null +++ b/src/test/scala/scala/async/run/toughtype/ToughType.scala @@ -0,0 +1,39 @@ +/** + * Copyright (C) 2012 Typesafe Inc. + */ + +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.Test +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 + + +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) + } +} + +@RunWith(classOf[JUnit4]) +class ToughTypeSpec { + + @Test def `propogates tough types`() { + val fut = ToughTypeObject.m2 + val res: (List[_], scala.async.run.toughtype.ToughTypeObject.Inner) = Await.result(fut, 2 seconds) + res._1 mustBe (Nil) + } +} -- cgit v1.2.3 From 34ef6a43e58cca3067a36579a46df5e3d4bfd7c6 Mon Sep 17 00:00:00 2001 From: Jason Zaugg Date: Fri, 9 Nov 2012 18:43:16 +0100 Subject: Remove unneed (). --- src/main/scala/scala/async/FutureSystem.scala | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/main/scala/scala/async/FutureSystem.scala b/src/main/scala/scala/async/FutureSystem.scala index 64c5a66..738de34 100644 --- a/src/main/scala/scala/async/FutureSystem.scala +++ b/src/main/scala/scala/async/FutureSystem.scala @@ -82,11 +82,8 @@ object ScalaConcurrentFutureSystem extends FutureSystem { } 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) - context.literalUnit.splice - } + execContext: Expr[ExecContext]): Expr[Unit] = reify { + future.splice.onComplete(fun.splice)(execContext.splice) } def completeProm[A](prom: Expr[Prom[A]], value: Expr[scala.util.Try[A]]): Expr[Unit] = reify { -- cgit v1.2.3 From c3d7ad1407baf25934334097ef8b0e45fa2cacf2 Mon Sep 17 00:00:00 2001 From: Jason Zaugg Date: Sat, 10 Nov 2012 23:25:50 +0100 Subject: Synthesize a single PartialFunction rather than two and an orElse call. Give the handler a real class name. --- src/main/scala/scala/async/Async.scala | 29 ++++++++---------------- src/main/scala/scala/async/ExprBuilder.scala | 34 ++++++++++++++++++++-------- 2 files changed, 33 insertions(+), 30 deletions(-) diff --git a/src/main/scala/scala/async/Async.scala b/src/main/scala/scala/async/Async.scala index 0a3bbbf..13cca71 100644 --- a/src/main/scala/scala/async/Async.scala +++ b/src/main/scala/scala/async/Async.scala @@ -23,7 +23,7 @@ object Async extends AsyncBase { def async[T](body: T) = macro asyncImpl[T] override def asyncImpl[T: c.WeakTypeTag](c: Context)(body: c.Expr[T]): c.Expr[Future[T]] = super.asyncImpl[T](c)(body) -} + } object AsyncId extends AsyncBase { lazy val futureSystem = IdentityFutureSystem @@ -82,29 +82,25 @@ abstract class AsyncBase extends AsyncUtils { case Block(stats, expr) => val asyncBlockBuilder = new builder.AsyncBlockBuilder(stats, expr, 0, 1000, 1000, Map()) - vprintln(s"states of current method:") asyncBlockBuilder.asyncStates foreach vprintln - val handlerExpr = asyncBlockBuilder.mkCombinedHandlerExpr() - - vprintln(s"GENERATED handler expr:") - vprintln(handlerExpr) + val handlerCases: List[(CaseDef, Int)] = asyncBlockBuilder.mkCombinedHandlerCases() - val handlerForLastState: c.Expr[PartialFunction[Int, Unit]] = { + val caseForLastState: (CaseDef, Int) = { val lastState = asyncBlockBuilder.asyncStates.last val lastStateBody = c.Expr[T](lastState.body) - builder.mkHandler(lastState.state, futureSystemOps.completeProm(c.Expr[futureSystem.Prom[T]](Ident(name.result)), reify(scala.util.Success(lastStateBody.splice)))) + val rhs = futureSystemOps.completeProm(c.Expr[futureSystem.Prom[T]](Ident(name.result)), reify(scala.util.Success(lastStateBody.splice))) + builder.mkHandlerCase(lastState.state, rhs.tree) -> lastState.state } - vprintln("GENERATED handler for last state:") - vprintln(handlerForLastState) + val combinedHander = c.Expr[PartialFunction[Int, Unit]](builder.mkHandlerTreeFor(handlerCases :+ caseForLastState)) val localVarTrees = asyncBlockBuilder.asyncStates.init.flatMap(_.allVarDefs).toList /* def resume(): Unit = { try { - (handlerExpr.splice orElse handlerForLastState.splice)(state) + combinedHander(state) } catch { case NonFatal(t) => result.failure(t) } @@ -114,8 +110,7 @@ abstract class AsyncBase extends AsyncUtils { val resumeFunTree: c.Tree = DefDef(Modifiers(), name.resume, List(), List(List()), Ident(definitions.UnitClass), Try( reify { - val combinedHandler = mkPartialFunction_orElse(handlerExpr)(handlerForLastState).splice - combinedHandler.apply(c.Expr[Int](Ident(name.state)).splice) + combinedHander.splice.apply(c.Expr[Int](Ident(name.state)).splice) }.tree , List( @@ -139,15 +134,9 @@ abstract class AsyncBase extends AsyncUtils { result$async } val result = futureSystemOps.promiseToFuture(prom) -// println(s"${c.macroApplication} \nexpands to:\n ${result.tree}") -// val positions = result.tree.collect { -// case t => (t.toString.take(10).replaceAll("\n", "\\n"), t.pos) -// } -// println(positions.mkString("\n")) - + vprintln(s"${c.macroApplication} \nexpands to:\n ${result.tree}") result - case tree => c.abort(c.macroApplication.pos, s"expression not supported by async: ${tree}") } diff --git a/src/main/scala/scala/async/ExprBuilder.scala b/src/main/scala/scala/async/ExprBuilder.scala index b563869..6578df8 100644 --- a/src/main/scala/scala/async/ExprBuilder.scala +++ b/src/main/scala/scala/async/ExprBuilder.scala @@ -21,17 +21,19 @@ final class ExprBuilder[C <: Context, FS <: FutureSystem](val c: C, val futureSy object name { // TODO do we need to freshen any of these? - def expanded(prefix: String) = newTermName(prefix + "$async") - val resume = expanded("resume") - val state = expanded("state") - val result = expanded("result") + def suffix(string: String) = string + "$async" + def expandedTermName(prefix: String) = newTermName(suffix(prefix)) + def expandedTypeName(prefix: String) = newTypeName(suffix(prefix)) + val resume = expandedTermName("resume") + val state = expandedTermName("state") + val result = expandedTermName("result") val tr = newTermName("tr") val any = newTermName("any") val x1 = newTermName("x$1") val apply = newTermName("apply") val isDefinedAt = newTermName("isDefinedAt") - val anon = newTypeName("$anon") + val asyncHander = expandedTypeName("Handler") } private val execContext = futureSystemOps.execContext @@ -98,31 +100,36 @@ final class ExprBuilder[C <: Context, FS <: FutureSystem](val c: C, val futureSy rhs ) + private def paramValDef(name: TermName, sym: Symbol) = ValDef(Modifiers(PARAM), name, Ident(sym), EmptyTree) + private def paramValDef(name: TermName, tpe: Type) = ValDef(Modifiers(PARAM), name, TypeTree(tpe), EmptyTree) + def mkHandlerTreeFor(cases: List[(CaseDef, Int)]): c.Tree = { val partFunIdent = Ident(defn.PartialFunctionClass) +// val partFunTpe = appliedType(defn.PartialFunctionClass.tpe, definitions.IntTpe, definitions.UnitTpe) val intIdent = Ident(definitions.IntClass) val unitIdent = Ident(definitions.UnitClass) val caseCheck = defn.mkList_contains(defn.mkList_apply(cases.map(p => c.literal(p._2))))(c.Expr(Ident(name.x1))) + val handlerName = name.asyncHander Block(List( // anonymous subclass of PartialFunction[Int, Unit] // TODO subclass AbstractPartialFunction - ClassDef(Modifiers(FINAL), name.anon, List(), Template(List(AppliedTypeTree(partFunIdent, List(intIdent, unitIdent))), + ClassDef(Modifiers(FINAL), handlerName, List(), Template(List(AppliedTypeTree(partFunIdent, List(intIdent, unitIdent))), emptyValDef, List( DefDef(Modifiers(), nme.CONSTRUCTOR, List(), List(List()), TypeTree(), Block(List(Apply(Select(Super(This(tpnme.EMPTY), tpnme.EMPTY), nme.CONSTRUCTOR), List())), c.literalUnit.tree)), - DefDef(Modifiers(), name.isDefinedAt, List(), List(List(ValDef(Modifiers(PARAM), name.x1, intIdent, EmptyTree))), TypeTree(), + DefDef(Modifiers(), name.isDefinedAt, List(), List(List(paramValDef(name.x1, definitions.IntClass))), TypeTree(), caseCheck.tree), - DefDef(Modifiers(), name.apply, List(), List(List(ValDef(Modifiers(PARAM), name.x1, intIdent, EmptyTree))), TypeTree(), + DefDef(Modifiers(), name.apply, List(), List(List(paramValDef(name.x1, definitions.IntClass))), TypeTree(), Match(Ident(name.x1), cases.map(_._1)) // combine all cases into a single match ) )) )), - Apply(Select(New(Ident(name.anon)), nme.CONSTRUCTOR), List()) + Apply(Select(New(Ident(handlerName)), nme.CONSTRUCTOR), List()) ) } @@ -246,7 +253,7 @@ final class ExprBuilder[C <: Context, FS <: FutureSystem](val c: C, val futureSy val updateState = mkStateTree(nextState) val handlerTree = - Function(List(ValDef(Modifiers(PARAM), name.tr, TypeTree(tryType), EmptyTree)), Block(tryGetTree, updateState, mkResumeApply)) + Function(List(paramValDef(name.tr, tryType)), Block(tryGetTree, updateState, mkResumeApply)) futureSystemOps.onComplete(c.Expr(awaitable), c.Expr(handlerTree), execContext).tree } @@ -552,6 +559,13 @@ final class ExprBuilder[C <: Context, FS <: FutureSystem](val c: C, val futureSy c.Expr(mkHandlerTreeFor(cases zip asyncStates.init.map(_.state))) } + def mkCombinedHandlerCases(): List[(CaseDef, Int)] = { + assert(asyncStates.size > 1) + + val cases = for (state <- asyncStates.toList) yield state.mkHandlerCaseForState() + cases zip asyncStates.init.map(_.state) + } + /* Builds the handler expression for a sequence of async states. */ def mkHandlerExpr(): c.Expr[PartialFunction[Int, Unit]] = { -- cgit v1.2.3 From c60ad476bc056aaec32be805b06023061197c605 Mon Sep 17 00:00:00 2001 From: Jason Zaugg Date: Sat, 10 Nov 2012 23:35:47 +0100 Subject: Deleting unused code. --- src/main/scala/scala/async/ExprBuilder.scala | 144 +-------------------------- 1 file changed, 1 insertion(+), 143 deletions(-) diff --git a/src/main/scala/scala/async/ExprBuilder.scala b/src/main/scala/scala/async/ExprBuilder.scala index 6578df8..0fdcb79 100644 --- a/src/main/scala/scala/async/ExprBuilder.scala +++ b/src/main/scala/scala/async/ExprBuilder.scala @@ -44,30 +44,6 @@ final class ExprBuilder[C <: Context, FS <: FutureSystem](val c: C, val futureSy private def mkResumeApply = Apply(Ident(name.resume), List()) - /* Make a partial function literal handling case #num: - * - * { - * case any if any == num => rhs - * } - */ - def mkHandler(num: Int, rhs: c.Expr[Any]): c.Expr[PartialFunction[Int, Unit]] = { - /* - val numLiteral = c.Expr[Int](Literal(Constant(num))) - - reify(new PartialFunction[Int, Unit] { - def isDefinedAt(`x$1`: Int) = - `x$1` == numLiteral.splice - def apply(`x$1`: Int) = `x$1` match { - case any: Int if any == numLiteral.splice => - rhs.splice - } - }) - */ - val rhsTree = resetDuplicate(rhs.tree) - val handlerTree = mkHandlerTree(num, rhsTree) - c.Expr(handlerTree) - } - def mkStateTree(nextState: Int): c.Tree = mkStateTree(c.literal(nextState).tree) @@ -133,12 +109,6 @@ final class ExprBuilder[C <: Context, FS <: FutureSystem](val c: C, val futureSy ) } - def mkHandlerTree(num: Int, rhs: c.Tree): c.Tree = - mkHandlerTreeFor(List(mkHandlerCase(num, rhs) -> num)) - - def mkHandlerTree(num: Int, rhs: List[c.Tree]): c.Tree = - mkHandlerTree(num, Block(rhs: _*)) - class AsyncState(stats: List[c.Tree], val state: Int, val nextState: Int) { val body: c.Tree = stats match { case stat :: Nil => stat @@ -150,12 +120,6 @@ final class ExprBuilder[C <: Context, FS <: FutureSystem](val c: C, val futureSy def mkHandlerCaseForState(): CaseDef = mkHandlerCase(state, stats :+ mkStateTree(nextState) :+ mkResumeApply) - def mkHandlerTreeForState(): c.Tree = - mkHandlerTree(state, stats :+ mkStateTree(nextState) :+ mkResumeApply) - - def mkHandlerTreeForState(nextState: Int): c.Tree = - mkHandlerTree(state, stats :+ mkStateTree(nextState) :+ mkResumeApply) - def varDefForResult: Option[c.Tree] = None @@ -170,11 +134,6 @@ final class ExprBuilder[C <: Context, FS <: FutureSystem](val c: C, val futureSy extends AsyncState(stats, state, 0) { // nextState unused, since encoded in then and else branches - override def mkHandlerTreeForState(): c.Tree = - mkHandlerTree(state, stats) - - //TODO mkHandlerTreeForState(nextState: Int) - override def mkHandlerCaseForState(): CaseDef = mkHandlerCase(state, stats) @@ -193,32 +152,6 @@ final class ExprBuilder[C <: Context, FS <: FutureSystem](val c: C, val futureSy override val toString: String = s"AsyncStateWithAwait #$state, next = $nextState" - /* Make an `onComplete` invocation: - * - * awaitable.onComplete { - * case tr => - * resultName = tr.get - * resume() - * } - */ - def mkOnCompleteTree: c.Tree = { - val assignTree = - Assign( - Ident(resultName), - mkTry_get(c.Expr(Ident(name.tr))).tree - ) - val handlerTree = - Match( - EmptyTree, - List( - CaseDef(Bind(name.tr, Ident("_")), EmptyTree, - Block(assignTree, mkResumeApply) // rhs of case - ) - ) - ) - futureSystemOps.onComplete(c.Expr(awaitable), c.Expr(handlerTree), execContext).tree - } - /* Make an `onComplete` invocation which increments the state upon resuming: * * awaitable.onComplete { @@ -258,46 +191,6 @@ final class ExprBuilder[C <: Context, FS <: FutureSystem](val c: C, val futureSy futureSystemOps.onComplete(c.Expr(awaitable), c.Expr(handlerTree), execContext).tree } - /* Make a partial function literal handling case #num: - * - * { - * case any if any == num => - * stats - * awaitable.onComplete { - * (tr: Try[A]) => - * resultName = tr.get - * resume() - * } - * } - */ - def mkHandlerForState(num: Int): c.Expr[PartialFunction[Int, Unit]] = { - assert(awaitable != null) - builder.mkHandler(num, c.Expr[Unit](Block((stats :+ mkOnCompleteTree): _*))) - } - - /* Make a partial function literal handling case #num: - * - * { - * case any if any == num => - * stats - * awaitable.onComplete { - * case tr => - * resultName = tr.get - * state += 1 - * resume() - * } - * } - */ - override def mkHandlerTreeForState(): c.Tree = { - assert(awaitable != null) - mkHandlerTree(state, stats :+ mkOnCompleteIncrStateTree) - } - - override def mkHandlerTreeForState(nextState: Int): c.Tree = { - assert(awaitable != null) - mkHandlerTree(state, stats :+ mkOnCompleteStateTree(nextState)) - } - override def mkHandlerCaseForState(): CaseDef = { assert(awaitable != null) mkHandlerCase(state, stats :+ mkOnCompleteIncrStateTree) @@ -310,7 +203,7 @@ final class ExprBuilder[C <: Context, FS <: FutureSystem](val c: C, val futureSy /* * Builder for a single state of an async method. */ - class AsyncStateBuilder(state: Int, private var nameMap: Map[c.Symbol, c.Name]) extends Builder[c.Tree, AsyncState] { + class AsyncStateBuilder(state: Int, private var nameMap: Map[c.Symbol, c.Name]) { self => /* Statements preceding an await call. */ @@ -364,13 +257,6 @@ final class ExprBuilder[C <: Context, FS <: FutureSystem](val c: C, val futureSy override val varDefs = self.varDefs.toList } - def clear(): Unit = { - stats.clear() - awaitable = null - resultName = null - resultType = null - } - /* Result needs to be created as a var at the beginning of the transformed method body, so that * it is visible in subsequent states of the state machine. * @@ -552,40 +438,12 @@ final class ExprBuilder[C <: Context, FS <: FutureSystem](val c: C, val futureSy val lastState = stateBuilder.complete(endState).result asyncStates += lastState - def mkCombinedHandlerExpr(): c.Expr[PartialFunction[Int, Unit]] = { - assert(asyncStates.size > 1) - - val cases = for (state <- asyncStates.toList) yield state.mkHandlerCaseForState() - c.Expr(mkHandlerTreeFor(cases zip asyncStates.init.map(_.state))) - } - def mkCombinedHandlerCases(): List[(CaseDef, Int)] = { assert(asyncStates.size > 1) val cases = for (state <- asyncStates.toList) yield state.mkHandlerCaseForState() cases zip asyncStates.init.map(_.state) } - - /* Builds the handler expression for a sequence of async states. - */ - def mkHandlerExpr(): c.Expr[PartialFunction[Int, Unit]] = { - assert(asyncStates.size > 1) - - var handlerExpr = - c.Expr[PartialFunction[Int, Unit]](asyncStates.head.mkHandlerTreeForState()) - - if (asyncStates.size == 2) - handlerExpr - else { - for (asyncState <- asyncStates.tail.init) { - // do not traverse first or last state - val handlerTreeForNextState = asyncState.mkHandlerTreeForState() - val currentHandlerTreeNaked = resetDuplicate(handlerExpr.tree) - handlerExpr = mkPartialFunction_orElse(c.Expr(currentHandlerTreeNaked))(c.Expr(handlerTreeForNextState)) - } - handlerExpr - } - } } /** `termSym( (_: Foo).bar(null: A, null: B)` will return the symbol of `bar`, after overload resolution. */ -- cgit v1.2.3 From fce622a1d3969676725391c85102f89bfd11b9c4 Mon Sep 17 00:00:00 2001 From: Jason Zaugg Date: Sun, 11 Nov 2012 00:09:08 +0100 Subject: Minor refactorings --- src/main/scala/scala/async/Async.scala | 4 ++-- src/main/scala/scala/async/ExprBuilder.scala | 34 ++++++++++++++-------------- 2 files changed, 19 insertions(+), 19 deletions(-) diff --git a/src/main/scala/scala/async/Async.scala b/src/main/scala/scala/async/Async.scala index 13cca71..d69fd95 100644 --- a/src/main/scala/scala/async/Async.scala +++ b/src/main/scala/scala/async/Async.scala @@ -107,7 +107,7 @@ abstract class AsyncBase extends AsyncUtils { } */ val nonFatalModule = builder.defn.NonFatalClass - val resumeFunTree: c.Tree = DefDef(Modifiers(), name.resume, List(), List(List()), Ident(definitions.UnitClass), + val resumeFunTree: c.Tree = DefDef(Modifiers(), name.resume, Nil, List(Nil), Ident(definitions.UnitClass), Try( reify { combinedHander.splice.apply(c.Expr[Int](Ident(name.state)).splice) @@ -129,7 +129,7 @@ abstract class AsyncBase extends AsyncUtils { futureSystemOps.future[Unit] { c.Expr[Unit](Block( localVarTrees :+ resumeFunTree, - Apply(Ident(name.resume), List()))) + Apply(Ident(name.resume), Nil))) }(futureSystemOps.execContext).splice result$async } diff --git a/src/main/scala/scala/async/ExprBuilder.scala b/src/main/scala/scala/async/ExprBuilder.scala index 0fdcb79..6e8827d 100644 --- a/src/main/scala/scala/async/ExprBuilder.scala +++ b/src/main/scala/scala/async/ExprBuilder.scala @@ -20,16 +20,19 @@ final class ExprBuilder[C <: Context, FS <: FutureSystem](val c: C, val futureSy import defn._ object name { - // TODO do we need to freshen any of these? def suffix(string: String) = string + "$async" def expandedTermName(prefix: String) = newTermName(suffix(prefix)) def expandedTypeName(prefix: String) = newTypeName(suffix(prefix)) - val resume = expandedTermName("resume") + val state = expandedTermName("state") val result = expandedTermName("result") val tr = newTermName("tr") + + // TODO do we need to freshen any of these? + val resume = expandedTermName("resume") val any = newTermName("any") val x1 = newTermName("x$1") + val apply = newTermName("apply") val isDefinedAt = newTermName("isDefinedAt") @@ -42,7 +45,7 @@ final class ExprBuilder[C <: Context, FS <: FutureSystem](val c: C, val futureSy private val awaitMethod = awaitSym(c) - private def mkResumeApply = Apply(Ident(name.resume), List()) + private def mkResumeApply = Apply(Ident(name.resume), Nil) def mkStateTree(nextState: Int): c.Tree = mkStateTree(c.literal(nextState).tree) @@ -77,35 +80,36 @@ final class ExprBuilder[C <: Context, FS <: FutureSystem](val c: C, val futureSy ) private def paramValDef(name: TermName, sym: Symbol) = ValDef(Modifiers(PARAM), name, Ident(sym), EmptyTree) + private def paramValDef(name: TermName, tpe: Type) = ValDef(Modifiers(PARAM), name, TypeTree(tpe), EmptyTree) def mkHandlerTreeFor(cases: List[(CaseDef, Int)]): c.Tree = { val partFunIdent = Ident(defn.PartialFunctionClass) -// val partFunTpe = appliedType(defn.PartialFunctionClass.tpe, definitions.IntTpe, definitions.UnitTpe) val intIdent = Ident(definitions.IntClass) val unitIdent = Ident(definitions.UnitClass) + val (caseDefs, states) = cases.unzip val caseCheck = - defn.mkList_contains(defn.mkList_apply(cases.map(p => c.literal(p._2))))(c.Expr(Ident(name.x1))) + defn.mkList_contains(defn.mkList_apply(states map c.literal))(c.Expr(Ident(name.x1))) val handlerName = name.asyncHander Block(List( // anonymous subclass of PartialFunction[Int, Unit] // TODO subclass AbstractPartialFunction - ClassDef(Modifiers(FINAL), handlerName, List(), Template(List(AppliedTypeTree(partFunIdent, List(intIdent, unitIdent))), + ClassDef(Modifiers(FINAL), handlerName, Nil, Template(List(AppliedTypeTree(partFunIdent, List(intIdent, unitIdent))), emptyValDef, List( - DefDef(Modifiers(), nme.CONSTRUCTOR, List(), List(List()), TypeTree(), - Block(List(Apply(Select(Super(This(tpnme.EMPTY), tpnme.EMPTY), nme.CONSTRUCTOR), List())), c.literalUnit.tree)), + DefDef(NoMods, nme.CONSTRUCTOR, Nil, List(Nil), TypeTree(), + Block(List(Apply(Select(Super(This(tpnme.EMPTY), tpnme.EMPTY), nme.CONSTRUCTOR), Nil)), c.literalUnit.tree)), - DefDef(Modifiers(), name.isDefinedAt, List(), List(List(paramValDef(name.x1, definitions.IntClass))), TypeTree(), + DefDef(NoMods, name.isDefinedAt, Nil, List(List(paramValDef(name.x1, definitions.IntClass))), TypeTree(), caseCheck.tree), - DefDef(Modifiers(), name.apply, List(), List(List(paramValDef(name.x1, definitions.IntClass))), TypeTree(), - Match(Ident(name.x1), cases.map(_._1)) // combine all cases into a single match + DefDef(NoMods, name.apply, Nil, List(List(paramValDef(name.x1, definitions.IntClass))), TypeTree(), + Match(Ident(name.x1), caseDefs) // combine all cases into a single match ) )) )), - Apply(Select(New(Ident(handlerName)), nme.CONSTRUCTOR), List()) + Apply(Select(New(Ident(handlerName)), nme.CONSTRUCTOR), Nil) ) } @@ -115,7 +119,7 @@ final class ExprBuilder[C <: Context, FS <: FutureSystem](val c: C, val futureSy case _ => Block(stats: _*) } - val varDefs: List[(TermName, Type)] = List() + val varDefs: List[(TermName, Type)] = Nil def mkHandlerCaseForState(): CaseDef = mkHandlerCase(state, stats :+ mkStateTree(nextState) :+ mkResumeApply) @@ -461,10 +465,6 @@ final class ExprBuilder[C <: Context, FS <: FutureSystem](val c: C, val futureSy def mkList_contains[A](self: Expr[List[A]])(elem: Expr[Any]) = reify(self.splice.contains(elem.splice)) - def mkPartialFunction_orElse[A, B](self: Expr[PartialFunction[A, B]])(other: Expr[PartialFunction[A, B]]) = reify { - self.splice.orElse(other.splice) - } - def mkFunction_apply[A, B](self: Expr[Function1[A, B]])(arg: Expr[A]) = reify { self.splice.apply(arg.splice) } -- cgit v1.2.3 From fb4cbb9823376136217724956056d61288296a06 Mon Sep 17 00:00:00 2001 From: Jason Zaugg Date: Sun, 11 Nov 2012 00:49:25 +0100 Subject: Synthesize a Function1, rather than a PartialFunction. --- src/main/scala/scala/async/Async.scala | 11 ++----- src/main/scala/scala/async/ExprBuilder.scala | 46 ++++++++-------------------- 2 files changed, 14 insertions(+), 43 deletions(-) diff --git a/src/main/scala/scala/async/Async.scala b/src/main/scala/scala/async/Async.scala index d69fd95..cdeadd8 100644 --- a/src/main/scala/scala/async/Async.scala +++ b/src/main/scala/scala/async/Async.scala @@ -84,16 +84,9 @@ abstract class AsyncBase extends AsyncUtils { asyncBlockBuilder.asyncStates foreach vprintln - val handlerCases: List[(CaseDef, Int)] = asyncBlockBuilder.mkCombinedHandlerCases() + val handlerCases: List[CaseDef] = asyncBlockBuilder.mkCombinedHandlerCases[T]() - val caseForLastState: (CaseDef, Int) = { - val lastState = asyncBlockBuilder.asyncStates.last - val lastStateBody = c.Expr[T](lastState.body) - val rhs = futureSystemOps.completeProm(c.Expr[futureSystem.Prom[T]](Ident(name.result)), reify(scala.util.Success(lastStateBody.splice))) - builder.mkHandlerCase(lastState.state, rhs.tree) -> lastState.state - } - - val combinedHander = c.Expr[PartialFunction[Int, Unit]](builder.mkHandlerTreeFor(handlerCases :+ caseForLastState)) + val combinedHander = c.Expr[Int => Unit](builder.mkFunction(handlerCases)) val localVarTrees = asyncBlockBuilder.asyncStates.init.flatMap(_.allVarDefs).toList diff --git a/src/main/scala/scala/async/ExprBuilder.scala b/src/main/scala/scala/async/ExprBuilder.scala index 6e8827d..7d4bf35 100644 --- a/src/main/scala/scala/async/ExprBuilder.scala +++ b/src/main/scala/scala/async/ExprBuilder.scala @@ -83,35 +83,8 @@ final class ExprBuilder[C <: Context, FS <: FutureSystem](val c: C, val futureSy private def paramValDef(name: TermName, tpe: Type) = ValDef(Modifiers(PARAM), name, TypeTree(tpe), EmptyTree) - def mkHandlerTreeFor(cases: List[(CaseDef, Int)]): c.Tree = { - val partFunIdent = Ident(defn.PartialFunctionClass) - val intIdent = Ident(definitions.IntClass) - val unitIdent = Ident(definitions.UnitClass) - val (caseDefs, states) = cases.unzip - - val caseCheck = - defn.mkList_contains(defn.mkList_apply(states map c.literal))(c.Expr(Ident(name.x1))) - val handlerName = name.asyncHander - - Block(List( - // anonymous subclass of PartialFunction[Int, Unit] - // TODO subclass AbstractPartialFunction - ClassDef(Modifiers(FINAL), handlerName, Nil, Template(List(AppliedTypeTree(partFunIdent, List(intIdent, unitIdent))), - emptyValDef, List( - DefDef(NoMods, nme.CONSTRUCTOR, Nil, List(Nil), TypeTree(), - Block(List(Apply(Select(Super(This(tpnme.EMPTY), tpnme.EMPTY), nme.CONSTRUCTOR), Nil)), c.literalUnit.tree)), - - DefDef(NoMods, name.isDefinedAt, Nil, List(List(paramValDef(name.x1, definitions.IntClass))), TypeTree(), - caseCheck.tree), - - DefDef(NoMods, name.apply, Nil, List(List(paramValDef(name.x1, definitions.IntClass))), TypeTree(), - Match(Ident(name.x1), caseDefs) // combine all cases into a single match - ) - )) - )), - Apply(Select(New(Ident(handlerName)), nme.CONSTRUCTOR), Nil) - ) - } + def mkFunction(cases: List[CaseDef]): c.Tree = + Function(List(paramValDef(name.x1, definitions.IntClass)), Match(Ident(name.x1), cases)) class AsyncState(stats: List[c.Tree], val state: Int, val nextState: Int) { val body: c.Tree = stats match { @@ -442,11 +415,18 @@ final class ExprBuilder[C <: Context, FS <: FutureSystem](val c: C, val futureSy val lastState = stateBuilder.complete(endState).result asyncStates += lastState - def mkCombinedHandlerCases(): List[(CaseDef, Int)] = { + def mkCombinedHandlerCases[T](): List[CaseDef] = { assert(asyncStates.size > 1) + val initCases = for (state <- asyncStates.toList.init) yield state.mkHandlerCaseForState() - val cases = for (state <- asyncStates.toList) yield state.mkHandlerCaseForState() - cases zip asyncStates.init.map(_.state) + val caseForLastState: CaseDef = { + val lastState = asyncStates.last + val lastStateBody = c.Expr[T](lastState.body) + val rhs = futureSystemOps.completeProm(c.Expr[futureSystem.Prom[T]](Ident(name.result)), reify(scala.util.Success(lastStateBody.splice))) + mkHandlerCase(lastState.state, rhs.tree) + } + + initCases :+ caseForLastState } } @@ -483,9 +463,7 @@ final class ExprBuilder[C <: Context, FS <: FutureSystem](val c: C, val futureSy val Try_get = methodSym(reify((null: scala.util.Try[Any]).get)) - val PartialFunctionClass = c.mirror.staticClass("scala.PartialFunction") val TryClass = c.mirror.staticClass("scala.util.Try") val NonFatalClass = c.mirror.staticModule("scala.util.control.NonFatal") } - } -- cgit v1.2.3 From cf444641bff609964ee16c5aec19029c1ae04da1 Mon Sep 17 00:00:00 2001 From: Jason Zaugg Date: Sun, 11 Nov 2012 00:53:05 +0100 Subject: Synthesize `case 23 => rhs` rather than `case any @ (_: Int) if any == 25)` --- src/main/scala/scala/async/ExprBuilder.scala | 14 +------------- src/test/scala/scala/async/run/await0/Await0Spec.scala | 1 - 2 files changed, 1 insertion(+), 14 deletions(-) diff --git a/src/main/scala/scala/async/ExprBuilder.scala b/src/main/scala/scala/async/ExprBuilder.scala index 7d4bf35..e298a05 100644 --- a/src/main/scala/scala/async/ExprBuilder.scala +++ b/src/main/scala/scala/async/ExprBuilder.scala @@ -30,13 +30,7 @@ final class ExprBuilder[C <: Context, FS <: FutureSystem](val c: C, val futureSy // TODO do we need to freshen any of these? val resume = expandedTermName("resume") - val any = newTermName("any") val x1 = newTermName("x$1") - - val apply = newTermName("apply") - val isDefinedAt = newTermName("isDefinedAt") - - val asyncHander = expandedTypeName("Handler") } private val execContext = futureSystemOps.execContext @@ -71,13 +65,7 @@ final class ExprBuilder[C <: Context, FS <: FutureSystem](val c: C, val futureSy mkHandlerCase(num, Block(rhs: _*)) def mkHandlerCase(num: Int, rhs: c.Tree): CaseDef = - CaseDef( - // pattern - Bind(name.any, Typed(Ident(nme.WILDCARD), Ident(definitions.IntClass))), - // guard - mkAny_==(c.Expr(Ident(name.any)))(c.literal(num)).tree, - rhs - ) + CaseDef(c.literal(num).tree, EmptyTree, rhs) private def paramValDef(name: TermName, sym: Symbol) = ValDef(Modifiers(PARAM), name, Ident(sym), EmptyTree) diff --git a/src/test/scala/scala/async/run/await0/Await0Spec.scala b/src/test/scala/scala/async/run/await0/Await0Spec.scala index 50c8fa2..42d4ef2 100644 --- a/src/test/scala/scala/async/run/await0/Await0Spec.scala +++ b/src/test/scala/scala/async/run/await0/Await0Spec.scala @@ -70,4 +70,3 @@ class Await0Spec { res mustBe (26.0) } } - -- cgit v1.2.3 From f8646f99648773145baa853c6defdf5554ca0283 Mon Sep 17 00:00:00 2001 From: Jason Zaugg Date: Sun, 11 Nov 2012 00:59:21 +0100 Subject: Shuffle a bit more code around. --- src/main/scala/scala/async/Async.scala | 6 ++---- src/main/scala/scala/async/AsyncUtils.scala | 22 +++------------------- src/main/scala/scala/async/ExprBuilder.scala | 23 ++++++++++++++--------- 3 files changed, 19 insertions(+), 32 deletions(-) diff --git a/src/main/scala/scala/async/Async.scala b/src/main/scala/scala/async/Async.scala index cdeadd8..2d7f324 100644 --- a/src/main/scala/scala/async/Async.scala +++ b/src/main/scala/scala/async/Async.scala @@ -10,7 +10,7 @@ import scala.collection.mutable.ListBuffer import scala.concurrent.{Future, Promise, ExecutionContext, future} import ExecutionContext.Implicits.global import scala.util.control.NonFatal -import scala.util.continuations.{shift, reset, cpsParam} +import AsyncUtils.vprintln /* @@ -45,7 +45,7 @@ object AsyncId extends AsyncBase { * * The default implementation, [[scala.async.Async]], binds the macro to `scala.concurrent._`. */ -abstract class AsyncBase extends AsyncUtils { +abstract class AsyncBase { self => type FS <: FutureSystem @@ -76,8 +76,6 @@ abstract class AsyncBase extends AsyncUtils { import builder.name import builder.futureSystemOps - val awaitMethod = awaitSym(c) - body.tree match { case Block(stats, expr) => val asyncBlockBuilder = new builder.AsyncBlockBuilder(stats, expr, 0, 1000, 1000, Map()) diff --git a/src/main/scala/scala/async/AsyncUtils.scala b/src/main/scala/scala/async/AsyncUtils.scala index d288d34..3b590be 100644 --- a/src/main/scala/scala/async/AsyncUtils.scala +++ b/src/main/scala/scala/async/AsyncUtils.scala @@ -3,29 +3,13 @@ */ package scala.async -import scala.reflect.macros.Context - /* * @author Philipp Haller */ -trait AsyncUtils { +object AsyncUtils { - val verbose = false + private val verbose = false - protected def vprintln(s: Any): Unit = if (verbose) + private[async] def vprintln(s: Any): Unit = if (verbose) println("[async] "+s) - - /* Symbol of the `Async.await` method in context `c`. - */ - protected def awaitSym(c: Context): c.universe.Symbol = { - val asyncMod = c.mirror.staticModule("scala.async.Async") - val tpe = asyncMod.moduleClass.asType.toType - tpe.member(c.universe.newTermName("await")) - } - - protected def awaitCpsSym(c: Context): c.universe.Symbol = { - val asyncMod = c.mirror.staticModule("scala.async.Async") - val tpe = asyncMod.moduleClass.asType.toType - tpe.member(c.universe.newTermName("awaitCps")) - } } diff --git a/src/main/scala/scala/async/ExprBuilder.scala b/src/main/scala/scala/async/ExprBuilder.scala index e298a05..f360c92 100644 --- a/src/main/scala/scala/async/ExprBuilder.scala +++ b/src/main/scala/scala/async/ExprBuilder.scala @@ -4,13 +4,14 @@ package scala.async import scala.reflect.macros.Context -import scala.collection.mutable.{ListBuffer, Builder} +import scala.collection.mutable.ListBuffer import concurrent.Future +import AsyncUtils.vprintln /* * @author Philipp Haller */ -final class ExprBuilder[C <: Context, FS <: FutureSystem](val c: C, val futureSystem: FS) extends AsyncUtils { +final class ExprBuilder[C <: Context, FS <: FutureSystem](val c: C, val futureSystem: FS) { builder => lazy val futureSystemOps = futureSystem.mkOps(c) @@ -21,7 +22,9 @@ final class ExprBuilder[C <: Context, FS <: FutureSystem](val c: C, val futureSy object name { def suffix(string: String) = string + "$async" + def expandedTermName(prefix: String) = newTermName(suffix(prefix)) + def expandedTypeName(prefix: String) = newTypeName(suffix(prefix)) val state = expandedTermName("state") @@ -37,17 +40,13 @@ final class ExprBuilder[C <: Context, FS <: FutureSystem](val c: C, val futureSy private def resetDuplicate(tree: Tree) = c.resetAllAttrs(tree.duplicate) - private val awaitMethod = awaitSym(c) - private def mkResumeApply = Apply(Ident(name.resume), Nil) def mkStateTree(nextState: Int): c.Tree = mkStateTree(c.literal(nextState).tree) def mkStateTree(nextState: Tree): c.Tree = - Assign( - Ident(name.state), - nextState) + Assign(Ident(name.state), nextState) def defaultValue(tpe: Type): Literal = { val defaultValue: Any = @@ -307,7 +306,7 @@ final class ExprBuilder[C <: Context, FS <: FutureSystem](val c: C, val futureSy /* TODO Fall back to CPS plug-in if tree contains an `await` call. */ def checkForUnsupportedAwait(tree: c.Tree) = if (tree exists { - case Apply(fun, _) if fun.symbol == awaitMethod => true + case Apply(fun, _) if fun.symbol == Async_await => true case _ => false }) c.abort(tree.pos, "await unsupported in this position") //throw new FallbackToCpsException @@ -322,7 +321,7 @@ final class ExprBuilder[C <: Context, FS <: FutureSystem](val c: C, val futureSy // populate asyncStates for (stat <- stats) stat match { // the val name = await(..) pattern - case ValDef(mods, name, tpt, Apply(fun, args)) if fun.symbol == awaitMethod => + case ValDef(mods, name, tpt, Apply(fun, args)) if fun.symbol == Async_await => val newName = c.fresh(name) toRename += (stat.symbol -> newName) @@ -453,5 +452,11 @@ final class ExprBuilder[C <: Context, FS <: FutureSystem](val c: C, val futureSy val TryClass = c.mirror.staticClass("scala.util.Try") val NonFatalClass = c.mirror.staticModule("scala.util.control.NonFatal") + + val Async_await = { + val asyncMod = c.mirror.staticModule("scala.async.Async") + val tpe = asyncMod.moduleClass.asType.toType + tpe.member(c.universe.newTermName("await")) + } } } -- cgit v1.2.3 From a5f3af44f04e03d212e6cb31e4ca73e2fca52e29 Mon Sep 17 00:00:00 2001 From: Jason Zaugg Date: Sun, 11 Nov 2012 14:58:23 +0100 Subject: Synthezise a match directly. Rather than a function applied to the state. --- src/main/scala/scala/async/Async.scala | 11 +++-------- src/main/scala/scala/async/ExprBuilder.scala | 22 ++++++---------------- 2 files changed, 9 insertions(+), 24 deletions(-) diff --git a/src/main/scala/scala/async/Async.scala b/src/main/scala/scala/async/Async.scala index 2d7f324..ac965b4 100644 --- a/src/main/scala/scala/async/Async.scala +++ b/src/main/scala/scala/async/Async.scala @@ -23,7 +23,7 @@ object Async extends AsyncBase { def async[T](body: T) = macro asyncImpl[T] override def asyncImpl[T: c.WeakTypeTag](c: Context)(body: c.Expr[T]): c.Expr[Future[T]] = super.asyncImpl[T](c)(body) - } +} object AsyncId extends AsyncBase { lazy val futureSystem = IdentityFutureSystem @@ -84,14 +84,12 @@ abstract class AsyncBase { val handlerCases: List[CaseDef] = asyncBlockBuilder.mkCombinedHandlerCases[T]() - val combinedHander = c.Expr[Int => Unit](builder.mkFunction(handlerCases)) - val localVarTrees = asyncBlockBuilder.asyncStates.init.flatMap(_.allVarDefs).toList /* def resume(): Unit = { try { - combinedHander(state) + state match { case 0 => ... ; ... } } catch { case NonFatal(t) => result.failure(t) } @@ -100,10 +98,7 @@ abstract class AsyncBase { val nonFatalModule = builder.defn.NonFatalClass val resumeFunTree: c.Tree = DefDef(Modifiers(), name.resume, Nil, List(Nil), Ident(definitions.UnitClass), Try( - reify { - combinedHander.splice.apply(c.Expr[Int](Ident(name.state)).splice) - }.tree - , + Match(Ident(name.state), handlerCases), List( CaseDef( Apply(Ident(nonFatalModule), List(Bind(name.tr, Ident(nme.WILDCARD)))), diff --git a/src/main/scala/scala/async/ExprBuilder.scala b/src/main/scala/scala/async/ExprBuilder.scala index f360c92..3f82754 100644 --- a/src/main/scala/scala/async/ExprBuilder.scala +++ b/src/main/scala/scala/async/ExprBuilder.scala @@ -22,18 +22,15 @@ final class ExprBuilder[C <: Context, FS <: FutureSystem](val c: C, val futureSy object name { def suffix(string: String) = string + "$async" + def suffixedName(prefix: String) = newTermName(suffix(prefix)) - def expandedTermName(prefix: String) = newTermName(suffix(prefix)) - - def expandedTypeName(prefix: String) = newTypeName(suffix(prefix)) - - val state = expandedTermName("state") - val result = expandedTermName("result") - val tr = newTermName("tr") + val state = suffixedName("state") + val result = suffixedName("result") + val resume = suffixedName("resume") // TODO do we need to freshen any of these? - val resume = expandedTermName("resume") val x1 = newTermName("x$1") + val tr = newTermName("tr") } private val execContext = futureSystemOps.execContext @@ -66,13 +63,6 @@ final class ExprBuilder[C <: Context, FS <: FutureSystem](val c: C, val futureSy def mkHandlerCase(num: Int, rhs: c.Tree): CaseDef = CaseDef(c.literal(num).tree, EmptyTree, rhs) - private def paramValDef(name: TermName, sym: Symbol) = ValDef(Modifiers(PARAM), name, Ident(sym), EmptyTree) - - private def paramValDef(name: TermName, tpe: Type) = ValDef(Modifiers(PARAM), name, TypeTree(tpe), EmptyTree) - - def mkFunction(cases: List[CaseDef]): c.Tree = - Function(List(paramValDef(name.x1, definitions.IntClass)), Match(Ident(name.x1), cases)) - class AsyncState(stats: List[c.Tree], val state: Int, val nextState: Int) { val body: c.Tree = stats match { case stat :: Nil => stat @@ -150,7 +140,7 @@ final class ExprBuilder[C <: Context, FS <: FutureSystem](val c: C, val futureSy val updateState = mkStateTree(nextState) val handlerTree = - Function(List(paramValDef(name.tr, tryType)), Block(tryGetTree, updateState, mkResumeApply)) + Function(List(ValDef(Modifiers(PARAM), name.tr, TypeTree(tryType), EmptyTree)), Block(tryGetTree, updateState, mkResumeApply)) futureSystemOps.onComplete(c.Expr(awaitable), c.Expr(handlerTree), execContext).tree } -- cgit v1.2.3 From 737779f2dd98d0e7f03a738ae8cb867772b0a298 Mon Sep 17 00:00:00 2001 From: Jason Zaugg Date: Sun, 11 Nov 2012 16:12:17 +0100 Subject: Collapse all the onComplete handlers into a single function. We're now down to two inner classes per async block: the `onComplete` function, and the by-name argument to the initial call to `future`. Also caches the execution context in a val. --- src/main/scala/scala/async/Async.scala | 53 +++++++++++++++++++++---- src/main/scala/scala/async/AsyncUtils.scala | 2 +- src/main/scala/scala/async/ExprBuilder.scala | 59 ++++++++++------------------ 3 files changed, 68 insertions(+), 46 deletions(-) diff --git a/src/main/scala/scala/async/Async.scala b/src/main/scala/scala/async/Async.scala index ac965b4..261279e 100644 --- a/src/main/scala/scala/async/Async.scala +++ b/src/main/scala/scala/async/Async.scala @@ -80,28 +80,51 @@ abstract class AsyncBase { case Block(stats, expr) => val asyncBlockBuilder = new builder.AsyncBlockBuilder(stats, expr, 0, 1000, 1000, Map()) - asyncBlockBuilder.asyncStates foreach vprintln + asyncBlockBuilder.asyncStates foreach (s => vprintln(s)) val handlerCases: List[CaseDef] = asyncBlockBuilder.mkCombinedHandlerCases[T]() - val localVarTrees = asyncBlockBuilder.asyncStates.init.flatMap(_.allVarDefs).toList + val initStates = asyncBlockBuilder.asyncStates.init + val localVarTrees = initStates.flatMap(_.allVarDefs).toList + + /* + lazy val onCompleteHandler = (tr: Try[Any]) => state match { + case 0 => { + x11 = tr.get.asInstanceOf[Double]; + state = 1; + resume() + } + ... + */ + val onCompleteHandler = { + val onCompleteHandlers = initStates.flatMap(_.mkOnCompleteHandler).toList + ValDef(Modifiers(LAZY), name.onCompleteHandler, TypeTree(), + Function( + List(ValDef(Modifiers(PARAM), name.tr, TypeTree(TryAnyType), EmptyTree)), + Match(Ident(name.state), onCompleteHandlers))) + } /* def resume(): Unit = { try { - state match { case 0 => ... ; ... } + state match { + case 0 => { + f11 = exprReturningFuture + f11.onComplete(onCompleteHandler)(context) + } + ... + } } catch { case NonFatal(t) => result.failure(t) } } */ - val nonFatalModule = builder.defn.NonFatalClass val resumeFunTree: c.Tree = DefDef(Modifiers(), name.resume, Nil, List(Nil), Ident(definitions.UnitClass), Try( Match(Ident(name.state), handlerCases), List( CaseDef( - Apply(Ident(nonFatalModule), List(Bind(name.tr, Ident(nme.WILDCARD)))), + Apply(Ident(NonFatalClass), List(Bind(name.tr, Ident(nme.WILDCARD)))), EmptyTree, Block(List({ val t = c.Expr[Throwable](Ident(name.tr)) @@ -110,17 +133,33 @@ abstract class AsyncBase { val prom: Expr[futureSystem.Prom[T]] = reify { + // Create the empty promise val result$async = futureSystemOps.createProm[T].splice + // Initialize the state var state$async = 0 + // Resolve the execution context + var execContext$async = futureSystemOps.execContext.splice + + // Spawn a future to: futureSystemOps.future[Unit] { c.Expr[Unit](Block( - localVarTrees :+ resumeFunTree, + // define vars for all intermediate results + localVarTrees :+ + // define the resume() method + resumeFunTree :+ + // define the onComplete function + onCompleteHandler, + // and get things started by calling resume() Apply(Ident(name.resume), Nil))) - }(futureSystemOps.execContext).splice + }(c.Expr[futureSystem.ExecContext](Ident(name.execContext))).splice + // Return the promise from this reify block... result$async } + // ... and return its Future from the macro. val result = futureSystemOps.promiseToFuture(prom) + vprintln(s"${c.macroApplication} \nexpands to:\n ${result.tree}") + result case tree => diff --git a/src/main/scala/scala/async/AsyncUtils.scala b/src/main/scala/scala/async/AsyncUtils.scala index 3b590be..77c155f 100644 --- a/src/main/scala/scala/async/AsyncUtils.scala +++ b/src/main/scala/scala/async/AsyncUtils.scala @@ -10,6 +10,6 @@ object AsyncUtils { private val verbose = false - private[async] def vprintln(s: Any): Unit = if (verbose) + private[async] def vprintln(s: => Any): Unit = if (verbose) println("[async] "+s) } diff --git a/src/main/scala/scala/async/ExprBuilder.scala b/src/main/scala/scala/async/ExprBuilder.scala index 3f82754..22f3738 100644 --- a/src/main/scala/scala/async/ExprBuilder.scala +++ b/src/main/scala/scala/async/ExprBuilder.scala @@ -27,10 +27,12 @@ final class ExprBuilder[C <: Context, FS <: FutureSystem](val c: C, val futureSy val state = suffixedName("state") val result = suffixedName("result") val resume = suffixedName("resume") + val execContext = suffixedName("execContext") // TODO do we need to freshen any of these? val x1 = newTermName("x$1") val tr = newTermName("tr") + val onCompleteHandler = suffixedName("onCompleteHandler") } private val execContext = futureSystemOps.execContext @@ -74,6 +76,21 @@ final class ExprBuilder[C <: Context, FS <: FutureSystem](val c: C, val futureSy def mkHandlerCaseForState(): CaseDef = mkHandlerCase(state, stats :+ mkStateTree(nextState) :+ mkResumeApply) + def mkOnCompleteHandler(): Option[CaseDef] = { + this match { + case aw: AsyncStateWithAwait => + val tryGetTree = + Assign( + Ident(aw.resultName), + TypeApply(Select(Select(Ident(name.tr), Try_get), newTermName("asInstanceOf")), List(TypeTree(aw.resultType))) + ) + val updateState = mkStateTree(nextState) // or increment? + Some(mkHandlerCase(state, List(tryGetTree, updateState, mkResumeApply))) + case _ => + None + } + } + def varDefForResult: Option[c.Tree] = None @@ -106,48 +123,13 @@ final class ExprBuilder[C <: Context, FS <: FutureSystem](val c: C, val futureSy override val toString: String = s"AsyncStateWithAwait #$state, next = $nextState" - /* Make an `onComplete` invocation which increments the state upon resuming: - * - * awaitable.onComplete { - * case tr => - * resultName = tr.get - * state += 1 - * resume() - * } - */ - def mkOnCompleteIncrStateTree: c.Tree = - mkOnCompleteTree(mkInt_+(c.Expr[Int](Ident(name.state)))(c.literal(1)).tree) - - /* Make an `onComplete` invocation which sets the state to `nextState` upon resuming: - * - * awaitable.onComplete { - * case tr => - * resultName = tr.get - * state = `nextState` - * resume() - * } - */ - def mkOnCompleteStateTree(nextState: Int): c.Tree = - mkOnCompleteTree(c.literal(nextState).tree) - - private def mkOnCompleteTree(nextState: Tree): c.Tree = { - val tryGetTree = - Assign( - Ident(resultName), - Select(Ident(name.tr), Try_get) - ) - - val updateState = mkStateTree(nextState) - - val handlerTree = - Function(List(ValDef(Modifiers(PARAM), name.tr, TypeTree(tryType), EmptyTree)), Block(tryGetTree, updateState, mkResumeApply)) - - futureSystemOps.onComplete(c.Expr(awaitable), c.Expr(handlerTree), execContext).tree + private def mkOnCompleteTree: c.Tree = { + futureSystemOps.onComplete(c.Expr(awaitable), c.Expr(Ident(name.onCompleteHandler)), c.Expr(Ident(name.execContext))).tree } override def mkHandlerCaseForState(): CaseDef = { assert(awaitable != null) - mkHandlerCase(state, stats :+ mkOnCompleteIncrStateTree) + mkHandlerCase(state, stats :+ mkOnCompleteTree) } override def varDefForResult: Option[c.Tree] = @@ -441,6 +423,7 @@ final class ExprBuilder[C <: Context, FS <: FutureSystem](val c: C, val futureSy val Try_get = methodSym(reify((null: scala.util.Try[Any]).get)) val TryClass = c.mirror.staticClass("scala.util.Try") + val TryAnyType = appliedType(TryClass.toType, List(definitions.AnyTpe)) val NonFatalClass = c.mirror.staticModule("scala.util.control.NonFatal") val Async_await = { -- cgit v1.2.3 From ff74779ba949737b53cb3189dadf412815faf027 Mon Sep 17 00:00:00 2001 From: Jason Zaugg Date: Sun, 11 Nov 2012 16:27:39 +0100 Subject: Refer to an relevant bug in the comments. --- src/main/scala/scala/async/Async.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/scala/scala/async/Async.scala b/src/main/scala/scala/async/Async.scala index 261279e..c6b7c19 100644 --- a/src/main/scala/scala/async/Async.scala +++ b/src/main/scala/scala/async/Async.scala @@ -41,7 +41,7 @@ object AsyncId extends AsyncBase { * - 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 a bug in the macro system) + * (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._`. */ -- cgit v1.2.3 From 412682afb20def5e50533c300439828078d5e657 Mon Sep 17 00:00:00 2001 From: Jason Zaugg Date: Sun, 11 Nov 2012 16:39:56 +0100 Subject: Use a var rather than a lazy val to break a cycle. (onCompleteHandler and resume refer to each other) --- src/main/scala/scala/async/Async.scala | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/main/scala/scala/async/Async.scala b/src/main/scala/scala/async/Async.scala index c6b7c19..16bca6b 100644 --- a/src/main/scala/scala/async/Async.scala +++ b/src/main/scala/scala/async/Async.scala @@ -98,10 +98,9 @@ abstract class AsyncBase { */ val onCompleteHandler = { val onCompleteHandlers = initStates.flatMap(_.mkOnCompleteHandler).toList - ValDef(Modifiers(LAZY), name.onCompleteHandler, TypeTree(), Function( List(ValDef(Modifiers(PARAM), name.tr, TypeTree(TryAnyType), EmptyTree)), - Match(Ident(name.state), onCompleteHandlers))) + Match(Ident(name.state), onCompleteHandlers)) } /* @@ -139,6 +138,7 @@ abstract class AsyncBase { var state$async = 0 // Resolve the execution context var execContext$async = futureSystemOps.execContext.splice + var onCompleteHandler$async: util.Try[Any] => Unit = null // Spawn a future to: futureSystemOps.future[Unit] { @@ -147,8 +147,8 @@ abstract class AsyncBase { localVarTrees :+ // define the resume() method resumeFunTree :+ - // define the onComplete function - onCompleteHandler, + // assign onComplete function. (The var breaks the circular dependency with resume)` + Assign(Ident(name.onCompleteHandler), onCompleteHandler), // and get things started by calling resume() Apply(Ident(name.resume), Nil))) }(c.Expr[futureSystem.ExecContext](Ident(name.execContext))).splice -- cgit v1.2.3 From 23e24fc738e2d4ad967090c375cd356cf45745ce Mon Sep 17 00:00:00 2001 From: Jason Zaugg Date: Sun, 11 Nov 2012 17:22:17 +0100 Subject: Fixes #7, allow async blocks without await or with a single expression. --- src/main/scala/scala/async/Async.scala | 158 ++++++++++----------- src/main/scala/scala/async/ExprBuilder.scala | 14 +- .../scala/async/run/noawait/NoAwaitSpec.scala | 34 +++++ 3 files changed, 121 insertions(+), 85 deletions(-) create mode 100644 src/test/scala/scala/async/run/noawait/NoAwaitSpec.scala diff --git a/src/main/scala/scala/async/Async.scala b/src/main/scala/scala/async/Async.scala index 16bca6b..9f43b0b 100644 --- a/src/main/scala/scala/async/Async.scala +++ b/src/main/scala/scala/async/Async.scala @@ -75,95 +75,93 @@ abstract class AsyncBase { import builder.defn._ import builder.name import builder.futureSystemOps + val (stats, expr) = body.tree match { + case Block(stats, expr) => (stats, expr) + case tree => (Nil, tree) + } - body.tree match { - case Block(stats, expr) => - val asyncBlockBuilder = new builder.AsyncBlockBuilder(stats, expr, 0, 1000, 1000, Map()) + val asyncBlockBuilder = new builder.AsyncBlockBuilder(stats, expr, 0, 1000, 1000, Map()) - asyncBlockBuilder.asyncStates foreach (s => vprintln(s)) + asyncBlockBuilder.asyncStates foreach (s => vprintln(s)) - val handlerCases: List[CaseDef] = asyncBlockBuilder.mkCombinedHandlerCases[T]() + val handlerCases: List[CaseDef] = asyncBlockBuilder.mkCombinedHandlerCases[T]() - val initStates = asyncBlockBuilder.asyncStates.init - val localVarTrees = initStates.flatMap(_.allVarDefs).toList + val initStates = asyncBlockBuilder.asyncStates.init + val localVarTrees = initStates.flatMap(_.allVarDefs).toList - /* - lazy val onCompleteHandler = (tr: Try[Any]) => state match { - case 0 => { - x11 = tr.get.asInstanceOf[Double]; - state = 1; - resume() - } - ... - */ - val onCompleteHandler = { - val onCompleteHandlers = initStates.flatMap(_.mkOnCompleteHandler).toList - Function( - List(ValDef(Modifiers(PARAM), name.tr, TypeTree(TryAnyType), EmptyTree)), - Match(Ident(name.state), onCompleteHandlers)) + /* + lazy val onCompleteHandler = (tr: Try[Any]) => state match { + case 0 => { + x11 = tr.get.asInstanceOf[Double]; + state = 1; + resume() } + ... + */ + val onCompleteHandler = { + val onCompleteHandlers = initStates.flatMap(_.mkOnCompleteHandler).toList + Function( + List(ValDef(Modifiers(PARAM), name.tr, TypeTree(TryAnyType), EmptyTree)), + Match(Ident(name.state), onCompleteHandlers)) + } - /* - def resume(): Unit = { - try { - state match { - case 0 => { - f11 = exprReturningFuture - f11.onComplete(onCompleteHandler)(context) - } - ... - } - } catch { - case NonFatal(t) => result.failure(t) - } - } - */ - val resumeFunTree: c.Tree = DefDef(Modifiers(), name.resume, Nil, List(Nil), Ident(definitions.UnitClass), - Try( - Match(Ident(name.state), handlerCases), - List( - CaseDef( - Apply(Ident(NonFatalClass), List(Bind(name.tr, Ident(nme.WILDCARD)))), - EmptyTree, - Block(List({ - val t = c.Expr[Throwable](Ident(name.tr)) - futureSystemOps.completeProm[T](c.Expr[futureSystem.Prom[T]](Ident(name.result)), reify(scala.util.Failure(t.splice))).tree - }), c.literalUnit.tree))), EmptyTree)) - - - val prom: Expr[futureSystem.Prom[T]] = reify { - // Create the empty promise - val result$async = futureSystemOps.createProm[T].splice - // Initialize the state - var state$async = 0 - // Resolve the execution context - var execContext$async = futureSystemOps.execContext.splice - var onCompleteHandler$async: util.Try[Any] => Unit = null - - // Spawn a future to: - futureSystemOps.future[Unit] { - c.Expr[Unit](Block( - // define vars for all intermediate results - localVarTrees :+ - // define the resume() method - resumeFunTree :+ - // assign onComplete function. (The var breaks the circular dependency with resume)` - Assign(Ident(name.onCompleteHandler), onCompleteHandler), - // and get things started by calling resume() - Apply(Ident(name.resume), Nil))) - }(c.Expr[futureSystem.ExecContext](Ident(name.execContext))).splice - // Return the promise from this reify block... - result$async + /* + def resume(): Unit = { + try { + state match { + case 0 => { + f11 = exprReturningFuture + f11.onComplete(onCompleteHandler)(context) + } + ... + } + } catch { + case NonFatal(t) => result.failure(t) } - // ... and return its Future from the macro. - val result = futureSystemOps.promiseToFuture(prom) - - vprintln(s"${c.macroApplication} \nexpands to:\n ${result.tree}") + } + */ + val resumeFunTree: c.Tree = DefDef(Modifiers(), name.resume, Nil, List(Nil), Ident(definitions.UnitClass), + Try( + Match(Ident(name.state), handlerCases), + List( + CaseDef( + Apply(Ident(NonFatalClass), List(Bind(name.tr, Ident(nme.WILDCARD)))), + EmptyTree, + Block(List({ + val t = c.Expr[Throwable](Ident(name.tr)) + futureSystemOps.completeProm[T](c.Expr[futureSystem.Prom[T]](Ident(name.result)), reify(scala.util.Failure(t.splice))).tree + }), c.literalUnit.tree))), EmptyTree)) + + + val prom: Expr[futureSystem.Prom[T]] = reify { + // Create the empty promise + val result$async = futureSystemOps.createProm[T].splice + // Initialize the state + var state$async = 0 + // Resolve the execution context + var execContext$async = futureSystemOps.execContext.splice + var onCompleteHandler$async: util.Try[Any] => Unit = null + + // Spawn a future to: + futureSystemOps.future[Unit] { + c.Expr[Unit](Block( + // define vars for all intermediate results + localVarTrees :+ + // define the resume() method + resumeFunTree :+ + // assign onComplete function. (The var breaks the circular dependency with resume)` + Assign(Ident(name.onCompleteHandler), onCompleteHandler), + // and get things started by calling resume() + Apply(Ident(name.resume), Nil))) + }(c.Expr[futureSystem.ExecContext](Ident(name.execContext))).splice + // Return the promise from this reify block... + result$async + } + // ... and return its Future from the macro. + val result = futureSystemOps.promiseToFuture(prom) - result + vprintln(s"${c.macroApplication} \nexpands to:\n ${result.tree}") - case tree => - c.abort(c.macroApplication.pos, s"expression not supported by async: ${tree}") - } + result } } diff --git a/src/main/scala/scala/async/ExprBuilder.scala b/src/main/scala/scala/async/ExprBuilder.scala index 22f3738..09489d4 100644 --- a/src/main/scala/scala/async/ExprBuilder.scala +++ b/src/main/scala/scala/async/ExprBuilder.scala @@ -22,6 +22,7 @@ final class ExprBuilder[C <: Context, FS <: FutureSystem](val c: C, val futureSy object name { def suffix(string: String) = string + "$async" + def suffixedName(prefix: String) = newTermName(suffix(prefix)) val state = suffixedName("state") @@ -375,17 +376,19 @@ final class ExprBuilder[C <: Context, FS <: FutureSystem](val c: C, val futureSy asyncStates += lastState def mkCombinedHandlerCases[T](): List[CaseDef] = { - assert(asyncStates.size > 1) - val initCases = for (state <- asyncStates.toList.init) yield state.mkHandlerCaseForState() - val caseForLastState: CaseDef = { val lastState = asyncStates.last val lastStateBody = c.Expr[T](lastState.body) val rhs = futureSystemOps.completeProm(c.Expr[futureSystem.Prom[T]](Ident(name.result)), reify(scala.util.Success(lastStateBody.splice))) mkHandlerCase(lastState.state, rhs.tree) } - - initCases :+ caseForLastState + asyncStates.toList match { + case s :: Nil => + List(caseForLastState) + case _ => + val initCases = for (state <- asyncStates.toList.init) yield state.mkHandlerCaseForState() + initCases :+ caseForLastState + } } } @@ -432,4 +435,5 @@ final class ExprBuilder[C <: Context, FS <: FutureSystem](val c: C, val futureSy tpe.member(c.universe.newTermName("await")) } } + } diff --git a/src/test/scala/scala/async/run/noawait/NoAwaitSpec.scala b/src/test/scala/scala/async/run/noawait/NoAwaitSpec.scala new file mode 100644 index 0000000..90be946 --- /dev/null +++ b/src/test/scala/scala/async/run/noawait/NoAwaitSpec.scala @@ -0,0 +1,34 @@ +package scala.async +package run +package noawait + +import AsyncId._ +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 + +@RunWith(classOf[JUnit4]) +class NoAwaitSpec { + @Test + def `async block without await`() { + def foo = 1 + async { + foo + foo + } mustBe (foo) + } + + @Test + def `async block without await 2`() { + async { + def x = 0 + if (x > 0) 0 else 1 + } mustBe (1) + } + + @Test + def `async expr without await`() { + def foo = 1 + async(foo) mustBe (foo) + } +} -- cgit v1.2.3 From 40da520a74fbb13b0ed1c5acb7ddf5c521fd8acc Mon Sep 17 00:00:00 2001 From: Jason Zaugg Date: Sun, 11 Nov 2012 22:12:50 +0100 Subject: Add a test to interrogate the expanded tree. - checks that only one function is synthesized - checks the set of vars created (TODO minimize these) - use x$1 rather than x1 for the freshened names for lifted vars. - make execContext a val, not a var. --- src/main/scala/scala/async/Async.scala | 2 +- src/main/scala/scala/async/ExprBuilder.scala | 6 ++-- src/test/scala/scala/async/TestUtils.scala | 9 +++-- src/test/scala/scala/async/TreeInterrogation.scala | 38 ++++++++++++++++++++++ 4 files changed, 49 insertions(+), 6 deletions(-) create mode 100644 src/test/scala/scala/async/TreeInterrogation.scala diff --git a/src/main/scala/scala/async/Async.scala b/src/main/scala/scala/async/Async.scala index 9f43b0b..4f7fa01 100644 --- a/src/main/scala/scala/async/Async.scala +++ b/src/main/scala/scala/async/Async.scala @@ -139,7 +139,7 @@ abstract class AsyncBase { // Initialize the state var state$async = 0 // Resolve the execution context - var execContext$async = futureSystemOps.execContext.splice + val execContext$async = futureSystemOps.execContext.splice var onCompleteHandler$async: util.Try[Any] => Unit = null // Spawn a future to: diff --git a/src/main/scala/scala/async/ExprBuilder.scala b/src/main/scala/scala/async/ExprBuilder.scala index 09489d4..1a3f866 100644 --- a/src/main/scala/scala/async/ExprBuilder.scala +++ b/src/main/scala/scala/async/ExprBuilder.scala @@ -34,6 +34,8 @@ final class ExprBuilder[C <: Context, FS <: FutureSystem](val c: C, val futureSy val x1 = newTermName("x$1") val tr = newTermName("tr") val onCompleteHandler = suffixedName("onCompleteHandler") + + def fresh(name: TermName) = newTermName(c.fresh("" + name + "$")) } private val execContext = futureSystemOps.execContext @@ -295,7 +297,7 @@ final class ExprBuilder[C <: Context, FS <: FutureSystem](val c: C, val futureSy for (stat <- stats) stat match { // the val name = await(..) pattern case ValDef(mods, name, tpt, Apply(fun, args)) if fun.symbol == Async_await => - val newName = c.fresh(name) + val newName = builder.name.fresh(name) toRename += (stat.symbol -> newName) asyncStates += stateBuilder.complete(args.head, newName, tpt, toRename).result // complete with await @@ -309,7 +311,7 @@ final class ExprBuilder[C <: Context, FS <: FutureSystem](val c: C, val futureSy case ValDef(mods, name, tpt, rhs) => checkForUnsupportedAwait(rhs) - val newName = c.fresh(name) + val newName = builder.name.fresh(name) toRename += (stat.symbol -> newName) // when adding assignment need to take `toRename` into account stateBuilder.addVarDef(mods, newName, tpt, rhs, toRename) diff --git a/src/test/scala/scala/async/TestUtils.scala b/src/test/scala/scala/async/TestUtils.scala index 0920659..bac22a3 100644 --- a/src/test/scala/scala/async/TestUtils.scala +++ b/src/test/scala/scala/async/TestUtils.scala @@ -40,11 +40,14 @@ trait TestUtils { } def eval(code: String, compileOptions: String = ""): Any = { + val tb = mkToolbox(compileOptions) + tb.eval(tb.parse(code)) + } + + def mkToolbox(compileOptions: String = "") = { val m = scala.reflect.runtime.currentMirror import scala.tools.reflect.ToolBox - val tb = m.mkToolBox(options = compileOptions) - val result = tb.eval(tb.parse(code)) - result + m.mkToolBox(options = compileOptions) } def expectError(errorSnippet: String, compileOptions: String = "")(code: String) { diff --git a/src/test/scala/scala/async/TreeInterrogation.scala b/src/test/scala/scala/async/TreeInterrogation.scala new file mode 100644 index 0000000..7c277b1 --- /dev/null +++ b/src/test/scala/scala/async/TreeInterrogation.scala @@ -0,0 +1,38 @@ +package scala.async + +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 +import org.junit.Test +import AsyncId._ + +@RunWith(classOf[JUnit4]) +class TreeInterrogation { + @Test + def `a minimal set of vals are lifted to vars`() { + val cm = reflect.runtime.currentMirror + val tb = mkToolbox() + val tree = mkToolbox().parse( + """| import _root_.scala.async.AsyncId._ + | async { + | val x = await(1) + | val y = x * 2 + | val z = await(x * 3) + | z + | }""".stripMargin) + val tree1 = tb.typeCheck(tree) + + // println(cm.universe.showRaw(tree1)) + + import tb.mirror.universe._ + val functions = tree1.collect { + case f: Function => f + } + functions.size mustBe 1 + + val varDefs = tree1.collect { + case ValDef(mods, name, _, _) if mods.hasFlag(Flag.MUTABLE) => name + } + // TODO no need to lift `y` as it is only accessed from a single state. + varDefs.map(_.decoded).toSet mustBe(Set("state$async", "onCompleteHandler$async", "x$1", "z$1", "y$1")) + } +} -- cgit v1.2.3