2014-10-10 60 views
1

我有一个案例类注释字段,如:Scala的注释都没有发现

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 [Annotation],由symbol.annotations返回。也许我没有正确修改代码,但直接影响是宏扩展期间的异常。我会尝试再玩一些。

(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

你可以尝试'field.mods.annotations'?我不认为这个领域甚至会有一个象征,你可以阅读注释,但看看它的修饰符应该工作。 – 2014-10-10 19:46:23

+0

这个宏如何获得类的声明? – 2014-10-10 21:10:35

+0

谢谢!请参阅我的问题的编辑。 – silverberry 2014-10-12 13:24:27

回答

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