Scala:在 Context.eval 参考中可以编码什么? [英] Scala: what can code in Context.eval reference?
问题描述
Context.eval
的输入似乎只能引用来自不同编译单元的值:
//项目 1对象 Z {val foo = "WOOF"def impl(c: Context)(x: c.Expr[String]) = {val x1 = c.Expr[String](c.untypecheck(x.tree.duplicate))println(s"编译时值为:${c.eval(x1)}")X}def test(x: String) = 宏 impl}//项目 2对象 Y {val foo = "GOOF"val boo = Z.test(Z.foo)}println(Y.boo)
打印出 "WOOF"
,但如果我将 boo 替换为 val boo = Z.test(Y.foo)
,我会收到以下编译错误:
Error:(32, 29) 宏扩展时异常:java.lang.ClassNotFoundException: Y$在 scala.reflect.internal.util.AbstractFileClassLoader.findClass(AbstractFileClassLoader.scala:72)...
有没有办法解决这个问题?我知道用 quill.io 定义的查询可以引用同一范围内的方法,但我找不到他们用来允许它的技巧.
Context#eval
无法计算运行时值.它写在它的 scaladoc 中:https://github.com/scala/scala/blob/2.13.x/src/reflect/scala/reflect/macros/Evals.scala#L61-L67
让我们修改你的宏
def impl(c: blackbox.Context)(x: c.Expr[String]): c.Expr[String] = {导入 c.universe._println(s"input: ${showRaw(x.tree)}")//添加val x1 = c.Expr[String](c.untypecheck(x.tree.duplicate))println(s"编译时值为:${c.eval(x1)}")X}
那么我们就有了
对象应用程序{/*类*/对象 Y {val foo = "GOOF";val boo = Z.test(Z.foo)//警告:scalac: input: Select(Select(Ident(Macros), Macros.Z), TermName("foo"))//警告:scalac:编译时值为:WOOF//val boo1 = Z.test(Y.foo)//警告:scalac: input: Select(Select(This(TypeName(App")), App.Y), TermName(foo"))//错误:宏扩展时出现异常://java.lang.ClassNotFoundException: App$Y$//val boo2 = Z.test((new Y).foo)//警告:scalac: input: Select(Apply(Select(New(Select(This(TypeName("App")), App.Y)), termNames.CONSTRUCTOR), List()), TermName(foo"))//错误:宏扩展时出现异常://java.lang.ClassNotFoundException: App$Y//val boo3 = Z.test(foo)//Warning:scalac: input: Select(This(TypeName("Y")), TermName("foo"))//错误:宏扩展时出现异常://scala.tools.reflect.ToolBoxError:反射编译失败://内部错误:无法找到对象 __wrapper$1$fd3cb1297ce8421e809ee5e821c2f708 的外部访问器符号//或者//错误:宏扩展时出现异常://java.lang.ClassNotFoundException: App$Y$val boo4 = Z.test("abc")//警告:scalac: input: Literal(Constant("abc"))//警告:scalac:编译时值为:abcval boo5 = Z.test("abc" + "DEF")//警告:scalac: input: Literal(Constant("abcDEF"))//警告:scalac:编译时值为:abcDEF}}
Tree This
表示它代表一个运行时值.只是 ClassNotFoundException
有时比 ToolBoxError
发生得更快.您使用宏 project 1
的子项目不依赖于子项目 project 2
因此在编译宏时找不到 Y
.
Z.foo
和 foo
(又名 Y.foo
)的区别在于 foo
实际上是 this.foo
(编译器不关心 Y
是类还是对象)并且可以在子类中被覆盖.
Quill 不使用 eval
.它将树解析为自己的 AST 如果可以或离开 Dynamic
如果它不能(即如果树对应于运行时值).然后它以不同的方式处理这两种情况:在宏扩展期间使用 QueryMeta
或在编译时 + 运行时使用 Decoder
因此解决方法是在运行时使用运行时值.
def impl(c: blackbox.Context)(x: c.Expr[String]): c.Expr[String] = {导入 c.universe._println(s"输入:${showRaw(x.tree)}")尝试 {val x1 = c.Expr[String](c.untypecheck(x.tree.duplicate))val x2 = c.eval(x1)println(s"编译时值为:$x2")c.Expr[String](q"$x2")} 抓住 {案例例如:Throwable =>println(ex.getMessage)X}}
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 (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
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
这篇关于Scala:在 Context.eval 参考中可以编码什么?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!