2

Disclaimer: this code is not of practical use and serves education purposes only.

tl;dr: most of it is just a result of me trying to debug the issue, so only first 3 snippets matter.

Here is the macro definition:

def tx[T](ds: GraphDatabaseService)(block: => T): Option[T] = 
  macro txmacros.blockTxImpl[T]

Here is the implementation:

def blockTxImpl[T: c.WeakTypeTag](c: whitebox.Context)(ds: c.Tree)(block: c.Tree):
    c.Tree = {
  import c.universe._

  q"""
    val tx = $ds.beginTx()
    val newRetVal = try {
      val retVal = {
        $block
      }
      tx.success()
      Option(retVal)
    } catch {
      case _ : Throwable =>
        tx.failure()
        None
    } finally {
      tx.close()
    }
    newRetVal
  """
}

And here is how it's called:

val nodePropK5 = tx(db) {
  // simplified for brevity
  val node = db.find(label, "key", 100).iterator().next()
  node.getProperty("k5", 300)
}

nodePropK5 should be (Some(200))

The whole project can be found to https://github.com/cdshines/txMacro/ (ready to be built an run).

Such an invocation fails with the following message:

[error] symbol value node does not exist in MacroTest$$anonfun$3.apply$mcV$sp
[trace] Stack trace suppressed: run last core/test:compile for the full output.
[error] (core/test:compile) scala.reflect.internal.FatalError: symbol value node does not exist in MacroTest$$anonfun$3.apply$mcV$sp

If I, however, change the problematic code to

val nodePropK5 = tx(db) {
  db.findNodesByLabelAndProperty(label, "k4", 100).iterator().next().getProperty("k5", 300)
}

the return value is Some(300) as expected. Adding lines that don't declare new variables (or use node) doesn't break the behavior, while

val nodePropK5 = tx(db) {
  db.findNodesByLabelAndProperty(label, "k4", 100).iterator().next().getProperty("k5", 300)
  val x = 5
  x
}

results in the same message.

Another peculiar thing: if I print the failing block during macro expansion, i get the following code:

{
  val tx = MacroTest.this.db.beginTx();
  val newRetVal = try {
    val retVal = {
      val node: org.neo4j.graphdb.Node = MacroTest.this.db.findNodesByLabelAndProperty(MacroTest.this.label, "k4", 100).iterator().next();
      node.getProperty("k5", 300)
    };
    tx.success();
    Option(retVal)
  } catch {
    case (_: Throwable) => {
      tx.failure();
      None
    }
  } finally tx.close();
  newRetVal
}

Which, being manually substituted, works just fine.

What am I missing here? Am I free to assume that this is a compiler bug?

tkroman
  • 4,811
  • 1
  • 26
  • 46

1 Answers1

2

When you see this kind of error the first thing to try is un-typechecking code that you're "reusing" in your macro output. In this case replacing the following:

val retVal = {
  $block
}

With:

val retVal = {
  ${ c.untypecheck(block) }
}

Should do the trick.

Note that in 2.10 the equivalent of untypecheck was resetAllAttrs / resetLocalAttrs. If you search for those names you'll find a lot of discussion of the details of the problem you're seeing.

Travis Brown
  • 138,631
  • 12
  • 375
  • 680
  • There is still an interesting thing to ponder on for me: why does annotation macro work without 'untypechecking'? Is it somehow related to the fact that def macro is typed and annotation macro isn't in this case? – tkroman Aug 22 '14 at 16:38
  • Yes, arguments of def macros are typechecked, whereas arguments of macro annotations are not. – Eugene Burmako Aug 25 '14 at 07:28
  • 1
    Also, more on the untypechecking problems/workarounds: https://github.com/scalamacros/macrology201/tree/part1. – Eugene Burmako Aug 25 '14 at 07:28