Scala 宏提升符号或类型

Scala macro lifting symbol or types

我正在尝试编写一个可以使用有关 class 字段的信息来生成架构的宏。例如,假设我有一个名为 SchemaWriter[T] 的类型 class,我希望使用宏为其生成实现。

trait SchemaWriter[T] {
  def schema: org.bibble.MsonSchema
}

case class MsonSchema(fields: Seq[MsonType])
case class MsonType(name: String, `type`: Class[_]) // might want other stuff here too, derived from a symbol or type-signature
object MsonType {
  def apply(symbol:Symbol): MsonType = ... 
}

我的想法是我的宏会吐出类似于以下的代码:

class FooSchemaWriter extends SchemaWriter[Foo] {
  def schema : org.bibble.MsonSchema= {
   val fields = for (symbol <- fields of class) yield {
    MsonType(symbol)
   }
   org.bibble.MsonSchema(fields)
 }
}

我可以实现一个宏,例如:

object Macros {

  def writerImpl[T: c.WeakTypeTag](c: Context): c.Expr[SchemaWriter[T]] = {

    import c.universe._
    val T = weakTypeOf[T]

    val fields = T.declarations.collectFirst {
      case m: MethodSymbol if m.isPrimaryConstructor => m
    }.get.paramss.head

    val fieldTrees: [Tree] = fields.map { f =>
      q"""org.bibble.MsonType(${f})"""
    }

    c.Expr[SchemaWriter[T]]( q"""
      new SchemaWriter[$T] {
        def schema = {
         val fields = Seq(..$fieldTrees)
         org.bibble.MsonSchema(fields)
        }
      }
    """)
  }
}

但是为字段创建 qq 会导致可提升错误或取消引用错误。我昨天问了一个类似的问题 ,得到了一个很好的答案,但我的实现不能仅限于传递字符串,因为我需要更多类型信息来生成模式的详细信息,这是我认为我的困惑所在.

我认为您的主要困惑是您似乎没有完全了解宏是如何处理的。 这完全是编译时间。当您的宏 运行s 时,它可以访问有关已编译程序的信息,包括 Symbols。 该宏随后会生成一些代码,这些代码将成为您程序的一部分,但生成的代码本身无法再访问 Symbols 或宏有权访问的任何其他内容。

所以 MsonType.apply 在你的例子中不会有任何用处,因为输入是一个 Symbol ,它只在宏中可用(在编译时), 并且输出是一个 MsonSchema,你在 运行 时需要它。有意义的是将 return 类型从 MsonType 更改为 c.Expr[MsonType](或简单地 c.universe.Tree), 或者换句话说,MsonType.apply 现在需要一个符号和 return 一棵代表 MsonType 实例的 (与 returning 一个 MsonType 实例相反),然后您的宏可以调用它并将其包含在最终树中 returned 给编译器 (然后编译器会将其包含在调用站点的程序中)。 在这种特殊情况下,我认为最好完全删除 MsonType.apply,并实现从 Symbol 的转换 c.universe.Tree 就在 writerImpl 宏中。

这个转换其实很容易。您只需要构建 MsonType 字段名称和一个 Class 实例,就可以了:

q"""org.bibble.MsonType(${f.name.decoded}, classOf[${f.typeSignature}])"""

对于 Bar 类型的字段 foo 这将生成一个表示以下表达式的树:

org.bibble.MsonType("foo", classOf[Bar])

就是这样,我们可以开始了。

您将在下面找到完整的实现。请注意,我冒昧地以一种对我来说更合乎逻辑和通用的方式更改了您的架构类型的定义。 特别是,每个字段现在都有其关联的模式(考虑到您之前的问题,这显然是您首先想要的)。

scala> :paste
// Entering paste mode (ctrl-D to finish)

import scala.language.experimental.macros
import scala.reflect.macros._

case class MsonSchema(`type`: Class[_], fields: Seq[MsonField])
case class MsonField(name: String, schema: MsonSchema)
trait SchemaWriter[T] {
  def schema: MsonSchema
}
object SchemaWriter {
  def apply[T:SchemaWriter]: SchemaWriter[T] = implicitly
  implicit def defaultWriter[T] = macro Macros.writerImpl[T]
}
object Macros {
  def writerImpl[T: c.WeakTypeTag](c: Context): c.Expr[SchemaWriter[T]] = {
    import c.universe._
    c.Expr[SchemaWriter[T]](generateSchemaWriterTree(c)(weakTypeOf[T]))
  }
  private def generateSchemaWriterTree(c: Context)(T: c.universe.Type): c.universe.Tree = {
    import c.universe._
    val fields = T.declarations.collectFirst {
      case m: MethodSymbol if m.isPrimaryConstructor => m
    }.get.paramss.head

    val MsonFieldSym = typeOf[MsonField].typeSymbol
    val SchemaWriterSym = typeOf[SchemaWriter[_]].typeSymbol
    val fieldTrees: Seq[Tree] = fields.map { f =>
      q"""new $MsonFieldSym(
        ${f.name.decoded},
        _root_.scala.Predef.implicitly[$SchemaWriterSym[${f.typeSignature}]].schema
      )"""
    }

    c.resetLocalAttrs(q"""
      new $SchemaWriterSym[$T] {
        val schema = MsonSchema(classOf[$T], Seq(..$fieldTrees))
      }
    """)
  }
}

// Exiting paste mode, now interpreting.

warning: there were 7 deprecation warnings; re-run with -deprecation for details
warning: there was one feature warning; re-run with -feature for details
import scala.language.experimental.macros
import scala.reflect.macros._
defined class MsonSchema
defined class MsonField
defined trait SchemaWriter
defined object SchemaWriter
defined object Macros

scala> case class Foo(ab: String, cd: Int)
defined class Foo

scala> SchemaWriter[Foo].schema
res0: MsonSchema = MsonSchema(class Foo,List(MsonField(ab,MsonSchema(class java.lang.String,List())), MsonField(cd,MsonSchema(int,List()))))