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

时间:2013-10-24 11:20:42

标签: scala scala-macros

如果我有一个tranforms代码的宏,例如:

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

为了匹配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
    }
  }

由于AST的None部分的showRaw看起来像:

Select(Ident(scala), None)

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

val t = reify {

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

}.tree 

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

Ident(scala.None)

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

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

1 个答案:

答案 0 :(得分:0)

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

case q"_root_.scala.None" => ...

将匹配两个AST。您还可以使用q"_root_.scala.None"创建树,这样您就不必担心表示。当使用scala 2.11发布quasiquotes时,Reify将会过时。要使用scala 2.10的quasiquotes,您可以使用macro paradise

Here is a nice WIP guide on scala quasiquotes