Как создать макрос для создания списка val в классе case? - PullRequest
0 голосов
/ 21 декабря 2018

Я пытаюсь создать макрос, чтобы получить список значений 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")

1 Ответ

0 голосов
/ 22 декабря 2018

Не эксперт по макросам, поэтому возьмите его с крошкой соли.Но я проверил это с Intellij.

Во-первых, чтобы использовать weakTypeOf, вам нужно взять WeakTypeTag в качестве неявного в вашем макросе, например:

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

Во-вторых, чтобысоздавая литералы, вы используете эту конструкцию вместо своей квазицитаты (которая, я полагаю, на самом деле ничего не делает):

Literal(Constant(m.name.toString))

В заключение, я рекомендую использовать эту охрану вместо isVal:

m.isCaseAccessor && m.isGetter

Правильно проверяет параметр класса случая, а также является получателем (параметры класса случая дублируются, один как isGetter, другой как isParam).Причиной этого является то, что isVal имена для классов case неожиданно создают имя, оканчивающееся пробелом.

Окончательная реализация, которая работает для меня, выглядит следующим образом:

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))
  }

}

Как альтернатива(поскольку макросы немного затрудняют настройку - вы не можете использовать макрос в том же подпроекте, который его определяет), и вам он не нужен очень часто, вы можете избежать неприятностей с shapeless однострочник:

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")
...