Макросы Scala: получение сигнатуры типа из ValDef

Очень простая аннотация макроса, поддерживаемая совместимостью с макросами.

def impl(c: blackbox.Context)(annottees: c.Expr[Any]*): c.Expr[Any] = {
    import c.universe._

    annottees.map(_.tree) match {
      case (classDef @ q"$mods class $tpname[..$tparams] $ctorMods(...$params) extends { ..$earlydefns } with ..$parents { $self => ..$stats }")
        :: Nil if mods.hasFlag(Flag.CASE) =>
        val name = tpname.toTermName
        val typeName = tpname.toTypeName
        val res = q"""
         $classDef
         object $name {
           ..${doStuff(c)(typeName, name, params.head)}
         }
         """
        c.Expr[Any](res)

      case _ => c.abort(c.enclosingPosition, "Invalid annotation target, this must be a case class")
    }
  }

Так что все очень простое незамысловатое развлечение. Бит, вызывающий проблемы, происходит из $params выше, которые являются всего лишь List[List[ValDef]], а именно каким-то образом сигнатура типа теряется.

def accessors(c: blackbox.Context)(
    params: Seq[c.universe.ValDef]
  ): Iterable[(c.universe.TermName, c.universe.TypeName)] = {
    import c.universe._

    params.map {
      case ValDef(mods: Modifiers, name: TermName, tpt: Tree, rhs: Tree) => {
        // tpt.tpe = kaboom, null pointer
        name -> TypeName(tpt.tpe.typeSymbol.fullName)
      }
    }
  }

tpe на ValDef возвращается как null, поэтому определения не типизированы, но мне нужна подпись типа параметров для достижения того, что я хочу. Как я могу получить сигнатуру типа параметров без ее взрыва?

По иронии судьбы, showCode(tpt) создает строку правильного типа, так что это можно обойти с помощью TypeName(tpt.toString), но я не уверен, почему tpe недоступен.


person flavian    schedule 14.09.2016    source источник
comment
Возможно, вы захотите взглянуть на ответ и комментарии Евгения Бурмако в этом вопросе: stackoverflow.com/questions/23671379/   -  person Jasper-M    schedule 14.09.2016


Ответы (1)


Правильный способ сделать это — оценить аргументы типа, используя c.typepcheck в c.TypeMode следующим образом:

  /**
    * Retrieves the accessor fields on a case class and returns an iterable of tuples of the form Name -> Type.
    * For every single field in a case class, a reference to the string name and string type of the field are returned.
    *
    * Example:
    *
    * {{{
    *   case class Test(id: UUID, name: String, age: Int)
    *
    *   accessors(Test) = Iterable("id" -> "UUID", "name" -> "String", age: "Int")
    * }}}
    *
    * @param params The list of params retrieved from the case class.
    * @return An iterable of tuples where each tuple encodes the string name and string type of a field.
    */
  def accessors(
    params: Seq[ValDef]
  ): Iterable[Accessor] = {
    params.map {
      case ValDef(_, name: TermName, tpt: Tree, _) => {
        Accessor(
          name,
          c.typecheck(tq"$tpt", c.TYPEmode).tpe
        )
      }
    }
  }

В этом случае Accessor — это пользовательский case class, который должен быть определен внутри области, где доступен import c.universe._:

  case class Accessor(
    name: TermName,
    paramType: Type
  ) {
    def typeName: TypeName = symbol.name.toTypeName

    def symbol = paramType.typeSymbol
  }
person flavian    schedule 26.10.2016