简体   繁体   中英

Scala: what can code in Context.eval reference?

It seems that the input of Context.eval can reference only values from different compilation unit:

// project 1
object Z {

  val foo = "WOOF"

  def impl(c: Context)(x: c.Expr[String]) = {
    val x1 = c.Expr[String](c.untypecheck(x.tree.duplicate))
    println(s"compile-time value is: ${c.eval(x1)}")
    x
  }
  def test(x: String) = macro impl
}

// project 2
object Y {
  val foo = "GOOF"
  val boo = Z.test(Z.foo)
}


println(Y.boo)

prints out "WOOF" , but if I replace boo with val boo = Z.test(Y.foo) , I get the following compilation error:

Error:(32, 29) exception during macro expansion:
java.lang.ClassNotFoundException: Y$
at scala.reflect.internal.util.AbstractFileClassLoader.findClass(AbstractFileClassLoader.scala:72)
...

Is there any way around this problem? I know that the queries defined with quill.io can reference methods from the same scope, but I wasn't able to find the trick they use to allow it.

Context#eval can't evaluate runtime values. It's written in its scaladoc: https://github.com/scala/scala/blob/2.13.x/src/reflect/scala/reflect/macros/Evals.scala#L61-L67

Let's modify your macro

def impl(c: blackbox.Context)(x: c.Expr[String]): c.Expr[String] = {
  import c.universe._
  println(s"input: ${showRaw(x.tree)}") // added
  val x1 = c.Expr[String](c.untypecheck(x.tree.duplicate))
  println(s"compile-time value is: ${c.eval(x1)}")
  x
}

Then we'll have

object App {
  /*class*/ object Y {
    val foo = "GOOF"

    val boo = Z.test(Z.foo)//Warning:scalac: input: Select(Select(Ident(Macros), Macros.Z), TermName("foo"))
                           //Warning:scalac: compile-time value is: WOOF
//  val boo1 = Z.test(Y.foo)//Warning:scalac: input: Select(Select(This(TypeName("App")), App.Y), TermName("foo"))
                            //Error: exception during macro expansion:  
                            //  java.lang.ClassNotFoundException: App$Y$
//  val boo2 = Z.test((new Y).foo)//Warning:scalac: input: Select(Apply(Select(New(Select(This(TypeName("App")), App.Y)), termNames.CONSTRUCTOR), List()), TermName("foo"))
                                  //Error: exception during macro expansion: 
                                  //  java.lang.ClassNotFoundException: App$Y
//  val boo3 = Z.test(foo) //Warning:scalac: input: Select(This(TypeName("Y")), TermName("foo"))
                           //Error: exception during macro expansion:
                           //  scala.tools.reflect.ToolBoxError: reflective compilation has failed:
                           //    Internal error: unable to find the outer accessor symbol of object __wrapper$1$fd3cb1297ce8421e809ee5e821c2f708
                     // or
                           //Error: exception during macro expansion:  
                           //  java.lang.ClassNotFoundException: App$Y$
    val boo4 = Z.test("abc")//Warning:scalac: input: Literal(Constant("abc"))
                            //Warning:scalac: compile-time value is: abc
    val boo5 = Z.test("abc" + "DEF")//Warning:scalac: input: Literal(Constant("abcDEF"))
                                    //Warning:scalac: compile-time value is: abcDEF
  }
}

Tree This means that it represents a runtime value. Just ClassNotFoundException sometimes happens faster than ToolBoxError . You subproject with macros project 1 doesn't depend on subproject project 2 so during compilation of macros Y is not found.

Difference between Z.foo and foo (aka Y.foo ) is that foo is actually this.foo (compiler doesn't care here if Y is a class or object) and can be overriden in subclasses.

Quill doesn't use eval . It parses a tree into its own AST if it can or leave Dynamic if it can't (ie if the tree corresponds to runtime value). And then it works with these two case differently: either during macros expansion with QueryMeta or during compile time + runtime with Decoder

https://github.com/getquill/quill/blob/master/quill-core/src/main/scala/io/getquill/context/QueryMacro.scala#L34-L38

So the workaround is to work with runtime values at runtime.

def impl(c: blackbox.Context)(x: c.Expr[String]): c.Expr[String] = {
  import c.universe._
  println(s"input: ${showRaw(x.tree)}")
  try {
    val x1 = c.Expr[String](c.untypecheck(x.tree.duplicate))
    val x2 = c.eval(x1)
    println(s"compile-time value is: $x2")
    c.Expr[String](q"$x2")
  } catch {
    case ex: Throwable =>
      println(ex.getMessage)
      x
  }
}

This is similar to https://github.com/getquill/quill/blob/master/quill-core/src/main/scala/io/getquill/context/ContextMacro.scala#L66-L68

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM