Scala:Context.eval参考中可以编码什么? [英] Scala: what can code in Context.eval reference?

查看:60
本文介绍了Scala:Context.eval参考中可以编码什么?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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:字符串)=宏展示}//专案2物体Y {val foo ="GOOF"val boo = Z.test(Z.foo)}println(Y.boo) 

打印出"WOOF" ,但是如果我将boo替换为 val boo = Z.test(Y.foo),则会收到以下编译错误:

 错误:(32,29)宏扩展期间发生异常:java.lang.ClassNotFoundException:Y $在scala.reflect.internal.util.AbstractFileClassLoader.findClass(AbstractFileClassLoader.scala:72)... 

有什么办法可以解决这个问题?我知道用 quill.io 定义的查询可以引用来自同一范围的方法,但我无法找到他们用来允许它的技巧.

解决方案

Context#eval 无法评估运行时值.它写在其scaladoc中:

那我们就拥有

 对象应用{/* class */对象Y {val foo ="GOOF";val boo = Z.test(Z.foo)//警告:scalac:输入:Select(Select(Ident(Macros),Macros.Z),TermName("foo"))//警告:scalac:编译时值为:WOOF//val boo1 = Z.test(Y.foo)//Warning:scalac:输入:Select(Select(This(TypeName("App";)),App.Y),TermName("foo"))//错误:宏扩展期间发生异常://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")))//错误:宏扩展期间发生异常://java.lang.ClassNotFoundException:App $ Y//val boo3 = Z.test(foo)//警告:scalac:输入:Select(This(TypeName("Y"))),TermName("foo"))//错误:宏扩展期间发生异常://scala.tools.reflect.ToolBoxError:反射编译失败://内部错误:无法找到对象__wrapper $ 1 $ fd3cb1297ce8421e809ee5e821c2f708的外部访问器符号//或者//错误:宏扩展期间发生异常://java.lang.ClassNotFoundException:App $ Y $val boo4 = Z.test("abc")//Warning:scalac:input:Literal(Constant("abc")))//警告:scalac:编译时值为:abcval boo5 = Z.test("abc" +"DEF")//警告:scalac:输入:Literal(Constant("abcDEF"))//警告:scalac:编译时值为:abcDEF}} 

表示它表示运行时值.只是 ClassNotFoundException 有时会比 ToolBoxError 发生得更快.您使用宏 project 1 的子项目不依赖于子项目 project 2 ,因此在宏编译期间未找到 Y .

Z.foo foo (又名 Y.foo )之间的区别是 foo 实际上是 this.foo (如果 Y 是类或对象,编译器不在乎),并且可以在子类中覆盖它.

Quill不使用 eval .它将一棵树解析成自己的 AST (如果可以)或 https://github.com/getquill/quill/blob/master/quill-core/src/main/scala/io/getquill/context/QueryMacro.scala#L34-L38

因此解决方法是在运行时使用运行时值.

  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))值x2 = c.eval(x1)println(s编译时值是:$ x2")c.Expr [String](q"$ x2")} 抓住 {例如:Throwable =>println(ex.getMessage)X}} 

这类似于

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

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

这篇关于Scala:Context.eval参考中可以编码什么?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆