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

查看:12
本文介绍了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: 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.foofoo(又名 Y.foo)的区别在于 foo 实际上是 this.foo(编译器不关心 Y 是类还是对象)并且可以在子类中被覆盖.

Quill 不使用 eval.它将树解析为自己的 AST 如果可以或离开 Dynamic 如果它不能(即如果树对应于运行时值).然后它以不同的方式处理这两种情况:在宏扩展期间使用 QueryMeta 或在编译时 + 运行时使用 Decoder

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

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

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}}

这类似于 https://github.com/getquill/quill/blob/master/quill-core/src/main/scala/io/getquill/context/ContextMacro.scala#L66-L68

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

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天全站免登陆