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 (i.e. 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