如何在reify子句中使用Scala宏中计算的类型?

mgo*_*nto 12 scala scala-2.10 scala-macros

我一直在使用Scala Macros并在宏中使用以下代码:

    val fieldMemberType = fieldMember.typeSignatureIn(objectType) match {
      case NullaryMethodType(tpe)   => tpe
      case _                      => doesntCompile(s"$propertyName isn't a field, it must be another thing")
    }

    reify{
      new TypeBuilder() {
        type fieldType = fieldMemberType.type
      }
    }
Run Code Online (Sandbox Code Playgroud)

正如你所看到的,我已经成功了c.universe.Type fieldMemberType.这表示对象中某个字段的类型.一旦我得到了,我想TypeBuilder在reify中创建一个新对象.TypeBuilder是一个带抽象参数的抽象类.这个抽象参数是fieldType.我希望这fieldType是我之前找到的类型.

运行此处显示的代码会返回一个fieldMemberType not found.有什么方法可以让我fieldMemberType在reify子句中工作吗?

Tra*_*own 24

问题是你传递给的代码reify本质上是逐字放置在扩展宏的位置,fieldMemberType并不代表那里的任何东西.

在某些情况下,您可以使用splice将在宏扩展时具有的表达式隐藏到您正在实现的代码中.例如,如果我们尝试创建此特征的实例:

trait Foo { def i: Int }
Run Code Online (Sandbox Code Playgroud)

并且在宏扩展时有这个变量:

val myInt = 10
Run Code Online (Sandbox Code Playgroud)

我们可以写下面的内容:

reify { new Foo { def i = c.literal(myInt).splice } }
Run Code Online (Sandbox Code Playgroud)

这不会在这里起作用,这意味着你将不得不忘记好一点,reify并手工写出AST.不幸的是,你会发现这种情况发生了很多.我的标准方法是启动一个新的REPL并输入如下内容:

import scala.reflect.runtime.universe._

trait TypeBuilder { type fieldType }

showRaw(reify(new TypeBuilder { type fieldType = String }))
Run Code Online (Sandbox Code Playgroud)

这将吐出几行AST,然后您可以剪切并粘贴到宏定义中作为起点.然后你摆弄它,取代这样的事情:

Ident(TypeBuilder)
Run Code Online (Sandbox Code Playgroud)

有了这个:

Ident(newTypeName("TypeBuilder"))
Run Code Online (Sandbox Code Playgroud)

FINALFlag.FINAL,等等.我希望toStringAST类型的方法更准确地与构建它们所需的代码相对应,但是你很快就会知道你需要改变什么.你最终会得到这样的东西:

c.Expr(
  Block(
    ClassDef(
      Modifiers(Flag.FINAL),
      anon,
      Nil,
      Template(
        Ident(newTypeName("TypeBuilder")) :: Nil,
        emptyValDef,
        List(
          constructor(c),
          TypeDef(
            Modifiers(),
            newTypeName("fieldType"),
            Nil,
            TypeTree(fieldMemberType)
          )
        )
      )
    ),
    Apply(Select(New(Ident(anon)), nme.CONSTRUCTOR), Nil)
  )
)
Run Code Online (Sandbox Code Playgroud)

哪里anon是你预先创建了您的匿名类的类型名称,constructor是一种方便的方法我用做这种事情少一点丑陋(你可以在下面找到其定义这个完整的工作示例).

现在,如果我们像这样包起来表达这个,我们可以写出如下:

scala> TypeMemberExample.builderWithType[String]
res0: TypeBuilder{type fieldType = String} = $1$$1@fb3f1f3
Run Code Online (Sandbox Code Playgroud)

所以它有效.我们采用了一个c.universe.Type(我从WeakTypeTag类型参数中获取builderWithType,但它将以与任何旧的完全相同的方式工作Type)并使用它来定义我们的TypeBuilder特征的类型成员.

  • 您可以使用`-Yreify-copypaste`和/或`showRaw`来获取构建您感兴趣的代码片段的代码. (3认同)

Leo*_*Leo 6

对于您的用例,有一种比树编写更简单的方法.事实上,我一直用它来遮挡树木,因为用树木编程真的很困难.我更喜欢计算类型并使用reify来生成树.这使得宏更健壮,更"卫生",减少了编译时错误.使用树的IMO必须是最后的手段,仅适用于少数情况,例如树变换或一系列类型(如元组)的通用编程.

这里的技巧是定义一个函数作为类型参数,你想在reify体中使用的类型,以及WeakTypeTag上的上下文绑定.然后通过显式传递可以从Universe类型构建的WeakTypeTags来调用此函数,这要归功于上下文WeakTypeTag方法.

所以在你的情况下,这将给出以下内容.

  val fieldMemberType: Type = fieldMember.typeSignatureIn(objectType) match {
      case NullaryMethodType(tpe)   => tpe
      case _                      => doesntCompile(s"$propertyName isn't a field, it must be            another thing")
  }

  def genRes[T: WeakTypeTag] = reify{
    new TypeBuilder() {
      type fieldType = T
    }
  }

  genRes(c.WeakTypeTag(fieldMemberType))
Run Code Online (Sandbox Code Playgroud)