storm-enroute / coroutines

Scala coroutines implementation.
http://storm-enroute.com/coroutines
BSD 3-Clause "New" or "Revised" License
163 stars 20 forks source link

Macro error when expanding partial function #19

Open smithjessk opened 8 years ago

smithjessk commented 8 years ago

Demo code: https://git.io/vrHmh

As recommended in #15, I'm porting some of the tests from Async into Coroutines. I translated this test and discovered a NullPointerException during compilation. A truncated SBT dump is at the bottom of this issue. I believe this may be related to @retronym's comment in #15. I will investigate this more soon; just wanted to go ahead and bring the issue up.

> test
[info] Compiling 1 Scala source to /home/jess/projects/coroutines/target/scala-2.11/test-classes...
[info] Run completed in 20 milliseconds.
[info] Total number of tests run: 0
[info] Suites: completed 0, aborted 0
[info] Tests: succeeded 0, failed 0, canceled 0, ignored 0, pending 0
[info] No tests were executed.
[error] /home/jess/projects/coroutines/src/test/scala/org/coroutines/async-await-tests.scala:65: exception during macro expansion: 
[error] java.lang.NullPointerException
[error]     at org.coroutines.Analyzer$class.isCoroutineDefMarker(Analyzer.scala:328)
[error]     at org.coroutines.Synthesizer.isCoroutineDefMarker(Synthesizer.scala:15)
[error]     at org.coroutines.ThreeAddressFormTransformation$NestedContextValidator.traverse(ThreeAddressFormTransformation.scala:48)
[error]     at scala.reflect.internal.Trees$class.traverseComponents$1(Trees.scala:1232)
[error]     at scala.reflect.internal.Trees$class.itraverse(Trees.scala:1323)
[error]     at scala.reflect.internal.SymbolTable.itraverse(SymbolTable.scala:16)
[error]     at scala.reflect.internal.SymbolTable.itraverse(SymbolTable.scala:16)
[error]     at scala.reflect.api.Trees$Traverser.traverse(Trees.scala:2475)
[error]     at org.coroutines.ThreeAddressFormTransformation$NestedContextValidator.traverse(ThreeAddressFormTransformation.scala:62)
[error]     at scala.reflect.api.Trees$Traverser.traverseTrees(Trees.scala:2484)
[error]     at scala.reflect.api.Trees$Traverser.traverseCases(Trees.scala:2487)
[error]     at scala.reflect.internal.Trees$class.traverseComponents$1(Trees.scala:1258)
[error]     at scala.reflect.internal.Trees$class.itraverse(Trees.scala:1323)
[error]     at scala.reflect.internal.SymbolTable.itraverse(SymbolTable.scala:16)
[error]     at scala.reflect.internal.SymbolTable.itraverse(SymbolTable.scala:16)
smithjessk commented 8 years ago

This call to isCoroutineDefMarker is problematic because typer.typeOf(co) == null. Thus, I doubt that the issue is related to @retronym's comment.

smithjessk commented 8 years ago

This test generates this problematic rawlambda:

raw = (() => {
  val f: PartialFunction[Int,Int] = (({
    class $anonfun extends scala.runtime.AbstractPartialFunction[Int,Int] with Serializable {
      def <init>(): <$anon: Int => Int> = {
        $anonfun.super.<init>();
        ()
      };
      final override def applyOrElse[A1 <: Int, B1 >: Int](x1: A1, default: A1 => B1): B1 = ((x1.asInstanceOf[Int]: Int): Int @unchecked) match {
        case (x @ _) => x.+(1)
        case (defaultCase$ @ _) => default.apply(x1)
      };
      final def isDefinedAt(x1: Int): Boolean = ((x1.asInstanceOf[Int]: Int): Int @unchecked) match {
        case (x @ _) => true
        case (defaultCase$ @ _) => false
      }
    };
    new $anonfun()
  }: PartialFunction[Int,Int]): PartialFunction[Int,Int]);
  AsyncAwaitTest.await[Int].apply[(scala.concurrent.Future[Int], org.coroutines.AsyncAwaitTest.Cell[Int]), Int](scala.concurrent.Future.apply[Int](f.apply(2))(scala.concurrent.ExecutionContext.Implicits.global))(Predef.this.=:=.tpEquals[((scala.concurrent.Future[Int], org.coroutines.AsyncAwaitTest.Cell[Int]), Int)])
})

Canonicalization fails when going over default.apply(x1).

typer.typeOf(co) == null because default is never added to typer.treeMapping and co.tpe == null. default is never added because this case does not cause traversal of the second cases inside the above partial functions.

axel22 commented 8 years ago

Can you extend traverser to correctly cover the other cases?

smithjessk commented 8 years ago

Yes! I'm working on it in this branch.

smithjessk commented 8 years ago

It is not the second case that is dropped by the quasiquotes. Rather, it is the default case, e.g.:

case (defaultCase$ @ _) => default.apply(x1)

For instance, see that this partial function:

val f = { 
  case x: Int => x + 1
  case x: String => x.length 
}: PartialFunction[Any, Int]

has the following values for cs0 and cs1, respectively:

List(case (x @ (_: scala.Int)) => x.+(1), case (x @ (_: scala.this.Predef.String)) => x.length())
List(case (x @ (_: Int)) => x.+(1), case (x @ (_: String)) => x.length())

Thus, we might want to stop canonicalization for the default cases. What do you think?

axel22 commented 8 years ago

No, that's not the problem. Add a println statement to case _ in TraverserUtil. The anonymous partial function class was not traversed by it for some reason. Find out why, fix TraverserUtil, and this should work.

smithjessk commented 8 years ago

I think I'm misunderstanding your statement.

The partial function case matches on the anonymous partial function. This call to traverse is caused by the ascription case, in which e0 is the anonymous class that extends AbstractPartialFunction.

println("t0 = " + t0) inside the PF case prints the following output:

t0 = ({
  final <synthetic> class $anonfun extends scala.runtime.AbstractPartialFunction[Int,Int] with Serializable {
    def <init>() = {
      super.<init>();
      ()
    };
    final override def applyOrElse[A1 <: Int, B1 >: Int](x1: A1, default: scala.this.Function1[A1, B1]) = ((x1.asInstanceOf[Int]: Int): (x1.asInstanceOf[Int]: Int): @scala.unchecked) match {
      case (x @ _) => x.+(1)
      case (defaultCase$ @ _) => default.apply(x1)
    };
    final def isDefinedAt(x1: Int): Boolean = ((x1.asInstanceOf[Int]: Int): (x1.asInstanceOf[Int]: Int): @scala.unchecked) match {
      case (x @ _) => true
      case (defaultCase$ @ _) => false
    }
  };
  new $anonfun()
}: PartialFunction[Int,Int])

However, defaultCase is not a part of cs0.