为什么运行时反射Universe和宏Universe为scala.None创建了两个不同的树?

sim*_*905 13 scala scala-macros

如果我有一个宏tranforms代码如下:

  (src: a.b.c.TestEntity) =>
    {
      z.y.TestTable(None)
    }
Run Code Online (Sandbox Code Playgroud)

要匹配该AST的None部分,我可以使用提取器,例如:

  object NoneExtractor {
    def unapply(t: Tree): Boolean = t match {
      case Select(Ident(scala), none) if scala.encoded == "scala" && none.encoded == "None" => true
      case _ => false
    }
  }
Run Code Online (Sandbox Code Playgroud)

因为showRawAST的None部分看起来像:

Select(Ident(scala), None)
Run Code Online (Sandbox Code Playgroud)

然而,如果我想编写单元测试,NoneExtractor我不想编译和重建宏,并在宏编译的项目中托管测试.我想在宏的项目中对提取器进行单元测试,这表明运行时反射是可行的方法:

val t = reify {

  (src: a.b.c.TestEntity) =>
    {
      z.y.TestTable(None)
    }

}.tree 
Run Code Online (Sandbox Code Playgroud)

然而树是完全不同的,在showRaw那棵树中看起来像是:

Ident(scala.None)
Run Code Online (Sandbox Code Playgroud)

这对于编写负面测试和检查宏的错误处理来说是个坏消息.您不能使用来自另一个项目的宏为宏编写负面测试,因为代码无法编译(并且您无法使用编译错误调试负面测试).

为什么在编译时反射和运行时反射之间,像None一样基本的表示形式如此不同?有没有办法在宏项目中创建可测试的树片段,这与在编译时反射期间传递给宏的AST相同?

Mar*_*ing 0

要解决这种不一致问题,您可以在模式匹配中使用即将推出的quasiqoutes 。它们抽象了 AST,因此可以使用两种表示形式(无论如何,AST 是特定于编译器的,Scala 目前是单一编译器语言,但依赖编译器的内部表示形式并不是很好):

case q"_root_.scala.None" => ...
Run Code Online (Sandbox Code Playgroud)

将匹配两个 AST。您还可以创建树,q"_root_.scala.None"这样您就不必担心表示问题。当 scala 2.11 发布 quasiquotes 时,Reify 将被淘汰。要在 scala 2.10 中使用 quasiquotes,您可以使用Macro heaven

这是关于 scala quasiquotes 的一个很好的 WIP 指南