0

我正在尝试创建一个宏来为我提供特定案例类的 val 列表。

object CaseClass {

  def valList[T]: List[String] = macro implValList[T]

  def implValList[T](c: whitebox.Context): c.Expr[List[String]] = {
    import c.universe._

    val listApply = Select(reify(List).tree, TermName("apply"))

    val vals = weakTypeOf[T].decls.collect {
      case m: TermSymbol if m.isVal => q"${m.name}"
    }

    c.Expr[List[String]](Apply(listApply, vals.toList))
  }

}

所以给定

case class AClass(
   val a: String,
   val b: Int
)

我想要一份清单CaseClass.valList[AClass] = List("a", "b")

4

1 回答 1

1

不是宏方面的专家,所以对它持保留态度。但我用 Intellij 对其进行了测试。

首先,要使用weakTypeOf你需要WeakTypeTag像这样在你的宏 impl 中隐含 a :

def implValList[T](c: whitebox.Context)(implicit wt: c.WeakTypeTag[T]) ...

其次,要创建文字,您使用此构造而不是您的 quasiquote,(我相信它实际上什么都不做):

Literal(Constant(m.name.toString))

最后,我建议使用这个守卫而不是isVal

m.isCaseAccessor && m.isGetter

这是正确检查案例类参数并且也是一个吸气剂(案例类参数是重复的,一个为isGetter,另一个为isParam)。这样做的原因是isVal案例类的名称令人惊讶地产生了一个以空格结尾的名称。

对我有用的最终实现如下:

object CaseClass {

  def valList[T]: List[String] = macro implValList[T]

  def implValList[T](c: whitebox.Context)(implicit wt: c.WeakTypeTag[T]): c.Expr[List[String]] = {
    import c.universe._

    val listApply = Select(reify(List).tree, TermName("apply"))

    val vals = weakTypeOf[T].decls.collect {
      case m: TermSymbol if m.isCaseAccessor && m.isGetter => Literal(Constant(m.name.toString))
    }

    c.Expr[List[String]](Apply(listApply, vals.toList))
  }

}

作为替代方案(因为宏设置起来有点麻烦——你不能在定义它的同一个子项目中使用宏),而且你并不经常需要它,你也许可以用一个不成形的宏来逃脱——衬垫:

import shapeless._
import shapeless.ops.record.Keys

case class Foo(a: Int, b: String)

Keys[the.`LabelledGeneric[Foo]`.Repr].apply().toList.map(_.name) // List("a", "b")
于 2018-12-21T22:20:37.753 回答