2

I am writing a Scala macro (Scala 2.11) where I'd like to obtain the tree representing an implicit variable inside the macro using inferImplicitValue, evaluate that syntax tree, and use the value. I have actually done this, but it doesn't seem to work in all circumstances[1]. I constructed a simplified example where it fails.

// a class for implicit evidence
class DemoEvidence(val value: Int)

// define 'foo' method for invoking the macro
object demoModule {
  def foo: Int = macro DemoMacros.fooImpl
}

class DemoMacros(val c: whitebox.Context) {
  import c.universe._

  def fooImpl: Tree = {
    val vInt = try {
      // get the tree representing the implicit value
      val impl = c.inferImplicitValue(typeOf[DemoEvidence], silent = false)
      // print it out
      println(s"impl= $impl")
      // try to evaluate the tree (this is failing)
      val eval = c.eval(c.Expr[DemoEvidence](c.untypecheck(impl.duplicate)))
      eval.value
    } catch {
      case e: Throwable => {
        // on failure print out the failure message
        println(s"Eval failed with: $e\nStack trace:\n${e.printStackTrace}")
        0
      }
    }
    q"$vInt"  // return tree representing the integer value
  }
}

If I compile the above, and then invoke it:

object demo {
  implicit val demoEvidence: DemoEvidence = new DemoEvidence(42)
  val i: Int = demoModule.foo
}

I see the compilation fail in the following way:

impl= demo.this.demoEvidence
java.lang.reflect.InvocationTargetException
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at scala.tools.reflect.ToolBoxFactory$ToolBoxImpl$ToolBoxGlobal$$anonfun$compile$1.apply(ToolBoxFactory.scala:275)
...

Full output at: https://gist.github.com/erikerlandson/df48f64329be6ab9de9caef5f5be4a83

So, you can see it is finding the tree for the declared implicit value demo.this.demoEvidence, but evaluation of that tree is failing. I have seen this basic approach work elsewhere in my project. Not sure what the difference is, and why it fails here.

[1] UPDATE: If the implicit value is defined in a (sub)project, and compiled, and then used exterior to that project, it works as expected. That was the case where this approach is working for me.

So the question is whether that's just a fundamental constraint I have to live with, or if there is some clever workaround, or if this is a "bug" with inferring implicit values inside macros that might be fixed.

UPDATE: I filed a Scala issue for this: https://github.com/scala/scala-dev/issues/353

eje
  • 945
  • 11
  • 22
  • You could at least include stack trace as well as the exception's `toString`. – Alexey Romanov Mar 30 '17 at 06:45
  • I wasn't getting any trace. But I'll try removing the try/catch and see what I get – eje Mar 30 '17 at 13:38
  • 1
    Or just use `e.printStackTrace` in your `catch`. – Alexey Romanov Mar 30 '17 at 13:44
  • I updated with call to `printStackTrace`; full output is here: https://gist.github.com/erikerlandson/df48f64329be6ab9de9caef5f5be4a83 Weirdly, it now dumps the full stack _before_ it prints the error (and the stack trace on the error is empty) – eje Mar 30 '17 at 15:30
  • One difference between this failing scenario and the scenario where it is working, is that here I am declaring the implicit value, and invoking the macro that looks for it, in the same block of code `object demo`. In the working scenario, implicits are declared and compiled _before_ the macros that look for them are invoked – eje Mar 30 '17 at 15:33
  • I verified that _if_ the implicit value is defined in a (sub)project, and compiled, and then used exterior to that project, it works as expected. So the question is whether that's just a fundamental constraint or if there is any clever workaround. – eje Mar 30 '17 at 20:44
  • I'm not sure about the underlying issue, but "untypecheck" is known to corrupt trees sometimes...http://www.scala-lang.org/api/current/scala-reflect/scala/reflect/macros/Typers.html#untypecheck(tree:Typers.this.Tree):Typers.this.Tree – dk14 Apr 03 '17 at 10:20

1 Answers1

1

From the look of the stack trace, the eval is expecting object demo to exist in classfile form for execution, which makes sense given that the value you're trying to compute depends on val demoEvidence which is a member of object demo.

But the eval is happening during the typechecking of object demo so the classfile doesn't exist yet, hence the error. In the version with the implicit value defined in a subproject I imagine the subproject is compiled first, hence the classfiles required for the eval exist and so evaluation proceeds as you were expecting.

Miles Sabin
  • 23,015
  • 6
  • 61
  • 95
  • I can see how that causes a problem. It makes `inferImplicitValue` a bit asymmetric with regular implicit parameters, since just declaring `foo` with an implicit parameter _does_ work in that scenario. In a perfect world, I was hoping that `inferImplicitValue` would return a tree equivalent to `q"new DemoEvidence(42)"` inside the macro. In theory it seems like that should be available somewhere in the current macro context. Unsure how feasible that would be. – eje Apr 03 '17 at 20:02
  • In the non macro case you've just described there's no call to `eval` so no need for code to be compiled for execution ... the problem is all in the `eval`. – Miles Sabin Apr 05 '17 at 10:25