1

Context:

I'm working on a library for working with JMX in Scala. One of the objectives is to have a strong typed interface to Managed Beans. I guess akin to to the Spring framework JMX library.

Objective: Macro to Deserialise TabularData to a case class:

// interface for which I'd like to generate an implementation using a macro
trait JMXTabularAssembler[T <: Product] {
  def assemble(data: TabularData): T
}

object JMXAnnotations {
  case class Attribute(name: String) extends StaticAnnotation
}
case class example(
  @Attribute("name") name: String,
  @Attribute("age") age: Int,
  unmarked: String
)

Problem: There are plenty of examples of composing tree's using the q"" interpolators. But I can't figure out how to use the tq"" interpolator to extract the fields out of a case class from a type context.

private def mkAssembler[T <: Product : c.WeakTypeTag](c: Context): c.universe.Tree = {
  import c.universe._
  val tt = weakTypeOf[T]
}

Question: How do I use the QuasiQuote machinery to destructure the fields of my case class so that I can loop over them and filter out fields with my annotation (my Attribute annotation is not available from the approach I am currently taking"). An implementation of the following that returns the fields with annotations in declaration order is what I am after.

private def harvestFieldsWithAnnotations[T<: Product: c.WeakTypeTag](c: Context): 
    List[(c.universe.Name, String, c.universe.Type,   List[c.universe.Annotation])] = ???

Bonus: The objective is to get the attribute fields, generate trees for each field that extract the field from the TabularData and use these trees to create the JMXTabularAssembler Functor. If you could show me how to do this for the example above it would bootstrap my efforts :D.


What I have tried: I started solving the problem by using reflection. This does not seem the right way to do it. Snippets:

...
val dec = tt.decls.sorted
def getFields = dec.withFilter( t=> t.isTerm && ! t.isMethod)
def getCaseAccessors = dec.withFilter( t => t.isMethod && t.asMethod.isCaseAccessor)

dec.foreach { d=>
  println(d.name, d.annotations)
}

getFields.foreach { f =>
  println(f.annotations)
}

val types = getCaseAccessors.map { d =>
  println(d.annotations)
  (d.name, tt.member(d.name).asMethod.returnType)
}
...
Hassan Syed
  • 20,075
  • 11
  • 87
  • 171
  • This link has a lot of useful information: [link](http://www.strongtyped.io/blog/2014/05/23/case-class-related-macros/). It looks similar to the approach described, and I doubt it will return the annotations. It also does not quasi quotes but the raw api. – Hassan Syed Jul 22 '15 at 12:48

1 Answers1

0

The following method does the trick, it does not use quasi quotes. The key is to access the backing field of a symbol representing the field accessor of a case class (the accessed call).

private def harvestFieldsWithAnnotations[T <: Product : c.WeakTypeTag](c: Context) = {
    import c.universe._
    val tt = weakTypeOf[T]

    tt.decls.sorted.filter(t => t.isMethod && t.asMethod.isCaseAccessor).map { ca =>
      val asMethod = tt.member(ca.name).asMethod
      (ca.name, asMethod.returnType, asMethod.accessed.annotations)
    }
  }

Field annotations won't get retained unless they are explicitly annotated with scala.annotation.meta.field.

So the Attribute annotation should be:

@field
case class Attribute(name: String) extends StaticAnnotation
Hassan Syed
  • 20,075
  • 11
  • 87
  • 171