diff --git a/build.sbt b/build.sbt index c0e062ed..6589450f 100644 --- a/build.sbt +++ b/build.sbt @@ -1,5 +1,8 @@ scalaVersion := "2.10.1" +// Uncomment to test with a locally built copy of Scala. +// scalaHome := Some(file("/code/scala2/build/pack")) + organization := "org.typesafe.async" // TODO new org name under scala-lang. name := "scala-async" @@ -23,14 +26,27 @@ parallelExecution in Global := false autoCompilerPlugins := true -libraryDependencies <<= (scalaVersion, libraryDependencies) { - (ver, deps) => - deps :+ compilerPlugin("org.scala-lang.plugins" % "continuations" % ver) +libraryDependencies <++= (scalaVersion, scalaHome) { + (ver, sh) => + sh match { + case Some(sh) => + Nil + case None => + compilerPlugin("org.scala-lang.plugins" % "continuations" % ver) :: Nil + } } +scalacOptions ++= Seq("-deprecation", "-unchecked", "-Xlint", "-feature") + scalacOptions += "-P:continuations:enable" -scalacOptions ++= Seq("-deprecation", "-unchecked", "-Xlint", "-feature") +scalacOptions <++= scalaHome map { + case Some(sh) => + val continuationsJar = (sh / "misc" / "scala-devel" / "plugins" / "continuations.jar") + ("-Xplugin:" + continuationsJar.getAbsolutePath) :: Nil + case None => + Nil +} description := "An asynchronous programming facility for Scala, in the spirit of C# await/async" @@ -60,3 +76,30 @@ pomExtra := ( scm:git:git@github.com:scala/async.git ) + +// Run ;gen-idea;patch-idea to point the generated config files for IntelliJ +// to use the libraries and sources from `scalaHome`, if defined above. +TaskKey[Unit]("patch-idea") <<= (baseDirectory, scalaHome, scalaVersion) map { (bd, shOpt, sv) => + import java.nio.charset.Charset._ + shOpt foreach { sh => + Seq("library", "compiler", "reflect") foreach { jar => + Seq("_test", "") foreach { suffix => + val fileName = "SBT__org_scala_lang_scala_" + jar + "_" + sv.replaceAllLiterally(".", "_") + suffix + ".xml" + val f = bd / ".idea" / "libraries" / fileName + if (f.exists) { + val origContent = IO.read(bd / ".idea" / "libraries" / fileName) + val origSource = "jar://$USER_HOME$/.ivy2/cache/org.scala-lang/scala-" + jar + "/srcs/scala-" + jar + "-" + sv + "-sources.jar!/" + // Three '/' required by IntelliJ here for some reason. + val newSource = "file:///" + (sh.getParentFile.getParentFile / "src" / jar).getAbsolutePath + val origLib = jar match { + case "reflect" => "jar://$USER_HOME$/.ivy2/cache/org.scala-lang/scala-reflect/jars/scala-reflect-" + sv + ".jar!/" + case _ => "jar://$USER_HOME$/.sbt/boot/scala-" + sv + "/lib/scala-" + jar + ".jar!/" + } + val newLib = (sh / "lib" / ("scala-" + jar + ".jar")).toURI.toString + val newContent = origContent.replaceAllLiterally(origSource, newSource).replaceAllLiterally(origLib, newLib) + IO.write(f, newContent, forName("UTF-8"), append = false) + } + } + } + } +} diff --git a/src/main/scala/scala/async/AnfTransform.scala b/src/main/scala/scala/async/AnfTransform.scala index da375a5d..334f625b 100644 --- a/src/main/scala/scala/async/AnfTransform.scala +++ b/src/main/scala/scala/async/AnfTransform.scala @@ -98,25 +98,6 @@ private[async] final case class AnfTransform[C <: Context](c: C) { } } - private object trace { - private var indent = -1 - - 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(${oneLine(args)})") - val result = t - AsyncUtils.trace(s"${indentString}= ${oneLine(result)}") - result - } finally { - indent -= 1 - } - } - } - private object inline { def transformToList(tree: Tree): List[Tree] = trace("inline", tree) { val stats :+ expr = anf.transformToList(tree) @@ -248,7 +229,8 @@ private[async] final case class AnfTransform[C <: Context](c: C) { val (valDefs, mappings) = (pat collect { case b@Bind(name, _) => val newName = newTermName(utils.name.fresh(name.toTermName + utils.name.bindSuffix)) - val vd = ValDef(NoMods, newName, TypeTree(), Ident(b.symbol)) + val tpt = bindResTypeTree(b) + val vd = ValDef(NoMods, newName, tpt, Ident(b.symbol)) (vd, (b.symbol, newName)) }).unzip val Block(stats1, expr1) = utils.substituteNames(block, mappings.toMap).asInstanceOf[Block] @@ -271,4 +253,26 @@ private[async] final case class AnfTransform[C <: Context](c: C) { } } } + + // TODO figure out if represents a bug in scalac, or here, or something in between. + // We get here in case of ` case s: Seq[_] => await(fut)`. See test case + // ToughType.existentialBind + private def bindResTypeTree(b: Bind): Tree = { + object originalTrans extends Transformer { + override def transform(tree: Tree): Tree = tree match { + case tt: TypeTree => + val realOrig = tt.original match { + case Bind(tpnme.WILDCARD, EmptyTree) => Ident(tpnme.WILDCARD) + case t => t + } + super.transform(realOrig) + case _ => + super.transform(tree) + } + } + b.body match { + case Typed(_, tpt1) => originalTrans.transform(tpt1) + case x => TypeTree() + } + } } diff --git a/src/main/scala/scala/async/Async.scala b/src/main/scala/scala/async/Async.scala index 35d36878..200dd2a6 100644 --- a/src/main/scala/scala/async/Async.scala +++ b/src/main/scala/scala/async/Async.scala @@ -15,7 +15,7 @@ object Async extends AsyncBase { lazy val futureSystem = ScalaConcurrentFutureSystem type FS = ScalaConcurrentFutureSystem.type - def async[T](body: T) = macro asyncImpl[T] + def async[T](body: T): Future[T] = macro asyncImpl[T] override def asyncImpl[T: c.WeakTypeTag](c: Context)(body: c.Expr[T]): c.Expr[Future[T]] = super.asyncImpl[T](c)(body) } @@ -24,7 +24,7 @@ object AsyncId extends AsyncBase { lazy val futureSystem = IdentityFutureSystem type FS = IdentityFutureSystem.type - def async[T](body: T) = macro asyncImpl[T] + def async[T](body: T): T = macro asyncImpl[T] override def asyncImpl[T: c.WeakTypeTag](c: Context)(body: c.Expr[T]): c.Expr[T] = super.asyncImpl[T](c)(body) } diff --git a/src/main/scala/scala/async/TransformUtils.scala b/src/main/scala/scala/async/TransformUtils.scala index ebd546f7..7e41e9d8 100644 --- a/src/main/scala/scala/async/TransformUtils.scala +++ b/src/main/scala/scala/async/TransformUtils.scala @@ -241,7 +241,7 @@ private[async] final case class TransformUtils[C <: Context](c: C) { import language.existentials - override def transform(tree: Tree): Tree = super.transform { + override def transform(tree: Tree): Tree = trace("reset", tree) {super.transform { def isExternal = tree.symbol != NoSymbol && !internalSyms(tree.symbol) tree match { @@ -252,7 +252,7 @@ private[async] final case class TransformUtils[C <: Context](c: C) { case (_: Ident | _: This) if isExternal => tree // #35 Don't reset the symbol of Ident/This bound outside of the async block case _ => resetTree(tree) } - } + }} private def resetTypeTree(tpt: TypeTree): Tree = { if (tpt.original != null) @@ -371,4 +371,23 @@ private[async] final case class TransformUtils[C <: Context](c: C) { } }.unzip } + + private[async] object trace { + private var indent = -1 + + 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(${oneLine(args)})") + val result = t + AsyncUtils.trace(s"${indentString}= ${oneLine(result)}") + result + } finally { + indent -= 1 + } + } + } } diff --git a/src/test/scala/scala/async/TreeInterrogation.scala b/src/test/scala/scala/async/TreeInterrogation.scala index deaee03e..d5c99978 100644 --- a/src/test/scala/scala/async/TreeInterrogation.scala +++ b/src/test/scala/scala/async/TreeInterrogation.scala @@ -68,17 +68,18 @@ object TreeInterrogation extends App { withDebug { val cm = reflect.runtime.currentMirror - val tb = mkToolbox("-cp target/scala-2.10/classes -Xprint:flatten") + val tb = mkToolbox("-cp target/scala-2.10/classes -Xprint:flatten -uniqid") import scala.async.Async._ val tree = tb.parse( """ import _root_.scala.async.AsyncId.{async, await} - | def foo[T](a0: Int)(b0: Int*) = s"a0 = $a0, b0 = ${b0.head}" - | val res = async { - | var i = 0 - | def get = async {i += 1; i} - | foo[Int](await(get))(await(get) :: Nil : _*) - | } - | res + |def m7(a: Any) = async { + | a match { + | case s: Seq[_] => + | val x = s.size + | await(x) + | } + |} + | () | """.stripMargin) println(tree) val tree1 = tb.typeCheck(tree.duplicate) diff --git a/src/test/scala/scala/async/run/toughtype/ToughType.scala b/src/test/scala/scala/async/run/toughtype/ToughType.scala index 83f5a2d1..0958abb7 100644 --- a/src/test/scala/scala/async/run/toughtype/ToughType.scala +++ b/src/test/scala/scala/async/run/toughtype/ToughType.scala @@ -67,4 +67,18 @@ class ToughTypeSpec { await(f(2)) } mustBe 3 } + + @Test def existentialBind() { + import AsyncId.{await, async} + def m7(a: Any) = async { + a match { + case s: Seq[_] => + val x = s.size + var ss = s + ss = s + await(x) + } + } + m7(Nil) mustBe 0 + } }