From 8658394b2cbd70db4fcb9048c347bb6b6c4db628 Mon Sep 17 00:00:00 2001 From: Jason Zaugg Date: Fri, 23 Nov 2012 15:56:58 +0100 Subject: Move TestUtils contents directly into the package object. Why suffer SI-4695 needlessly? --- src/test/scala/scala/async/TestUtils.scala | 58 ------------------------------ src/test/scala/scala/async/package.scala | 43 +++++++++++++++++++++- 2 files changed, 42 insertions(+), 59 deletions(-) delete mode 100644 src/test/scala/scala/async/TestUtils.scala diff --git a/src/test/scala/scala/async/TestUtils.scala b/src/test/scala/scala/async/TestUtils.scala deleted file mode 100644 index 0ae78b8..0000000 --- a/src/test/scala/scala/async/TestUtils.scala +++ /dev/null @@ -1,58 +0,0 @@ -package scala.async - -import language.reflectiveCalls -import language.postfixOps -import language.implicitConversions - -import scala.reflect.{ClassTag, classTag} - -import scala.collection.mutable -import scala.concurrent.{Future, Awaitable, CanAwait} -import java.util.concurrent.{TimeoutException, CountDownLatch, TimeUnit} -import scala.concurrent.duration.Duration -import org.junit.runner.RunWith -import org.junit.runners.JUnit4 -import async._ -import tools.reflect.ToolBoxError - - -trait TestUtils { - - implicit class objectops(obj: Any) { - def mustBe(other: Any) = assert(obj == other, obj + " is not " + other) - - def mustEqual(other: Any) = mustBe(other) - } - - implicit class stringops(text: String) { - def mustContain(substring: String) = assert(text contains substring, text) - } - - def intercept[T <: Throwable : ClassTag](body: => Any): T = { - try { - body - throw new Exception(s"Exception of type ${classTag[T]} was not thrown") - } catch { - case t: Throwable => - if (classTag[T].runtimeClass != t.getClass) throw t - else t.asInstanceOf[T] - } - } - - def eval(code: String, compileOptions: String = ""): Any = { - val tb = mkToolbox(compileOptions) - tb.eval(tb.parse(code)) - } - - def mkToolbox(compileOptions: String = "") = { - val m = scala.reflect.runtime.currentMirror - import scala.tools.reflect.ToolBox - m.mkToolBox(options = compileOptions) - } - - def expectError(errorSnippet: String, compileOptions: String = "", baseCompileOptions: String = "-cp target/scala-2.10/classes")(code: String) { - intercept[ToolBoxError] { - eval(code, compileOptions + " " + baseCompileOptions) - }.getMessage mustContain errorSnippet - } -} diff --git a/src/test/scala/scala/async/package.scala b/src/test/scala/scala/async/package.scala index 32e8be4..868024d 100644 --- a/src/test/scala/scala/async/package.scala +++ b/src/test/scala/scala/async/package.scala @@ -1,5 +1,46 @@ package scala -package object async extends TestUtils { +import reflect._ +import tools.reflect.ToolBoxError +package object async { + + + implicit class objectops(obj: Any) { + def mustBe(other: Any) = assert(obj == other, obj + " is not " + other) + + def mustEqual(other: Any) = mustBe(other) + } + + implicit class stringops(text: String) { + def mustContain(substring: String) = assert(text contains substring, text) + } + + def intercept[T <: Throwable : ClassTag](body: => Any): T = { + try { + body + throw new Exception(s"Exception of type ${classTag[T]} was not thrown") + } catch { + case t: Throwable => + if (classTag[T].runtimeClass != t.getClass) throw t + else t.asInstanceOf[T] + } + } + + def eval(code: String, compileOptions: String = ""): Any = { + val tb = mkToolbox(compileOptions) + tb.eval(tb.parse(code)) + } + + def mkToolbox(compileOptions: String = "") = { + val m = scala.reflect.runtime.currentMirror + import scala.tools.reflect.ToolBox + m.mkToolBox(options = compileOptions) + } + + def expectError(errorSnippet: String, compileOptions: String = "", baseCompileOptions: String = "-cp target/scala-2.10/classes")(code: String) { + intercept[ToolBoxError] { + eval(code, compileOptions + " " + baseCompileOptions) + }.getMessage mustContain errorSnippet + } } -- cgit v1.2.3 From 4c1c9fcf075d6e1e5975a04c5a2ced674716069a Mon Sep 17 00:00:00 2001 From: Jason Zaugg Date: Fri, 23 Nov 2012 15:58:13 +0100 Subject: Fix #31, Unfinished ANF transform Prepend {(); ... } before starting the ANF transform. Add tracing to the anf/inline transform. Also enables and addresses SIP-18 warnings. --- build.sbt | 4 +-- src/main/scala/scala/async/AnfTransform.scala | 35 +++++++++++++++++++--- src/main/scala/scala/async/Async.scala | 7 ++--- src/test/scala/scala/async/TreeInterrogation.scala | 2 +- .../scala/async/run/anf/AnfTransformSpec.scala | 33 ++++++++++++++++++++ 5 files changed, 69 insertions(+), 12 deletions(-) diff --git a/build.sbt b/build.sbt index ba0544b..9b0a6bd 100644 --- a/build.sbt +++ b/build.sbt @@ -30,9 +30,9 @@ libraryDependencies <<= (scalaVersion, libraryDependencies) { scalacOptions += "-P:continuations:enable" -scalacOptions ++= Seq("-deprecation", "-unchecked", "-Xlint") +scalacOptions ++= Seq("-deprecation", "-unchecked", "-Xlint", "-feature") -description := "An asynchronous programminig facility for Scala, in the spirit of C# await/async" +description := "An asynchronous programming facility for Scala, in the spirit of C# await/async" homepage := Some(url("http://github.com/phaller/scala-async")) diff --git a/src/main/scala/scala/async/AnfTransform.scala b/src/main/scala/scala/async/AnfTransform.scala index 6bf6e9a..a69fd4e 100644 --- a/src/main/scala/scala/async/AnfTransform.scala +++ b/src/main/scala/scala/async/AnfTransform.scala @@ -3,11 +3,21 @@ package scala.async import scala.reflect.macros.Context +object AnfTranform { + def apply[C <: Context](c: C): AnfTransform[c.type] = new AnfTransform[c.type](c) +} + class AnfTransform[C <: Context](override val c: C) extends TransformUtils(c) { import c.universe._ - def uniqueNames(tree: Tree): Tree = { + def apply(tree: Tree): List[Tree] = { + val unique = uniqueNames(tree) + // Must prepend the () for issue #31. + anf.transformToList(Block(List(c.literalUnit.tree), unique)) + } + + private def uniqueNames(tree: Tree): Tree = { new UniqueNames(tree).transform(tree) } @@ -65,8 +75,25 @@ class AnfTransform[C <: Context](override val c: C) extends TransformUtils(c) { } } + final class Indent { + private var indent = -1 + def indentString = " " * indent + def apply[T](prefix: String, args: Any)(t: => T): T = { + indent += 1 + try { + //println(s"${indentString}$prefix($args)") + val result = t + //println(s"${indentString}= $result") + result + } finally { + indent -= 1 + } + } + } + private val indent = new Indent + object inline { - def transformToList(tree: Tree): List[Tree] = { + def transformToList(tree: Tree): List[Tree] = indent("inline", tree) { val stats :+ expr = anf.transformToList(tree) expr match { case Apply(fun, args) if isAwait(fun) => @@ -133,9 +160,9 @@ class AnfTransform[C <: Context](override val c: C) extends TransformUtils(c) { vd } } - object anf { - def transformToList(tree: Tree): List[Tree] = { + + private[AnfTransform] def transformToList(tree: Tree): List[Tree] = indent("anf", tree) { def containsAwait = tree exists isAwait tree match { case Select(qual, sel) if containsAwait => diff --git a/src/main/scala/scala/async/Async.scala b/src/main/scala/scala/async/Async.scala index 0b77c78..ed79d0b 100644 --- a/src/main/scala/scala/async/Async.scala +++ b/src/main/scala/scala/async/Async.scala @@ -78,12 +78,9 @@ abstract class AsyncBase { // - no await calls in qualifiers or arguments, // - if/match only used in statement position. val anfTree: Block = { - val transform = new AnfTransform[c.type](c) - val unique = transform.uniqueNames(body.tree) - val stats1 :+ expr1 = transform.anf.transformToList(unique) - + val anf = AnfTranform(c) + val stats1 :+ expr1 = anf(body.tree) val block = Block(stats1, expr1) - c.typeCheck(block).asInstanceOf[Block] } diff --git a/src/test/scala/scala/async/TreeInterrogation.scala b/src/test/scala/scala/async/TreeInterrogation.scala index 9e68005..16cbcf7 100644 --- a/src/test/scala/scala/async/TreeInterrogation.scala +++ b/src/test/scala/scala/async/TreeInterrogation.scala @@ -11,7 +11,7 @@ class TreeInterrogation { def `a minimal set of vals are lifted to vars`() { val cm = reflect.runtime.currentMirror val tb = mkToolbox("-cp target/scala-2.10/classes") - val tree = mkToolbox().parse( + val tree = tb.parse( """| import _root_.scala.async.AsyncId._ | async { | val x = await(1) diff --git a/src/test/scala/scala/async/run/anf/AnfTransformSpec.scala b/src/test/scala/scala/async/run/anf/AnfTransformSpec.scala index 872e44d..8bdb80f 100644 --- a/src/test/scala/scala/async/run/anf/AnfTransformSpec.scala +++ b/src/test/scala/scala/async/run/anf/AnfTransformSpec.scala @@ -172,4 +172,37 @@ class AnfTransformSpec { } result mustBe (103) } + + @Test + def nestedAwaitAsBareExpression() { + import ExecutionContext.Implicits.global + import _root_.scala.async.AsyncId.{async, await} + val result = async { + await(await("").isEmpty) + } + result mustBe (true) + } + + @Test + def nestedAwaitInBlock() { + import ExecutionContext.Implicits.global + import _root_.scala.async.AsyncId.{async, await} + val result = async { + () + await(await("").isEmpty) + } + result mustBe (true) + } + + @Test + def nestedAwaitInIf() { + import ExecutionContext.Implicits.global + import _root_.scala.async.AsyncId.{async, await} + val result = async { + if ("".isEmpty) + await(await("").isEmpty) + else 0 + } + result mustBe (true) + } } -- cgit v1.2.3 From 73ab189dcb45bccc0ba6e2e0d445b08057391496 Mon Sep 17 00:00:00 2001 From: Jason Zaugg Date: Fri, 23 Nov 2012 17:18:43 +0100 Subject: Tightenen up modifiers and other refactoring. --- src/main/scala/scala/async/AnfTransform.scala | 21 ++++++++------------- src/main/scala/scala/async/Async.scala | 6 +++--- src/main/scala/scala/async/AsyncAnalysis.scala | 2 +- src/main/scala/scala/async/ExprBuilder.scala | 2 +- 4 files changed, 13 insertions(+), 18 deletions(-) diff --git a/src/main/scala/scala/async/AnfTransform.scala b/src/main/scala/scala/async/AnfTransform.scala index a69fd4e..4c78b5a 100644 --- a/src/main/scala/scala/async/AnfTransform.scala +++ b/src/main/scala/scala/async/AnfTransform.scala @@ -3,11 +3,7 @@ package scala.async import scala.reflect.macros.Context -object AnfTranform { - def apply[C <: Context](c: C): AnfTransform[c.type] = new AnfTransform[c.type](c) -} - -class AnfTransform[C <: Context](override val c: C) extends TransformUtils(c) { +private[async] final case class AnfTransform[C <: Context](override val c: C) extends TransformUtils(c) { import c.universe._ @@ -26,7 +22,7 @@ class AnfTransform[C <: Context](override val c: C) extends TransformUtils(c) { * * This step is needed to allow us to safely merge blocks during the `inline` transform below. */ - final class UniqueNames(tree: Tree) extends Transformer { + private final class UniqueNames(tree: Tree) extends Transformer { val repeatedNames: Set[Name] = tree.collect { case dt: DefTree => dt.symbol.name }.groupBy(x => x).filter(_._2.size > 1).keySet @@ -75,7 +71,7 @@ class AnfTransform[C <: Context](override val c: C) extends TransformUtils(c) { } } - final class Indent { + private object trace { private var indent = -1 def indentString = " " * indent def apply[T](prefix: String, args: Any)(t: => T): T = { @@ -90,10 +86,9 @@ class AnfTransform[C <: Context](override val c: C) extends TransformUtils(c) { } } } - private val indent = new Indent - object inline { - def transformToList(tree: Tree): List[Tree] = indent("inline", tree) { + private object inline { + def transformToList(tree: Tree): List[Tree] = trace("inline", tree) { val stats :+ expr = anf.transformToList(tree) expr match { case Apply(fun, args) if isAwait(fun) => @@ -160,9 +155,10 @@ class AnfTransform[C <: Context](override val c: C) extends TransformUtils(c) { vd } } - object anf { - private[AnfTransform] def transformToList(tree: Tree): List[Tree] = indent("anf", tree) { + private object anf { + + private[AnfTransform] def transformToList(tree: Tree): List[Tree] = trace("anf", tree) { def containsAwait = tree exists isAwait tree match { case Select(qual, sel) if containsAwait => @@ -217,5 +213,4 @@ class AnfTransform[C <: Context](override val c: C) extends TransformUtils(c) { } } } - } diff --git a/src/main/scala/scala/async/Async.scala b/src/main/scala/scala/async/Async.scala index ed79d0b..106bdf0 100644 --- a/src/main/scala/scala/async/Async.scala +++ b/src/main/scala/scala/async/Async.scala @@ -65,8 +65,8 @@ abstract class AsyncBase { import c.universe._ import Flag._ - val builder = new ExprBuilder[c.type, futureSystem.type](c, self.futureSystem) - val anaylzer = new AsyncAnalysis[c.type](c) + val builder = ExprBuilder[c.type, futureSystem.type](c, self.futureSystem) + val anaylzer = AsyncAnalysis[c.type](c) import builder.defn._ import builder.name @@ -78,7 +78,7 @@ abstract class AsyncBase { // - no await calls in qualifiers or arguments, // - if/match only used in statement position. val anfTree: Block = { - val anf = AnfTranform(c) + val anf = AnfTransform[c.type](c) val stats1 :+ expr1 = anf(body.tree) val block = Block(stats1, expr1) c.typeCheck(block).asInstanceOf[Block] diff --git a/src/main/scala/scala/async/AsyncAnalysis.scala b/src/main/scala/scala/async/AsyncAnalysis.scala index 180e006..35d2854 100644 --- a/src/main/scala/scala/async/AsyncAnalysis.scala +++ b/src/main/scala/scala/async/AsyncAnalysis.scala @@ -3,7 +3,7 @@ package scala.async import scala.reflect.macros.Context import collection.mutable -private[async] final class AsyncAnalysis[C <: Context](override val c: C) extends TransformUtils(c) { +private[async] final case class AsyncAnalysis[C <: Context](override val c: C) extends TransformUtils(c) { import c.universe._ /** diff --git a/src/main/scala/scala/async/ExprBuilder.scala b/src/main/scala/scala/async/ExprBuilder.scala index b7cfc8f..4b81d1e 100644 --- a/src/main/scala/scala/async/ExprBuilder.scala +++ b/src/main/scala/scala/async/ExprBuilder.scala @@ -10,7 +10,7 @@ import collection.mutable /* * @author Philipp Haller */ -final class ExprBuilder[C <: Context, FS <: FutureSystem](override val c: C, val futureSystem: FS) +final case class ExprBuilder[C <: Context, FS <: FutureSystem](override val c: C, val futureSystem: FS) extends TransformUtils(c) { builder => -- cgit v1.2.3 From f9c25fc4acac6a5965d68584d68eee7539e4ba45 Mon Sep 17 00:00:00 2001 From: Jason Zaugg Date: Fri, 23 Nov 2012 17:35:52 +0100 Subject: Forbid await in try/catch. --- src/main/scala/scala/async/AsyncAnalysis.scala | 10 +++++++++ src/test/scala/scala/async/neg/NakedAwait.scala | 30 +++++++++++++++++++++++++ 2 files changed, 40 insertions(+) diff --git a/src/main/scala/scala/async/AsyncAnalysis.scala b/src/main/scala/scala/async/AsyncAnalysis.scala index 35d2854..c160776 100644 --- a/src/main/scala/scala/async/AsyncAnalysis.scala +++ b/src/main/scala/scala/async/AsyncAnalysis.scala @@ -53,6 +53,16 @@ private[async] final case class AsyncAnalysis[C <: Context](override val c: C) e reportUnsupportedAwait(function, "nested function") } + override def traverse(tree: Tree) { + def containsAwait = tree exists isAwait + tree match { + case Try(_, _, _) if containsAwait => + reportUnsupportedAwait(tree, "try/catch") + case _ => + super.traverse(tree) + } + } + /** * @return true, if the tree contained an unsupported await. */ diff --git a/src/test/scala/scala/async/neg/NakedAwait.scala b/src/test/scala/scala/async/neg/NakedAwait.scala index 8b85977..a0c4e4d 100644 --- a/src/test/scala/scala/async/neg/NakedAwait.scala +++ b/src/test/scala/scala/async/neg/NakedAwait.scala @@ -87,4 +87,34 @@ class NakedAwait { """.stripMargin } } + + @Test + def tryBody() { + expectError("await must not be used under a try/catch.") { + """ + | import _root_.scala.async.AsyncId._ + | async { try { await(false) } catch { case _ => } } + """.stripMargin + } + } + + @Test + def catchBody() { + expectError("await must not be used under a try/catch.") { + """ + | import _root_.scala.async.AsyncId._ + | async { try { () } catch { case _ => await(false) } } + """.stripMargin + } + } + + @Test + def finallyBody() { + expectError("await must not be used under a try/catch.") { + """ + | import _root_.scala.async.AsyncId._ + | async { try { () } finally { await(false) } } + """.stripMargin + } + } } -- cgit v1.2.3 From db5fd4638c0aac51d66244404dad4dd779f184fa Mon Sep 17 00:00:00 2001 From: Jason Zaugg Date: Fri, 23 Nov 2012 18:19:19 +0100 Subject: Test for await in while (currently forbidden). Lookup the ValDef symbol from Assign#rhs.symbol. --- src/main/scala/scala/async/AsyncAnalysis.scala | 4 ++-- src/main/scala/scala/async/ExprBuilder.scala | 2 +- src/test/scala/scala/async/TreeInterrogation.scala | 21 +++++++++++++++++++++ src/test/scala/scala/async/neg/NakedAwait.scala | 16 ++++++++++++++++ 4 files changed, 40 insertions(+), 3 deletions(-) diff --git a/src/main/scala/scala/async/AsyncAnalysis.scala b/src/main/scala/scala/async/AsyncAnalysis.scala index c160776..88a1bb0 100644 --- a/src/main/scala/scala/async/AsyncAnalysis.scala +++ b/src/main/scala/scala/async/AsyncAnalysis.scala @@ -102,10 +102,10 @@ private[async] final case class AsyncAnalysis[C <: Context](override val c: C) e if (isAwait(vd.rhs)) valDefsToLift += vd case as: Assign => if (isAwait(as.rhs)) { - assert(as.symbol != null, "internal error: null symbol for Assign tree:" + as) + assert(as.lhs.symbol != null, "internal error: null symbol for Assign tree:" + as + " " + as.lhs.symbol) // TODO test the orElse case, try to remove the restriction. - val (vd, defBlockId) = valDefChunkId.getOrElse(as.symbol, c.abort(as.pos, "await may only be assigned to a var/val defined in the async block. " + as.symbol)) + val (vd, defBlockId) = valDefChunkId.getOrElse(as.lhs.symbol, c.abort(as.pos, s"await may only be assigned to a var/val defined in the async block. ${as.lhs} ${as.lhs.symbol}")) valDefsToLift += vd } super.traverse(tree) diff --git a/src/main/scala/scala/async/ExprBuilder.scala b/src/main/scala/scala/async/ExprBuilder.scala index 4b81d1e..8ea7ecf 100644 --- a/src/main/scala/scala/async/ExprBuilder.scala +++ b/src/main/scala/scala/async/ExprBuilder.scala @@ -269,7 +269,7 @@ final case class ExprBuilder[C <: Context, FS <: FutureSystem](override val c: C def checkForUnsupportedAwait(tree: c.Tree) = if (tree exists { case Apply(fun, _) if isAwait(fun) => true case _ => false - }) c.abort(tree.pos, "await unsupported in this position") //throw new FallbackToCpsException + }) c.abort(tree.pos, "await must not be used in this position") //throw new FallbackToCpsException def builderForBranch(tree: c.Tree, state: Int, nextState: Int): AsyncBlockBuilder = { val (branchStats, branchExpr) = tree match { diff --git a/src/test/scala/scala/async/TreeInterrogation.scala b/src/test/scala/scala/async/TreeInterrogation.scala index 16cbcf7..cf5948c 100644 --- a/src/test/scala/scala/async/TreeInterrogation.scala +++ b/src/test/scala/scala/async/TreeInterrogation.scala @@ -34,4 +34,25 @@ class TreeInterrogation { } varDefs.map(_.decoded).toSet mustBe(Set("state$async", "onCompleteHandler$async", "await$1", "await$2")) } + + + // @Test + def sandbox() { + val cm = reflect.runtime.currentMirror + val tb = mkToolbox("-cp target/scala-2.10/classes") + val tree = tb.parse( + """| import _root_.scala.async.AsyncId._ + | async { + | var x = 0 + | var y = 0 + | while (x <= 2) { + | y = await(x) + | x += 1 + | } + | y + | }""".stripMargin) + val tree1 = tb.typeCheck(tree) + + println(cm.universe.show(tree1)) + } } diff --git a/src/test/scala/scala/async/neg/NakedAwait.scala b/src/test/scala/scala/async/neg/NakedAwait.scala index a0c4e4d..d400729 100644 --- a/src/test/scala/scala/async/neg/NakedAwait.scala +++ b/src/test/scala/scala/async/neg/NakedAwait.scala @@ -117,4 +117,20 @@ class NakedAwait { """.stripMargin } } + + @Test + def whileBody() { + expectError("await must not be used in this position") { + """ import _root_.scala.async.AsyncId._ + | async { + | var x = 0 + | var y = 0 + | while (x <= 2) { + | y = await(x) + | x += 1 + | } + | y + | }""".stripMargin + } + } } -- cgit v1.2.3 From 4855a5ff60a0b4992da141054c074438b456c3fc Mon Sep 17 00:00:00 2001 From: Jason Zaugg Date: Fri, 23 Nov 2012 23:03:59 +0100 Subject: Support await in a while loop. --- src/main/scala/scala/async/AnfTransform.scala | 3 ++ src/main/scala/scala/async/AsyncAnalysis.scala | 2 + src/main/scala/scala/async/ExprBuilder.scala | 56 ++++++++++++++++------ src/main/scala/scala/async/TransformUtils.scala | 6 +++ src/test/scala/scala/async/TreeInterrogation.scala | 29 ++++++----- src/test/scala/scala/async/neg/NakedAwait.scala | 16 ------- .../scala/scala/async/run/ifelse0/WhileSpec.scala | 43 +++++++++++++++++ 7 files changed, 112 insertions(+), 43 deletions(-) create mode 100644 src/test/scala/scala/async/run/ifelse0/WhileSpec.scala diff --git a/src/main/scala/scala/async/AnfTransform.scala b/src/main/scala/scala/async/AnfTransform.scala index 4c78b5a..0756baf 100644 --- a/src/main/scala/scala/async/AnfTransform.scala +++ b/src/main/scala/scala/async/AnfTransform.scala @@ -204,6 +204,9 @@ private[async] final case class AnfTransform[C <: Context](override val c: C) ex } scrutStats :+ c.typeCheck(attachCopy.Match(tree)(scrutExpr, caseDefs)) + case LabelDef(name, params, rhs) if containsAwait => + List(LabelDef(name, params, Block(inline.transformToList(rhs), Literal(Constant(()))))) + case TypeApply(fun, targs) if containsAwait => val funStats :+ simpleFun = inline.transformToList(fun) funStats :+ attachCopy.TypeApply(tree)(simpleFun, targs).setSymbol(tree.symbol) diff --git a/src/main/scala/scala/async/AsyncAnalysis.scala b/src/main/scala/scala/async/AsyncAnalysis.scala index 88a1bb0..9e24130 100644 --- a/src/main/scala/scala/async/AsyncAnalysis.scala +++ b/src/main/scala/scala/async/AsyncAnalysis.scala @@ -93,6 +93,8 @@ private[async] final case class AsyncAnalysis[C <: Context](override val c: C) e traverseChunks(List(cond, thenp, elsep)) case Match(selector, cases) if tree exists isAwait => traverseChunks(selector :: cases) + case LabelDef(name, params, rhs) if rhs exists isAwait => + traverseChunks(rhs :: Nil) case Apply(fun, args) if isAwait(fun) => super.traverse(tree) nextChunk() diff --git a/src/main/scala/scala/async/ExprBuilder.scala b/src/main/scala/scala/async/ExprBuilder.scala index 8ea7ecf..60430c4 100644 --- a/src/main/scala/scala/async/ExprBuilder.scala +++ b/src/main/scala/scala/async/ExprBuilder.scala @@ -54,8 +54,21 @@ final case class ExprBuilder[C <: Context, FS <: FutureSystem](override val c: C private def mkHandlerCase(num: Int, rhs: List[c.Tree]): CaseDef = mkHandlerCase(num, Block(rhs: _*)) - private def mkHandlerCase(num: Int, rhs: c.Tree): CaseDef = - CaseDef(c.literal(num).tree, EmptyTree, rhs) + private def mkHandlerCase(num: Int, rhs: c.Tree): CaseDef = { + val rhs1 = new Transformer { + override def transform(tree: Tree): Tree = tree match { + case Apply(Ident(name), args) => + val jumpTarget = labelDefStates get name // TODO attempt to be symful + jumpTarget match { + case Some(state) => Return(Block(mkStateTree(state), mkResumeApply)) + case None => super.transform(tree) + } + case _ => super.transform(tree) + } + }.transform(rhs) + + CaseDef(c.literal(num).tree, EmptyTree, rhs1) + } class AsyncState(stats: List[c.Tree], val state: Int, val nextState: Int) { val body: c.Tree = stats match { @@ -209,7 +222,7 @@ final case class ExprBuilder[C <: Context, FS <: FutureSystem](override val c: C 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 = resetDuplicate(condTree) + val cond = resetDuplicate(renamer.transform(condTree)) this += If(cond, Block(mkStateTree(thenState), mkResumeApply), Block(mkStateTree(elseState), mkResumeApply)) @@ -240,6 +253,13 @@ final case class ExprBuilder[C <: Context, FS <: FutureSystem](override val c: C } } + def resultWithLabel(startLabelState: Int): AsyncState = { + this += Block(mkStateTree(startLabelState), mkResumeApply) + new AsyncStateWithoutAwait(stats.toList, state) { + 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" @@ -248,6 +268,8 @@ final case class ExprBuilder[C <: Context, FS <: FutureSystem](override val c: C val stateAssigner = new StateAssigner + val labelDefStates = collection.mutable.Map[Name, Int]() + /** * An `AsyncBlockBuilder` builds a `ListBuffer[AsyncState]` based on the expressions of a `Block(stats, expr)` (see `Async.asyncImpl`). * @@ -262,7 +284,6 @@ final case class ExprBuilder[C <: Context, FS <: FutureSystem](override val c: C val asyncStates = ListBuffer[builder.AsyncState]() private var stateBuilder = new builder.AsyncStateBuilder(startState, toRename) - // current state builder private var currState = startState /* TODO Fall back to CPS plug-in if tree contains an `await` call. */ @@ -272,10 +293,7 @@ final case class ExprBuilder[C <: Context, FS <: FutureSystem](override val c: C }) c.abort(tree.pos, "await must not be used in this position") //throw new FallbackToCpsException def builderForBranch(tree: c.Tree, state: Int, nextState: Int): AsyncBlockBuilder = { - val (branchStats, branchExpr) = tree match { - case Block(s, e) => (s, e) - case _ => (List(tree), c.literalUnit.tree) - } + val (branchStats, branchExpr) = statsAndExpr(tree) new AsyncBlockBuilder(branchStats, branchExpr, state, nextState, toRename) } @@ -324,18 +342,26 @@ final case class ExprBuilder[C <: Context, FS <: FutureSystem](override val c: C stateBuilder.resultWithMatch(scrutinee, cases, caseStates) for ((cas, num) <- cases.zipWithIndex) { - val (casStats, casExpr) = cas match { - case CaseDef(_, _, Block(s, e)) => (s, e) - case CaseDef(_, _, rhs) => (List(rhs), c.literalUnit.tree) - } + val (casStats, casExpr) = statsAndExpr(cas.body) val builder = new AsyncBlockBuilder(casStats, casExpr, caseStates(num), afterMatchState, toRename) asyncStates ++= builder.asyncStates } currState = afterMatchState - stateBuilder = new builder.AsyncStateBuilder(currState, toRename) - - case _ => + stateBuilder = new AsyncStateBuilder(currState, toRename) + + case ld@LabelDef(name, params, rhs) if rhs exists isAwait => + val startLabelState = stateAssigner.nextState() + val afterLabelState = stateAssigner.nextState() + asyncStates += stateBuilder.resultWithLabel(startLabelState) + val (stats, expr) = statsAndExpr(rhs) + labelDefStates(ld.symbol.name) = startLabelState + val builder = new AsyncBlockBuilder(stats, expr, startLabelState, afterLabelState, toRename) + asyncStates ++= builder.asyncStates + + currState = afterLabelState + stateBuilder = new AsyncStateBuilder(currState, toRename) + case _ => checkForUnsupportedAwait(stat) stateBuilder += stat } diff --git a/src/main/scala/scala/async/TransformUtils.scala b/src/main/scala/scala/async/TransformUtils.scala index b79be87..e37f66d 100644 --- a/src/main/scala/scala/async/TransformUtils.scala +++ b/src/main/scala/scala/async/TransformUtils.scala @@ -78,6 +78,11 @@ class TransformUtils[C <: Context](val c: C) { } } + protected def statsAndExpr(tree: Tree): (List[Tree], Tree) = tree match { + case Block(stats, expr) => (stats, expr) + case _ => (List(tree), Literal(Constant(()))) + } + private[async] object defn { def mkList_apply[A](args: List[Expr[A]]): Expr[List[A]] = { c.Expr(Apply(Ident(definitions.List_apply), args.map(_.tree))) @@ -157,4 +162,5 @@ class TransformUtils[C <: Context](val c: C) { def ValDef(tree: Tree)(mods: Modifiers, name: TermName, tpt: Tree, rhs: Tree): ValDef = copyAttach(tree, c.universe.ValDef(mods, name, tpt, rhs)) } + } diff --git a/src/test/scala/scala/async/TreeInterrogation.scala b/src/test/scala/scala/async/TreeInterrogation.scala index cf5948c..9ac0dce 100644 --- a/src/test/scala/scala/async/TreeInterrogation.scala +++ b/src/test/scala/scala/async/TreeInterrogation.scala @@ -36,23 +36,28 @@ class TreeInterrogation { } - // @Test + @Test def sandbox() { val cm = reflect.runtime.currentMirror val tb = mkToolbox("-cp target/scala-2.10/classes") val tree = tb.parse( - """| import _root_.scala.async.AsyncId._ + """ import _root_.scala.async.AsyncId._ | async { - | var x = 0 - | var y = 0 - | while (x <= 2) { - | y = await(x) - | x += 1 - | } - | y + | var xxx: Int = 0 + | var y = 0 + | println("before while") + | while (xxx < 3) { + | println("in while before await") + | y = await(xxx) + | println("in while after await") + | xxx = xxx + 1 + | } + | println("after while") + | y | }""".stripMargin) - val tree1 = tb.typeCheck(tree) - - println(cm.universe.show(tree1)) + //println(tree) + val tree1 = tb.typeCheck(tree.duplicate) + //println(cm.universe.show(tree1)) + //println(tb.eval(tree)) } } diff --git a/src/test/scala/scala/async/neg/NakedAwait.scala b/src/test/scala/scala/async/neg/NakedAwait.scala index d400729..a0c4e4d 100644 --- a/src/test/scala/scala/async/neg/NakedAwait.scala +++ b/src/test/scala/scala/async/neg/NakedAwait.scala @@ -117,20 +117,4 @@ class NakedAwait { """.stripMargin } } - - @Test - def whileBody() { - expectError("await must not be used in this position") { - """ import _root_.scala.async.AsyncId._ - | async { - | var x = 0 - | var y = 0 - | while (x <= 2) { - | y = await(x) - | x += 1 - | } - | y - | }""".stripMargin - } - } } diff --git a/src/test/scala/scala/async/run/ifelse0/WhileSpec.scala b/src/test/scala/scala/async/run/ifelse0/WhileSpec.scala new file mode 100644 index 0000000..d08e2c5 --- /dev/null +++ b/src/test/scala/scala/async/run/ifelse0/WhileSpec.scala @@ -0,0 +1,43 @@ +package scala.async +package run +package ifelse0 + +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 +import org.junit.Test + +@RunWith(classOf[JUnit4]) +class WhileSpec { + + @Test + def whiling1() { + import AsyncId._ + + val result = async { + var xxx: Int = 0 + var y = 0 + while (xxx < 3) { + y = await(xxx) + xxx = xxx + 1 + } + y + } + result mustBe (2) + } + + @Test + def whiling2() { + import AsyncId._ + + val result = async { + var xxx: Int = 0 + var y = 0 + while (false) { + y = await(xxx) + xxx = xxx + 1 + } + y + } + result mustBe (0) + } +} \ No newline at end of file -- cgit v1.2.3 From 7253b5e7a62e0e255a3fece591b7b5991a24d3a9 Mon Sep 17 00:00:00 2001 From: Jason Zaugg Date: Fri, 23 Nov 2012 23:22:30 +0100 Subject: Enable debug logging with a system property. --- src/main/scala/scala/async/AsyncUtils.scala | 2 +- src/test/scala/scala/async/TreeInterrogation.scala | 9 +++++---- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/main/scala/scala/async/AsyncUtils.scala b/src/main/scala/scala/async/AsyncUtils.scala index 77c155f..b2f6747 100644 --- a/src/main/scala/scala/async/AsyncUtils.scala +++ b/src/main/scala/scala/async/AsyncUtils.scala @@ -8,7 +8,7 @@ package scala.async */ object AsyncUtils { - private val verbose = false + private val verbose = false || sys.props.getOrElse("scala.async.debug", "false").equalsIgnoreCase("true") private[async] def vprintln(s: => Any): Unit = if (verbose) println("[async] "+s) diff --git a/src/test/scala/scala/async/TreeInterrogation.scala b/src/test/scala/scala/async/TreeInterrogation.scala index 9ac0dce..1212045 100644 --- a/src/test/scala/scala/async/TreeInterrogation.scala +++ b/src/test/scala/scala/async/TreeInterrogation.scala @@ -36,8 +36,9 @@ class TreeInterrogation { } - @Test + //@Test def sandbox() { + sys.props("scala.async.debug") = "true" val cm = reflect.runtime.currentMirror val tb = mkToolbox("-cp target/scala-2.10/classes") val tree = tb.parse( @@ -55,9 +56,9 @@ class TreeInterrogation { | println("after while") | y | }""".stripMargin) - //println(tree) + println(tree) val tree1 = tb.typeCheck(tree.duplicate) - //println(cm.universe.show(tree1)) - //println(tb.eval(tree)) + println(cm.universe.show(tree1)) + println(tb.eval(tree)) } } -- cgit v1.2.3 From d193065827c40002de1e56c316eb026907536c17 Mon Sep 17 00:00:00 2001 From: Jason Zaugg Date: Fri, 23 Nov 2012 23:30:22 +0100 Subject: Explicitly prohibit await in nested methods. --- src/main/scala/scala/async/AsyncAnalysis.scala | 4 ++++ src/main/scala/scala/async/TransformUtils.scala | 4 ++++ src/test/scala/scala/async/TreeInterrogation.scala | 3 +-- src/test/scala/scala/async/neg/NakedAwait.scala | 10 ++++++++++ 4 files changed, 19 insertions(+), 2 deletions(-) diff --git a/src/main/scala/scala/async/AsyncAnalysis.scala b/src/main/scala/scala/async/AsyncAnalysis.scala index 9e24130..38fd22f 100644 --- a/src/main/scala/scala/async/AsyncAnalysis.scala +++ b/src/main/scala/scala/async/AsyncAnalysis.scala @@ -45,6 +45,10 @@ private[async] final case class AsyncAnalysis[C <: Context](override val c: C) e } } + override def nestedMethod(module: DefDef) { + reportUnsupportedAwait(module, "nested method") + } + override def byNameArgument(arg: Tree) { reportUnsupportedAwait(arg, "by-name argument") } diff --git a/src/main/scala/scala/async/TransformUtils.scala b/src/main/scala/scala/async/TransformUtils.scala index e37f66d..5095875 100644 --- a/src/main/scala/scala/async/TransformUtils.scala +++ b/src/main/scala/scala/async/TransformUtils.scala @@ -37,6 +37,9 @@ class TransformUtils[C <: Context](val c: C) { def nestedModule(module: ModuleDef) { } + def nestedMethod(module: DefDef) { + } + def byNameArgument(arg: Tree) { } @@ -47,6 +50,7 @@ class TransformUtils[C <: Context](val c: C) { tree match { case cd: ClassDef => nestedClass(cd) case md: ModuleDef => nestedModule(md) + case dd: DefDef => nestedMethod(dd) case fun: Function => function(fun) case Apply(fun, args) => val isInByName = isByName(fun) diff --git a/src/test/scala/scala/async/TreeInterrogation.scala b/src/test/scala/scala/async/TreeInterrogation.scala index 1212045..08d2c78 100644 --- a/src/test/scala/scala/async/TreeInterrogation.scala +++ b/src/test/scala/scala/async/TreeInterrogation.scala @@ -35,8 +35,7 @@ class TreeInterrogation { varDefs.map(_.decoded).toSet mustBe(Set("state$async", "onCompleteHandler$async", "await$1", "await$2")) } - - //@Test + @Test def sandbox() { sys.props("scala.async.debug") = "true" val cm = reflect.runtime.currentMirror diff --git a/src/test/scala/scala/async/neg/NakedAwait.scala b/src/test/scala/scala/async/neg/NakedAwait.scala index a0c4e4d..61490e2 100644 --- a/src/test/scala/scala/async/neg/NakedAwait.scala +++ b/src/test/scala/scala/async/neg/NakedAwait.scala @@ -117,4 +117,14 @@ class NakedAwait { """.stripMargin } } + + @Test + def nestedMethod() { + expectError("await must not be used under a nested method.") { + """ + | import _root_.scala.async.AsyncId._ + | async { def foo = await(false) } + """.stripMargin + } + } } -- cgit v1.2.3 From 08bd93c20bcdd1bddc172c1477e811e8fc7f8ebd Mon Sep 17 00:00:00 2001 From: Jason Zaugg Date: Fri, 23 Nov 2012 23:44:24 +0100 Subject: Prohibit return. Closes #21 --- src/main/scala/scala/async/AsyncAnalysis.scala | 2 ++ src/test/scala/scala/async/neg/NakedAwait.scala | 12 ++++++++++++ 2 files changed, 14 insertions(+) diff --git a/src/main/scala/scala/async/AsyncAnalysis.scala b/src/main/scala/scala/async/AsyncAnalysis.scala index 38fd22f..e319fe8 100644 --- a/src/main/scala/scala/async/AsyncAnalysis.scala +++ b/src/main/scala/scala/async/AsyncAnalysis.scala @@ -62,6 +62,8 @@ private[async] final case class AsyncAnalysis[C <: Context](override val c: C) e tree match { case Try(_, _, _) if containsAwait => reportUnsupportedAwait(tree, "try/catch") + case Return(_) => + c.abort(tree.pos, "return is illegal within a async block") case _ => super.traverse(tree) } diff --git a/src/test/scala/scala/async/neg/NakedAwait.scala b/src/test/scala/scala/async/neg/NakedAwait.scala index 61490e2..229feb6 100644 --- a/src/test/scala/scala/async/neg/NakedAwait.scala +++ b/src/test/scala/scala/async/neg/NakedAwait.scala @@ -127,4 +127,16 @@ class NakedAwait { """.stripMargin } } + + @Test + def returnIllegal() { + expectError("return is illegal") { + """ + | import _root_.scala.async.AsyncId._ + | def foo(): Any = async { return false } + | () + | + |""".stripMargin + } + } } -- cgit v1.2.3 From 3ef2995f0923fe7b2346cfd5f816196fce0f00ac Mon Sep 17 00:00:00 2001 From: Jason Zaugg Date: Sat, 24 Nov 2012 00:21:41 +0100 Subject: Prohibit await in if condition (for now) Test nested loops. Control ANF tracing with another system property. --- src/main/scala/scala/async/AnfTransform.scala | 4 +-- src/main/scala/scala/async/AsyncAnalysis.scala | 4 +++ src/main/scala/scala/async/AsyncUtils.scala | 12 ++++++--- src/test/scala/scala/async/TreeInterrogation.scala | 31 ++++++++++++---------- src/test/scala/scala/async/neg/NakedAwait.scala | 11 ++++++++ .../scala/scala/async/run/ifelse0/WhileSpec.scala | 20 ++++++++++++++ 6 files changed, 62 insertions(+), 20 deletions(-) diff --git a/src/main/scala/scala/async/AnfTransform.scala b/src/main/scala/scala/async/AnfTransform.scala index 0756baf..cf6ed04 100644 --- a/src/main/scala/scala/async/AnfTransform.scala +++ b/src/main/scala/scala/async/AnfTransform.scala @@ -77,9 +77,9 @@ private[async] final case class AnfTransform[C <: Context](override val c: C) ex def apply[T](prefix: String, args: Any)(t: => T): T = { indent += 1 try { - //println(s"${indentString}$prefix($args)") + AsyncUtils.trace(s"${indentString}$prefix($args)") val result = t - //println(s"${indentString}= $result") + AsyncUtils.trace(s"${indentString}= $result") result } finally { indent -= 1 diff --git a/src/main/scala/scala/async/AsyncAnalysis.scala b/src/main/scala/scala/async/AsyncAnalysis.scala index e319fe8..c1ee7f4 100644 --- a/src/main/scala/scala/async/AsyncAnalysis.scala +++ b/src/main/scala/scala/async/AsyncAnalysis.scala @@ -62,6 +62,10 @@ private[async] final case class AsyncAnalysis[C <: Context](override val c: C) e tree match { case Try(_, _, _) if containsAwait => reportUnsupportedAwait(tree, "try/catch") + super.traverse(tree) + case If(cond, _, _) if containsAwait => + reportUnsupportedAwait(cond, "condition") + super.traverse(tree) case Return(_) => c.abort(tree.pos, "return is illegal within a async block") case _ => diff --git a/src/main/scala/scala/async/AsyncUtils.scala b/src/main/scala/scala/async/AsyncUtils.scala index b2f6747..525f187 100644 --- a/src/main/scala/scala/async/AsyncUtils.scala +++ b/src/main/scala/scala/async/AsyncUtils.scala @@ -8,8 +8,12 @@ package scala.async */ object AsyncUtils { - private val verbose = false || sys.props.getOrElse("scala.async.debug", "false").equalsIgnoreCase("true") - - private[async] def vprintln(s: => Any): Unit = if (verbose) - println("[async] "+s) + private def enabled(level: String) = sys.props.getOrElse(s"scala.async.$level", "false").equalsIgnoreCase("true") + + private val verbose = enabled("debug") + private val trace = enabled("trace") + + private[async] def vprintln(s: => Any): Unit = if (verbose) println(s"[async] $s") + + private[async] def trace(s: => Any): Unit = if (trace) println(s"[async] $s") } diff --git a/src/test/scala/scala/async/TreeInterrogation.scala b/src/test/scala/scala/async/TreeInterrogation.scala index 08d2c78..485831b 100644 --- a/src/test/scala/scala/async/TreeInterrogation.scala +++ b/src/test/scala/scala/async/TreeInterrogation.scala @@ -32,29 +32,32 @@ class TreeInterrogation { val varDefs = tree1.collect { case ValDef(mods, name, _, _) if mods.hasFlag(Flag.MUTABLE) => name } - varDefs.map(_.decoded).toSet mustBe(Set("state$async", "onCompleteHandler$async", "await$1", "await$2")) + varDefs.map(_.decoded).toSet mustBe (Set("state$async", "onCompleteHandler$async", "await$1", "await$2")) } - @Test + //@Test def sandbox() { - sys.props("scala.async.debug") = "true" + sys.props("scala.async.debug") = true.toString + sys.props("scala.async.trace") = false.toString + val cm = reflect.runtime.currentMirror val tb = mkToolbox("-cp target/scala-2.10/classes") val tree = tb.parse( """ import _root_.scala.async.AsyncId._ | async { - | var xxx: Int = 0 - | var y = 0 - | println("before while") - | while (xxx < 3) { - | println("in while before await") - | y = await(xxx) - | println("in while after await") - | xxx = xxx + 1 + | var sum = 0 + | var i = 0 + | while (i < 5) { + | var j = 0 + | while (j < 5) { + | sum += await(i) * await(j) + | j += 1 + | } + | i += 1 | } - | println("after while") - | y - | }""".stripMargin) + | sum + | } + | """.stripMargin) println(tree) val tree1 = tb.typeCheck(tree.duplicate) println(cm.universe.show(tree1)) diff --git a/src/test/scala/scala/async/neg/NakedAwait.scala b/src/test/scala/scala/async/neg/NakedAwait.scala index 229feb6..b1c2d43 100644 --- a/src/test/scala/scala/async/neg/NakedAwait.scala +++ b/src/test/scala/scala/async/neg/NakedAwait.scala @@ -139,4 +139,15 @@ class NakedAwait { |""".stripMargin } } + + // TODO Anf transform if to have a simple condition. + @Test + def ifCondition() { + expectError("await must not be used under a condition.") { + """ + | import _root_.scala.async.AsyncId._ + | async { if (await(true)) () } + |""".stripMargin + } + } } diff --git a/src/test/scala/scala/async/run/ifelse0/WhileSpec.scala b/src/test/scala/scala/async/run/ifelse0/WhileSpec.scala index d08e2c5..ba1d7a5 100644 --- a/src/test/scala/scala/async/run/ifelse0/WhileSpec.scala +++ b/src/test/scala/scala/async/run/ifelse0/WhileSpec.scala @@ -40,4 +40,24 @@ class WhileSpec { } result mustBe (0) } + + @Test + def nestedWhile() { + import AsyncId._ + + val result = async { + var sum = 0 + var i = 0 + while (i < 5) { + var j = 0 + while (j < 5) { + sum += await(i) * await(j) + j += 1 + } + i += 1 + } + sum + } + result mustBe (100) + } } \ No newline at end of file -- cgit v1.2.3 From 77fcdb0e5bc5827583566b684a1242326606d0cb Mon Sep 17 00:00:00 2001 From: Jason Zaugg Date: Sat, 24 Nov 2012 08:47:38 +0100 Subject: Cleaner while rewriting -preserve LabelDef symbols to allow use of Symbol, rather than Names as map keys. -a label jump overwrites the nextState of an AsyncState. This is cleaner than inserting a return { state = 12; resume() }. --- src/main/scala/scala/async/AnfTransform.scala | 2 +- src/main/scala/scala/async/ExprBuilder.scala | 41 ++++++++++++++------------- 2 files changed, 22 insertions(+), 21 deletions(-) diff --git a/src/main/scala/scala/async/AnfTransform.scala b/src/main/scala/scala/async/AnfTransform.scala index cf6ed04..9241e7e 100644 --- a/src/main/scala/scala/async/AnfTransform.scala +++ b/src/main/scala/scala/async/AnfTransform.scala @@ -205,7 +205,7 @@ private[async] final case class AnfTransform[C <: Context](override val c: C) ex scrutStats :+ c.typeCheck(attachCopy.Match(tree)(scrutExpr, caseDefs)) case LabelDef(name, params, rhs) if containsAwait => - List(LabelDef(name, params, Block(inline.transformToList(rhs), Literal(Constant(()))))) + List(LabelDef(name, params, Block(inline.transformToList(rhs), Literal(Constant(())))).setSymbol(tree.symbol)) case TypeApply(fun, targs) if containsAwait => val funStats :+ simpleFun = inline.transformToList(fun) diff --git a/src/main/scala/scala/async/ExprBuilder.scala b/src/main/scala/scala/async/ExprBuilder.scala index 60430c4..572dd30 100644 --- a/src/main/scala/scala/async/ExprBuilder.scala +++ b/src/main/scala/scala/async/ExprBuilder.scala @@ -55,19 +55,7 @@ final case class ExprBuilder[C <: Context, FS <: FutureSystem](override val c: C mkHandlerCase(num, Block(rhs: _*)) private def mkHandlerCase(num: Int, rhs: c.Tree): CaseDef = { - val rhs1 = new Transformer { - override def transform(tree: Tree): Tree = tree match { - case Apply(Ident(name), args) => - val jumpTarget = labelDefStates get name // TODO attempt to be symful - jumpTarget match { - case Some(state) => Return(Block(mkStateTree(state), mkResumeApply)) - case None => super.transform(tree) - } - case _ => super.transform(tree) - } - }.transform(rhs) - - CaseDef(c.literal(num).tree, EmptyTree, rhs1) + CaseDef(c.literal(num).tree, EmptyTree, rhs) } class AsyncState(stats: List[c.Tree], val state: Int, val nextState: Int) { @@ -159,7 +147,8 @@ final case class ExprBuilder[C <: Context, FS <: FutureSystem](override val c: C /* Result type of an await call. */ var resultType: Type = null - var nextState: Int = -1 + var nextState : Int = -1 + var nextJumpState: Option[Int] = None private val varDefs = ListBuffer[(TermName, Type)]() @@ -173,7 +162,16 @@ final case class ExprBuilder[C <: Context, FS <: FutureSystem](override val c: C } def +=(stat: c.Tree): this.type = { - stats += resetDuplicate(renamer.transform(stat)) + assert(nextJumpState.isEmpty, s"statement appeared after a label jump: $stat") + def addStat() = stats += resetDuplicate(renamer.transform(stat)) + stat match { + case Apply(fun, Nil) => + labelDefStates get fun.symbol match { + case Some(nextState) => nextJumpState = Some(nextState) + case None => addStat() + } + case _ => addStat() + } this } @@ -184,18 +182,20 @@ final case class ExprBuilder[C <: Context, FS <: FutureSystem](override val c: C this } - def result(): AsyncState = + def result(): AsyncState = { + val effectiveNestState = nextJumpState.getOrElse(nextState) if (awaitable == null) - new AsyncState(stats.toList, state, nextState) { + new AsyncState(stats.toList, state, effectiveNestState) { override val varDefs = self.varDefs.toList } else - new AsyncStateWithAwait(stats.toList, state, nextState) { + new AsyncStateWithAwait(stats.toList, state, effectiveNestState) { val awaitable = self.awaitable val resultName = self.resultName val resultType = self.resultType override val varDefs = self.varDefs.toList } + } /* 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. @@ -268,7 +268,7 @@ final case class ExprBuilder[C <: Context, FS <: FutureSystem](override val c: C val stateAssigner = new StateAssigner - val labelDefStates = collection.mutable.Map[Name, Int]() + val labelDefStates = collection.mutable.Map[Symbol, Int]() /** * An `AsyncBlockBuilder` builds a `ListBuffer[AsyncState]` based on the expressions of a `Block(stats, expr)` (see `Async.asyncImpl`). @@ -355,7 +355,7 @@ final case class ExprBuilder[C <: Context, FS <: FutureSystem](override val c: C val afterLabelState = stateAssigner.nextState() asyncStates += stateBuilder.resultWithLabel(startLabelState) val (stats, expr) = statsAndExpr(rhs) - labelDefStates(ld.symbol.name) = startLabelState + labelDefStates(ld.symbol) = startLabelState val builder = new AsyncBlockBuilder(stats, expr, startLabelState, afterLabelState, toRename) asyncStates ++= builder.asyncStates @@ -386,4 +386,5 @@ final case class ExprBuilder[C <: Context, FS <: FutureSystem](override val c: C } } } + } -- cgit v1.2.3 From 535ca23858a233978ce874d5cbad7d586f1e8635 Mon Sep 17 00:00:00 2001 From: Jason Zaugg Date: Sat, 24 Nov 2012 09:07:22 +0100 Subject: Cleanup var lifting. Seeing as we know in advance the full set of lifted vars, we don't need to thread this information through ExprBuilder. --- src/main/scala/scala/async/Async.scala | 5 +++- src/main/scala/scala/async/ExprBuilder.scala | 40 ++++++---------------------- 2 files changed, 12 insertions(+), 33 deletions(-) diff --git a/src/main/scala/scala/async/Async.scala b/src/main/scala/scala/async/Async.scala index 106bdf0..7d9ddf5 100644 --- a/src/main/scala/scala/async/Async.scala +++ b/src/main/scala/scala/async/Async.scala @@ -103,7 +103,10 @@ abstract class AsyncBase { import asyncBlockBuilder.asyncStates logDiagnostics(c)(anfTree, asyncStates.map(_.toString)) val initStates = asyncStates.init - val localVarTrees = asyncStates.flatMap(_.allVarDefs).toList + val localVarTrees = anfTree.collect { + case vd@ValDef(_, _, tpt, _) if renameMap contains vd.symbol => + builder.mkVarDefTree(tpt.tpe, renameMap(vd.symbol)) + } /* lazy val onCompleteHandler = (tr: Try[Any]) => state match { diff --git a/src/main/scala/scala/async/ExprBuilder.scala b/src/main/scala/scala/async/ExprBuilder.scala index 572dd30..d716163 100644 --- a/src/main/scala/scala/async/ExprBuilder.scala +++ b/src/main/scala/scala/async/ExprBuilder.scala @@ -47,7 +47,7 @@ final case class ExprBuilder[C <: Context, FS <: FutureSystem](override val c: C private def mkStateTree(nextState: Tree): c.Tree = Assign(Ident(name.state), nextState) - private def mkVarDefTree(resultType: Type, resultName: TermName): c.Tree = { + def mkVarDefTree(resultType: Type, resultName: TermName): c.Tree = { ValDef(Modifiers(Flag.MUTABLE), resultName, TypeTree(resultType), defaultValue(resultType)) } @@ -84,12 +84,6 @@ final case class ExprBuilder[C <: Context, FS <: FutureSystem](override val c: C } } - def varDefForResult: Option[c.Tree] = - None - - def allVarDefs: List[c.Tree] = - varDefForResult.toList ++ varDefs.map(p => mkVarDefTree(p._2, p._1)) - override val toString: String = s"AsyncState #$state, next = $nextState" } @@ -124,9 +118,6 @@ final case class ExprBuilder[C <: Context, FS <: FutureSystem](override val c: C assert(awaitable != null) mkHandlerCase(state, stats :+ mkOnCompleteTree) } - - override def varDefForResult: Option[c.Tree] = - Some(mkVarDefTree(resultType, resultName)) } /* @@ -150,8 +141,6 @@ final case class ExprBuilder[C <: Context, FS <: FutureSystem](override val c: C var nextState : Int = -1 var nextJumpState: Option[Int] = None - private val varDefs = ListBuffer[(TermName, Type)]() - private val renamer = new Transformer { override def transform(tree: Tree) = tree match { case Ident(_) if nameMap.keySet contains tree.symbol => @@ -177,7 +166,6 @@ final case class ExprBuilder[C <: Context, FS <: FutureSystem](override val c: C //TODO do not ignore `mods` def addVarDef(mods: Any, name: TermName, tpt: c.Tree, rhs: c.Tree): this.type = { - varDefs += (name -> tpt.tpe) this += Assign(Ident(name), rhs) this } @@ -185,15 +173,12 @@ final case class ExprBuilder[C <: Context, FS <: FutureSystem](override val c: C def result(): AsyncState = { val effectiveNestState = nextJumpState.getOrElse(nextState) if (awaitable == null) - new AsyncState(stats.toList, state, effectiveNestState) { - override val varDefs = self.varDefs.toList - } + new AsyncState(stats.toList, state, effectiveNestState) else new AsyncStateWithAwait(stats.toList, state, effectiveNestState) { val awaitable = self.awaitable val resultName = self.resultName val resultType = self.resultType - override val varDefs = self.varDefs.toList } } @@ -223,12 +208,9 @@ final case class ExprBuilder[C <: Context, FS <: FutureSystem](override val c: C // 1. build changed if-else tree // 2. insert that tree at the end of the current state val cond = resetDuplicate(renamer.transform(condTree)) - this += If(cond, - Block(mkStateTree(thenState), mkResumeApply), - Block(mkStateTree(elseState), mkResumeApply)) - new AsyncStateWithoutAwait(stats.toList, state) { - override val varDefs = self.varDefs.toList - } + def mkBranch(state: Int) = Block(mkStateTree(state), mkResumeApply) + this += If(cond, mkBranch(thenState), mkBranch(elseState)) + new AsyncStateWithoutAwait(stats.toList, state) } /** @@ -248,16 +230,12 @@ final case class ExprBuilder[C <: Context, FS <: FutureSystem](override val c: C } // 2. insert changed match tree at the end of the current state this += Match(resetDuplicate(scrutTree), newCases) - new AsyncStateWithoutAwait(stats.toList, state) { - override val varDefs = self.varDefs.toList - } + new AsyncStateWithoutAwait(stats.toList, state) } def resultWithLabel(startLabelState: Int): AsyncState = { this += Block(mkStateTree(startLabelState), mkResumeApply) - new AsyncStateWithoutAwait(stats.toList, state) { - override val varDefs = self.varDefs.toList - } + new AsyncStateWithoutAwait(stats.toList, state) } override def toString: String = { @@ -308,9 +286,7 @@ final case class ExprBuilder[C <: Context, FS <: FutureSystem](override val c: C case ValDef(mods, name, tpt, rhs) if toRename contains stat.symbol => checkForUnsupportedAwait(rhs) - - // when adding assignment need to take `toRename` into account - stateBuilder.addVarDef(mods, toRename(stat.symbol).toTermName, tpt, rhs) + stateBuilder += Assign(Ident(toRename(stat.symbol).toTermName), rhs) case If(cond, thenp, elsep) if stat exists isAwait => checkForUnsupportedAwait(cond) -- cgit v1.2.3 From 49c169e123dc5e90bcc5f23f63f1415c645603d5 Mon Sep 17 00:00:00 2001 From: Jason Zaugg Date: Sat, 24 Nov 2012 09:15:36 +0100 Subject: Centralize naming. --- src/main/scala/scala/async/AnfTransform.scala | 14 ++++++------- src/main/scala/scala/async/ExprBuilder.scala | 22 ++------------------- src/main/scala/scala/async/TransformUtils.scala | 26 ++++++++++++++++++++++++- 3 files changed, 33 insertions(+), 29 deletions(-) diff --git a/src/main/scala/scala/async/AnfTransform.scala b/src/main/scala/scala/async/AnfTransform.scala index 9241e7e..7d19da2 100644 --- a/src/main/scala/scala/async/AnfTransform.scala +++ b/src/main/scala/scala/async/AnfTransform.scala @@ -38,7 +38,7 @@ private[async] final case class AnfTransform[C <: Context](override val c: C) ex val trans = super.transform(defTree) val origName = defTree.symbol.name val sym = defTree.symbol.asInstanceOf[symtab.Symbol] - val fresh = c.fresh("" + sym.name + "$") + val fresh = name.fresh(sym.name.toString) sym.name = defTree.symbol.name match { case _: TermName => symtab.newTermName(fresh) case _: TypeName => symtab.newTypeName(fresh) @@ -92,7 +92,7 @@ private[async] final case class AnfTransform[C <: Context](override val c: C) ex val stats :+ expr = anf.transformToList(tree) expr match { case Apply(fun, args) if isAwait(fun) => - val valDef = defineVal("await", expr, tree.pos) + val valDef = defineVal(name.await, expr, tree.pos) stats :+ valDef :+ Ident(valDef.name) case If(cond, thenp, elsep) => @@ -101,7 +101,7 @@ private[async] final case class AnfTransform[C <: Context](override val c: C) ex if (expr.tpe =:= definitions.UnitTpe) { stats :+ expr :+ Literal(Constant(())) } else { - val varDef = defineVar("ifres", expr.tpe, tree.pos) + val varDef = defineVar(name.ifRes, expr.tpe, tree.pos) def branchWithAssign(orig: Tree) = orig match { case Block(thenStats, thenExpr) => Block(thenStats, Assign(Ident(varDef.name), thenExpr)) case _ => Assign(Ident(varDef.name), orig) @@ -117,7 +117,7 @@ private[async] final case class AnfTransform[C <: Context](override val c: C) ex stats :+ expr :+ Literal(Constant(())) } else { - val varDef = defineVar("matchres", expr.tpe, tree.pos) + val varDef = defineVar(name.matchRes, expr.tpe, tree.pos) val casesWithAssign = cases map { case cd@CaseDef(pat, guard, Block(caseStats, caseExpr)) => attachCopy.CaseDef(cd)(pat, guard, Block(caseStats, Assign(Ident(varDef.name), caseExpr))) @@ -141,16 +141,14 @@ private[async] final case class AnfTransform[C <: Context](override val c: C) ex case stats :+ expr => Block(stats, expr) } - def liftedName(prefix: String) = c.fresh(prefix + "$") - private def defineVar(prefix: String, tp: Type, pos: Position): ValDef = { - val vd = ValDef(Modifiers(Flag.MUTABLE), liftedName(prefix), TypeTree(tp), defaultValue(tp)) + val vd = ValDef(Modifiers(Flag.MUTABLE), name.fresh(prefix), TypeTree(tp), defaultValue(tp)) vd.setPos(pos) vd } private def defineVal(prefix: String, lhs: Tree, pos: Position): ValDef = { - val vd = ValDef(NoMods, liftedName(prefix), TypeTree(), lhs) + val vd = ValDef(NoMods, name.fresh(prefix), TypeTree(), lhs) vd.setPos(pos) vd } diff --git a/src/main/scala/scala/async/ExprBuilder.scala b/src/main/scala/scala/async/ExprBuilder.scala index d716163..3a8a649 100644 --- a/src/main/scala/scala/async/ExprBuilder.scala +++ b/src/main/scala/scala/async/ExprBuilder.scala @@ -10,32 +10,14 @@ import collection.mutable /* * @author Philipp Haller */ -final case class ExprBuilder[C <: Context, FS <: FutureSystem](override val c: C, val futureSystem: FS) +private[async] final case class ExprBuilder[C <: Context, FS <: FutureSystem](override val c: C, val futureSystem: FS) extends TransformUtils(c) { builder => import c.universe._ import defn._ - private[async] object name { - def suffix(string: String) = string + "$async" - - def suffixedName(prefix: String) = newTermName(suffix(prefix)) - - 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") - - def fresh(name: TermName) = if (name.toString.contains("$")) name else newTermName(c.fresh("" + name + "$")) - } - - private[async] lazy val futureSystemOps = futureSystem.mkOps(c) + lazy val futureSystemOps = futureSystem.mkOps(c) private def resetDuplicate(tree: Tree) = c.resetAllAttrs(tree.duplicate) diff --git a/src/main/scala/scala/async/TransformUtils.scala b/src/main/scala/scala/async/TransformUtils.scala index 5095875..9a8814f 100644 --- a/src/main/scala/scala/async/TransformUtils.scala +++ b/src/main/scala/scala/async/TransformUtils.scala @@ -9,10 +9,34 @@ import reflect.ClassTag /** * Utilities used in both `ExprBuilder` and `AnfTransform`. */ -class TransformUtils[C <: Context](val c: C) { +private[async] class TransformUtils[C <: Context](val c: C) { import c.universe._ + private[async] object name { + def suffix(string: String) = string + "$async" + + def suffixedName(prefix: String) = newTermName(suffix(prefix)) + + 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") + + val matchRes = "matchres" + val ifRes = "ifres" + val await = "await" + + def fresh(name: TermName): TermName = newTermName(fresh(name.toString)) + + def fresh(name: String): String = if (name.toString.contains("$")) name else c.fresh("" + name + "$") + } + protected def defaultValue(tpe: Type): Literal = { val defaultValue: Any = if (tpe <:< definitions.BooleanTpe) false -- cgit v1.2.3 From a3ea46c3da6596013d8e6ea8b84839c59e92e781 Mon Sep 17 00:00:00 2001 From: Jason Zaugg Date: Sat, 24 Nov 2012 09:49:48 +0100 Subject: Refine tracing of ANF. - show the AST on one line. --- src/main/scala/scala/async/AnfTransform.scala | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/main/scala/scala/async/AnfTransform.scala b/src/main/scala/scala/async/AnfTransform.scala index 7d19da2..b45c5bb 100644 --- a/src/main/scala/scala/async/AnfTransform.scala +++ b/src/main/scala/scala/async/AnfTransform.scala @@ -76,10 +76,11 @@ private[async] final case class AnfTransform[C <: Context](override val c: C) ex def indentString = " " * indent def apply[T](prefix: String, args: Any)(t: => T): T = { indent += 1 + def oneLine(s: Any) = s.toString.replaceAll("""\n""", "\\\\n").take(127) try { - AsyncUtils.trace(s"${indentString}$prefix($args)") + AsyncUtils.trace(s"${indentString}$prefix(${oneLine(args)})") val result = t - AsyncUtils.trace(s"${indentString}= $result") + AsyncUtils.trace(s"${indentString}= ${oneLine(result)}") result } finally { indent -= 1 -- cgit v1.2.3 From ad75c04daa8521b066c755f2b3cf0b130441adbe Mon Sep 17 00:00:00 2001 From: Jason Zaugg Date: Sat, 24 Nov 2012 10:27:16 +0100 Subject: add missing copyright --- src/main/scala/scala/async/AnfTransform.scala | 4 ++++ src/main/scala/scala/async/Async.scala | 3 ++- src/main/scala/scala/async/AsyncAnalysis.scala | 4 ++++ src/main/scala/scala/async/AsyncUtils.scala | 2 +- src/main/scala/scala/async/ExprBuilder.scala | 2 +- src/main/scala/scala/async/FutureSystem.scala | 2 +- src/main/scala/scala/async/StateAssigner.scala | 4 ++++ src/main/scala/scala/async/TransformUtils.scala | 2 +- src/test/scala/scala/async/TestLatch.scala | 4 ++++ src/test/scala/scala/async/TreeInterrogation.scala | 4 ++++ src/test/scala/scala/async/neg/LocalClasses0Spec.scala | 4 ++++ src/test/scala/scala/async/neg/NakedAwait.scala | 4 ++++ src/test/scala/scala/async/neg/SampleNegSpec.scala | 4 ++++ src/test/scala/scala/async/package.scala | 4 ++++ src/test/scala/scala/async/run/anf/AnfTransformSpec.scala | 2 +- src/test/scala/scala/async/run/await0/Await0Spec.scala | 4 ++++ src/test/scala/scala/async/run/block0/AsyncSpec.scala | 2 +- src/test/scala/scala/async/run/block1/block1.scala | 2 +- src/test/scala/scala/async/run/hygiene/Hygiene.scala | 2 +- src/test/scala/scala/async/run/ifelse0/IfElse0.scala | 2 +- src/test/scala/scala/async/run/ifelse0/WhileSpec.scala | 4 ++++ src/test/scala/scala/async/run/ifelse1/IfElse1.scala | 2 +- src/test/scala/scala/async/run/ifelse2/ifelse2.scala | 2 +- src/test/scala/scala/async/run/ifelse3/IfElse3.scala | 2 +- src/test/scala/scala/async/run/match0/Match0.scala | 2 +- src/test/scala/scala/async/run/noawait/NoAwaitSpec.scala | 4 ++++ src/test/scala/scala/async/run/toughtype/ToughType.scala | 2 +- 27 files changed, 64 insertions(+), 15 deletions(-) diff --git a/src/main/scala/scala/async/AnfTransform.scala b/src/main/scala/scala/async/AnfTransform.scala index b45c5bb..12b3f26 100644 --- a/src/main/scala/scala/async/AnfTransform.scala +++ b/src/main/scala/scala/async/AnfTransform.scala @@ -1,4 +1,8 @@ +/* + * Copyright (C) 2012 Typesafe Inc. + */ + package scala.async import scala.reflect.macros.Context diff --git a/src/main/scala/scala/async/Async.scala b/src/main/scala/scala/async/Async.scala index 7d9ddf5..0a8ea1b 100644 --- a/src/main/scala/scala/async/Async.scala +++ b/src/main/scala/scala/async/Async.scala @@ -1,6 +1,7 @@ -/** +/* * Copyright (C) 2012 Typesafe Inc. */ + package scala.async import scala.language.experimental.macros diff --git a/src/main/scala/scala/async/AsyncAnalysis.scala b/src/main/scala/scala/async/AsyncAnalysis.scala index c1ee7f4..fa6ab58 100644 --- a/src/main/scala/scala/async/AsyncAnalysis.scala +++ b/src/main/scala/scala/async/AsyncAnalysis.scala @@ -1,3 +1,7 @@ +/* + * Copyright (C) 2012 Typesafe Inc. + */ + package scala.async import scala.reflect.macros.Context diff --git a/src/main/scala/scala/async/AsyncUtils.scala b/src/main/scala/scala/async/AsyncUtils.scala index 525f187..87a63d7 100644 --- a/src/main/scala/scala/async/AsyncUtils.scala +++ b/src/main/scala/scala/async/AsyncUtils.scala @@ -1,4 +1,4 @@ -/** +/* * Copyright (C) 2012 Typesafe Inc. */ package scala.async diff --git a/src/main/scala/scala/async/ExprBuilder.scala b/src/main/scala/scala/async/ExprBuilder.scala index 3a8a649..0a2a09d 100644 --- a/src/main/scala/scala/async/ExprBuilder.scala +++ b/src/main/scala/scala/async/ExprBuilder.scala @@ -1,4 +1,4 @@ -/** +/* * Copyright (C) 2012 Typesafe Inc. */ package scala.async diff --git a/src/main/scala/scala/async/FutureSystem.scala b/src/main/scala/scala/async/FutureSystem.scala index ed967cb..20bbea3 100644 --- a/src/main/scala/scala/async/FutureSystem.scala +++ b/src/main/scala/scala/async/FutureSystem.scala @@ -1,4 +1,4 @@ -/** +/* * Copyright (C) 2012 Typesafe Inc. */ package scala.async diff --git a/src/main/scala/scala/async/StateAssigner.scala b/src/main/scala/scala/async/StateAssigner.scala index 4f6c5a0..bc60a6d 100644 --- a/src/main/scala/scala/async/StateAssigner.scala +++ b/src/main/scala/scala/async/StateAssigner.scala @@ -1,3 +1,7 @@ +/* + * Copyright (C) 2012 Typesafe Inc. + */ + package scala.async private[async] final class StateAssigner { diff --git a/src/main/scala/scala/async/TransformUtils.scala b/src/main/scala/scala/async/TransformUtils.scala index 9a8814f..0eca5db 100644 --- a/src/main/scala/scala/async/TransformUtils.scala +++ b/src/main/scala/scala/async/TransformUtils.scala @@ -1,4 +1,4 @@ -/** +/* * Copyright (C) 2012 Typesafe Inc. */ package scala.async diff --git a/src/test/scala/scala/async/TestLatch.scala b/src/test/scala/scala/async/TestLatch.scala index 676ea63..a119a43 100644 --- a/src/test/scala/scala/async/TestLatch.scala +++ b/src/test/scala/scala/async/TestLatch.scala @@ -1,3 +1,7 @@ +/* + * Copyright (C) 2012 Typesafe Inc. + */ + package scala.async import concurrent.{CanAwait, Awaitable} diff --git a/src/test/scala/scala/async/TreeInterrogation.scala b/src/test/scala/scala/async/TreeInterrogation.scala index 485831b..dd239a3 100644 --- a/src/test/scala/scala/async/TreeInterrogation.scala +++ b/src/test/scala/scala/async/TreeInterrogation.scala @@ -1,3 +1,7 @@ +/* + * Copyright (C) 2012 Typesafe Inc. + */ + package scala.async import org.junit.runner.RunWith diff --git a/src/test/scala/scala/async/neg/LocalClasses0Spec.scala b/src/test/scala/scala/async/neg/LocalClasses0Spec.scala index 7932744..06a0e71 100644 --- a/src/test/scala/scala/async/neg/LocalClasses0Spec.scala +++ b/src/test/scala/scala/async/neg/LocalClasses0Spec.scala @@ -1,3 +1,7 @@ +/* + * Copyright (C) 2012 Typesafe Inc. + */ + package scala.async package neg diff --git a/src/test/scala/scala/async/neg/NakedAwait.scala b/src/test/scala/scala/async/neg/NakedAwait.scala index b1c2d43..f4cfca2 100644 --- a/src/test/scala/scala/async/neg/NakedAwait.scala +++ b/src/test/scala/scala/async/neg/NakedAwait.scala @@ -1,3 +1,7 @@ +/* + * Copyright (C) 2012 Typesafe Inc. + */ + package scala.async package neg diff --git a/src/test/scala/scala/async/neg/SampleNegSpec.scala b/src/test/scala/scala/async/neg/SampleNegSpec.scala index 94dbc1d..76f9c3e 100644 --- a/src/test/scala/scala/async/neg/SampleNegSpec.scala +++ b/src/test/scala/scala/async/neg/SampleNegSpec.scala @@ -1,3 +1,7 @@ +/* + * Copyright (C) 2012 Typesafe Inc. + */ + package scala.async package neg diff --git a/src/test/scala/scala/async/package.scala b/src/test/scala/scala/async/package.scala index 868024d..bc4ebac 100644 --- a/src/test/scala/scala/async/package.scala +++ b/src/test/scala/scala/async/package.scala @@ -1,3 +1,7 @@ +/* + * Copyright (C) 2012 Typesafe Inc. + */ + package scala import reflect._ diff --git a/src/test/scala/scala/async/run/anf/AnfTransformSpec.scala b/src/test/scala/scala/async/run/anf/AnfTransformSpec.scala index 8bdb80f..41eeaa5 100644 --- a/src/test/scala/scala/async/run/anf/AnfTransformSpec.scala +++ b/src/test/scala/scala/async/run/anf/AnfTransformSpec.scala @@ -1,4 +1,4 @@ -/** +/* * Copyright (C) 2012 Typesafe Inc. */ diff --git a/src/test/scala/scala/async/run/await0/Await0Spec.scala b/src/test/scala/scala/async/run/await0/Await0Spec.scala index 42d4ef2..111602a 100644 --- a/src/test/scala/scala/async/run/await0/Await0Spec.scala +++ b/src/test/scala/scala/async/run/await0/Await0Spec.scala @@ -1,3 +1,7 @@ +/* + * Copyright (C) 2012 Typesafe Inc. + */ + package scala.async package run package await0 diff --git a/src/test/scala/scala/async/run/block0/AsyncSpec.scala b/src/test/scala/scala/async/run/block0/AsyncSpec.scala index 5a7247c..5f38086 100644 --- a/src/test/scala/scala/async/run/block0/AsyncSpec.scala +++ b/src/test/scala/scala/async/run/block0/AsyncSpec.scala @@ -1,4 +1,4 @@ -/** +/* * Copyright (C) 2012 Typesafe Inc. */ diff --git a/src/test/scala/scala/async/run/block1/block1.scala b/src/test/scala/scala/async/run/block1/block1.scala index 0853498..bf9b56f 100644 --- a/src/test/scala/scala/async/run/block1/block1.scala +++ b/src/test/scala/scala/async/run/block1/block1.scala @@ -1,4 +1,4 @@ -/** +/* * Copyright (C) 2012 Typesafe Inc. */ diff --git a/src/test/scala/scala/async/run/hygiene/Hygiene.scala b/src/test/scala/scala/async/run/hygiene/Hygiene.scala index 0cc68a4..d0be2e0 100644 --- a/src/test/scala/scala/async/run/hygiene/Hygiene.scala +++ b/src/test/scala/scala/async/run/hygiene/Hygiene.scala @@ -1,4 +1,4 @@ -/** +/* * Copyright (C) 2012 Typesafe Inc. */ diff --git a/src/test/scala/scala/async/run/ifelse0/IfElse0.scala b/src/test/scala/scala/async/run/ifelse0/IfElse0.scala index 0363a75..0a72f1e 100644 --- a/src/test/scala/scala/async/run/ifelse0/IfElse0.scala +++ b/src/test/scala/scala/async/run/ifelse0/IfElse0.scala @@ -1,4 +1,4 @@ -/** +/* * Copyright (C) 2012 Typesafe Inc. */ diff --git a/src/test/scala/scala/async/run/ifelse0/WhileSpec.scala b/src/test/scala/scala/async/run/ifelse0/WhileSpec.scala index ba1d7a5..1f1033a 100644 --- a/src/test/scala/scala/async/run/ifelse0/WhileSpec.scala +++ b/src/test/scala/scala/async/run/ifelse0/WhileSpec.scala @@ -1,3 +1,7 @@ +/* + * Copyright (C) 2012 Typesafe Inc. + */ + package scala.async package run package ifelse0 diff --git a/src/test/scala/scala/async/run/ifelse1/IfElse1.scala b/src/test/scala/scala/async/run/ifelse1/IfElse1.scala index 3ca3a94..b567ee6 100644 --- a/src/test/scala/scala/async/run/ifelse1/IfElse1.scala +++ b/src/test/scala/scala/async/run/ifelse1/IfElse1.scala @@ -1,4 +1,4 @@ -/** +/* * Copyright (C) 2012 Typesafe Inc. */ diff --git a/src/test/scala/scala/async/run/ifelse2/ifelse2.scala b/src/test/scala/scala/async/run/ifelse2/ifelse2.scala index 84974b6..92a76e4 100644 --- a/src/test/scala/scala/async/run/ifelse2/ifelse2.scala +++ b/src/test/scala/scala/async/run/ifelse2/ifelse2.scala @@ -1,4 +1,4 @@ -/** +/* * Copyright (C) 2012 Typesafe Inc. */ diff --git a/src/test/scala/scala/async/run/ifelse3/IfElse3.scala b/src/test/scala/scala/async/run/ifelse3/IfElse3.scala index d475a0c..8a2ab13 100644 --- a/src/test/scala/scala/async/run/ifelse3/IfElse3.scala +++ b/src/test/scala/scala/async/run/ifelse3/IfElse3.scala @@ -1,4 +1,4 @@ -/** +/* * Copyright (C) 2012 Typesafe Inc. */ diff --git a/src/test/scala/scala/async/run/match0/Match0.scala b/src/test/scala/scala/async/run/match0/Match0.scala index 6a17e2b..f550a69 100644 --- a/src/test/scala/scala/async/run/match0/Match0.scala +++ b/src/test/scala/scala/async/run/match0/Match0.scala @@ -1,4 +1,4 @@ -/** +/* * Copyright (C) 2012 Typesafe Inc. */ diff --git a/src/test/scala/scala/async/run/noawait/NoAwaitSpec.scala b/src/test/scala/scala/async/run/noawait/NoAwaitSpec.scala index 90be946..e2c69d0 100644 --- a/src/test/scala/scala/async/run/noawait/NoAwaitSpec.scala +++ b/src/test/scala/scala/async/run/noawait/NoAwaitSpec.scala @@ -1,3 +1,7 @@ +/* + * Copyright (C) 2012 Typesafe Inc. + */ + package scala.async package run package noawait diff --git a/src/test/scala/scala/async/run/toughtype/ToughType.scala b/src/test/scala/scala/async/run/toughtype/ToughType.scala index f576ddc..9cfc1ca 100644 --- a/src/test/scala/scala/async/run/toughtype/ToughType.scala +++ b/src/test/scala/scala/async/run/toughtype/ToughType.scala @@ -1,4 +1,4 @@ -/** +/* * Copyright (C) 2012 Typesafe Inc. */ -- cgit v1.2.3 From d301bea1ca478652ae86397ee3655bc6e4332589 Mon Sep 17 00:00:00 2001 From: Jason Zaugg Date: Sat, 24 Nov 2012 12:26:32 +0100 Subject: Favouring composition over inheritance. --- src/main/scala/scala/async/AnfTransform.scala | 4 +++- src/main/scala/scala/async/Async.scala | 11 +++++------ src/main/scala/scala/async/AsyncAnalysis.scala | 9 ++++++--- src/main/scala/scala/async/ExprBuilder.scala | 24 ++++++++++-------------- src/main/scala/scala/async/TransformUtils.scala | 14 +++++++------- 5 files changed, 31 insertions(+), 31 deletions(-) diff --git a/src/main/scala/scala/async/AnfTransform.scala b/src/main/scala/scala/async/AnfTransform.scala index 12b3f26..0836634 100644 --- a/src/main/scala/scala/async/AnfTransform.scala +++ b/src/main/scala/scala/async/AnfTransform.scala @@ -7,9 +7,11 @@ package scala.async import scala.reflect.macros.Context -private[async] final case class AnfTransform[C <: Context](override val c: C) extends TransformUtils(c) { +private[async] final case class AnfTransform[C <: Context](val c: C) { import c.universe._ + val utils = TransformUtils[c.type](c) + import utils._ def apply(tree: Tree): List[Tree] = { val unique = uniqueNames(tree) diff --git a/src/main/scala/scala/async/Async.scala b/src/main/scala/scala/async/Async.scala index 0a8ea1b..8c956f1 100644 --- a/src/main/scala/scala/async/Async.scala +++ b/src/main/scala/scala/async/Async.scala @@ -68,9 +68,8 @@ abstract class AsyncBase { val builder = ExprBuilder[c.type, futureSystem.type](c, self.futureSystem) val anaylzer = AsyncAnalysis[c.type](c) - - import builder.defn._ - import builder.name + val utils = TransformUtils[c.type](c) + import utils.{name, defn} import builder.futureSystemOps anaylzer.reportUnsupportedAwaits(body.tree) @@ -91,7 +90,7 @@ abstract class AsyncBase { val renameMap: Map[Symbol, TermName] = { anaylzer.valDefsUsedInSubsequentStates(anfTree).map { vd => - (vd.symbol, builder.name.fresh(vd.name)) + (vd.symbol, name.fresh(vd.name)) }.toMap } @@ -121,7 +120,7 @@ abstract class AsyncBase { val onCompleteHandler = { val onCompleteHandlers = initStates.flatMap(_.mkOnCompleteHandler()).toList Function( - List(ValDef(Modifiers(PARAM), name.tr, TypeTree(TryAnyType), EmptyTree)), + List(ValDef(Modifiers(PARAM), name.tr, TypeTree(defn.TryAnyType), EmptyTree)), Match(Ident(name.state), onCompleteHandlers)) } @@ -145,7 +144,7 @@ abstract class AsyncBase { Match(Ident(name.state), handlerCases), List( CaseDef( - Apply(Ident(NonFatalClass), List(Bind(name.tr, Ident(nme.WILDCARD)))), + Apply(Ident(defn.NonFatalClass), List(Bind(name.tr, Ident(nme.WILDCARD)))), EmptyTree, Block(List({ val t = c.Expr[Throwable](Ident(name.tr)) diff --git a/src/main/scala/scala/async/AsyncAnalysis.scala b/src/main/scala/scala/async/AsyncAnalysis.scala index fa6ab58..4f5bf8d 100644 --- a/src/main/scala/scala/async/AsyncAnalysis.scala +++ b/src/main/scala/scala/async/AsyncAnalysis.scala @@ -7,9 +7,12 @@ package scala.async import scala.reflect.macros.Context import collection.mutable -private[async] final case class AsyncAnalysis[C <: Context](override val c: C) extends TransformUtils(c) { +private[async] final case class AsyncAnalysis[C <: Context](val c: C) { import c.universe._ + val utils = TransformUtils[c.type](c) + import utils._ + /** * Analyze the contents of an `async` block in order to: * - Report unsupported `await` calls under nested templates, functions, by-name arguments. @@ -33,7 +36,7 @@ private[async] final case class AsyncAnalysis[C <: Context](override val c: C) e analyzer.valDefsToLift.toList } - private class UnsupportedAwaitAnalyzer extends super.AsyncTraverser { + private class UnsupportedAwaitAnalyzer extends AsyncTraverser { override def nestedClass(classDef: ClassDef) { val kind = if (classDef.symbol.asClass.isTrait) "trait" else "class" if (!reportUnsupportedAwait(classDef, s"nested $kind")) { @@ -92,7 +95,7 @@ private[async] final case class AsyncAnalysis[C <: Context](override val c: C) e } } - private class AsyncDefinitionUseAnalyzer extends super.AsyncTraverser { + private class AsyncDefinitionUseAnalyzer extends AsyncTraverser { private var chunkId = 0 private def nextChunk() = chunkId += 1 diff --git a/src/main/scala/scala/async/ExprBuilder.scala b/src/main/scala/scala/async/ExprBuilder.scala index 0a2a09d..a3ba53e 100644 --- a/src/main/scala/scala/async/ExprBuilder.scala +++ b/src/main/scala/scala/async/ExprBuilder.scala @@ -10,11 +10,13 @@ import collection.mutable /* * @author Philipp Haller */ -private[async] final case class ExprBuilder[C <: Context, FS <: FutureSystem](override val c: C, val futureSystem: FS) - extends TransformUtils(c) { +private[async] final case class ExprBuilder[C <: Context, FS <: FutureSystem](val c: C, val futureSystem: FS) { builder => + val utils = TransformUtils[c.type](c) + import c.universe._ + import utils._ import defn._ lazy val futureSystemOps = futureSystem.mkOps(c) @@ -81,17 +83,12 @@ private[async] final case class ExprBuilder[C <: Context, FS <: FutureSystem](ov s"AsyncStateWithIf #$state, next = $nextState" } - abstract class AsyncStateWithAwait(stats: List[c.Tree], state: Int, nextState: Int) + final class AsyncStateWithAwait(stats: List[c.Tree], state: Int, nextState: Int, + awaitable: c.Tree, val resultName: TermName, val resultType: Type) extends AsyncState(stats, state, nextState) { - val awaitable : c.Tree - val resultName: TermName - val resultType: Type protected def tryType = appliedType(TryClass.toType, List(resultType)) - override val toString: String = - s"AsyncStateWithAwait #$state, next = $nextState" - private def mkOnCompleteTree: c.Tree = { futureSystemOps.onComplete(c.Expr(awaitable), c.Expr(Ident(name.onCompleteHandler)), c.Expr(Ident(name.execContext))).tree } @@ -100,6 +97,9 @@ private[async] final case class ExprBuilder[C <: Context, FS <: FutureSystem](ov assert(awaitable != null) mkHandlerCase(state, stats :+ mkOnCompleteTree) } + + override val toString: String = + s"AsyncStateWithAwait #$state, next = $nextState" } /* @@ -157,11 +157,7 @@ private[async] final case class ExprBuilder[C <: Context, FS <: FutureSystem](ov if (awaitable == null) new AsyncState(stats.toList, state, effectiveNestState) else - new AsyncStateWithAwait(stats.toList, state, effectiveNestState) { - val awaitable = self.awaitable - val resultName = self.resultName - val resultType = self.resultType - } + new AsyncStateWithAwait(stats.toList, state, effectiveNestState, awaitable, resultName, resultType) } /* Result needs to be created as a var at the beginning of the transformed method body, so that diff --git a/src/main/scala/scala/async/TransformUtils.scala b/src/main/scala/scala/async/TransformUtils.scala index 0eca5db..b7bb2ce 100644 --- a/src/main/scala/scala/async/TransformUtils.scala +++ b/src/main/scala/scala/async/TransformUtils.scala @@ -9,11 +9,11 @@ import reflect.ClassTag /** * Utilities used in both `ExprBuilder` and `AnfTransform`. */ -private[async] class TransformUtils[C <: Context](val c: C) { +private[async] final case class TransformUtils[C <: Context](val c: C) { import c.universe._ - private[async] object name { + object name { def suffix(string: String) = string + "$async" def suffixedName(prefix: String) = newTermName(suffix(prefix)) @@ -37,7 +37,7 @@ private[async] class TransformUtils[C <: Context](val c: C) { def fresh(name: String): String = if (name.toString.contains("$")) name else c.fresh("" + name + "$") } - protected def defaultValue(tpe: Type): Literal = { + def defaultValue(tpe: Type): Literal = { val defaultValue: Any = if (tpe <:< definitions.BooleanTpe) false else if (definitions.ScalaNumericValueClasses.exists(tpe <:< _.toType)) 0 @@ -46,7 +46,7 @@ private[async] class TransformUtils[C <: Context](val c: C) { Literal(Constant(defaultValue)) } - protected def isAwait(fun: Tree) = + def isAwait(fun: Tree) = fun.symbol == defn.Async_await /** Descends into the regions of the tree that are subject to the @@ -96,7 +96,7 @@ private[async] class TransformUtils[C <: Context](val c: C) { Set(Boolean_&&, Boolean_||) } - protected def isByName(fun: Tree): (Int => Boolean) = { + def isByName(fun: Tree): (Int => Boolean) = { if (Boolean_ShortCircuits contains fun.symbol) i => true else fun.tpe match { case MethodType(params, _) => @@ -106,12 +106,12 @@ private[async] class TransformUtils[C <: Context](val c: C) { } } - protected def statsAndExpr(tree: Tree): (List[Tree], Tree) = tree match { + def statsAndExpr(tree: Tree): (List[Tree], Tree) = tree match { case Block(stats, expr) => (stats, expr) case _ => (List(tree), Literal(Constant(()))) } - private[async] object defn { + object defn { def mkList_apply[A](args: List[Expr[A]]): Expr[List[A]] = { c.Expr(Apply(Ident(definitions.List_apply), args.map(_.tree))) } -- cgit v1.2.3 From dac1d9497a1521be937afa8f09dbdee2f2280f8b Mon Sep 17 00:00:00 2001 From: Jason Zaugg Date: Sat, 24 Nov 2012 12:38:37 +0100 Subject: Refactor name substitution out of ExprBuilder. --- src/main/scala/scala/async/ExprBuilder.scala | 16 ++++------------ src/main/scala/scala/async/TransformUtils.scala | 13 +++++++++++++ 2 files changed, 17 insertions(+), 12 deletions(-) diff --git a/src/main/scala/scala/async/ExprBuilder.scala b/src/main/scala/scala/async/ExprBuilder.scala index a3ba53e..415c47d 100644 --- a/src/main/scala/scala/async/ExprBuilder.scala +++ b/src/main/scala/scala/async/ExprBuilder.scala @@ -123,18 +123,11 @@ private[async] final case class ExprBuilder[C <: Context, FS <: FutureSystem](va var nextState : Int = -1 var nextJumpState: Option[Int] = None - private val renamer = new Transformer { - override def transform(tree: Tree) = tree match { - case Ident(_) if nameMap.keySet contains tree.symbol => - Ident(nameMap(tree.symbol)) - case _ => - super.transform(tree) - } - } + def rename(tree: Tree) = substituteNames(tree, nameMap) def +=(stat: c.Tree): this.type = { assert(nextJumpState.isEmpty, s"statement appeared after a label jump: $stat") - def addStat() = stats += resetDuplicate(renamer.transform(stat)) + def addStat() = stats += resetDuplicate(rename(stat)) stat match { case Apply(fun, Nil) => labelDefStates get fun.symbol match { @@ -169,8 +162,7 @@ private[async] final case class ExprBuilder[C <: Context, FS <: FutureSystem](va */ def complete(awaitArg: c.Tree, awaitResultName: TermName, awaitResultType: Tree, nextState: Int): this.type = { - val renamed = renamer.transform(awaitArg) - awaitable = resetDuplicate(renamed) + awaitable = resetDuplicate(rename(awaitArg)) resultName = awaitResultName resultType = awaitResultType.tpe this.nextState = nextState @@ -185,7 +177,7 @@ private[async] final case class ExprBuilder[C <: Context, FS <: FutureSystem](va 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 = resetDuplicate(renamer.transform(condTree)) + val cond = resetDuplicate(rename(condTree)) def mkBranch(state: Int) = Block(mkStateTree(state), mkResumeApply) this += If(cond, mkBranch(thenState), mkBranch(elseState)) new AsyncStateWithoutAwait(stats.toList, state) diff --git a/src/main/scala/scala/async/TransformUtils.scala b/src/main/scala/scala/async/TransformUtils.scala index b7bb2ce..03709ab 100644 --- a/src/main/scala/scala/async/TransformUtils.scala +++ b/src/main/scala/scala/async/TransformUtils.scala @@ -49,6 +49,19 @@ private[async] final case class TransformUtils[C <: Context](val c: C) { def isAwait(fun: Tree) = fun.symbol == defn.Async_await + /** Replace all `Ident` nodes referring to one of the keys n `renameMap` with a node + * referring to the corresponding new name + */ + def substituteNames(tree: Tree, renameMap: Map[Symbol, Name]): Tree = { + val renamer = new Transformer { + override def transform(tree: Tree) = tree match { + case Ident(_) => (renameMap get tree.symbol).fold(tree)(Ident(_)) + case _ => super.transform(tree) + } + } + renamer.transform(tree) + } + /** Descends into the regions of the tree that are subject to the * translation to a state machine by `async`. When a nested template, * function, or by-name argument is encountered, the descend stops, -- cgit v1.2.3 From 32c5e0d67abf333daa91b0e95b7b38393f2bd37c Mon Sep 17 00:00:00 2001 From: Jason Zaugg Date: Sat, 24 Nov 2012 12:46:26 +0100 Subject: More small refactorings. Goal is to make ExprBuilder less cluttered. --- src/main/scala/scala/async/Async.scala | 2 +- src/main/scala/scala/async/ExprBuilder.scala | 35 +++++++------------------ src/main/scala/scala/async/TransformUtils.scala | 4 +++ 3 files changed, 14 insertions(+), 27 deletions(-) diff --git a/src/main/scala/scala/async/Async.scala b/src/main/scala/scala/async/Async.scala index 8c956f1..473b948 100644 --- a/src/main/scala/scala/async/Async.scala +++ b/src/main/scala/scala/async/Async.scala @@ -105,7 +105,7 @@ abstract class AsyncBase { val initStates = asyncStates.init val localVarTrees = anfTree.collect { case vd@ValDef(_, _, tpt, _) if renameMap contains vd.symbol => - builder.mkVarDefTree(tpt.tpe, renameMap(vd.symbol)) + utils.mkVarDefTree(tpt.tpe, renameMap(vd.symbol)) } /* diff --git a/src/main/scala/scala/async/ExprBuilder.scala b/src/main/scala/scala/async/ExprBuilder.scala index 415c47d..298d5e7 100644 --- a/src/main/scala/scala/async/ExprBuilder.scala +++ b/src/main/scala/scala/async/ExprBuilder.scala @@ -26,21 +26,16 @@ private[async] final case class ExprBuilder[C <: Context, FS <: FutureSystem](va private def mkResumeApply = Apply(Ident(name.resume), Nil) private def mkStateTree(nextState: Int): c.Tree = - mkStateTree(c.literal(nextState).tree) - - private def mkStateTree(nextState: Tree): c.Tree = - Assign(Ident(name.state), nextState) - - def mkVarDefTree(resultType: Type, resultName: TermName): c.Tree = { - ValDef(Modifiers(Flag.MUTABLE), resultName, TypeTree(resultType), defaultValue(resultType)) - } + Assign(Ident(name.state), c.literal(nextState).tree) private def mkHandlerCase(num: Int, rhs: List[c.Tree]): CaseDef = mkHandlerCase(num, Block(rhs: _*)) - private def mkHandlerCase(num: Int, rhs: c.Tree): CaseDef = { + private def mkHandlerCase(num: Int, rhs: c.Tree): CaseDef = CaseDef(c.literal(num).tree, EmptyTree, rhs) - } + + val stateAssigner = new StateAssigner + val labelDefStates = collection.mutable.Map[Symbol, Int]() class AsyncState(stats: List[c.Tree], val state: Int, val nextState: Int) { val body: c.Tree = stats match { @@ -48,8 +43,6 @@ private[async] final case class ExprBuilder[C <: Context, FS <: FutureSystem](va case _ => Block(stats: _*) } - val varDefs: List[(TermName, Type)] = Nil - def mkHandlerCaseForState(): CaseDef = mkHandlerCase(state, stats :+ mkStateTree(nextState) :+ mkResumeApply) @@ -139,18 +132,12 @@ private[async] final case class ExprBuilder[C <: Context, FS <: FutureSystem](va this } - //TODO do not ignore `mods` - def addVarDef(mods: Any, name: TermName, tpt: c.Tree, rhs: c.Tree): this.type = { - this += Assign(Ident(name), rhs) - this - } - def result(): AsyncState = { - val effectiveNestState = nextJumpState.getOrElse(nextState) + val effectiveNextState = nextJumpState.getOrElse(nextState) if (awaitable == null) - new AsyncState(stats.toList, state, effectiveNestState) + new AsyncState(stats.toList, state, effectiveNextState) else - new AsyncStateWithAwait(stats.toList, state, effectiveNestState, awaitable, resultName, resultType) + new AsyncStateWithAwait(stats.toList, state, effectiveNextState, awaitable, resultName, resultType) } /* Result needs to be created as a var at the beginning of the transformed method body, so that @@ -214,10 +201,6 @@ private[async] final case class ExprBuilder[C <: Context, FS <: FutureSystem](va } } - val stateAssigner = new StateAssigner - - val labelDefStates = collection.mutable.Map[Symbol, Int]() - /** * An `AsyncBlockBuilder` builds a `ListBuffer[AsyncState]` based on the expressions of a `Block(stats, expr)` (see `Async.asyncImpl`). * @@ -227,7 +210,7 @@ private[async] final case class ExprBuilder[C <: Context, FS <: FutureSystem](va * @param endState the state to continue with * @param toRename a `Map` for renaming the given key symbols to the mangled value names */ - class AsyncBlockBuilder(stats: List[c.Tree], expr: c.Tree, startState: Int, endState: Int, + final class AsyncBlockBuilder(stats: List[c.Tree], expr: c.Tree, startState: Int, endState: Int, private val toRename: Map[Symbol, c.Name]) { val asyncStates = ListBuffer[builder.AsyncState]() diff --git a/src/main/scala/scala/async/TransformUtils.scala b/src/main/scala/scala/async/TransformUtils.scala index 03709ab..22099b5 100644 --- a/src/main/scala/scala/async/TransformUtils.scala +++ b/src/main/scala/scala/async/TransformUtils.scala @@ -124,6 +124,10 @@ private[async] final case class TransformUtils[C <: Context](val c: C) { case _ => (List(tree), Literal(Constant(()))) } + def mkVarDefTree(resultType: Type, resultName: TermName): c.Tree = { + ValDef(Modifiers(Flag.MUTABLE), resultName, TypeTree(resultType), defaultValue(resultType)) + } + object defn { def mkList_apply[A](args: List[Expr[A]]): Expr[List[A]] = { c.Expr(Apply(Ident(definitions.List_apply), args.map(_.tree))) -- cgit v1.2.3 From 1eb4a1c81d62a752183edfe1b099a306350f84a5 Mon Sep 17 00:00:00 2001 From: Jason Zaugg Date: Sat, 24 Nov 2012 13:04:43 +0100 Subject: Address a little duplication in ExprBuilder. --- src/main/scala/scala/async/ExprBuilder.scala | 39 ++++++++++++++-------------- 1 file changed, 19 insertions(+), 20 deletions(-) diff --git a/src/main/scala/scala/async/ExprBuilder.scala b/src/main/scala/scala/async/ExprBuilder.scala index 298d5e7..e5bb794 100644 --- a/src/main/scala/scala/async/ExprBuilder.scala +++ b/src/main/scala/scala/async/ExprBuilder.scala @@ -65,7 +65,7 @@ private[async] final case class ExprBuilder[C <: Context, FS <: FutureSystem](va s"AsyncState #$state, next = $nextState" } - class AsyncStateWithoutAwait(stats: List[c.Tree], state: Int) + final class AsyncStateWithoutAwait(stats: List[c.Tree], state: Int) extends AsyncState(stats, state, 0) { // nextState unused, since encoded in then and else branches @@ -98,7 +98,7 @@ private[async] final case class ExprBuilder[C <: Context, FS <: FutureSystem](va /* * Builder for a single state of an async method. */ - class AsyncStateBuilder(state: Int, private val nameMap: Map[Symbol, c.Name]) { + final class AsyncStateBuilder(state: Int, private val nameMap: Map[Symbol, c.Name]) { self => /* Statements preceding an await call. */ @@ -223,16 +223,18 @@ private[async] final case class ExprBuilder[C <: Context, FS <: FutureSystem](va case _ => false }) c.abort(tree.pos, "await must not be used in this position") //throw new FallbackToCpsException - def builderForBranch(tree: c.Tree, state: Int, nextState: Int): AsyncBlockBuilder = { - val (branchStats, branchExpr) = statsAndExpr(tree) - new AsyncBlockBuilder(branchStats, branchExpr, state, nextState, toRename) + def nestedBlockBuilder(nestedTree: Tree, startState: Int, endState: Int) = { + val (nestedStats, nestedExpr) = statsAndExpr(nestedTree) + new AsyncBlockBuilder(nestedStats, nestedExpr, startState, endState, toRename) } + import stateAssigner.nextState + // populate asyncStates for (stat <- stats) stat match { // the val name = await(..) pattern case ValDef(mods, name, tpt, Apply(fun, args)) if isAwait(fun) => - val afterAwaitState = stateAssigner.nextState() + val afterAwaitState = nextState() asyncStates += stateBuilder.complete(args.head, toRename(stat.symbol).toTermName, tpt, afterAwaitState).result // complete with await currState = afterAwaitState stateBuilder = new builder.AsyncStateBuilder(currState, toRename) @@ -244,17 +246,17 @@ private[async] final case class ExprBuilder[C <: Context, FS <: FutureSystem](va case If(cond, thenp, elsep) if stat exists isAwait => checkForUnsupportedAwait(cond) - val thenStartState = stateAssigner.nextState() - val elseStartState = stateAssigner.nextState() - val afterIfState = stateAssigner.nextState() + val thenStartState = nextState() + val elseStartState = nextState() + val afterIfState = nextState() asyncStates += // the two Int arguments are the start state of the then branch and the else branch, respectively stateBuilder.resultWithIf(cond, thenStartState, elseStartState) List((thenp, thenStartState), (elsep, elseStartState)) foreach { - case (tree, state) => - val builder = builderForBranch(tree, state, afterIfState) + case (branchTree, state) => + val builder = nestedBlockBuilder(branchTree, state, afterIfState) asyncStates ++= builder.asyncStates } @@ -264,15 +266,14 @@ private[async] final case class ExprBuilder[C <: Context, FS <: FutureSystem](va case Match(scrutinee, cases) if stat exists isAwait => checkForUnsupportedAwait(scrutinee) - val caseStates = cases.map(_ => stateAssigner.nextState()) - val afterMatchState = stateAssigner.nextState() + val caseStates = cases.map(_ => nextState()) + val afterMatchState = nextState() asyncStates += stateBuilder.resultWithMatch(scrutinee, cases, caseStates) for ((cas, num) <- cases.zipWithIndex) { - val (casStats, casExpr) = statsAndExpr(cas.body) - val builder = new AsyncBlockBuilder(casStats, casExpr, caseStates(num), afterMatchState, toRename) + val builder = nestedBlockBuilder(cas.body, caseStates(num), afterMatchState) asyncStates ++= builder.asyncStates } @@ -280,12 +281,11 @@ private[async] final case class ExprBuilder[C <: Context, FS <: FutureSystem](va stateBuilder = new AsyncStateBuilder(currState, toRename) case ld@LabelDef(name, params, rhs) if rhs exists isAwait => - val startLabelState = stateAssigner.nextState() - val afterLabelState = stateAssigner.nextState() + val startLabelState = nextState() + val afterLabelState = nextState() asyncStates += stateBuilder.resultWithLabel(startLabelState) - val (stats, expr) = statsAndExpr(rhs) labelDefStates(ld.symbol) = startLabelState - val builder = new AsyncBlockBuilder(stats, expr, startLabelState, afterLabelState, toRename) + val builder = nestedBlockBuilder(rhs, startLabelState, afterLabelState) asyncStates ++= builder.asyncStates currState = afterLabelState @@ -315,5 +315,4 @@ private[async] final case class ExprBuilder[C <: Context, FS <: FutureSystem](va } } } - } -- cgit v1.2.3 From 31f7837cd4cb8763de9661738014727e36567d66 Mon Sep 17 00:00:00 2001 From: Jason Zaugg Date: Sat, 24 Nov 2012 13:19:45 +0100 Subject: More refactoring in ExprBuilder. - Moving some private methods out of the top-billing at the head of the file - Replace three nullable fields with one Option. --- src/main/scala/scala/async/ExprBuilder.scala | 74 +++++++++++++--------------- 1 file changed, 33 insertions(+), 41 deletions(-) diff --git a/src/main/scala/scala/async/ExprBuilder.scala b/src/main/scala/scala/async/ExprBuilder.scala index e5bb794..1ce5edb 100644 --- a/src/main/scala/scala/async/ExprBuilder.scala +++ b/src/main/scala/scala/async/ExprBuilder.scala @@ -21,24 +21,11 @@ private[async] final case class ExprBuilder[C <: Context, FS <: FutureSystem](va lazy val futureSystemOps = futureSystem.mkOps(c) - private def resetDuplicate(tree: Tree) = c.resetAllAttrs(tree.duplicate) - - private def mkResumeApply = Apply(Ident(name.resume), Nil) - - private def mkStateTree(nextState: Int): c.Tree = - Assign(Ident(name.state), c.literal(nextState).tree) - - private def mkHandlerCase(num: Int, rhs: List[c.Tree]): CaseDef = - mkHandlerCase(num, Block(rhs: _*)) - - private def mkHandlerCase(num: Int, rhs: c.Tree): CaseDef = - CaseDef(c.literal(num).tree, EmptyTree, rhs) - val stateAssigner = new StateAssigner val labelDefStates = collection.mutable.Map[Symbol, Int]() class AsyncState(stats: List[c.Tree], val state: Int, val nextState: Int) { - val body: c.Tree = stats match { + final val body: c.Tree = stats match { case stat :: Nil => stat case _ => Block(stats: _*) } @@ -46,7 +33,7 @@ private[async] final case class ExprBuilder[C <: Context, FS <: FutureSystem](va def mkHandlerCaseForState(): CaseDef = mkHandlerCase(state, stats :+ mkStateTree(nextState) :+ mkResumeApply) - def mkOnCompleteHandler(): Option[CaseDef] = { + final def mkOnCompleteHandler(): Option[CaseDef] = { this match { case aw: AsyncStateWithAwait => val tryGetTree = @@ -54,8 +41,7 @@ private[async] final case class ExprBuilder[C <: Context, FS <: FutureSystem](va Ident(aw.resultName), TypeApply(Select(Select(Ident(name.tr), Try_get), newTermName("asInstanceOf")), List(TypeTree(aw.resultType))) ) - val updateState = mkStateTree(nextState) - Some(mkHandlerCase(state, List(tryGetTree, updateState, mkResumeApply))) + Some(mkHandlerCase(state, List(tryGetTree, mkStateTree(nextState), mkResumeApply))) case _ => None } @@ -80,14 +66,11 @@ private[async] final case class ExprBuilder[C <: Context, FS <: FutureSystem](va awaitable: c.Tree, val resultName: TermName, val resultType: Type) extends AsyncState(stats, state, nextState) { - protected def tryType = appliedType(TryClass.toType, List(resultType)) - 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 :+ mkOnCompleteTree) } @@ -104,17 +87,13 @@ private[async] final case class ExprBuilder[C <: Context, FS <: FutureSystem](va /* Statements preceding an await call. */ private val stats = ListBuffer[c.Tree]() - /* Argument of an await call. */ - var awaitable: c.Tree = null + private final case class Awaitable(expr: Tree, resultName: TermName, resultType: Type) - /* Result name of an await call. */ - var resultName: TermName = null + /* Argument, type, and associated field of an await call. */ + private var awaitable: Option[Awaitable] = None - /* Result type of an await call. */ - var resultType: Type = null - - var nextState : Int = -1 - var nextJumpState: Option[Int] = None + private var nextState : Int = -1 + private var nextJumpState: Option[Int] = None def rename(tree: Tree) = substituteNames(tree, nameMap) @@ -134,10 +113,12 @@ private[async] final case class ExprBuilder[C <: Context, FS <: FutureSystem](va def result(): AsyncState = { val effectiveNextState = nextJumpState.getOrElse(nextState) - if (awaitable == null) - new AsyncState(stats.toList, state, effectiveNextState) - else - new AsyncStateWithAwait(stats.toList, state, effectiveNextState, awaitable, resultName, resultType) + awaitable match { + case None => + new AsyncState(stats.toList, state, effectiveNextState) + case Some(aw) => + new AsyncStateWithAwait(stats.toList, state, effectiveNextState, aw.expr, aw.resultName, aw.resultType) + } } /* Result needs to be created as a var at the beginning of the transformed method body, so that @@ -149,9 +130,7 @@ private[async] final case class ExprBuilder[C <: Context, FS <: FutureSystem](va */ def complete(awaitArg: c.Tree, awaitResultName: TermName, awaitResultType: Tree, nextState: Int): this.type = { - awaitable = resetDuplicate(rename(awaitArg)) - resultName = awaitResultName - resultType = awaitResultType.tpe + awaitable = Some(Awaitable(resetDuplicate(rename(awaitArg)), awaitResultName, awaitResultType.tpe)) this.nextState = nextState this } @@ -197,7 +176,7 @@ private[async] final case class ExprBuilder[C <: Context, FS <: FutureSystem](va override def toString: String = { val statsBeforeAwait = stats.mkString("\n") - s"ASYNC STATE:\n$statsBeforeAwait \nawaitable: $awaitable \nresult name: $resultName" + s"ASYNC STATE:\n$statsBeforeAwait \nawaitable: $awaitable" } } @@ -211,10 +190,10 @@ private[async] final case class ExprBuilder[C <: Context, FS <: FutureSystem](va * @param toRename a `Map` for renaming the given key symbols to the mangled value names */ final class AsyncBlockBuilder(stats: List[c.Tree], expr: c.Tree, startState: Int, endState: Int, - private val toRename: Map[Symbol, c.Name]) { + private val toRename: Map[Symbol, c.Name]) { val asyncStates = ListBuffer[builder.AsyncState]() - private var stateBuilder = new builder.AsyncStateBuilder(startState, toRename) + private var stateBuilder = new AsyncStateBuilder(startState, toRename) private var currState = startState /* TODO Fall back to CPS plug-in if tree contains an `await` call. */ @@ -237,7 +216,7 @@ private[async] final case class ExprBuilder[C <: Context, FS <: FutureSystem](va val afterAwaitState = nextState() asyncStates += stateBuilder.complete(args.head, toRename(stat.symbol).toTermName, tpt, afterAwaitState).result // complete with await currState = afterAwaitState - stateBuilder = new builder.AsyncStateBuilder(currState, toRename) + stateBuilder = new AsyncStateBuilder(currState, toRename) case ValDef(mods, name, tpt, rhs) if toRename contains stat.symbol => checkForUnsupportedAwait(rhs) @@ -261,7 +240,7 @@ private[async] final case class ExprBuilder[C <: Context, FS <: FutureSystem](va } currState = afterIfState - stateBuilder = new builder.AsyncStateBuilder(currState, toRename) + stateBuilder = new AsyncStateBuilder(currState, toRename) case Match(scrutinee, cases) if stat exists isAwait => checkForUnsupportedAwait(scrutinee) @@ -315,4 +294,17 @@ private[async] final case class ExprBuilder[C <: Context, FS <: FutureSystem](va } } } + + private def resetDuplicate(tree: Tree) = c.resetAllAttrs(tree.duplicate) + + private def mkResumeApply = Apply(Ident(name.resume), Nil) + + private def mkStateTree(nextState: Int): c.Tree = + Assign(Ident(name.state), c.literal(nextState).tree) + + private def mkHandlerCase(num: Int, rhs: List[c.Tree]): CaseDef = + mkHandlerCase(num, Block(rhs: _*)) + + private def mkHandlerCase(num: Int, rhs: c.Tree): CaseDef = + CaseDef(c.literal(num).tree, EmptyTree, rhs) } -- cgit v1.2.3 From ea54972b15bdbe9b80660efe5a40ccc640668129 Mon Sep 17 00:00:00 2001 From: Jason Zaugg Date: Sat, 24 Nov 2012 14:08:20 +0100 Subject: Refactor AsyncState heirarchy. No more extension from concrete classes; no more dummy values for nextState. --- src/main/scala/scala/async/ExprBuilder.scala | 72 +++++++++++++++------------- 1 file changed, 38 insertions(+), 34 deletions(-) diff --git a/src/main/scala/scala/async/ExprBuilder.scala b/src/main/scala/scala/async/ExprBuilder.scala index 1ce5edb..8840ba7 100644 --- a/src/main/scala/scala/async/ExprBuilder.scala +++ b/src/main/scala/scala/async/ExprBuilder.scala @@ -24,54 +24,58 @@ private[async] final case class ExprBuilder[C <: Context, FS <: FutureSystem](va val stateAssigner = new StateAssigner val labelDefStates = collection.mutable.Map[Symbol, Int]() - class AsyncState(stats: List[c.Tree], val state: Int, val nextState: Int) { - final val body: c.Tree = stats match { + trait AsyncState { + def state: Int + def mkHandlerCaseForState(): CaseDef + def mkOnCompleteHandler(): Option[CaseDef] = None + def stats: List[Tree] + final def body: c.Tree = stats match { case stat :: Nil => stat case _ => Block(stats: _*) } + } + + /** A sequence of statements the concludes with a unconditional transition to `nextState` */ + final class SimpleAsyncState(val stats: List[Tree], val state: Int, nextState: Int) + extends AsyncState { def mkHandlerCaseForState(): CaseDef = mkHandlerCase(state, stats :+ mkStateTree(nextState) :+ mkResumeApply) - final 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))) - ) - Some(mkHandlerCase(state, List(tryGetTree, mkStateTree(nextState), mkResumeApply))) - case _ => - None - } - } - override val toString: String = s"AsyncState #$state, next = $nextState" } - final class AsyncStateWithoutAwait(stats: List[c.Tree], state: Int) - extends AsyncState(stats, state, 0) { - // nextState unused, since encoded in then and else branches - + /** A sequence of statements with a conditional transition to the next state, which will represent + * a branch of an `if` or a `match`. + */ + final class AsyncStateWithoutAwait(val stats: List[c.Tree], val state: Int) extends AsyncState { override def mkHandlerCaseForState(): CaseDef = mkHandlerCase(state, stats) override val toString: String = - s"AsyncStateWithIf #$state, next = $nextState" + s"AsyncStateWithoutAwait #$state" } - final class AsyncStateWithAwait(stats: List[c.Tree], state: Int, nextState: Int, - awaitable: c.Tree, val resultName: TermName, val resultType: Type) - extends AsyncState(stats, state, nextState) { + /** A sequence of statements that concludes with an `await` call. The `onComplete` + * handler will unconditionally transition to `nestState`.`` + */ + final class AsyncStateWithAwait(val stats: List[c.Tree], val state: Int, nextState: Int, + awaitable: Awaitable) + extends AsyncState { - 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 = { + val callOnComplete = futureSystemOps.onComplete(c.Expr(awaitable.expr), c.Expr(Ident(name.onCompleteHandler)), c.Expr(Ident(name.execContext))).tree + mkHandlerCase(state, stats :+ callOnComplete) } - override def mkHandlerCaseForState(): CaseDef = { - mkHandlerCase(state, stats :+ mkOnCompleteTree) + override def mkOnCompleteHandler(): Option[CaseDef] = { + val tryGetTree = + Assign( + Ident(awaitable.resultName), + TypeApply(Select(Select(Ident(name.tr), Try_get), newTermName("asInstanceOf")), List(TypeTree(awaitable.resultType))) + ) + Some(mkHandlerCase(state, List(tryGetTree, mkStateTree(nextState), mkResumeApply))) } override val toString: String = @@ -87,8 +91,6 @@ private[async] final case class ExprBuilder[C <: Context, FS <: FutureSystem](va /* Statements preceding an await call. */ private val stats = ListBuffer[c.Tree]() - private final case class Awaitable(expr: Tree, resultName: TermName, resultType: Type) - /* Argument, type, and associated field of an await call. */ private var awaitable: Option[Awaitable] = None @@ -115,9 +117,9 @@ private[async] final case class ExprBuilder[C <: Context, FS <: FutureSystem](va val effectiveNextState = nextJumpState.getOrElse(nextState) awaitable match { case None => - new AsyncState(stats.toList, state, effectiveNextState) + new SimpleAsyncState(stats.toList, state, effectiveNextState) case Some(aw) => - new AsyncStateWithAwait(stats.toList, state, effectiveNextState, aw.expr, aw.resultName, aw.resultType) + new AsyncStateWithAwait(stats.toList, state, effectiveNextState, aw) } } @@ -191,7 +193,7 @@ private[async] final case class ExprBuilder[C <: Context, FS <: FutureSystem](va */ final class AsyncBlockBuilder(stats: List[c.Tree], expr: c.Tree, startState: Int, endState: Int, private val toRename: Map[Symbol, c.Name]) { - val asyncStates = ListBuffer[builder.AsyncState]() + val asyncStates = ListBuffer[AsyncState]() private var stateBuilder = new AsyncStateBuilder(startState, toRename) private var currState = startState @@ -202,7 +204,7 @@ private[async] final case class ExprBuilder[C <: Context, FS <: FutureSystem](va case _ => false }) c.abort(tree.pos, "await must not be used in this position") //throw new FallbackToCpsException - def nestedBlockBuilder(nestedTree: Tree, startState: Int, endState: Int) = { + private def nestedBlockBuilder(nestedTree: Tree, startState: Int, endState: Int) = { val (nestedStats, nestedExpr) = statsAndExpr(nestedTree) new AsyncBlockBuilder(nestedStats, nestedExpr, startState, endState, toRename) } @@ -295,6 +297,8 @@ private[async] final case class ExprBuilder[C <: Context, FS <: FutureSystem](va } } + private final case class Awaitable(expr: Tree, resultName: TermName, resultType: Type) + private def resetDuplicate(tree: Tree) = c.resetAllAttrs(tree.duplicate) private def mkResumeApply = Apply(Ident(name.resume), Nil) -- cgit v1.2.3 From a001ec82dee9af3911840a45168270f242a51a3d Mon Sep 17 00:00:00 2001 From: Jason Zaugg Date: Sat, 24 Nov 2012 14:20:01 +0100 Subject: Refactoring to use Awaitable Rather than three separate parameters. --- src/main/scala/scala/async/ExprBuilder.scala | 35 +++++++++++++--------------- 1 file changed, 16 insertions(+), 19 deletions(-) diff --git a/src/main/scala/scala/async/ExprBuilder.scala b/src/main/scala/scala/async/ExprBuilder.scala index 8840ba7..dcd950f 100644 --- a/src/main/scala/scala/async/ExprBuilder.scala +++ b/src/main/scala/scala/async/ExprBuilder.scala @@ -113,26 +113,12 @@ private[async] final case class ExprBuilder[C <: Context, FS <: FutureSystem](va this } - def result(): AsyncState = { - val effectiveNextState = nextJumpState.getOrElse(nextState) - awaitable match { - case None => - new SimpleAsyncState(stats.toList, state, effectiveNextState) - case Some(aw) => - new AsyncStateWithAwait(stats.toList, state, effectiveNextState, aw) - } - } - /* 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. - * - * @param awaitArg the argument of await - * @param awaitResultName the name of the variable that the result of await is assigned to - * @param awaitResultType the type of the result of await */ - def complete(awaitArg: c.Tree, awaitResultName: TermName, awaitResultType: Tree, + def complete(awaitable: Awaitable, nextState: Int): this.type = { - awaitable = Some(Awaitable(resetDuplicate(rename(awaitArg)), awaitResultName, awaitResultType.tpe)) + this.awaitable = Some(awaitable.copy(expr = resetDuplicate(rename(awaitable.expr)))) this.nextState = nextState this } @@ -142,6 +128,16 @@ private[async] final case class ExprBuilder[C <: Context, FS <: FutureSystem](va this } + def result: AsyncState = { + val effectiveNextState = nextJumpState.getOrElse(nextState) + awaitable match { + case None => + new SimpleAsyncState(stats.toList, state, effectiveNextState) + case Some(aw) => + new AsyncStateWithAwait(stats.toList, state, effectiveNextState, aw) + } + } + 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 @@ -214,9 +210,10 @@ private[async] final case class ExprBuilder[C <: Context, FS <: FutureSystem](va // populate asyncStates for (stat <- stats) stat match { // the val name = await(..) pattern - case ValDef(mods, name, tpt, Apply(fun, args)) if isAwait(fun) => + case ValDef(mods, name, tpt, Apply(fun, arg :: Nil)) if isAwait(fun) => val afterAwaitState = nextState() - asyncStates += stateBuilder.complete(args.head, toRename(stat.symbol).toTermName, tpt, afterAwaitState).result // complete with await + val awaitable = Awaitable(arg, toRename(stat.symbol).toTermName, tpt.tpe) + asyncStates += stateBuilder.complete(awaitable, afterAwaitState).result // complete with await currState = afterAwaitState stateBuilder = new AsyncStateBuilder(currState, toRename) @@ -277,7 +274,7 @@ private[async] final case class ExprBuilder[C <: Context, FS <: FutureSystem](va } // complete last state builder (representing the expressions after the last await) stateBuilder += expr - val lastState = stateBuilder.complete(endState).result() + val lastState = stateBuilder.complete(endState).result asyncStates += lastState def mkCombinedHandlerCases[T](): List[CaseDef] = { -- cgit v1.2.3 From 50194151da3b472bdda3d2b723f01885b4b7e84b Mon Sep 17 00:00:00 2001 From: Jason Zaugg Date: Sat, 24 Nov 2012 14:43:29 +0100 Subject: Refactoring to avoid unneeded mutable fields. Brings uniformity to the result generation of AsyncStateBuilder. --- src/main/scala/scala/async/ExprBuilder.scala | 55 +++++++++++----------------- 1 file changed, 21 insertions(+), 34 deletions(-) diff --git a/src/main/scala/scala/async/ExprBuilder.scala b/src/main/scala/scala/async/ExprBuilder.scala index dcd950f..a269740 100644 --- a/src/main/scala/scala/async/ExprBuilder.scala +++ b/src/main/scala/scala/async/ExprBuilder.scala @@ -26,9 +26,13 @@ private[async] final case class ExprBuilder[C <: Context, FS <: FutureSystem](va trait AsyncState { def state: Int + def mkHandlerCaseForState(): CaseDef + def mkOnCompleteHandler(): Option[CaseDef] = None + def stats: List[Tree] + final def body: c.Tree = stats match { case stat :: Nil => stat case _ => Block(stats: _*) @@ -58,8 +62,8 @@ private[async] final case class ExprBuilder[C <: Context, FS <: FutureSystem](va } /** A sequence of statements that concludes with an `await` call. The `onComplete` - * handler will unconditionally transition to `nestState`.`` - */ + * handler will unconditionally transition to `nestState`.`` + */ final class AsyncStateWithAwait(val stats: List[c.Tree], val state: Int, nextState: Int, awaitable: Awaitable) extends AsyncState { @@ -90,18 +94,14 @@ private[async] final case class ExprBuilder[C <: Context, FS <: FutureSystem](va /* Statements preceding an await call. */ private val stats = ListBuffer[c.Tree]() - - /* Argument, type, and associated field of an await call. */ - private var awaitable: Option[Awaitable] = None - - private var nextState : Int = -1 + /** The state of the target of a LabelDef application (while loop jump) */ private var nextJumpState: Option[Int] = None - def rename(tree: Tree) = substituteNames(tree, nameMap) + private def renameReset(tree: Tree) = resetDuplicate(substituteNames(tree, nameMap)) def +=(stat: c.Tree): this.type = { assert(nextJumpState.isEmpty, s"statement appeared after a label jump: $stat") - def addStat() = stats += resetDuplicate(rename(stat)) + def addStat() = stats += renameReset(stat) stat match { case Apply(fun, Nil) => labelDefStates get fun.symbol match { @@ -113,35 +113,22 @@ private[async] final case class ExprBuilder[C <: Context, FS <: FutureSystem](va this } - /* 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. - */ - def complete(awaitable: Awaitable, - nextState: Int): this.type = { - this.awaitable = Some(awaitable.copy(expr = resetDuplicate(rename(awaitable.expr)))) - this.nextState = nextState - this - } - - def complete(nextState: Int): this.type = { - this.nextState = nextState - this + def resultWithAwait(awaitable: Awaitable, + nextState: Int): AsyncState = { + val sanitizedAwaitable = awaitable.copy(expr = renameReset(awaitable.expr)) + val effectiveNextState = nextJumpState.getOrElse(nextState) + new AsyncStateWithAwait(stats.toList, state, effectiveNextState, sanitizedAwaitable) } - def result: AsyncState = { + def resultSimple(nextState: Int): AsyncState = { val effectiveNextState = nextJumpState.getOrElse(nextState) - awaitable match { - case None => - new SimpleAsyncState(stats.toList, state, effectiveNextState) - case Some(aw) => - new AsyncStateWithAwait(stats.toList, state, effectiveNextState, aw) - } + new SimpleAsyncState(stats.toList, state, effectiveNextState) } 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 = resetDuplicate(rename(condTree)) + val cond = renameReset(condTree) def mkBranch(state: Int) = Block(mkStateTree(state), mkResumeApply) this += If(cond, mkBranch(thenState), mkBranch(elseState)) new AsyncStateWithoutAwait(stats.toList, state) @@ -163,7 +150,7 @@ private[async] final case class ExprBuilder[C <: Context, FS <: FutureSystem](va case CaseDef(pat, guard, rhs) => CaseDef(pat, guard, Block(mkStateTree(caseStates(num)), mkResumeApply)) } // 2. insert changed match tree at the end of the current state - this += Match(resetDuplicate(scrutTree), newCases) + this += Match(renameReset(scrutTree), newCases) new AsyncStateWithoutAwait(stats.toList, state) } @@ -174,7 +161,7 @@ private[async] final case class ExprBuilder[C <: Context, FS <: FutureSystem](va override def toString: String = { val statsBeforeAwait = stats.mkString("\n") - s"ASYNC STATE:\n$statsBeforeAwait \nawaitable: $awaitable" + s"ASYNC STATE:\n$statsBeforeAwait" } } @@ -213,7 +200,7 @@ private[async] final case class ExprBuilder[C <: Context, FS <: FutureSystem](va case ValDef(mods, name, tpt, Apply(fun, arg :: Nil)) if isAwait(fun) => val afterAwaitState = nextState() val awaitable = Awaitable(arg, toRename(stat.symbol).toTermName, tpt.tpe) - asyncStates += stateBuilder.complete(awaitable, afterAwaitState).result // complete with await + asyncStates += stateBuilder.resultWithAwait(awaitable, afterAwaitState) // complete with await currState = afterAwaitState stateBuilder = new AsyncStateBuilder(currState, toRename) @@ -274,7 +261,7 @@ private[async] final case class ExprBuilder[C <: Context, FS <: FutureSystem](va } // complete last state builder (representing the expressions after the last await) stateBuilder += expr - val lastState = stateBuilder.complete(endState).result + val lastState = stateBuilder.resultSimple(endState) asyncStates += lastState def mkCombinedHandlerCases[T](): List[CaseDef] = { -- cgit v1.2.3 From fe65f9275a908de09aa051c8699de29ffeb413d9 Mon Sep 17 00:00:00 2001 From: Jason Zaugg Date: Sat, 24 Nov 2012 14:51:26 +0100 Subject: Tiny aesthetic refactorings. --- src/main/scala/scala/async/Async.scala | 4 ++-- src/main/scala/scala/async/ExprBuilder.scala | 24 ++++++++++++------------ 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/src/main/scala/scala/async/Async.scala b/src/main/scala/scala/async/Async.scala index 473b948..4c5c8f2 100644 --- a/src/main/scala/scala/async/Async.scala +++ b/src/main/scala/scala/async/Async.scala @@ -98,7 +98,7 @@ abstract class AsyncBase { val endState = Int.MaxValue val asyncBlockBuilder = new builder.AsyncBlockBuilder(anfTree.stats, anfTree.expr, startState, endState, renameMap) - val handlerCases: List[CaseDef] = asyncBlockBuilder.mkCombinedHandlerCases[T]() + val handlerCases: List[CaseDef] = asyncBlockBuilder.mkCombinedHandlerCases[T] import asyncBlockBuilder.asyncStates logDiagnostics(c)(anfTree, asyncStates.map(_.toString)) @@ -118,7 +118,7 @@ abstract class AsyncBase { ... */ val onCompleteHandler = { - val onCompleteHandlers = initStates.flatMap(_.mkOnCompleteHandler()).toList + val onCompleteHandlers = initStates.flatMap(_.mkOnCompleteHandler).toList Function( List(ValDef(Modifiers(PARAM), name.tr, TypeTree(defn.TryAnyType), EmptyTree)), Match(Ident(name.state), onCompleteHandlers)) diff --git a/src/main/scala/scala/async/ExprBuilder.scala b/src/main/scala/scala/async/ExprBuilder.scala index a269740..00b06b1 100644 --- a/src/main/scala/scala/async/ExprBuilder.scala +++ b/src/main/scala/scala/async/ExprBuilder.scala @@ -27,9 +27,9 @@ private[async] final case class ExprBuilder[C <: Context, FS <: FutureSystem](va trait AsyncState { def state: Int - def mkHandlerCaseForState(): CaseDef + def mkHandlerCaseForState: CaseDef - def mkOnCompleteHandler(): Option[CaseDef] = None + def mkOnCompleteHandler: Option[CaseDef] = None def stats: List[Tree] @@ -43,7 +43,7 @@ private[async] final case class ExprBuilder[C <: Context, FS <: FutureSystem](va final class SimpleAsyncState(val stats: List[Tree], val state: Int, nextState: Int) extends AsyncState { - def mkHandlerCaseForState(): CaseDef = + def mkHandlerCaseForState: CaseDef = mkHandlerCase(state, stats :+ mkStateTree(nextState) :+ mkResumeApply) override val toString: String = @@ -54,7 +54,7 @@ private[async] final case class ExprBuilder[C <: Context, FS <: FutureSystem](va * a branch of an `if` or a `match`. */ final class AsyncStateWithoutAwait(val stats: List[c.Tree], val state: Int) extends AsyncState { - override def mkHandlerCaseForState(): CaseDef = + override def mkHandlerCaseForState: CaseDef = mkHandlerCase(state, stats) override val toString: String = @@ -68,12 +68,13 @@ private[async] final case class ExprBuilder[C <: Context, FS <: FutureSystem](va awaitable: Awaitable) extends AsyncState { - override def mkHandlerCaseForState(): CaseDef = { - val callOnComplete = futureSystemOps.onComplete(c.Expr(awaitable.expr), c.Expr(Ident(name.onCompleteHandler)), c.Expr(Ident(name.execContext))).tree + override def mkHandlerCaseForState: CaseDef = { + val callOnComplete = futureSystemOps.onComplete(c.Expr(awaitable.expr), + c.Expr(Ident(name.onCompleteHandler)), c.Expr(Ident(name.execContext))).tree mkHandlerCase(state, stats :+ callOnComplete) } - override def mkOnCompleteHandler(): Option[CaseDef] = { + override def mkOnCompleteHandler: Option[CaseDef] = { val tryGetTree = Assign( Ident(awaitable.resultName), @@ -90,8 +91,6 @@ private[async] final case class ExprBuilder[C <: Context, FS <: FutureSystem](va * Builder for a single state of an async method. */ final class AsyncStateBuilder(state: Int, private val nameMap: Map[Symbol, c.Name]) { - self => - /* Statements preceding an await call. */ private val stats = ListBuffer[c.Tree]() /** The state of the target of a LabelDef application (while loop jump) */ @@ -264,18 +263,19 @@ private[async] final case class ExprBuilder[C <: Context, FS <: FutureSystem](va val lastState = stateBuilder.resultSimple(endState) asyncStates += lastState - def mkCombinedHandlerCases[T](): List[CaseDef] = { + def mkCombinedHandlerCases[T]: List[CaseDef] = { 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))) + val rhs = futureSystemOps.completeProm( + c.Expr[futureSystem.Prom[T]](Ident(name.result)), reify(scala.util.Success(lastStateBody.splice))) mkHandlerCase(lastState.state, rhs.tree) } asyncStates.toList match { case s :: Nil => List(caseForLastState) case _ => - val initCases = for (state <- asyncStates.toList.init) yield state.mkHandlerCaseForState() + val initCases = for (state <- asyncStates.toList.init) yield state.mkHandlerCaseForState initCases :+ caseForLastState } } -- cgit v1.2.3 From d216aacd47b4a39d7627e4dd22724927856b01a5 Mon Sep 17 00:00:00 2001 From: Jason Zaugg Date: Sat, 24 Nov 2012 15:30:26 +0100 Subject: Refactor some tree creation from Async to ExprBuilder. --- src/main/scala/scala/async/Async.scala | 57 ++----------- src/main/scala/scala/async/ExprBuilder.scala | 103 +++++++++++++++++++----- src/main/scala/scala/async/TransformUtils.scala | 1 - 3 files changed, 89 insertions(+), 72 deletions(-) diff --git a/src/main/scala/scala/async/Async.scala b/src/main/scala/scala/async/Async.scala index 4c5c8f2..ef506a5 100644 --- a/src/main/scala/scala/async/Async.scala +++ b/src/main/scala/scala/async/Async.scala @@ -64,7 +64,6 @@ abstract class AsyncBase { def asyncImpl[T: c.WeakTypeTag](c: Context)(body: c.Expr[T]): c.Expr[futureSystem.Fut[T]] = { import c.universe._ - import Flag._ val builder = ExprBuilder[c.type, futureSystem.type](c, self.futureSystem) val anaylzer = AsyncAnalysis[c.type](c) @@ -94,63 +93,17 @@ abstract class AsyncBase { }.toMap } - val startState = builder.stateAssigner.nextState() - val endState = Int.MaxValue - - val asyncBlockBuilder = new builder.AsyncBlockBuilder(anfTree.stats, anfTree.expr, startState, endState, renameMap) - val handlerCases: List[CaseDef] = asyncBlockBuilder.mkCombinedHandlerCases[T] - - import asyncBlockBuilder.asyncStates + val asyncBlock: builder.AsyncBlock = builder.build(anfTree, renameMap) + import asyncBlock.asyncStates logDiagnostics(c)(anfTree, asyncStates.map(_.toString)) - val initStates = asyncStates.init + val localVarTrees = anfTree.collect { case vd@ValDef(_, _, tpt, _) if renameMap contains vd.symbol => utils.mkVarDefTree(tpt.tpe, renameMap(vd.symbol)) } - /* - 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(defn.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(defn.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 onCompleteHandler = asyncBlock.onCompleteHandler + val resumeFunTree = asyncBlock.resumeFunTree[T] val prom: Expr[futureSystem.Prom[T]] = reify { // Create the empty promise diff --git a/src/main/scala/scala/async/ExprBuilder.scala b/src/main/scala/scala/async/ExprBuilder.scala index 00b06b1..5ae01f9 100644 --- a/src/main/scala/scala/async/ExprBuilder.scala +++ b/src/main/scala/scala/async/ExprBuilder.scala @@ -92,7 +92,7 @@ private[async] final case class ExprBuilder[C <: Context, FS <: FutureSystem](va */ final class AsyncStateBuilder(state: Int, private val nameMap: Map[Symbol, c.Name]) { /* Statements preceding an await call. */ - private val stats = ListBuffer[c.Tree]() + private val stats = ListBuffer[c.Tree]() /** The state of the target of a LabelDef application (while loop jump) */ private var nextJumpState: Option[Int] = None @@ -173,12 +173,12 @@ private[async] final case class ExprBuilder[C <: Context, FS <: FutureSystem](va * @param endState the state to continue with * @param toRename a `Map` for renaming the given key symbols to the mangled value names */ - final class AsyncBlockBuilder(stats: List[c.Tree], expr: c.Tree, startState: Int, endState: Int, - private val toRename: Map[Symbol, c.Name]) { + final private class AsyncBlockBuilder(stats: List[c.Tree], expr: c.Tree, startState: Int, endState: Int, + private val toRename: Map[Symbol, c.Name]) { val asyncStates = ListBuffer[AsyncState]() - private var stateBuilder = new AsyncStateBuilder(startState, toRename) - private var currState = startState + var stateBuilder = new AsyncStateBuilder(startState, toRename) + var currState = startState /* TODO Fall back to CPS plug-in if tree contains an `await` call. */ def checkForUnsupportedAwait(tree: c.Tree) = if (tree exists { @@ -186,7 +186,7 @@ private[async] final case class ExprBuilder[C <: Context, FS <: FutureSystem](va case _ => false }) c.abort(tree.pos, "await must not be used in this position") //throw new FallbackToCpsException - private def nestedBlockBuilder(nestedTree: Tree, startState: Int, endState: Int) = { + def nestedBlockBuilder(nestedTree: Tree, startState: Int, endState: Int) = { val (nestedStats, nestedExpr) = statsAndExpr(nestedTree) new AsyncBlockBuilder(nestedStats, nestedExpr, startState, endState, toRename) } @@ -262,22 +262,87 @@ private[async] final case class ExprBuilder[C <: Context, FS <: FutureSystem](va stateBuilder += expr val lastState = stateBuilder.resultSimple(endState) asyncStates += lastState + } + + trait AsyncBlock { + def asyncStates: List[AsyncState] + + def onCompleteHandler: Tree + + def resumeFunTree[T]: Tree + } + + def build(block: Block, toRename: Map[Symbol, c.Name]): AsyncBlock = { + val Block(stats, expr) = block + val startState = stateAssigner.nextState() + val endState = Int.MaxValue - def mkCombinedHandlerCases[T]: List[CaseDef] = { - 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) + val blockBuilder = new AsyncBlockBuilder(stats, expr, startState, endState, toRename) + + new AsyncBlock { + def asyncStates = blockBuilder.asyncStates.toList + + def mkCombinedHandlerCases[T]: List[CaseDef] = { + 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) + } + asyncStates.toList match { + case s :: Nil => + List(caseForLastState) + case _ => + val initCases = for (state <- asyncStates.toList.init) yield state.mkHandlerCaseForState + initCases :+ caseForLastState + } } - asyncStates.toList match { - case s :: Nil => - List(caseForLastState) - case _ => - val initCases = for (state <- asyncStates.toList.init) yield state.mkHandlerCaseForState - initCases :+ caseForLastState + + val initStates = asyncStates.init + + /** + * lazy val onCompleteHandler = (tr: Try[Any]) => state match { + * case 0 => { + * x11 = tr.get.asInstanceOf[Double]; + * state = 1; + * resume() + * } + */ + val onCompleteHandler: Tree = { + val onCompleteHandlers = initStates.flatMap(_.mkOnCompleteHandler).toList + Function( + List(ValDef(Modifiers(Flag.PARAM), name.tr, TypeTree(defn.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) + * } + * } + */ + def resumeFunTree[T]: Tree = + DefDef(Modifiers(), name.resume, Nil, List(Nil), Ident(definitions.UnitClass), + Try( + Match(Ident(name.state), mkCombinedHandlerCases[T]), + List( + CaseDef( + Apply(Ident(defn.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)) } } diff --git a/src/main/scala/scala/async/TransformUtils.scala b/src/main/scala/scala/async/TransformUtils.scala index 22099b5..8838bb3 100644 --- a/src/main/scala/scala/async/TransformUtils.scala +++ b/src/main/scala/scala/async/TransformUtils.scala @@ -24,7 +24,6 @@ private[async] final case class TransformUtils[C <: Context](val c: C) { 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") -- cgit v1.2.3