From 549a656fa22af5f7f0c5e89dd6e0a19ed4b604f5 Mon Sep 17 00:00:00 2001 From: Jason Zaugg Date: Fri, 23 Oct 2015 16:25:02 +1000 Subject: 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. --- src/test/scala/scala/async/TreeInterrogation.scala | 2 +- .../scala/scala/async/run/late/LateExpansion.scala | 337 ++++++++++++++++++++- 2 files changed, 324 insertions(+), 15 deletions(-) (limited to 'src/test') 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 { }` +@field final class lateasync extends StaticAnnotation // Calls to methods with this annotation are translated to `AsyncId.await()` +@getter final class autoawait extends StaticAnnotation -- cgit v1.2.3