Skip to content

Workaround for existential bind / resetAttrs interaction #19

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 47 additions & 4 deletions build.sbt
Original file line number Diff line number Diff line change
@@ -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"
Expand All @@ -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"

Expand Down Expand Up @@ -60,3 +76,30 @@ pomExtra := (
<connection>scm:git:git@github.com:scala/async.git</connection>
</scm>
)

// 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)
}
}
}
}
}
44 changes: 24 additions & 20 deletions src/main/scala/scala/async/AnfTransform.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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]
Expand All @@ -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()
}
}
}
4 changes: 2 additions & 2 deletions src/main/scala/scala/async/Async.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand All @@ -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)
}
Expand Down
23 changes: 21 additions & 2 deletions src/main/scala/scala/async/TransformUtils.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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)
Expand Down Expand Up @@ -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
}
}
}
}
17 changes: 9 additions & 8 deletions src/test/scala/scala/async/TreeInterrogation.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
14 changes: 14 additions & 0 deletions src/test/scala/scala/async/run/toughtype/ToughType.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
}