11

Thanks to the answers to my previous question, I was able to create a function macro such that it returns a Map that maps each field name to its value of a class, e.g.

...

trait Model

case class User (name: String, age: Int, posts: List[String]) extends Model {
  val numPosts: Int = posts.length

  ...

  def foo = "bar"

  ...
}

So this command

val myUser = User("Foo", 25, List("Lorem", "Ipsum"))

myUser.asMap

returns

Map("name" -> "Foo", "age" -> 25, "posts" -> List("Lorem", "Ipsum"), "numPosts" -> 2)

This is where Tuples for the Map are generated (see Travis Brown's answer):

...

val pairs = weakTypeOf[T].declarations.collect {
  case m: MethodSymbol if m.isAccessor =>
    val name = c.literal(m.name.decoded)
    val value = c.Expr(Select(model, m.name))
    reify(name.splice -> value.splice).tree
}

...

Now I want to ignore fields that have @transient annotation. How would I check if a method has a @transient annotation?

I'm thinking of modifying the snippet above as

val pairs = weakTypeOf[T].declarations.collect {
   case m: MethodSymbol if m.isAccessor && !m.annotations.exists(???) =>
      val name = c.literal(m.name.decoded)
      val value = c.Expr(Select(model, m.name))
      reify(name.splice -> value.splice).tree
}

but I can't find what I need to write in exists part. How would I get @transient as an Annotation so I could pass it there?

Thanks in advance!

Community
  • 1
  • 1
Emre
  • 1,023
  • 2
  • 9
  • 24

1 Answers1

11

The annotation will be on the val itself, not on the accessor. The easiest way to access the val is through the accessed method on MethodSymbol:

def isTransient(m: MethodSymbol) = m.accessed.annotations.exists(
  _.tpe =:= typeOf[scala.transient]
)

Now you can just write the following in your collect:

case m: MethodSymbol if m.isAccessor && !isTransient(m) =>

Note that the version of isTransient I've given here has to be defined in your macro, since it needs the imports from c.universe, but you could factor it out by adding a Universe argument if you're doing this kind of thing in several macros.

Travis Brown
  • 138,631
  • 12
  • 375
  • 680
  • 1
    Thanks again! I can't seem to obtain annotations. I added `@transient` before `name` and `numPosts` in the example class above, but they are still added to the map. Strangely none of the methods that went through `isTransient` seems to have any annotations available. Both `m.accessed.annotations` and `m.annotations` are empty. – Emre Jun 21 '13 at 14:45
  • 1
    That's very odd—it works for me as expected in both cases. Can you maybe post your exact code? – Travis Brown Jun 21 '13 at 14:52
  • Here's the [code](http://pastebin.com/PmW5qg3P). Note that I am building something slightly different here - a map of all fields and field types of case classes under a given package. Is the problem not having a WeakTypeTag? Thanks in advance. – Emre Jun 23 '13 at 22:08
  • Started working by itself today... I don't know what happened. Thanks for the answer. – Emre Jun 26 '13 at 10:53
  • 1
    @EugeneBurmako, exactly. That's exact problem. I just found out that calling `println(m + ", " + m.typeSignature + " -> " + m.accessed.annotations.foreach(a => println(a.tpe)))` makes the macro work whereas `println(m + " -> " + m.accessed.annotations.foreach(a => println(a.tpe)))` doesn't display anything. Came here to report it. What a coincidence, I used the exact methods to find out what was wrong. Thanks very much. That was a nasty one! :) – Emre Jun 28 '13 at 13:38
  • @TravisBrown can you also get values from the StaticAnnotation? See my question in http://stackoverflow.com/questions/20908671/scala-macros-how-to-read-an-annotations-parameter – Jaap Jan 03 '14 at 17:23