aboutsummaryrefslogtreecommitdiff
path: root/src/test
diff options
context:
space:
mode:
authorJason Zaugg <jzaugg@gmail.com>2015-10-23 16:25:02 +1000
committerJason Zaugg <jzaugg@gmail.com>2016-01-19 14:27:31 +1000
commit549a656fa22af5f7f0c5e89dd6e0a19ed4b604f5 (patch)
tree7a7c778a24143923a674acb9db30ecdc5e3f8f5e /src/test
parent634c454dbd546e2f3db6321b4047b3cebd2f899a (diff)
downloadscala-async-549a656fa22af5f7f0c5e89dd6e0a19ed4b604f5.tar.gz
scala-async-549a656fa22af5f7f0c5e89dd6e0a19ed4b604f5.tar.bz2
scala-async-549a656fa22af5f7f0c5e89dd6e0a19ed4b604f5.zip
Various fixes to late expansion
- Detect cross-state symbol references where the RefTree is nested in a LabelDef. Failure to do so led to ill-scoped local variable references which sometimes manifest as VerifyErrors. - Emit a default case in the Match intended to be a tableswitch. We have to do this ourselves if we expand after pattern matcher - Cleanup generated code to avoid redundant blocks - Avoid unnecessary `matchRes` temporary variable for unit-typed pattern matches - Fix the trace level logging in the ANF transform to restore indented output. - Emit `{ state = nextState; ... }` rather than `try { ... } finally { state = nextState }` in state handlers. This simplifies generated code and has the same meaning, as the code in the state machine isn't reentrant and can't observe the "early" transition of the state.
Diffstat (limited to 'src/test')
-rw-r--r--src/test/scala/scala/async/TreeInterrogation.scala2
-rw-r--r--src/test/scala/scala/async/run/late/LateExpansion.scala337
2 files changed, 324 insertions, 15 deletions
diff --git a/src/test/scala/scala/async/TreeInterrogation.scala b/src/test/scala/scala/async/TreeInterrogation.scala
index 09fa69e..1637102 100644
--- a/src/test/scala/scala/async/TreeInterrogation.scala
+++ b/src/test/scala/scala/async/TreeInterrogation.scala
@@ -54,7 +54,7 @@ class TreeInterrogation {
}
object TreeInterrogation extends App {
- def withDebug[T](t: => T) {
+ def withDebug[T](t: => T): T = {
def set(level: String, value: Boolean) = System.setProperty(s"scala.async.$level", value.toString)
val levels = Seq("trace", "debug")
def setAll(value: Boolean) = levels.foreach(set(_, value))
diff --git a/src/test/scala/scala/async/run/late/LateExpansion.scala b/src/test/scala/scala/async/run/late/LateExpansion.scala
index b866527..a40b1af 100644
--- a/src/test/scala/scala/async/run/late/LateExpansion.scala
+++ b/src/test/scala/scala/async/run/late/LateExpansion.scala
@@ -3,10 +3,12 @@ package scala.async.run.late
import java.io.File
import junit.framework.Assert.assertEquals
-import org.junit.Test
+import org.junit.{Assert, Test}
import scala.annotation.StaticAnnotation
-import scala.async.internal.{AsyncId, AsyncMacro}
+import scala.annotation.meta.{field, getter}
+import scala.async.TreeInterrogation
+import scala.async.internal.AsyncId
import scala.reflect.internal.util.ScalaClassLoader.URLClassLoader
import scala.tools.nsc._
import scala.tools.nsc.plugins.{Plugin, PluginComponent}
@@ -16,6 +18,7 @@ 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(
"""
@@ -75,6 +78,263 @@ class LateExpansion {
assertEquals("case 3: blerg3", result)
}
+ @Test def polymorphicMethod(): Unit = {
+ val result = run(
+ """
+ |import scala.async.run.late.{autoawait,lateasync}
+ |object Test {
+ | class C { override def toString = "C" }
+ | @autoawait def foo[A <: C](a: A): A = a
+ | @lateasync
+ | def test1[CC <: C](c: CC): (CC, CC) = {
+ | val x: (CC, CC) = 0 match { case _ if false => ???; case _ => (foo(c), foo(c)) }
+ | x
+ | }
+ | def test(): (C, C) = test1(new C)
+ |}
+ | """.stripMargin)
+ assertEquals("(C,C)", result.toString)
+ }
+
+ @Test def shadowing(): Unit = {
+ val result = run(
+ """
+ |import scala.async.run.late.{autoawait,lateasync}
+ |object Test {
+ | trait Foo
+ | trait Bar extends Foo
+ | @autoawait def boundary = ""
+ | @lateasync
+ | def test: Unit = {
+ | (new Bar {}: Any) match {
+ | case foo: Bar =>
+ | boundary
+ | 0 match {
+ | case _ => foo; ()
+ | }
+ | ()
+ | }
+ | ()
+ | }
+ |}
+ | """.stripMargin)
+ }
+
+ @Test def shadowing0(): Unit = {
+ val result = run(
+ """
+ |import scala.async.run.late.{autoawait,lateasync}
+ |object Test {
+ | trait Foo
+ | trait Bar
+ | def test: Any = test(new C)
+ | @autoawait def asyncBoundary: String = ""
+ | @lateasync
+ | def test(foo: Foo): Foo = foo match {
+ | case foo: Bar =>
+ | val foo2: Foo with Bar = new Foo with Bar {}
+ | asyncBoundary
+ | null match {
+ | case _ => foo2
+ | }
+ | case other => foo
+ | }
+ | class C extends Foo with Bar
+ |}
+ | """.stripMargin)
+ }
+ @Test def shadowing2(): Unit = {
+ val result = run(
+ """
+ |import scala.async.run.late.{autoawait,lateasync}
+ |object Test {
+ | trait Base; trait Foo[T <: Base] { @autoawait def func: Option[Foo[T]] = None }
+ | class Sub extends Base
+ | trait Bar extends Foo[Sub]
+ | def test: Any = test(new Bar {})
+ | @lateasync
+ | def test[T <: Base](foo: Foo[T]): Foo[T] = foo match {
+ | case foo: Bar =>
+ | val res = foo.func
+ | res match {
+ | case _ =>
+ | }
+ | foo
+ | case other => foo
+ | }
+ | test(new Bar {})
+ |}
+ | """.stripMargin)
+ }
+
+ @Test def patternAlternative(): Unit = {
+ val result = wrapAndRun(
+ """
+ | @autoawait def one = 1
+ |
+ | @lateasync def test = {
+ | Option(true) match {
+ | case null | None => false
+ | case Some(v) => one; v
+ | }
+ | }
+ | """.stripMargin)
+ }
+
+ @Test def patternAlternativeBothAnnotations(): Unit = {
+ val result = wrapAndRun(
+ """
+ |import scala.async.run.late.{autoawait,lateasync}
+ |object Test {
+ | @autoawait def func1() = "hello"
+ | @lateasync def func(a: Option[Boolean]) = a match {
+ | case null | None => func1 + " world"
+ | case _ => "okay"
+ | }
+ | def test: Any = func(None)
+ |}
+ | """.stripMargin)
+ }
+
+ @Test def shadowingRefinedTypes(): Unit = {
+ val result = run(
+ s"""
+ |import scala.async.run.late.{autoawait,lateasync}
+ |trait Base
+ |class Sub extends Base
+ |trait Foo[T <: Base] {
+ | @autoawait def func: Option[Foo[T]] = None
+ |}
+ |trait Bar extends Foo[Sub]
+ |object Test {
+ | @lateasync def func[T <: Base](foo: Foo[T]): Foo[T] = foo match { // the whole pattern match will be wrapped with async{ }
+ | case foo: Bar =>
+ | val res = foo.func // will be rewritten into: await(foo.func)
+ | res match {
+ | case Some(v) => v // this will report type mismtach
+ | case other => foo
+ | }
+ | case other => foo
+ | }
+ | def test: Any = { val b = new Bar{}; func(b) == b }
+ |}""".stripMargin)
+ assertEquals(true, result)
+ }
+
+ @Test def testMatchEndIssue(): Unit = {
+ val result = run(
+ """
+ |import scala.async.run.late.{autoawait,lateasync}
+ |sealed trait Subject
+ |final class Principal(val name: String) extends Subject
+ |object Principal {
+ | def unapply(p: Principal): Option[String] = Some(p.name)
+ |}
+ |object Test {
+ | @autoawait @lateasync
+ | def containsPrincipal(search: String, value: Subject): Boolean = value match {
+ | case Principal(name) if name == search => true
+ | case Principal(name) => containsPrincipal(search, value)
+ | case other => false
+ | }
+ |
+ | @lateasync
+ | def test = containsPrincipal("test", new Principal("test"))
+ |}
+ | """.stripMargin)
+ }
+
+ @Test def testGenericTypeBoundaryIssue(): Unit = {
+ val result = run(
+ """
+ import scala.async.run.late.{autoawait,lateasync}
+ trait InstrumentOfValue
+ trait Security[T <: InstrumentOfValue] extends InstrumentOfValue
+ class Bound extends Security[Bound]
+ class Futures extends Security[Futures]
+ object TestGenericTypeBoundIssue {
+ @autoawait @lateasync def processBound(bound: Bound): Unit = { println("process Bound") }
+ @autoawait @lateasync def processFutures(futures: Futures): Unit = { println("process Futures") }
+ @autoawait @lateasync def doStuff(sec: Security[_]): Unit = {
+ sec match {
+ case bound: Bound => processBound(bound)
+ case futures: Futures => processFutures(futures)
+ case _ => throw new Exception("Unknown Security type: " + sec)
+ }
+ }
+ }
+ """.stripMargin)
+ }
+
+ @Test def testReturnTupleIssue(): Unit = {
+ val result = run(
+ """
+ import scala.async.run.late.{autoawait,lateasync}
+ class TestReturnExprIssue(str: String) {
+ @autoawait @lateasync def getTestValue = Some(42)
+ @autoawait @lateasync def doStuff: Int = {
+ val opt: Option[Int] = getTestValue // here we have an async method invoke
+ opt match {
+ case Some(li) => li // use the result somehow
+ case None =>
+ }
+ 42 // type mismatch; found : AnyVal required: Int
+ }
+ }
+ """.stripMargin)
+ }
+
+
+ @Test def testAfterRefchecksIssue(): Unit = {
+ val result = run(
+ """
+ import scala.async.run.late.{autoawait,lateasync}
+ trait Factory[T] { def create: T }
+ sealed trait TimePoint
+ class TimeLine[TP <: TimePoint](val tpInitial: Factory[TP]) {
+ @autoawait @lateasync private[TimeLine] val tp: TP = tpInitial.create
+ @autoawait @lateasync def timePoint: TP = tp
+ }
+ object Test {
+ def test: Unit = ()
+ }
+ """)
+ }
+
+ @Test def testArrayIndexOutOfBoundIssue(): Unit = {
+ val result = run(
+ """
+ import scala.async.run.late.{autoawait,lateasync}
+
+ sealed trait Result
+ case object A extends Result
+ case object B extends Result
+ case object C extends Result
+
+ object Test {
+ protected def doStuff(res: Result) = {
+ class C {
+ @autoawait def needCheck = false
+
+ @lateasync def m = {
+ if (needCheck) "NO"
+ else {
+ res match {
+ case A => 1
+ case _ => 2
+ }
+ }
+ }
+ }
+ }
+
+
+ @lateasync
+ def test() = doStuff(B)
+ }
+ """)
+ }
+
def wrapAndRun(code: String): Any = {
run(
s"""
@@ -88,10 +348,49 @@ class LateExpansion {
| """.stripMargin)
}
+
+ @Test def testNegativeArraySizeException(): Unit = {
+ val result = run(
+ """
+ import scala.async.run.late.{autoawait,lateasync}
+
+ object Test {
+ def foo(foo: Any, bar: Any) = ()
+ @autoawait def getValue = 4.2
+ @lateasync def func(f: Any) = {
+ foo(f match { case _ if "".isEmpty => 2 }, getValue);
+ }
+
+ @lateasync
+ def test() = func(4)
+ }
+ """)
+ }
+ @Test def testNegativeArraySizeExceptionFine1(): Unit = {
+ val result = run(
+ """
+ import scala.async.run.late.{autoawait,lateasync}
+ case class FixedFoo(foo: Int)
+ class Foobar(val foo: Int, val bar: Double) {
+ @autoawait @lateasync def getValue = 4.2
+ @autoawait @lateasync def func(f: Any) = {
+ new Foobar(foo = f match {
+ case FixedFoo(x) => x
+ case _ => 2
+ },
+ bar = getValue)
+ }
+ }
+ object Test {
+ @lateasync def test() = new Foobar(0, 0).func(4)
+ }
+ """)
+ }
def run(code: String): Any = {
val reporter = new StoreReporter
val settings = new Settings(println(_))
- settings.outdir.value = sys.props("java.io.tmpdir")
+ // settings.processArgumentString("-Xprint:patmat,postpatmat,jvm -Ybackend:GenASM -nowarn")
+ settings.outdir.value = "/tmp"
settings.embeddedDefaults(getClass.getClassLoader)
val isInSBT = !settings.classpath.isSetByUser
if (isInSBT) settings.usejavacp.value = true
@@ -108,8 +407,10 @@ class LateExpansion {
val run = new Run
val source = newSourceFile(code)
- run.compileSources(source :: Nil)
- assert(!reporter.hasErrors, reporter.infos.mkString("\n"))
+// TreeInterrogation.withDebug {
+ run.compileSources(source :: Nil)
+// }
+ Assert.assertTrue(reporter.infos.mkString("\n"), !reporter.hasErrors)
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)
@@ -133,20 +434,26 @@ abstract class LatePlugin extends Plugin {
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 sel@Select(fun, _) if sel.symbol.hasAnnotation(autoAwaitSym) && !(tree.tpe.isInstanceOf[MethodTypeApi] || tree.tpe.isInstanceOf[PolyTypeApi] ) =>
+ localTyper.typed(Apply(TypeApply(gen.mkAttributedRef(asyncIdSym.typeOfThis, awaitSym), TypeTree(sel.tpe) :: Nil), sel :: 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))
+ deriveDefDef(dd){ rhs: Tree =>
+ val invoke = Apply(TypeApply(gen.mkAttributedRef(asyncIdSym.typeOfThis, asyncSym), TypeTree(rhs.tpe) :: Nil), List(rhs))
+ localTyper.typed(atPos(dd.pos)(invoke))
+ }
}
+ case vd: ValDef if vd.symbol.hasAnnotation(lateAsyncSym) => atOwner(vd.symbol) {
+ deriveValDef(vd){ rhs: Tree =>
+ val invoke = Apply(TypeApply(gen.mkAttributedRef(asyncIdSym.typeOfThis, asyncSym), TypeTree(rhs.tpe) :: Nil), List(rhs))
+ localTyper.typed(atPos(vd.pos)(invoke))
+ }
+ }
+ case vd: ValDef =>
+ vd
case x => x
}
}
}
-
override def newPhase(prev: Phase): Phase = new StdPhase(prev) {
override def apply(unit: CompilationUnit): Unit = {
val translated = newTransformer(unit).transformUnit(unit)
@@ -155,7 +462,7 @@ abstract class LatePlugin extends Plugin {
}
}
- override val runsAfter: List[String] = "patmat" :: Nil
+ override val runsAfter: List[String] = "refchecks" :: Nil
override val phaseName: String = "postpatmat"
})
@@ -164,7 +471,9 @@ abstract class LatePlugin extends Plugin {
}
// Methods with this annotation are translated to having the RHS wrapped in `AsyncId.async { <original RHS> }`
+@field
final class lateasync extends StaticAnnotation
// Calls to methods with this annotation are translated to `AsyncId.await(<call>)`
+@getter
final class autoawait extends StaticAnnotation