2014-10-10 64 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})""" 
    } 
}