diff options
author | Jason Zaugg <jzaugg@gmail.com> | 2015-07-23 23:15:37 +1000 |
---|---|---|
committer | Jason Zaugg <jzaugg@gmail.com> | 2015-09-22 16:53:33 +1000 |
commit | e3ff0382ae4e015fc69da8335450718951714982 (patch) | |
tree | 3f89dace31be3cd125531c0ba24270aa45100d7e /src/test | |
parent | 93f207fee780652d08f93e1ea40e018db59fee99 (diff) | |
download | scala-async-e3ff0382ae4e015fc69da8335450718951714982.tar.gz scala-async-e3ff0382ae4e015fc69da8335450718951714982.tar.bz2 scala-async-e3ff0382ae4e015fc69da8335450718951714982.zip |
Enable a compiler plugin to use the async transform after patmat
Currently, the async transformation is performed during the typer
phase, like all other macros.
We have to levy a few artificial restrictions on whern an async
boundary may be: for instance we don't support await within a
pattern guard. A more natural home for the transform would be
after patterns have been translated.
The test case in this commit shows how to use the async transform
from a custom compiler phase after patmat.
The remainder of the commit updates the implementation to handle
the new tree shapes.
For states that correspond to a label definition, we use `-symbol.id`
as the state ID. This made it easier to emit the forward jumps to when
processing the label application before we had seen the label
definition.
I've also made the transformation more efficient in the way it checks
whether a given tree encloses an `await` call: we traverse the input
tree at the start of the macro, and decorate it with tree attachments
containig the answer to this question. Even after the ANF and state
machine transforms introduce new layers of synthetic trees, the
`containsAwait` code need only traverse shallowly through those
trees to find a child that has the cached answer from the original
traversal.
I had to special case the ANF transform for expressions that always
lead to a label jump: we avoids trying to push an assignment to a result
variable into `if (cond) jump1() else jump2()`, in trees of the form:
```
% cat sandbox/jump.scala
class Test {
def test = {
(null: Any) match {
case _: String => ""
case _ => ""
}
}
}
% qscalac -Xprint:patmat -Xprint-types sandbox/jump.scala
def test: String = {
case <synthetic> val x1: Any = (null{Null(null)}: Any){Any};
case5(){
if (x1.isInstanceOf{[T0]=> Boolean}[String]{Boolean})
matchEnd4{(x: String)String}(""{String("")}){String}
else
case6{()String}(){String}{String}
}{String};
case6(){
matchEnd4{(x: String)String}(""{String("")}){String}
}{String};
matchEnd4(x: String){
x{String}
}{String}
}{String}
```
Diffstat (limited to 'src/test')
-rw-r--r-- | src/test/scala/scala/async/TreeInterrogation.scala | 2 | ||||
-rw-r--r-- | src/test/scala/scala/async/run/late/LateExpansion.scala | 170 |
2 files changed, 172 insertions, 0 deletions
diff --git a/src/test/scala/scala/async/TreeInterrogation.scala b/src/test/scala/scala/async/TreeInterrogation.scala index d6c619f..09fa69e 100644 --- a/src/test/scala/scala/async/TreeInterrogation.scala +++ b/src/test/scala/scala/async/TreeInterrogation.scala @@ -82,6 +82,8 @@ object TreeInterrogation extends App { 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/run/late/LateExpansion.scala b/src/test/scala/scala/async/run/late/LateExpansion.scala new file mode 100644 index 0000000..b866527 --- /dev/null +++ b/src/test/scala/scala/async/run/late/LateExpansion.scala @@ -0,0 +1,170 @@ +package scala.async.run.late + +import java.io.File + +import junit.framework.Assert.assertEquals +import org.junit.Test + +import scala.annotation.StaticAnnotation +import scala.async.internal.{AsyncId, AsyncMacro} +import scala.reflect.internal.util.ScalaClassLoader.URLClassLoader +import scala.tools.nsc._ +import scala.tools.nsc.plugins.{Plugin, PluginComponent} +import scala.tools.nsc.reporters.StoreReporter +import scala.tools.nsc.transform.TypingTransformers + +// Tests for customized use of the async transform from a compiler plugin, which +// calls it from a new phase that runs after patmat. +class LateExpansion { + @Test def test0(): Unit = { + val result = wrapAndRun( + """ + | @autoawait def id(a: String) = a + | id("foo") + id("bar") + | """.stripMargin) + assertEquals("foobar", result) + } + @Test def testGuard(): Unit = { + val result = wrapAndRun( + """ + | @autoawait def id[A](a: A) = a + | "" match { case _ if id(false) => ???; case _ => "okay" } + | """.stripMargin) + assertEquals("okay", result) + } + + @Test def testExtractor(): Unit = { + val result = wrapAndRun( + """ + | object Extractor { @autoawait def unapply(a: String) = Some((a, a)) } + | "" match { case Extractor(a, b) if "".isEmpty => a == b } + | """.stripMargin) + assertEquals(true, result) + } + + @Test def testNestedMatchExtractor(): Unit = { + val result = wrapAndRun( + """ + | object Extractor { @autoawait def unapply(a: String) = Some((a, a)) } + | "" match { + | case _ if "".isEmpty => + | "" match { case Extractor(a, b) => a == b } + | } + | """.stripMargin) + assertEquals(true, result) + } + + @Test def testCombo(): Unit = { + val result = wrapAndRun( + """ + | object Extractor1 { @autoawait def unapply(a: String) = Some((a + 1, a + 2)) } + | object Extractor2 { @autoawait def unapply(a: String) = Some(a + 3) } + | @autoawait def id(a: String) = a + | println("Test.test") + | val r1 = Predef.identity("blerg") match { + | case x if " ".isEmpty => "case 2: " + x + | case Extractor1(Extractor2(x), y: String) if x == "xxx" => "case 1: " + x + ":" + y + | x match { + | case Extractor1(Extractor2(x), y: String) => + | case _ => + | } + | case Extractor2(x) => "case 3: " + x + | } + | r1 + | """.stripMargin) + assertEquals("case 3: blerg3", result) + } + + def wrapAndRun(code: String): Any = { + run( + s""" + |import scala.async.run.late.{autoawait,lateasync} + |object Test { + | @lateasync + | def test: Any = { + | $code + | } + |} + | """.stripMargin) + } + + def run(code: String): Any = { + val reporter = new StoreReporter + val settings = new Settings(println(_)) + settings.outdir.value = sys.props("java.io.tmpdir") + settings.embeddedDefaults(getClass.getClassLoader) + val isInSBT = !settings.classpath.isSetByUser + if (isInSBT) settings.usejavacp.value = true + val global = new Global(settings, reporter) { + self => + + object late extends { + val global: self.type = self + } with LatePlugin + + override protected def loadPlugins(): List[Plugin] = late :: Nil + } + import global._ + + val run = new Run + val source = newSourceFile(code) + run.compileSources(source :: Nil) + assert(!reporter.hasErrors, reporter.infos.mkString("\n")) + val loader = new URLClassLoader(Seq(new File(settings.outdir.value).toURI.toURL), global.getClass.getClassLoader) + val cls = loader.loadClass("Test") + cls.getMethod("test").invoke(null) + } +} + +abstract class LatePlugin extends Plugin { + import global._ + + override val components: List[PluginComponent] = List(new PluginComponent with TypingTransformers { + val global: LatePlugin.this.global.type = LatePlugin.this.global + + lazy val asyncIdSym = symbolOf[AsyncId.type] + lazy val asyncSym = asyncIdSym.info.member(TermName("async")) + lazy val awaitSym = asyncIdSym.info.member(TermName("await")) + lazy val autoAwaitSym = symbolOf[autoawait] + lazy val lateAsyncSym = symbolOf[lateasync] + + def newTransformer(unit: CompilationUnit) = new TypingTransformer(unit) { + override def transform(tree: Tree): Tree = { + super.transform(tree) match { + case ap@Apply(fun, args) if fun.symbol.hasAnnotation(autoAwaitSym) => + localTyper.typed(Apply(TypeApply(gen.mkAttributedRef(asyncIdSym.typeOfThis, awaitSym), TypeTree(ap.tpe) :: Nil), ap :: Nil)) + case dd: DefDef if dd.symbol.hasAnnotation(lateAsyncSym) => atOwner(dd.symbol) { + val expandee = localTyper.context.withMacrosDisabled( + localTyper.typed(Apply(TypeApply(gen.mkAttributedRef(asyncIdSym.typeOfThis, asyncSym), TypeTree(dd.rhs.tpe) :: Nil), List(dd.rhs))) + ) + val c = analyzer.macroContext(localTyper, gen.mkAttributedRef(asyncIdSym), expandee) + val asyncMacro = AsyncMacro(c, AsyncId)(dd.rhs) + val code = asyncMacro.asyncTransform[Any](localTyper.typed(Literal(Constant(()))))(c.weakTypeTag[Any]) + deriveDefDef(dd)(_ => localTyper.typed(code)) + } + case x => x + } + } + } + + override def newPhase(prev: Phase): Phase = new StdPhase(prev) { + override def apply(unit: CompilationUnit): Unit = { + val translated = newTransformer(unit).transformUnit(unit) + //println(show(unit.body)) + translated + } + } + + override val runsAfter: List[String] = "patmat" :: Nil + override val phaseName: String = "postpatmat" + + }) + override val description: String = "postpatmat" + override val name: String = "postpatmat" +} + +// Methods with this annotation are translated to having the RHS wrapped in `AsyncId.async { <original RHS> }` +final class lateasync extends StaticAnnotation + +// Calls to methods with this annotation are translated to `AsyncId.await(<call>)` +final class autoawait extends StaticAnnotation |