2014-10-10 4 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, несмотря на то, что аннотация явно сидит на поле.

Любая идея о том, что не так?

EDIT

Ответ первые два комментария:

(1) Я попытался mods.annotations, но это не сработало. Это фактически возвращает List [Tree] вместо List [Annotation], возвращенный символом. Annotations. Возможно, я неправильно модифицировал код, но непосредственный эффект был исключением при расширении макросов. Я постараюсь поиграть с ним еще немного.

(2) Объявление класса захватывается при обработке макроса аннотации, наложенного на класс case.

Полный код следует. Использование приведено ниже в тестовом коде.

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'? Я не думаю, что в поле даже будет символ, из которого вы можете прочитать аннотацию, но поиск его модификаторов должен работать. –

+0

Как этот макрос получает объявление класса? –

+0

Спасибо! См. Редактирование моего вопроса. – silverberry

ответ

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})""" 
    } 
} 
Смежные вопросы