From aae630a5ca807ce21981771c737cffa755923823 Mon Sep 17 00:00:00 2001 From: Jess smith Date: Thu, 2 Jun 2016 17:18:58 -0400 Subject: [PATCH 01/23] Partially implement reflection test from Scala Async --- project/Build.scala | 1 + .../org/coroutines/async-await-tests.scala | 24 +++++++++++++++---- 2 files changed, 21 insertions(+), 4 deletions(-) diff --git a/project/Build.scala b/project/Build.scala index 6f82753..43b1f7a 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -96,6 +96,7 @@ object CoroutinesBuild extends MechaRepoBuild { "org.scalatest" % "scalatest_2.11" % "2.2.6" % "test", "org.scala-lang.modules" %% "scala-parser-combinators" % "1.0.2", "org.scala-lang" % "scala-reflect" % "2.11.4", + "org.scala-lang" % "scala-compiler" % "2.11.4", "org.scala-lang.modules" % "scala-async_2.11" % "0.9.5" % "bench" ) case _ => Nil diff --git a/src/test/scala/org/coroutines/async-await-tests.scala b/src/test/scala/org/coroutines/async-await-tests.scala index 399d040..0ad82d0 100644 --- a/src/test/scala/org/coroutines/async-await-tests.scala +++ b/src/test/scala/org/coroutines/async-await-tests.scala @@ -2,7 +2,6 @@ package org.coroutines import org.scalatest._ -import scala.language.{ reflectiveCalls, postfixOps } import scala.annotation.unchecked.uncheckedVariance import scala.concurrent._ import scala.concurrent.duration._ @@ -716,10 +715,27 @@ class AsyncAwaitTest extends FunSuite with Matchers { } // Source: https://git.io/vrhT6 - /** NOTE: Currently fails compilation because I haven't found the right - * imports. + /** NOTE: Currently fails compilation. test("await in implicit apply") { - val tb = mkToolbox(s"-cp ${toolboxClasspath}") + val scalaBinaryVersion: String = { + val PreReleasePattern = """.*-(M|RC).*""".r + val Pattern = """(\d+\.\d+)\..*""".r + val SnapshotPattern = """(\d+\.\d+\.\d+)-\d+-\d+-.*""".r + scala.util.Properties.versionNumberString match { + case s @ PreReleasePattern(_) => s + case SnapshotPattern(v) => v + "-SNAPSHOT" + case Pattern(v) => v + case _ => "" + } + } + val toolboxClasspath: String = { + val f = new java.io.File(s"target/scala-${scalaBinaryVersion}/classes") + if (!f.exists) + sys.error(s"output directory ${f.getAbsolutePath} does not exist.") + f.getAbsolutePath + } + val mirror = scala.reflect.runtime.currentMirror + val tb = Toolbox.mkToolbox(s"-cp ${toolboxClasspath}") val tree = tb.typeCheck(tb.parse { """ | import scala.language.implicitConversions From c3545043c669ae08d6b2772b375afcabac7ec8c9 Mon Sep 17 00:00:00 2001 From: Jess smith Date: Mon, 6 Jun 2016 10:28:44 -0400 Subject: [PATCH 02/23] Copy method definitions into canonicalized AST --- .../org/coroutines/ASTCanonicalization.scala | 7 +++-- .../ast-canonicalization-tests.scala | 26 +++++++++++++++++ .../org/coroutines/async-await-tests.scala | 3 -- .../org/coroutines/coroutine-tests.scala | 28 ------------------- 4 files changed, 31 insertions(+), 33 deletions(-) diff --git a/src/main/scala/org/coroutines/ASTCanonicalization.scala b/src/main/scala/org/coroutines/ASTCanonicalization.scala index 997badf..2f5b492 100644 --- a/src/main/scala/org/coroutines/ASTCanonicalization.scala +++ b/src/main/scala/org/coroutines/ASTCanonicalization.scala @@ -385,10 +385,13 @@ trait ASTCanonicalization[C <: Context] { val (rhsdecls, rhsident) = canonicalize(rhs) val decls = rhsdecls ++ List(q"$mods var $v: $tpt = $rhsident") (decls, q"") - case q"$mods def $tname[..$tparams](...$paramss): $tpt = $expr" => + case q"$mods def $tname[..$tparams](...$paramss): $tpt = $rhs" => // method + val (rhsdecls, rhsident) = canonicalize(rhs) + val decls = rhsdecls ++ + List(q"$mods def $tname[..$tparams](...$paramss): $tpt = $rhsident") new NestedContextValidator().traverse(tree) - (Nil, tree) + (decls, q"") case q"$mods type $tpname[..$tparams] = $tpt" => // type new NestedContextValidator().traverse(tree) diff --git a/src/test/scala/org/coroutines/ast-canonicalization-tests.scala b/src/test/scala/org/coroutines/ast-canonicalization-tests.scala index bbac26f..bab9184 100644 --- a/src/test/scala/org/coroutines/ast-canonicalization-tests.scala +++ b/src/test/scala/org/coroutines/ast-canonicalization-tests.scala @@ -283,4 +283,30 @@ class ASTCanonicalizationTest extends FunSuite with Matchers { assert(c1.result == 1) assert(c1.isCompleted) } + + test("should be able to define uncalled function inside coroutine") { + val oy = coroutine { () => + def foo(): String = "bar" + val bar = "bar" + 1 + } + val c = call(oy()) + assert(!c.resume) + assert(c.hasResult) + assert(c.result == 1) + assert(c.isCompleted) + } + + test("should be able to define called function inside coroutine") { + val oy = coroutine { () => + def foo(): String = "bar" + val bar = foo() + 1 + } + val c = call(oy()) + assert(!c.resume) + assert(c.hasResult) + assert(c.result == 1) + assert(c.isCompleted) + } } diff --git a/src/test/scala/org/coroutines/async-await-tests.scala b/src/test/scala/org/coroutines/async-await-tests.scala index 0ad82d0..35dd55c 100644 --- a/src/test/scala/org/coroutines/async-await-tests.scala +++ b/src/test/scala/org/coroutines/async-await-tests.scala @@ -610,8 +610,6 @@ class AsyncAwaitTest extends FunSuite with Matchers { */ // Source: https://git.io/vrhUF - /** NOTE: Currently fails compilation because functions cannot be defined - * inside coroutines. test("named arguments respect evaluation order") { def foo(a: Int, b: Int) = (a, b) val c = async(coroutine { () => @@ -624,7 +622,6 @@ class AsyncAwaitTest extends FunSuite with Matchers { }) assert(Await.result(c, 5 seconds) == (2, 1)) } - */ // Source: https://git.io/vrhTe test("named and default arguments respect evaluation order") { diff --git a/src/test/scala/org/coroutines/coroutine-tests.scala b/src/test/scala/org/coroutines/coroutine-tests.scala index b95fac2..80df4df 100644 --- a/src/test/scala/org/coroutines/coroutine-tests.scala +++ b/src/test/scala/org/coroutines/coroutine-tests.scala @@ -689,32 +689,4 @@ class WideValueTypesTest extends FunSuite with Matchers { assert(c.result == 8.0) assert(c.isCompleted) } - - test("should be able to define uncalled function inside coroutine") { - val oy = coroutine { () => - def foo(): String = "bar" - val bar = "bar" - 1 - } - val c = call(oy()) - assert(!c.resume) - assert(c.hasResult) - assert(c.result == 1) - assert(c.isCompleted) - } - - /** NOTE: Currently fails compilation because the compiler cannot find `foo`. - test("should be able to define called function inside coroutine") { - val oy = coroutine { () => - def foo(): String = "bar" - val bar = foo() - 1 - } - val c = call(oy()) - assert(!c.resume) - assert(c.hasResult) - assert(c.result == 1) - assert(c.isCompleted) - } - */ } From 20ce23760017edb9395d633ed7e517dcbbdce6e6 Mon Sep 17 00:00:00 2001 From: Jess smith Date: Mon, 6 Jun 2016 11:02:04 -0400 Subject: [PATCH 03/23] Copy declarations and identifiers from method definitions into proper scope --- .../org/coroutines/ASTCanonicalization.scala | 9 +++++++-- .../scala/org/coroutines/async-await-tests.scala | 16 +--------------- 2 files changed, 8 insertions(+), 17 deletions(-) diff --git a/src/main/scala/org/coroutines/ASTCanonicalization.scala b/src/main/scala/org/coroutines/ASTCanonicalization.scala index 2f5b492..27f66a9 100644 --- a/src/main/scala/org/coroutines/ASTCanonicalization.scala +++ b/src/main/scala/org/coroutines/ASTCanonicalization.scala @@ -388,8 +388,13 @@ trait ASTCanonicalization[C <: Context] { case q"$mods def $tname[..$tparams](...$paramss): $tpt = $rhs" => // method val (rhsdecls, rhsident) = canonicalize(rhs) - val decls = rhsdecls ++ - List(q"$mods def $tname[..$tparams](...$paramss): $tpt = $rhsident") + val decls = List( + q""" + $mods def $tname[..$tparams](...$paramss): $tpt = { + ..$rhsdecls + $rhsident + } + """) new NestedContextValidator().traverse(tree) (decls, q"") case q"$mods type $tpname[..$tparams] = $tpt" => diff --git a/src/test/scala/org/coroutines/async-await-tests.scala b/src/test/scala/org/coroutines/async-await-tests.scala index 35dd55c..125f857 100644 --- a/src/test/scala/org/coroutines/async-await-tests.scala +++ b/src/test/scala/org/coroutines/async-await-tests.scala @@ -507,15 +507,6 @@ class AsyncAwaitTest extends FunSuite with Matchers { } // Source: https://git.io/vrA0Q - /** NOTE: Currently fails compilation. If I remove the parentheses after - * `next`, but keep the ones after the calls to `next` on the line - * `foo(next(), await(Future(next())))`, - * then there the compiler is able to find `next` and there is no macro - * expansion error. However, there is the expected compilation error saying - * that "`Int` does not take parameters." - * Removing the parentheses to make the line as such: - * `foo(next, await(Future(next)))` - * creates another macro expansion error. test("evaluation order respected") { def foo(a: Int, b: Int) = (a, b) val c = async(coroutine { () => @@ -527,14 +518,10 @@ class AsyncAwaitTest extends FunSuite with Matchers { foo(next(), await(Future(next()))) }) val result = Await.result(c, 5 seconds) - assert(result == 1) + assert(result == (1, 2)) } - */ // Source: https://git.io/vrAuv - /** NOTE: Currently fails compilation. As in the previous test, `get` cannot - be found.*/ - /** test("await in non-primary param section 1") { def foo(a0: Int)(b0: Int) = s"a0 = $a0, b0 = $b0" val c = async(coroutine {() => @@ -544,7 +531,6 @@ class AsyncAwaitTest extends FunSuite with Matchers { }) assert(Await.result(c, 5 seconds) == "a0 = 1, b0 = 2") } - */ // Source: https://git.io/vrAzt /** NOTE: All of these tests currently fail compilation because of errors about From a9a78853328ec3c3f68e4922b9d48ae147738116 Mon Sep 17 00:00:00 2001 From: Jess smith Date: Mon, 6 Jun 2016 11:11:53 -0400 Subject: [PATCH 04/23] Remove checks that asserted equality between unit types --- src/test/scala/org/coroutines/ast-canonicalization-tests.scala | 1 - src/test/scala/org/coroutines/coroutine-tests.scala | 2 -- src/test/scala/org/coroutines/regression-tests.scala | 2 -- src/test/scala/org/coroutines/try-catch-tests.scala | 2 -- 4 files changed, 7 deletions(-) diff --git a/src/test/scala/org/coroutines/ast-canonicalization-tests.scala b/src/test/scala/org/coroutines/ast-canonicalization-tests.scala index bab9184..56f15c5 100644 --- a/src/test/scala/org/coroutines/ast-canonicalization-tests.scala +++ b/src/test/scala/org/coroutines/ast-canonicalization-tests.scala @@ -90,7 +90,6 @@ class ASTCanonicalizationTest extends FunSuite with Matchers { val c = call(unit()) assert(!c.resume) - assert(c.result == (())) assert(!c.isLive) } diff --git a/src/test/scala/org/coroutines/coroutine-tests.scala b/src/test/scala/org/coroutines/coroutine-tests.scala index 80df4df..dc5bb11 100644 --- a/src/test/scala/org/coroutines/coroutine-tests.scala +++ b/src/test/scala/org/coroutines/coroutine-tests.scala @@ -85,7 +85,6 @@ class CoroutineTest extends FunSuite with Matchers { assert(c.resume) assert(c.value == List("5")) assert(!c.resume) - assert(c.result == (())) } test("should lub yieldtos and returns") { @@ -345,7 +344,6 @@ class CoroutineTest extends FunSuite with Matchers { assert(c.value == -i) } assert(!c.resume) - assert(c.result == (())) } test("an anonymous coroutine should be applied") { diff --git a/src/test/scala/org/coroutines/regression-tests.scala b/src/test/scala/org/coroutines/regression-tests.scala index bb2ce0d..7be5117 100644 --- a/src/test/scala/org/coroutines/regression-tests.scala +++ b/src/test/scala/org/coroutines/regression-tests.scala @@ -21,13 +21,11 @@ class RegressionTest extends FunSuite with Matchers { assert(c1.value == 5) assert(!c1.resume) assert(c1.isCompleted) - assert(c1.result == (())) val c2 = call(xOrY(-2, 7)) assert(c2.resume) assert(c2.value == 7) assert(!c2.resume) assert(c2.isCompleted) - assert(c2.result == (())) } test("coroutine should have a nested if statement") { diff --git a/src/test/scala/org/coroutines/try-catch-tests.scala b/src/test/scala/org/coroutines/try-catch-tests.scala index 62e6b0b..9085123 100644 --- a/src/test/scala/org/coroutines/try-catch-tests.scala +++ b/src/test/scala/org/coroutines/try-catch-tests.scala @@ -19,7 +19,6 @@ class TryCatchTest extends FunSuite with Matchers { val c0 = call(rube()) assert(!c0.resume) - assert(c0.result == (())) assert(c0.isCompleted) } @@ -65,7 +64,6 @@ class TryCatchTest extends FunSuite with Matchers { assert(!error) assert(!completed) assert(!c0.resume) - assert(c0.result == (())) assert(!runtime) assert(error) assert(completed) From e07ca5283434347e272a0986d7331cce129223e4 Mon Sep 17 00:00:00 2001 From: Jess smith Date: Sat, 11 Jun 2016 18:20:04 -0400 Subject: [PATCH 05/23] Make some progress on properly transforming by-name parameters However, this commit is not stable. There is an infinite loop when compiling `async-await-tests.scala`. --- .../org/coroutines/AstCanonicalization.scala | 53 +++++++++++++++++-- 1 file changed, 49 insertions(+), 4 deletions(-) diff --git a/src/main/scala/org/coroutines/AstCanonicalization.scala b/src/main/scala/org/coroutines/AstCanonicalization.scala index 51ff882..07f26c3 100644 --- a/src/main/scala/org/coroutines/AstCanonicalization.scala +++ b/src/main/scala/org/coroutines/AstCanonicalization.scala @@ -8,7 +8,6 @@ import scala.language.experimental.macros import scala.reflect.macros.whitebox.Context - /** Transforms the coroutine body into three address form with restricted control flow * that contains only try-catch statements, while loops, if-statements, value and * variable declarations, pattern matches, nested blocks and function calls. @@ -90,7 +89,7 @@ trait AstCanonicalization[C <: Context] { val localvarname = TermName(c.freshName("x")) val decls = List( q"var $localvarname = null.asInstanceOf[Boolean]", - q""" + q""" ..$conddecls if ($condident) { ..$thendecls @@ -122,6 +121,32 @@ trait AstCanonicalization[C <: Context] { (decls, q"$localvarname") case q"$selector[..$tpts](...$paramss)" if tpts.length > 0 || paramss.length > 0 => // application + val byNameParams: immutable.Seq[immutable.Seq[Boolean]] = { + if (selector.symbol != null && selector.symbol != NoSymbol) { + val methodSymbol = selector.symbol.asMethod + val noRepeatedParamsMap = methodSymbol.paramLists.map { paramList => + paramList.map { param => + param match { + case ts: TermSymbol => + ts.isByNameParam + case _ => + false + } + } + } + if (paramss.length > 0 && noRepeatedParamsMap.length > 0) { + var repeatedParamsMap = noRepeatedParamsMap + while (paramss(0).length > noRepeatedParamsMap(0).length) { + repeatedParamsMap = repeatedParamsMap :+ noRepeatedParamsMap(0).tail + } + repeatedParamsMap + } else { + noRepeatedParamsMap + } + } else { + immutable.Seq.fill(1, paramss(0).length)(false) + } + } val (rdecls, newselector) = selector match { case q"$r.$method" => val (rdecls, rident) = canonicalize(r) @@ -130,10 +155,30 @@ trait AstCanonicalization[C <: Context] { (Nil, q"$method") } for (tpt <- tpts) disallowCoroutinesIn(tpt) - val (pdeclss, pidents) = paramss.map(_.map(canonicalize).unzip).unzip + type TupleType = (List[c.universe.Tree], c.universe.Tree) + val paramsByNameUnmodified: List[List[TupleType]] = { + val modifiedParamLists = mutable.Seq.fill[List[TupleType]](paramss.length)(null) + for (i <- 0 until paramss.length) { + val modifiedParams = mutable.Seq.fill[TupleType](paramss(i).length)(null) + for (j <- 0 until modifiedParams.length) { + if (byNameParams(i)(j)) { + modifiedParams(j) = (List(q""), paramss(i)(j)) + } else { + modifiedParams(j) = canonicalize(paramss(i)(j)) + } + } + modifiedParamLists(i) = modifiedParams.toList + } + modifiedParamLists.toList + } + val pdeclss = + paramsByNameUnmodified.map((_.map(tuple => tuple._1))).flatten.flatten.filter{ decl => + decl != q"" + } + val pidents = paramsByNameUnmodified.map((_.map(tuple => tuple._2))) val localvarname = TermName(c.freshName("x")) val localvartree = q"val $localvarname = $newselector[..$tpts](...$pidents)" - (rdecls ++ pdeclss.flatten.flatten ++ List(localvartree), q"$localvarname") + (rdecls ++ pdeclss ++ List(localvartree), q"$localvarname") case q"$r[..$tpts]" if tpts.length > 0 => // type application for (tpt <- tpts) disallowCoroutinesIn(tpt) From 36ef738e50a84276ea3aa53cb35cda26180de65f Mon Sep 17 00:00:00 2001 From: Jess smith Date: Wed, 15 Jun 2016 10:21:31 -0400 Subject: [PATCH 06/23] Remove infinite looping introduced in the previous commit --- src/main/scala/org/coroutines/AstCanonicalization.scala | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/main/scala/org/coroutines/AstCanonicalization.scala b/src/main/scala/org/coroutines/AstCanonicalization.scala index 07f26c3..b2c2eca 100644 --- a/src/main/scala/org/coroutines/AstCanonicalization.scala +++ b/src/main/scala/org/coroutines/AstCanonicalization.scala @@ -135,9 +135,11 @@ trait AstCanonicalization[C <: Context] { } } if (paramss.length > 0 && noRepeatedParamsMap.length > 0) { - var repeatedParamsMap = noRepeatedParamsMap - while (paramss(0).length > noRepeatedParamsMap(0).length) { - repeatedParamsMap = repeatedParamsMap :+ noRepeatedParamsMap(0).tail + var repeatedParamsMap: List[List[Boolean]] = noRepeatedParamsMap + while (paramss(0).length > repeatedParamsMap(0).length) { + val newHead: List[Boolean] = repeatedParamsMap(0) :+ noRepeatedParamsMap(0).last + val newTail: List[List[Boolean]] = repeatedParamsMap.tail + repeatedParamsMap = newHead :: newTail } repeatedParamsMap } else { From c2f47df4064842d36f617d96faa0a586bb1aec39 Mon Sep 17 00:00:00 2001 From: Jess smith Date: Thu, 23 Jun 2016 16:28:57 -0400 Subject: [PATCH 07/23] Re-add tests that were accidentally removed in previous commit --- .../org/coroutines/async-await-tests.scala | 384 +++++++++++++++++- 1 file changed, 383 insertions(+), 1 deletion(-) diff --git a/src/test/scala/org/coroutines/async-await-tests.scala b/src/test/scala/org/coroutines/async-await-tests.scala index b2fb020..10193fe 100644 --- a/src/test/scala/org/coroutines/async-await-tests.scala +++ b/src/test/scala/org/coroutines/async-await-tests.scala @@ -7,7 +7,6 @@ import scala.annotation.unchecked.uncheckedVariance import scala.concurrent._ import scala.concurrent.duration._ import scala.concurrent.ExecutionContext.Implicits.global -import scala.language.{ reflectiveCalls, postfixOps } import scala.util.Success @@ -104,6 +103,36 @@ class AsyncAwaitTest extends FunSuite with Matchers { assert(result._1 == Nil) } + // Source: https://git.io/vrHmG + /** NOTE: Currently fails compilation. + test("pattern matching partial function") { + val c = async(coroutine { () => + await(Future(1)) + val a = await(Future(1)) + val f = { case x => x + a }: PartialFunction[Int, Int] + await(Future(f(2))) + }) + val res = Await.result(c, 2 seconds) + assert(res == 3) + } + */ + + // Source: https://git.io/vr79k + /** NOTE: Currently fails compilation. + test("pattern matching partial function nested") { + val c = AsyncAwaitTest.async(coroutine { () => + AsyncAwaitTest.await(Future(1)) + val neg1 = -1 + val a = AsyncAwaitTest.await(Future(1)) + val f = {case x => ({case x => neg1 * x}: + PartialFunction[Int, Int])(x + a)}: PartialFunction[Int, Int] + AsyncAwaitTest.await(Future(f(2))) + }) + val res = Await.result(c, 2 seconds) + assert(res == -3) + } + */ + // Source: https://git.io/vr7H9 test("pattern matching function") { val c = async(coroutine { () => @@ -145,6 +174,37 @@ class AsyncAwaitTest extends FunSuite with Matchers { }) } + // Source: https://git.io/vr7FE + /** NOTE: Currently fails compilation. + test("singleton type") { + class A { class B } + AsyncAwaitTest.async(coroutine { () => + val a = new A + def foo(b: a.B) = 0 + AsyncAwaitTest.await(Future(foo(new a.B))) + }) + } + */ + + + // Source: https://git.io/vr7F6 + /** NOTE: Currently fails compilation. + test("existential match") { + trait Container[+A] + case class ContainerImpl[A](value: A) extends Container[A] + def foo: Future[Container[_]] = AsyncAwaitTest.async(coroutine { () => + val a: Any = List(1) + a match { + case buf: Seq[_] => + val foo = AsyncAwaitTest.await(Future(5)) + val e0 = buf(0) + ContainerImpl(e0) + } + }) + foo + } + */ + // Source: https://git.io/vr7Fx test("existential if/else") { trait Container[+A] @@ -161,6 +221,39 @@ class AsyncAwaitTest extends FunSuite with Matchers { foo } + // Source: https://git.io/vr7bJ + /** NOTE: Currently fails compilation + test("nested method with inconsistency") { + import language.{reflectiveCalls, postfixOps} + + class Foo[A] + + object Bippy { + + import ExecutionContext.Implicits.global + + def bar(f: => Unit): Unit = f + + def quux: Future[String] = ??? + + def foo = AsyncAwaitTest.async(coroutine { () => + def r[A](m: Foo[A])(n: A) = { + bar { + locally(m) + locally(n) + identity[A] _ + } + } + + AsyncAwaitTest.await(quux) + + r(new Foo[String])("") + }) + } + Bippy + } + */ + // Source: https://git.io/vr7ba test("ticket 63 in scala/async") { object SomeExecutionContext extends ExecutionContext { @@ -213,6 +306,18 @@ class AsyncAwaitTest extends FunSuite with Matchers { assert(inner == new IntWrapper("foo")) } + // Source: https://git.io/vr7NJ + /** NOTE: This test currently fails compilation. + test("ticket 86 in scala/async-- using nested value class") { + val f = AsyncAwaitTest.async[Nothing, IntWrapper](coroutine { () => + val a = Future.successful(new IntWrapper("42")) + AsyncAwaitTest.await(Future(AsyncAwaitTest.await(a).plusStr)) + }) + val res = Await.result(f, 5 seconds) + assert(res == "42!") + } + */ + // Source: https://git.io/vr7Nk test("ticket 86 in scala/async-- using matched value class") { def doAThing(param: IntWrapper) = Future(None) @@ -231,6 +336,7 @@ class AsyncAwaitTest extends FunSuite with Matchers { } // Source: https://git.io/vr7NZ + // NOTE: Need to look at this test's implementation. Might be done incorrectly. test("ticket 86 in scala/async-- using matched parameterized value class") { def doAThing(param: ParamWrapper[String]) = Future(None) @@ -281,6 +387,53 @@ class AsyncAwaitTest extends FunSuite with Matchers { assert(inner == 1) } + // Source: https://git.io/vr7NB + /** NOTE: Currently fails compilation + test("ticket 106 in scala/async-- value class") { + AsyncAwaitTest.async(coroutine { () => + "whatever value" match { + case _ => + AsyncAwaitTest.await(Future("whatever return type")) + new IntWrapper("value case matters") + } + "whatever return type" + }) + } + */ + + // Source: https://git.io/vrFQt + /** NOTE: Currently fails compilation. + test("Inlining block does not produce duplicate definition") { + AsyncAwaitTest.async(coroutine { () => + val f = 12 + val x = AsyncAwaitTest.await(Future(f)) + { + type X = Int + val x: X = 42 + println(x) + } + type X = Int + x: X + }) + } + */ + + // Source: https://git.io/vrF5X + /** NOTE: Currently fails compilation. + test("Inlining block in tail position does not produce duplication definition") { + val c = AsyncAwaitTest.async(coroutine { () => + val f = 12 + val x = AsyncAwaitTest.await(Future(f)) + { + val x = 42 + x + } + }) + val res = Await.result(c, 5 seconds) + assert(res == 42) + } + */ + // Source: https://git.io/vrFp5 test("match as expression 1") { val c = AsyncAwaitTest.async(coroutine { () => @@ -328,6 +481,136 @@ class AsyncAwaitTest extends FunSuite with Matchers { assert(result == true) } + // Source: https://git.io/vrAWm + // NOTE: Currently fails compilation + /** + test("nested await in if") { + val c: Future[Any] = async(coroutine { () => + if ("".isEmpty) { + await(Future(await(Future("")).isEmpty)) + } else 0 + }) + assert(Await.result(c, 5 seconds) == true) + } + */ + + // Source: https://git.io/vrAlJ + /** NOTE: This test currently fails because the future times out. + * Interestingly, the future doesn't time out if `1` is passed as the first + * argument to `foo`. + */ + test("by-name expressions aren't lifted") { + def foo(ignored: => Any, b: Int) = b + val c = async(coroutine { () => + await(Future(foo(???, await(Future(1))))) + }) + val result = Await.result(c, 5 seconds) + assert(result == 1) + } + + // Source: https://git.io/vrA0Q + test("evaluation order respected") { + def foo(a: Int, b: Int) = (a, b) + val c = async(coroutine { () => + var i = 0 + def next(): Int = { + i += 1 + i + } + foo(next(), await(Future(next()))) + }) + val result = Await.result(c, 5 seconds) + assert(result == (1, 2)) + } + + // Source: https://git.io/vrAuv + test("await in non-primary param section 1") { + def foo(a0: Int)(b0: Int) = s"a0 = $a0, b0 = $b0" + val c = async(coroutine {() => + var i = 0 + def get = { i += 1; i } + foo(get)(await(Future(get))) + }) + assert(Await.result(c, 5 seconds) == "a0 = 1, b0 = 2") + } + + // Source: https://git.io/vrAzt + /** NOTE: All of these tests currently fail compilation because of errors about + * the yield types of the coroutines. The compiler expects the yield type of + * the coroutines `nilAsync` and `c` to be `(Future[?], Cell[?])`, but they + * are both `Nothing`. This does not work because the yield type is invariant. + * I'm not sure why compilation fails here but it succeeds above. + test("await in non-primary param section 2") { + def foo[T](a0: Int)(b0: Int*) = s"a0 = $a0, b0 = ${b0.head}" + + val c = async(coroutine { () => + var i = 0 + def get = async(coroutine { () => + i += 1 + i + }) + val nilAsync = async(coroutine { () => + Nil + }) + foo[Int](await(get))(await(get) :: + await(nilAsync) : _*) + }) + assert(Await.result(c, 5 seconds) == "a0 = 1, b0 = 2") + } + + // Source: https://git.io/vrpP8 + test("await in non-primary param section with lazy 1") { + def foo[T](a: => Int)(b: Int) = b + val c = async(coroutine { () => + def get = async(coroutine { () => 0 } ) + foo[Int](???)(await(get)) + }) + assert(Await.result(c, 5 seconds) == 0) + } + + // Source: https://git.io/vrpPN + test("await in non-primary param section with lazy 2") { + def foo[T](a: Int)(b: => Int) = a + val c = async(coroutine { () => + def get = async(coroutine { () => 0 } ) + foo[Int](await(get))(???) + }) + assert(Await.result(c, 5 seconds) == 0) + } + + // Source: https://git.io/vrpXL + test("await with lazy") { + def foo[T](a: Int, b: => Int) = a + val c = async(coroutine { () => + def get = async(coroutine { () => 0 } ) + foo[Int](await(get), ???) + }) + assert(Await.result(c, 5 seconds) == 0) + } + + // Source: https://git.io/vrpXz + test("await ok in receiver") { + class Foo { def bar(a: Int)(b: Int) = a + b } + async(coroutine { () => + await(async(coroutine { () => new Foo })).bar(1)(2) + }) + } + */ + + // Source: https://git.io/vrhUF + test("named arguments respect evaluation order") { + def foo(a: Int, b: Int) = (a, b) + val c = async(coroutine { () => + var i = 0 + def next() = { + i += 1; + i + } + foo(b = next(), a = await(Future(next()))) + }) + assert(Await.result(c, 5 seconds) == (2, 1)) + } + // Source: https://git.io/vrhTe test("named and default arguments respect evaluation order") { var i = 0 @@ -369,6 +652,22 @@ class AsyncAwaitTest extends FunSuite with Matchers { assert(Await.result(c, 5 seconds) == List(1, 2, 3)) } + // Source: https://git.io/vrhTc + /** NOTE: This test currently fails compilation because of typing issues. + * Also note that `thrower` is declared outside of the line that throws the + * exception because "coroutine blueprints can only be invoked directly + * inside the coroutine." + test("await in throw") { + val thrower = await(Future(0)) + val e = intercept[Exception] { + async(coroutine { () => + throw new Exception("msg: " + thrower) + }) + } + assert(e.getMessage == "msg: 0") + } + */ + // Source: https://git.io/vrhT0 test("await in typed") { val c = async(coroutine { () => @@ -399,4 +698,87 @@ class AsyncAwaitTest extends FunSuite with Matchers { }) assert(Await.result(sign, 5 seconds) == 1.0) } + + // Source: https://git.io/vrhT6 + /** NOTE: Currently fails compilation. + test("await in implicit apply") { + val scalaBinaryVersion: String = { + val PreReleasePattern = """.*-(M|RC).*""".r + val Pattern = """(\d+\.\d+)\..*""".r + val SnapshotPattern = """(\d+\.\d+\.\d+)-\d+-\d+-.*""".r + scala.util.Properties.versionNumberString match { + case s @ PreReleasePattern(_) => s + case SnapshotPattern(v) => v + "-SNAPSHOT" + case Pattern(v) => v + case _ => "" + } + } + val toolboxClasspath: String = { + val f = new java.io.File(s"target/scala-${scalaBinaryVersion}/classes") + if (!f.exists) + sys.error(s"output directory ${f.getAbsolutePath} does not exist.") + f.getAbsolutePath + } + val mirror = scala.reflect.runtime.currentMirror + val tb = Toolbox.mkToolbox(s"-cp ${toolboxClasspath}") + val tree = tb.typeCheck(tb.parse { + """ + | import scala.language.implicitConversions + | implicit def view(a: Int): String = "" + | async(coroutine { () => + | await(Future(0)).length + | }) + """.stripMargin + }) + val applyImplicitView = tree.collect { + case x if x.getClass.getName.endsWith("ApplyImplicitView") => x + } + applyImplicitView.map(_.toString) mustStartWith List("view(a$macro$") + } + */ + + // Source: https://git.io/vrhTD + /** NOTE: Compilation currently fails. The block is typed as Unit, I believe. + test("nothing typed if") { + val result = scala.util.Try(async(coroutine { () => + if (true) { + val n = await(Future(1)) + if (n < 2) { + throw new RuntimeException("case a") + } + else { + throw new RuntimeException("case b") + } + } + else { + "case c" + } + })) + assert(result.asInstanceOf[scala.util.Failure[_]].exception.getMessage == + "case a") + } + */ + + // Source: https://git.io/vrhTS + /** NOTE: Currently fails compilation because of type errors. + test("nothing typed match") { + val result = scala.util.Try(async(coroutine { () => + 0 match { + case _ if "".isEmpty => + val n = await(Future(1)) + n match { + case _ if n < 2 => + throw new RuntimeException("case a") + case _ => + throw new RuntimeException("case b") + } + case _ => + "case c" + } + })) + + assert(result.asInstanceOf[scala.util.Failure[_]].exception.getMessage == + "case a") + } + */ } From db52258325620a3512cff6825520c5c3034455e8 Mon Sep 17 00:00:00 2001 From: Jess smith Date: Tue, 5 Jul 2016 15:26:45 -0400 Subject: [PATCH 08/23] Don't canonicalize nested methods --- src/main/scala/org/coroutines/AstCanonicalization.scala | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/main/scala/org/coroutines/AstCanonicalization.scala b/src/main/scala/org/coroutines/AstCanonicalization.scala index b2c2eca..9d30b20 100644 --- a/src/main/scala/org/coroutines/AstCanonicalization.scala +++ b/src/main/scala/org/coroutines/AstCanonicalization.scala @@ -434,15 +434,11 @@ trait AstCanonicalization[C <: Context] { (decls, q"") case q"$mods def $tname[..$tparams](...$paramss): $tpt = $rhs" => // method - val (rhsdecls, rhsident) = canonicalize(rhs) val decls = List( q""" - $mods def $tname[..$tparams](...$paramss): $tpt = { - ..$rhsdecls - $rhsident - } + $mods def $tname[..$tparams](...$paramss): $tpt = $rhs """) - new NestedContextValidator().traverse(tree) + new NestedContextValidator().traverse(rhs) (decls, q"") case q"$mods type $tpname[..$tparams] = $tpt" => // type From 9ade44399d752e5e9d6e3bbe45ba410f1ddf6a0a Mon Sep 17 00:00:00 2001 From: Jess smith Date: Tue, 5 Jul 2016 16:54:52 -0400 Subject: [PATCH 09/23] Fix failing tests involving by-name parameters --- .../org/coroutines/async-await-tests.scala | 26 ++++++++++++------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/src/test/scala/org/coroutines/async-await-tests.scala b/src/test/scala/org/coroutines/async-await-tests.scala index 10193fe..2be58b3 100644 --- a/src/test/scala/org/coroutines/async-await-tests.scala +++ b/src/test/scala/org/coroutines/async-await-tests.scala @@ -462,20 +462,28 @@ class AsyncAwaitTest extends FunSuite with Matchers { assert(result == 103) } - // Source: https://git.io/vrFj3 + /** Source: https://git.io/vrFj3 + * Modified so that there is no coroutine passed as a by-name parameter. For more + * information, see https://git.io/vKkM5. + */ test("nested await as bare expression") { val c = async(coroutine { () => - await(Future(await(Future("")).isEmpty)) + val emptyString = await(Future("")) + await(Future(emptyString.isEmpty)) }) val result = Await.result(c, 5 seconds) assert(result == true) } - // Source: https://git.io/vrAnM + /** Source: https://git.io/vrAnM + * Modified so that there is no coroutine passed as a by-name parameter. For more + * information, see https://git.io/vKkM5. + */ test("nested await in block") { val c = async(coroutine { () => () - await(Future(await(Future("")).isEmpty)) + val emptyString = await(Future("")) + await(Future(emptyString.isEmpty)) }) val result = Await.result(c, 5 seconds) assert(result == true) @@ -494,15 +502,15 @@ class AsyncAwaitTest extends FunSuite with Matchers { } */ - // Source: https://git.io/vrAlJ - /** NOTE: This test currently fails because the future times out. - * Interestingly, the future doesn't time out if `1` is passed as the first - * argument to `foo`. + /** Source: https://git.io/vrAlJ + * Modified so that there is no coroutine passed as a by-name parameter. For more + * information, see https://git.io/vKkM5. */ test("by-name expressions aren't lifted") { def foo(ignored: => Any, b: Int) = b val c = async(coroutine { () => - await(Future(foo(???, await(Future(1))))) + val innerValue = await(Future(1)) + await(Future(foo(???, innerValue))) }) val result = Await.result(c, 5 seconds) assert(result == 1) From 1e78d94283827bc9cdc7de4b40eb34195238ad37 Mon Sep 17 00:00:00 2001 From: Jess smith Date: Tue, 5 Jul 2016 17:00:23 -0400 Subject: [PATCH 10/23] Remove failing and commented-out tests --- .../org/coroutines/async-await-tests.scala | 339 ------------------ 1 file changed, 339 deletions(-) diff --git a/src/test/scala/org/coroutines/async-await-tests.scala b/src/test/scala/org/coroutines/async-await-tests.scala index 2be58b3..9208893 100644 --- a/src/test/scala/org/coroutines/async-await-tests.scala +++ b/src/test/scala/org/coroutines/async-await-tests.scala @@ -103,36 +103,6 @@ class AsyncAwaitTest extends FunSuite with Matchers { assert(result._1 == Nil) } - // Source: https://git.io/vrHmG - /** NOTE: Currently fails compilation. - test("pattern matching partial function") { - val c = async(coroutine { () => - await(Future(1)) - val a = await(Future(1)) - val f = { case x => x + a }: PartialFunction[Int, Int] - await(Future(f(2))) - }) - val res = Await.result(c, 2 seconds) - assert(res == 3) - } - */ - - // Source: https://git.io/vr79k - /** NOTE: Currently fails compilation. - test("pattern matching partial function nested") { - val c = AsyncAwaitTest.async(coroutine { () => - AsyncAwaitTest.await(Future(1)) - val neg1 = -1 - val a = AsyncAwaitTest.await(Future(1)) - val f = {case x => ({case x => neg1 * x}: - PartialFunction[Int, Int])(x + a)}: PartialFunction[Int, Int] - AsyncAwaitTest.await(Future(f(2))) - }) - val res = Await.result(c, 2 seconds) - assert(res == -3) - } - */ - // Source: https://git.io/vr7H9 test("pattern matching function") { val c = async(coroutine { () => @@ -174,37 +144,6 @@ class AsyncAwaitTest extends FunSuite with Matchers { }) } - // Source: https://git.io/vr7FE - /** NOTE: Currently fails compilation. - test("singleton type") { - class A { class B } - AsyncAwaitTest.async(coroutine { () => - val a = new A - def foo(b: a.B) = 0 - AsyncAwaitTest.await(Future(foo(new a.B))) - }) - } - */ - - - // Source: https://git.io/vr7F6 - /** NOTE: Currently fails compilation. - test("existential match") { - trait Container[+A] - case class ContainerImpl[A](value: A) extends Container[A] - def foo: Future[Container[_]] = AsyncAwaitTest.async(coroutine { () => - val a: Any = List(1) - a match { - case buf: Seq[_] => - val foo = AsyncAwaitTest.await(Future(5)) - val e0 = buf(0) - ContainerImpl(e0) - } - }) - foo - } - */ - // Source: https://git.io/vr7Fx test("existential if/else") { trait Container[+A] @@ -221,39 +160,6 @@ class AsyncAwaitTest extends FunSuite with Matchers { foo } - // Source: https://git.io/vr7bJ - /** NOTE: Currently fails compilation - test("nested method with inconsistency") { - import language.{reflectiveCalls, postfixOps} - - class Foo[A] - - object Bippy { - - import ExecutionContext.Implicits.global - - def bar(f: => Unit): Unit = f - - def quux: Future[String] = ??? - - def foo = AsyncAwaitTest.async(coroutine { () => - def r[A](m: Foo[A])(n: A) = { - bar { - locally(m) - locally(n) - identity[A] _ - } - } - - AsyncAwaitTest.await(quux) - - r(new Foo[String])("") - }) - } - Bippy - } - */ - // Source: https://git.io/vr7ba test("ticket 63 in scala/async") { object SomeExecutionContext extends ExecutionContext { @@ -306,18 +212,6 @@ class AsyncAwaitTest extends FunSuite with Matchers { assert(inner == new IntWrapper("foo")) } - // Source: https://git.io/vr7NJ - /** NOTE: This test currently fails compilation. - test("ticket 86 in scala/async-- using nested value class") { - val f = AsyncAwaitTest.async[Nothing, IntWrapper](coroutine { () => - val a = Future.successful(new IntWrapper("42")) - AsyncAwaitTest.await(Future(AsyncAwaitTest.await(a).plusStr)) - }) - val res = Await.result(f, 5 seconds) - assert(res == "42!") - } - */ - // Source: https://git.io/vr7Nk test("ticket 86 in scala/async-- using matched value class") { def doAThing(param: IntWrapper) = Future(None) @@ -387,53 +281,6 @@ class AsyncAwaitTest extends FunSuite with Matchers { assert(inner == 1) } - // Source: https://git.io/vr7NB - /** NOTE: Currently fails compilation - test("ticket 106 in scala/async-- value class") { - AsyncAwaitTest.async(coroutine { () => - "whatever value" match { - case _ => - AsyncAwaitTest.await(Future("whatever return type")) - new IntWrapper("value case matters") - } - "whatever return type" - }) - } - */ - - // Source: https://git.io/vrFQt - /** NOTE: Currently fails compilation. - test("Inlining block does not produce duplicate definition") { - AsyncAwaitTest.async(coroutine { () => - val f = 12 - val x = AsyncAwaitTest.await(Future(f)) - { - type X = Int - val x: X = 42 - println(x) - } - type X = Int - x: X - }) - } - */ - - // Source: https://git.io/vrF5X - /** NOTE: Currently fails compilation. - test("Inlining block in tail position does not produce duplication definition") { - val c = AsyncAwaitTest.async(coroutine { () => - val f = 12 - val x = AsyncAwaitTest.await(Future(f)) - { - val x = 42 - x - } - }) - val res = Await.result(c, 5 seconds) - assert(res == 42) - } - */ - // Source: https://git.io/vrFp5 test("match as expression 1") { val c = AsyncAwaitTest.async(coroutine { () => @@ -489,19 +336,6 @@ class AsyncAwaitTest extends FunSuite with Matchers { assert(result == true) } - // Source: https://git.io/vrAWm - // NOTE: Currently fails compilation - /** - test("nested await in if") { - val c: Future[Any] = async(coroutine { () => - if ("".isEmpty) { - await(Future(await(Future("")).isEmpty)) - } else 0 - }) - assert(Await.result(c, 5 seconds) == true) - } - */ - /** Source: https://git.io/vrAlJ * Modified so that there is no coroutine passed as a by-name parameter. For more * information, see https://git.io/vKkM5. @@ -531,80 +365,6 @@ class AsyncAwaitTest extends FunSuite with Matchers { assert(result == (1, 2)) } - // Source: https://git.io/vrAuv - test("await in non-primary param section 1") { - def foo(a0: Int)(b0: Int) = s"a0 = $a0, b0 = $b0" - val c = async(coroutine {() => - var i = 0 - def get = { i += 1; i } - foo(get)(await(Future(get))) - }) - assert(Await.result(c, 5 seconds) == "a0 = 1, b0 = 2") - } - - // Source: https://git.io/vrAzt - /** NOTE: All of these tests currently fail compilation because of errors about - * the yield types of the coroutines. The compiler expects the yield type of - * the coroutines `nilAsync` and `c` to be `(Future[?], Cell[?])`, but they - * are both `Nothing`. This does not work because the yield type is invariant. - * I'm not sure why compilation fails here but it succeeds above. - test("await in non-primary param section 2") { - def foo[T](a0: Int)(b0: Int*) = s"a0 = $a0, b0 = ${b0.head}" - - val c = async(coroutine { () => - var i = 0 - def get = async(coroutine { () => - i += 1 - i - }) - val nilAsync = async(coroutine { () => - Nil - }) - foo[Int](await(get))(await(get) :: - await(nilAsync) : _*) - }) - assert(Await.result(c, 5 seconds) == "a0 = 1, b0 = 2") - } - - // Source: https://git.io/vrpP8 - test("await in non-primary param section with lazy 1") { - def foo[T](a: => Int)(b: Int) = b - val c = async(coroutine { () => - def get = async(coroutine { () => 0 } ) - foo[Int](???)(await(get)) - }) - assert(Await.result(c, 5 seconds) == 0) - } - - // Source: https://git.io/vrpPN - test("await in non-primary param section with lazy 2") { - def foo[T](a: Int)(b: => Int) = a - val c = async(coroutine { () => - def get = async(coroutine { () => 0 } ) - foo[Int](await(get))(???) - }) - assert(Await.result(c, 5 seconds) == 0) - } - - // Source: https://git.io/vrpXL - test("await with lazy") { - def foo[T](a: Int, b: => Int) = a - val c = async(coroutine { () => - def get = async(coroutine { () => 0 } ) - foo[Int](await(get), ???) - }) - assert(Await.result(c, 5 seconds) == 0) - } - - // Source: https://git.io/vrpXz - test("await ok in receiver") { - class Foo { def bar(a: Int)(b: Int) = a + b } - async(coroutine { () => - await(async(coroutine { () => new Foo })).bar(1)(2) - }) - } - */ - // Source: https://git.io/vrhUF test("named arguments respect evaluation order") { def foo(a: Int, b: Int) = (a, b) @@ -660,22 +420,6 @@ class AsyncAwaitTest extends FunSuite with Matchers { assert(Await.result(c, 5 seconds) == List(1, 2, 3)) } - // Source: https://git.io/vrhTc - /** NOTE: This test currently fails compilation because of typing issues. - * Also note that `thrower` is declared outside of the line that throws the - * exception because "coroutine blueprints can only be invoked directly - * inside the coroutine." - test("await in throw") { - val thrower = await(Future(0)) - val e = intercept[Exception] { - async(coroutine { () => - throw new Exception("msg: " + thrower) - }) - } - assert(e.getMessage == "msg: 0") - } - */ - // Source: https://git.io/vrhT0 test("await in typed") { val c = async(coroutine { () => @@ -706,87 +450,4 @@ class AsyncAwaitTest extends FunSuite with Matchers { }) assert(Await.result(sign, 5 seconds) == 1.0) } - - // Source: https://git.io/vrhT6 - /** NOTE: Currently fails compilation. - test("await in implicit apply") { - val scalaBinaryVersion: String = { - val PreReleasePattern = """.*-(M|RC).*""".r - val Pattern = """(\d+\.\d+)\..*""".r - val SnapshotPattern = """(\d+\.\d+\.\d+)-\d+-\d+-.*""".r - scala.util.Properties.versionNumberString match { - case s @ PreReleasePattern(_) => s - case SnapshotPattern(v) => v + "-SNAPSHOT" - case Pattern(v) => v - case _ => "" - } - } - val toolboxClasspath: String = { - val f = new java.io.File(s"target/scala-${scalaBinaryVersion}/classes") - if (!f.exists) - sys.error(s"output directory ${f.getAbsolutePath} does not exist.") - f.getAbsolutePath - } - val mirror = scala.reflect.runtime.currentMirror - val tb = Toolbox.mkToolbox(s"-cp ${toolboxClasspath}") - val tree = tb.typeCheck(tb.parse { - """ - | import scala.language.implicitConversions - | implicit def view(a: Int): String = "" - | async(coroutine { () => - | await(Future(0)).length - | }) - """.stripMargin - }) - val applyImplicitView = tree.collect { - case x if x.getClass.getName.endsWith("ApplyImplicitView") => x - } - applyImplicitView.map(_.toString) mustStartWith List("view(a$macro$") - } - */ - - // Source: https://git.io/vrhTD - /** NOTE: Compilation currently fails. The block is typed as Unit, I believe. - test("nothing typed if") { - val result = scala.util.Try(async(coroutine { () => - if (true) { - val n = await(Future(1)) - if (n < 2) { - throw new RuntimeException("case a") - } - else { - throw new RuntimeException("case b") - } - } - else { - "case c" - } - })) - assert(result.asInstanceOf[scala.util.Failure[_]].exception.getMessage == - "case a") - } - */ - - // Source: https://git.io/vrhTS - /** NOTE: Currently fails compilation because of type errors. - test("nothing typed match") { - val result = scala.util.Try(async(coroutine { () => - 0 match { - case _ if "".isEmpty => - val n = await(Future(1)) - n match { - case _ if n < 2 => - throw new RuntimeException("case a") - case _ => - throw new RuntimeException("case b") - } - case _ => - "case c" - } - })) - - assert(result.asInstanceOf[scala.util.Failure[_]].exception.getMessage == - "case a") - } - */ } From 48c1c85c5ccbbd7bf2c4d140f879a808a63d554e Mon Sep 17 00:00:00 2001 From: Jess smith Date: Tue, 5 Jul 2016 17:08:28 -0400 Subject: [PATCH 11/23] Remove tests and changes copied from add-async-tests smithjessk/add-async-tests contains many async tests that do not pass. Some of these, along with some improvements to the code, were copied into this branch. This commit removes the changes and tests that have to do with method definitions. --- .../org/coroutines/AstCanonicalization.scala | 10 ++----- .../ast-canonicalization-tests.scala | 26 ----------------- .../org/coroutines/async-await-tests.scala | 29 ------------------- 3 files changed, 3 insertions(+), 62 deletions(-) diff --git a/src/main/scala/org/coroutines/AstCanonicalization.scala b/src/main/scala/org/coroutines/AstCanonicalization.scala index 9d30b20..8989b6d 100644 --- a/src/main/scala/org/coroutines/AstCanonicalization.scala +++ b/src/main/scala/org/coroutines/AstCanonicalization.scala @@ -432,14 +432,10 @@ trait AstCanonicalization[C <: Context] { val (rhsdecls, rhsident) = canonicalize(rhs) val decls = rhsdecls ++ List(q"$mods var $v: $tpt = $rhsident") (decls, q"") - case q"$mods def $tname[..$tparams](...$paramss): $tpt = $rhs" => + case q"$mods def $tname[..$tparams](...$paramss): $tpt = $expr" => // method - val decls = List( - q""" - $mods def $tname[..$tparams](...$paramss): $tpt = $rhs - """) - new NestedContextValidator().traverse(rhs) - (decls, q"") + new NestedContextValidator().traverse(tree) + (Nil, tree) case q"$mods type $tpname[..$tparams] = $tpt" => // type new NestedContextValidator().traverse(tree) diff --git a/src/test/scala/org/coroutines/ast-canonicalization-tests.scala b/src/test/scala/org/coroutines/ast-canonicalization-tests.scala index dc5911f..692aa31 100644 --- a/src/test/scala/org/coroutines/ast-canonicalization-tests.scala +++ b/src/test/scala/org/coroutines/ast-canonicalization-tests.scala @@ -284,30 +284,4 @@ class ASTCanonicalizationTest extends FunSuite with Matchers { assert(c1.result == 1) assert(c1.isCompleted) } - - test("should be able to define uncalled function inside coroutine") { - val oy = coroutine { () => - def foo(): String = "bar" - val bar = "bar" - 1 - } - val c = call(oy()) - assert(!c.resume) - assert(c.hasResult) - assert(c.result == 1) - assert(c.isCompleted) - } - - test("should be able to define called function inside coroutine") { - val oy = coroutine { () => - def foo(): String = "bar" - val bar = foo() - 1 - } - val c = call(oy()) - assert(!c.resume) - assert(c.hasResult) - assert(c.result == 1) - assert(c.isCompleted) - } } diff --git a/src/test/scala/org/coroutines/async-await-tests.scala b/src/test/scala/org/coroutines/async-await-tests.scala index 9208893..0b634d7 100644 --- a/src/test/scala/org/coroutines/async-await-tests.scala +++ b/src/test/scala/org/coroutines/async-await-tests.scala @@ -350,35 +350,6 @@ class AsyncAwaitTest extends FunSuite with Matchers { assert(result == 1) } - // Source: https://git.io/vrA0Q - test("evaluation order respected") { - def foo(a: Int, b: Int) = (a, b) - val c = async(coroutine { () => - var i = 0 - def next(): Int = { - i += 1 - i - } - foo(next(), await(Future(next()))) - }) - val result = Await.result(c, 5 seconds) - assert(result == (1, 2)) - } - - // Source: https://git.io/vrhUF - test("named arguments respect evaluation order") { - def foo(a: Int, b: Int) = (a, b) - val c = async(coroutine { () => - var i = 0 - def next() = { - i += 1; - i - } - foo(b = next(), a = await(Future(next()))) - }) - assert(Await.result(c, 5 seconds) == (2, 1)) - } - // Source: https://git.io/vrhTe test("named and default arguments respect evaluation order") { var i = 0 From c35b6b7b72c5d6ac0389063050be2bc241dc5f8b Mon Sep 17 00:00:00 2001 From: Jess smith Date: Tue, 5 Jul 2016 18:24:44 -0400 Subject: [PATCH 12/23] Document and simplify canonicalization of function application --- .../org/coroutines/AstCanonicalization.scala | 52 ++++++++++++------- 1 file changed, 34 insertions(+), 18 deletions(-) diff --git a/src/main/scala/org/coroutines/AstCanonicalization.scala b/src/main/scala/org/coroutines/AstCanonicalization.scala index 8989b6d..8797ca7 100644 --- a/src/main/scala/org/coroutines/AstCanonicalization.scala +++ b/src/main/scala/org/coroutines/AstCanonicalization.scala @@ -121,29 +121,45 @@ trait AstCanonicalization[C <: Context] { (decls, q"$localvarname") case q"$selector[..$tpts](...$paramss)" if tpts.length > 0 || paramss.length > 0 => // application + + /** We first need to see which parameters, if any, are by-name. Those that are + * should not be canonicalized. + */ + + // Maps to the parameter lists, saying whether or not they are by-name. val byNameParams: immutable.Seq[immutable.Seq[Boolean]] = { + /** Check that `selector` has a non-trivial symbol. If it doesn't, we assume + * that there were no by-name parameters. + */ if (selector.symbol != null && selector.symbol != NoSymbol) { - val methodSymbol = selector.symbol.asMethod - val noRepeatedParamsMap = methodSymbol.paramLists.map { paramList => - paramList.map { param => - param match { - case ts: TermSymbol => - ts.isByNameParam - case _ => - false - } - } + val paramLists = selector.symbol.asMethod.paramLists + + // This value does not contain repeated parameters. + val noRepeatedParamsSeq = paramLists.map { currentParamList => + currentParamList.map { param => param.asTerm.isByNameParam } } - if (paramss.length > 0 && noRepeatedParamsMap.length > 0) { - var repeatedParamsMap: List[List[Boolean]] = noRepeatedParamsMap - while (paramss(0).length > repeatedParamsMap(0).length) { - val newHead: List[Boolean] = repeatedParamsMap(0) :+ noRepeatedParamsMap(0).last - val newTail: List[List[Boolean]] = repeatedParamsMap.tail - repeatedParamsMap = newHead :: newTail + + // Check that parameters were both passed in to and requested by the method. + if (paramss.length > 0 && noRepeatedParamsSeq.length > 0) { + // Initially, we assume that no parameters were repeated. + var paramsSeq = noRepeatedParamsSeq + + /** If the number of passed-in parameters is greater than the number of + * parameters needed by the method, then we assume that there are repeated + * parameters. + * + * This problem is fixed by appending the last value in + * `noRepeatedParamsSeq` to `paramsSeq`. This is done until the number of + * parameters are equal. + */ + while (paramss(0).length > paramsSeq(0).length) { + val newHead = paramsSeq(0) :+ paramsSeq(0).last + val newTail = paramsSeq.tail + paramsSeq = newHead :: newTail } - repeatedParamsMap + paramsSeq } else { - noRepeatedParamsMap + noRepeatedParamsSeq } } else { immutable.Seq.fill(1, paramss(0).length)(false) From c476e7d770b3c1c5b007b5c56119ea548525192f Mon Sep 17 00:00:00 2001 From: Jess smith Date: Wed, 6 Jul 2016 15:37:42 -0400 Subject: [PATCH 13/23] Clean up and document by-name parameter transformation --- .../org/coroutines/AstCanonicalization.scala | 28 ++++++++++--------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/src/main/scala/org/coroutines/AstCanonicalization.scala b/src/main/scala/org/coroutines/AstCanonicalization.scala index 8797ca7..e8639ca 100644 --- a/src/main/scala/org/coroutines/AstCanonicalization.scala +++ b/src/main/scala/org/coroutines/AstCanonicalization.scala @@ -122,15 +122,13 @@ trait AstCanonicalization[C <: Context] { case q"$selector[..$tpts](...$paramss)" if tpts.length > 0 || paramss.length > 0 => // application - /** We first need to see which parameters, if any, are by-name. Those that are - * should not be canonicalized. - */ + // We first need to see which parameters, if any, are by-name. Those that are + // should not be canonicalized. // Maps to the parameter lists, saying whether or not they are by-name. val byNameParams: immutable.Seq[immutable.Seq[Boolean]] = { - /** Check that `selector` has a non-trivial symbol. If it doesn't, we assume - * that there were no by-name parameters. - */ + // Check that `selector` has a non-trivial symbol. If it doesn't, we assume + // that there were no by-name parameters. if (selector.symbol != null && selector.symbol != NoSymbol) { val paramLists = selector.symbol.asMethod.paramLists @@ -173,11 +171,16 @@ trait AstCanonicalization[C <: Context] { (Nil, q"$method") } for (tpt <- tpts) disallowCoroutinesIn(tpt) - type TupleType = (List[c.universe.Tree], c.universe.Tree) - val paramsByNameUnmodified: List[List[TupleType]] = { - val modifiedParamLists = mutable.Seq.fill[List[TupleType]](paramss.length)(null) + + // Canonicalize parameters that are not by-name. + val paramsByNameUnmodified = { + val modifiedParamLists = mutable.Seq.fill[ + List[(List[c.universe.Tree], c.universe.Tree)]](paramss.length)(null) + + // Canonicalize each parameter list. for (i <- 0 until paramss.length) { - val modifiedParams = mutable.Seq.fill[TupleType](paramss(i).length)(null) + val modifiedParams = mutable.Seq.fill[ + (List[c.universe.Tree], c.universe.Tree)](paramss(i).length)(null) for (j <- 0 until modifiedParams.length) { if (byNameParams(i)(j)) { modifiedParams(j) = (List(q""), paramss(i)(j)) @@ -190,9 +193,8 @@ trait AstCanonicalization[C <: Context] { modifiedParamLists.toList } val pdeclss = - paramsByNameUnmodified.map((_.map(tuple => tuple._1))).flatten.flatten.filter{ decl => - decl != q"" - } + paramsByNameUnmodified.map((_.map(tuple => tuple._1))) + .flatten.flatten.filter{ decl => decl != q"" } val pidents = paramsByNameUnmodified.map((_.map(tuple => tuple._2))) val localvarname = TermName(c.freshName("x")) val localvartree = q"val $localvarname = $newselector[..$tpts](...$pidents)" From bd1c75aeebd2ee344171ad11ae6163204f3a0e04 Mon Sep 17 00:00:00 2001 From: Jess smith Date: Wed, 6 Jul 2016 15:41:42 -0400 Subject: [PATCH 14/23] Fix implementation of test from Scala Async --- src/test/scala/org/coroutines/async-await-tests.scala | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/test/scala/org/coroutines/async-await-tests.scala b/src/test/scala/org/coroutines/async-await-tests.scala index 0b634d7..13e9799 100644 --- a/src/test/scala/org/coroutines/async-await-tests.scala +++ b/src/test/scala/org/coroutines/async-await-tests.scala @@ -230,22 +230,20 @@ class AsyncAwaitTest extends FunSuite with Matchers { } // Source: https://git.io/vr7NZ - // NOTE: Need to look at this test's implementation. Might be done incorrectly. test("ticket 86 in scala/async-- using matched parameterized value class") { def doAThing(param: ParamWrapper[String]) = Future(None) val fut = AsyncAwaitTest.async(coroutine { () => Option(new ParamWrapper("value!")) match { case Some(valueHolder) => - AsyncAwaitTest.await(Future(doAThing(valueHolder))) + AsyncAwaitTest.await(doAThing(valueHolder)) case None => None } }) val result = Await.result(fut, 5 seconds) - assert(result.asInstanceOf[Future[ParamWrapper[String]]].value == - Some(Success(None))) + assert(result == None) } // Source: https://git.io/vr7NW From 1387ecb59edd54cce35012b32dcff6f32cc06afa Mon Sep 17 00:00:00 2001 From: Jess smith Date: Mon, 11 Jul 2016 13:23:08 -0400 Subject: [PATCH 15/23] Remove dependency on scala-compiler --- project/Build.scala | 1 - 1 file changed, 1 deletion(-) diff --git a/project/Build.scala b/project/Build.scala index 43b1f7a..6f82753 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -96,7 +96,6 @@ object CoroutinesBuild extends MechaRepoBuild { "org.scalatest" % "scalatest_2.11" % "2.2.6" % "test", "org.scala-lang.modules" %% "scala-parser-combinators" % "1.0.2", "org.scala-lang" % "scala-reflect" % "2.11.4", - "org.scala-lang" % "scala-compiler" % "2.11.4", "org.scala-lang.modules" % "scala-async_2.11" % "0.9.5" % "bench" ) case _ => Nil From 1bc8dedd1451e159951c3d40ebd6bd6ec26bd9ac Mon Sep 17 00:00:00 2001 From: Jess smith Date: Mon, 11 Jul 2016 13:47:18 -0400 Subject: [PATCH 16/23] Clean up by-name modifications in AstCanonicalization --- .../scala/org/coroutines/AstCanonicalization.scala | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/main/scala/org/coroutines/AstCanonicalization.scala b/src/main/scala/org/coroutines/AstCanonicalization.scala index e8639ca..ddd2148 100644 --- a/src/main/scala/org/coroutines/AstCanonicalization.scala +++ b/src/main/scala/org/coroutines/AstCanonicalization.scala @@ -8,6 +8,7 @@ import scala.language.experimental.macros import scala.reflect.macros.whitebox.Context + /** Transforms the coroutine body into three address form with restricted control flow * that contains only try-catch statements, while loops, if-statements, value and * variable declarations, pattern matches, nested blocks and function calls. @@ -122,11 +123,10 @@ trait AstCanonicalization[C <: Context] { case q"$selector[..$tpts](...$paramss)" if tpts.length > 0 || paramss.length > 0 => // application - // We first need to see which parameters, if any, are by-name. Those that are - // should not be canonicalized. + // Detect by-name parameters and prevent their canonicalization. // Maps to the parameter lists, saying whether or not they are by-name. - val byNameParams: immutable.Seq[immutable.Seq[Boolean]] = { + val byNameParams: Seq[Seq[Boolean]] = { // Check that `selector` has a non-trivial symbol. If it doesn't, we assume // that there were no by-name parameters. if (selector.symbol != null && selector.symbol != NoSymbol) { @@ -160,7 +160,9 @@ trait AstCanonicalization[C <: Context] { noRepeatedParamsSeq } } else { - immutable.Seq.fill(1, paramss(0).length)(false) + // immutable.Seq.fill(1, paramss(0).length)(false) + for (params <- paramss) yield + for (p <- params) yield false } } val (rdecls, newselector) = selector match { From 06e28ebd519c2d8dcd65f1b4d1292d1bd3b78b3f Mon Sep 17 00:00:00 2001 From: Jess smith Date: Mon, 11 Jul 2016 14:39:38 -0400 Subject: [PATCH 17/23] Remove commented-out code --- src/main/scala/org/coroutines/AstCanonicalization.scala | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/scala/org/coroutines/AstCanonicalization.scala b/src/main/scala/org/coroutines/AstCanonicalization.scala index ddd2148..4d9eb39 100644 --- a/src/main/scala/org/coroutines/AstCanonicalization.scala +++ b/src/main/scala/org/coroutines/AstCanonicalization.scala @@ -160,7 +160,6 @@ trait AstCanonicalization[C <: Context] { noRepeatedParamsSeq } } else { - // immutable.Seq.fill(1, paramss(0).length)(false) for (params <- paramss) yield for (p <- params) yield false } From 2b576cf13088c3cb1fb34c86c3b07bbac87b041a Mon Sep 17 00:00:00 2001 From: Jess Smith Date: Mon, 11 Jul 2016 17:04:36 -0400 Subject: [PATCH 18/23] Add tests to assert that coroutines cannot be passed as by-name parameters Note that these tests currently fail. --- .../scala/org/coroutines/by-name-tests.scala | 41 +++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 src/test/scala/org/coroutines/by-name-tests.scala diff --git a/src/test/scala/org/coroutines/by-name-tests.scala b/src/test/scala/org/coroutines/by-name-tests.scala new file mode 100644 index 0000000..daaf11f --- /dev/null +++ b/src/test/scala/org/coroutines/by-name-tests.scala @@ -0,0 +1,41 @@ +package org.coroutines + + + +import org.scalatest._ + + + +class ByNameTest extends FunSuite with Matchers { + test("coroutines should not be allowed as by-name parameters 1") { + import scala.concurrent.Future + import scala.concurrent.ExecutionContext.Implicits.global + + val c1 = coroutine { () => + 5 + } + """ + val c2 = coroutine { () => + val f = Future(c1) + 10 + } + """ shouldNot typeCheck + } + + test("coroutines should not be allowed as by-name parameters 2") { + def foo(a: Int, b: => Coroutine._0[Nothing, Int], c: Int): Int = { + val instance = call(b()) + instance.resume + a + instance.result + c + } + + val c1 = coroutine { () => + 2 + } + """ + val c2 = coroutine { () => + foo(1, c1, 3) + } + """ shouldNot typeCheck + } +} From ffd0ea73e367a502d133c3c019549b501f53414a Mon Sep 17 00:00:00 2001 From: Jess Smith Date: Mon, 11 Jul 2016 17:43:56 -0400 Subject: [PATCH 19/23] Add repeated parameters test to ByNameTest --- src/test/scala/org/coroutines/by-name-tests.scala | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/test/scala/org/coroutines/by-name-tests.scala b/src/test/scala/org/coroutines/by-name-tests.scala index daaf11f..be785ae 100644 --- a/src/test/scala/org/coroutines/by-name-tests.scala +++ b/src/test/scala/org/coroutines/by-name-tests.scala @@ -38,4 +38,17 @@ class ByNameTest extends FunSuite with Matchers { } """ shouldNot typeCheck } + + test("repeated parameters") { + def foo(a: => Int, otherInts: Int*): Int = { + otherInts.foldLeft(a)((sum: Int, current: Int) => sum + current) + } + + val c = coroutine { (starter: Int) => + foo(starter, 1, 2, 3) + } + val instance = call(c(4)) + assert(!instance.resume) + assert(instance.result == 10) + } } From 28adad93e7a8cb56684c92d7b1abfd7f7be91668 Mon Sep 17 00:00:00 2001 From: Jess Smith Date: Tue, 12 Jul 2016 11:32:04 -0400 Subject: [PATCH 20/23] Add test of by-name parameter traversal Assert that they are not lifted even when they surround a non by-name parameter. --- src/test/scala/org/coroutines/by-name-tests.scala | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/test/scala/org/coroutines/by-name-tests.scala b/src/test/scala/org/coroutines/by-name-tests.scala index be785ae..a80a2ac 100644 --- a/src/test/scala/org/coroutines/by-name-tests.scala +++ b/src/test/scala/org/coroutines/by-name-tests.scala @@ -51,4 +51,15 @@ class ByNameTest extends FunSuite with Matchers { assert(!instance.resume) assert(instance.result == 10) } + + // Inspired by https://git.io/vrAlJ + test("by-name arguments aren't lifted when they surround a non by-name one") { + def foo(firstIgnored: => Any, b: Int, secondIgnored: => Any) = b + val c = coroutine { () => + foo(???, 1, { throw new Exception }) + } + val instance = call(c()) + assert(!instance.resume) + assert(instance.result == 1) + } } From b2983ad636e410b509b75bba9dd761a6337f1c35 Mon Sep 17 00:00:00 2001 From: Jess Smith Date: Tue, 12 Jul 2016 11:35:06 -0400 Subject: [PATCH 21/23] Add another by-name parameter test --- src/test/scala/org/coroutines/by-name-tests.scala | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/test/scala/org/coroutines/by-name-tests.scala b/src/test/scala/org/coroutines/by-name-tests.scala index a80a2ac..7934bb5 100644 --- a/src/test/scala/org/coroutines/by-name-tests.scala +++ b/src/test/scala/org/coroutines/by-name-tests.scala @@ -62,4 +62,14 @@ class ByNameTest extends FunSuite with Matchers { assert(!instance.resume) assert(instance.result == 1) } + + test("mixed argument lists can end in non by-name parameters") { + def foo(firstIgnored: => Any, b: Int, secondIgnored: => Any, d: Int) = { b + d } + val c = coroutine { () => + foo(???, 1, { throw new Exception }, 2) + } + val instance = call(c()) + assert(!instance.resume) + assert(instance.result == 3) + } } From 570221f550798c66f1edd834fcba2490b1bc1930 Mon Sep 17 00:00:00 2001 From: Jess smith Date: Tue, 26 Jul 2016 16:02:18 -0400 Subject: [PATCH 22/23] Pass coroutine applications by-name in ByNameTest Previously, the parameters passed by-name were unapplied coroutines. --- .../scala/org/coroutines/by-name-tests.scala | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/test/scala/org/coroutines/by-name-tests.scala b/src/test/scala/org/coroutines/by-name-tests.scala index 7934bb5..695f640 100644 --- a/src/test/scala/org/coroutines/by-name-tests.scala +++ b/src/test/scala/org/coroutines/by-name-tests.scala @@ -7,34 +7,34 @@ import org.scalatest._ class ByNameTest extends FunSuite with Matchers { - test("coroutines should not be allowed as by-name parameters 1") { + test("coroutine applications should not be allowed as by-name parameters 1") { import scala.concurrent.Future import scala.concurrent.ExecutionContext.Implicits.global - val c1 = coroutine { () => - 5 + val c1 = coroutine { (i: Int) => + i } """ val c2 = coroutine { () => - val f = Future(c1) + val f = Future(c1(4)) 10 } - """ shouldNot typeCheck + """ shouldNot typeCheck } - test("coroutines should not be allowed as by-name parameters 2") { + test("coroutine applications should not be allowed as by-name parameters 2") { def foo(a: Int, b: => Coroutine._0[Nothing, Int], c: Int): Int = { val instance = call(b()) instance.resume a + instance.result + c } - val c1 = coroutine { () => - 2 + val c1 = coroutine { (i: Int) => + i } """ val c2 = coroutine { () => - foo(1, c1, 3) + foo(1, c1(2), 3) } """ shouldNot typeCheck } From fa552572a0196797b3e42483ba93f4785306c433 Mon Sep 17 00:00:00 2001 From: Jess smith Date: Tue, 26 Jul 2016 16:03:35 -0400 Subject: [PATCH 23/23] Run NestedContextValidator on by-name parameters --- src/main/scala/org/coroutines/AstCanonicalization.scala | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/scala/org/coroutines/AstCanonicalization.scala b/src/main/scala/org/coroutines/AstCanonicalization.scala index 4d9eb39..9d62f6a 100644 --- a/src/main/scala/org/coroutines/AstCanonicalization.scala +++ b/src/main/scala/org/coroutines/AstCanonicalization.scala @@ -184,6 +184,7 @@ trait AstCanonicalization[C <: Context] { (List[c.universe.Tree], c.universe.Tree)](paramss(i).length)(null) for (j <- 0 until modifiedParams.length) { if (byNameParams(i)(j)) { + new NestedContextValidator().traverse(paramss(i)(j)) modifiedParams(j) = (List(q""), paramss(i)(j)) } else { modifiedParams(j) = canonicalize(paramss(i)(j))