Scala: что можно кодировать в Context.eval? - PullRequest
1 голос
/ 08 июля 2019

Кажется, что ввод Context.eval может ссылаться только на значения из разных модулей компиляции:

// 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)

выводит "WOOF", но если я заменю boo на val boo = Z.test(Y.foo), я получу следующееошибка компиляции:

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

Есть ли способ обойти эту проблему?Я знаю, что запросы, определенные с помощью quill.io, могут ссылаться на методы из той же области, но я не смог найти уловку, которую они используют, чтобы это позволить.

1 Ответ

1 голос
/ 08 июля 2019

Панель инструментов не может оценить значения времени выполнения.Это написано в скалярном документе: 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] = {
  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
}

Тогда у нас будет

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

Дерево This означает, что оно представляетзначение времени выполнения.Просто ClassNotFoundException иногда происходит быстрее, чем ToolBoxError.Ваш подпроект с макросами project 1 не зависит от подпроекта project 2, поэтому во время компиляции макросов Y не найден.

Разница между Z.foo и foo (он же 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-L38

Таким образом, обходной путь составляет работать со значениями времени выполнения во время выполнения.

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

Это похоже на https://github.com/getquill/quill/blob/master/quill-core/src/main/scala/io/getquill/context/ContextMacro.scala#L66-L68

...