【问题标题】:Scala macros: Derive type signature from a ValDefScala 宏:从 ValDef 派生类型签名
【发布时间】:2016-10-26 19:16:57
【问题描述】:

一个非常简单的宏兼容支持宏注释。

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

ValDef 上的tpe 以null 的形式返回,因此没有键入定义,但我需要参数的类型签名来实现我想要的。如何在不爆炸的情况下获得参数的类型签名?

具有讽刺意味的是,showCode(tpt) 确实生成了正确的类型字符串,因此可以使用 TypeName(tpt.toString) 解决此问题,但我不确定为什么无法访问 tpe。

【问题讨论】:

标签: scala macros


【解决方案1】:

这样做的正确方法是使用c.typepcheck in 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
  }

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-10-22
    • 2011-05-24
    相关资源
    最近更新 更多