未找到Scala注释

时间:2014-10-10 18:09:33

标签: scala macros annotations scala-macros

我有一个带有注释字段的案例类,如下所示:

case class Foo(@alias("foo") bar: Int)

我有一个宏来处理这个类的声明:

val (className, access, fields, bases, body) = classDecl match {
  case q"case class $n $m(..$ps) extends ..$bs { ..$ss }" => (n, m, ps, bs, ss)
  case _ => abort
}

稍后,我搜索别名字段,如下所示:

val aliases = fields.asInstanceOf[List[ValDef]].flatMap {
  field => field.symbol.annotations.collect {
  //deprecated version:
  //case annotation if annotation.tpe <:< cv.weakTypeOf[alias] =>
    case annotation if annotation.tree.tpe <:< c.weakTypeOf[alias] =>
    //deprecated version:
    //annotation.scalaArgs.head match {
      annotation.tree.children.tail.head match {
        case Literal(Constant(param: String)) => (param, field.name)
      }
  }
}

但是,别名列表最终为空。我已经确定field.symbol.annotations.size实际上是0,尽管注释显然位于场上。

知道什么是错的?

修改

回答前两条评论:

(1)我尝试过mods.annotations,但是没有用。这实际上返回List [Tree]而不是List.annotations返回的List [Annotation]。也许我没有正确修改代码,但在宏扩展期间立即生效是一个例外。我会尝试再玩它。

(2)在处理案例类上的注释宏时,会抓取类声明。

完整的代码如下。用法在下面的测试代码中说明。

package com.xxx.util.macros

import scala.collection.immutable.HashMap
import scala.language.experimental.macros
import scala.annotation.StaticAnnotation
import scala.reflect.macros.whitebox

trait Mapped {
  def $(key: String) = _vals.get(key)

  protected def +=(key: String, value: Any) =
    _vals += ((key, value))

  private var _vals = new HashMap[String, Any]
}

class alias(val key: String) extends StaticAnnotation

class aliased extends StaticAnnotation {
  def macroTransform(annottees: Any*): Any = macro aliasedMacro.impl
}

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

    val (classDecl, compDecl) = annottees.map(_.tree) match {
      case (clazz: ClassDef) :: Nil => (clazz, None)
      case (clazz: ClassDef) :: (comp: ModuleDef) :: Nil => (clazz, Some(comp))
      case _ => abort(c, "@aliased must annotate a class")
    }

    val (className, access, fields, bases, body) = classDecl match {
      case q"case class $n $m(..$ps) extends ..$bs { ..$ss }" => (n, m, ps, bs, ss)
      case _ => abort(c, "@aliased is only supported on case class")
    }

    val mappings = fields.asInstanceOf[List[ValDef]].flatMap {
      field => field.symbol.annotations.collect {
        case annotation if annotation.tree.tpe <:< c.weakTypeOf[alias] =>
          annotation.tree.children.tail.head match {
            case Literal(Constant(param: String)) =>
              q"""this += ($param, ${field.name})"""
          }
      }
    }

    val classCode = q"""
      case class $className $access(..$fields) extends ..$bases {
        ..$body; ..$mappings
      }"""

    c.Expr(compDecl match {
      case Some(compCode) => q"""$compCode; $classCode"""
      case None => q"""$classCode"""
    })
  }

  protected def abort(c: whitebox.Context, message: String) =
    c.abort(c.enclosingPosition, message)
}

测试代码:

package test.xxx.util.macros

import org.scalatest.FunSuite
import org.scalatest.junit.JUnitRunner
import org.junit.runner.RunWith
import com.xxx.util.macros._

@aliased
case class Foo(@alias("foo") foo: Int,
               @alias("BAR") bar: String,
                             baz: String) extends Mapped

@RunWith(classOf[JUnitRunner])
class MappedTest extends FunSuite {
  val foo = 13
  val bar = "test"
  val obj = Foo(foo, bar, "extra")

  test("field aliased with its own name") {
    assertResult(Some(foo))(obj $ "foo")
  }

  test("field aliased with another string") {
    assertResult(Some(bar))(obj $ "BAR")
    assertResult(None)(obj $ "bar")
  }

  test("unaliased field") {
    assertResult(None)(obj $ "baz")
  }
}

1 个答案:

答案 0 :(得分:0)

感谢您的建议!最后,使用field.mods.annotations确实有帮助。这是如何:

val mappings = fields.asInstanceOf[List[ValDef]].flatMap {
  field => field.mods.annotations.collect {
    case Apply(Select(New(Ident(TypeName("alias"))), termNames.CONSTRUCTOR),
               List(Literal(Constant(param: String)))) =>
      q"""this += ($param, ${field.name})"""
  }
}