Question

I'm trying to use macro annotations in scala, where my macro annotation would take an argument of another type. It would then use scala reflection to look at the passed in type, and add some methods as appropriate.Eg.

trait MyTrait {
  def x: Int
  def y: Float
}

@MyAnnotation class MyClass //<-- somehow, this annotation should reference MyTrait

class MyAnnotation(val target: Any) extends StaticAnnotation {
  def macroTransform(annottees: Any*) = macro MyAnnotationImpl.impl
}
object MyAnnotationImpl {
  def impl(c: Context)(annottees: c.Expr[Any]*): c.Expr[Any] = {
    // if I can get a handle on the type MyTrait in here
    // then I can call .members on it, etc.
    ...
  }
}

Basically, the same thing as Using Scala reflection in Scala macros, except using macro annotations. However, when I try to template my macro annotation with a TypeTag

class MyAnnotation[T](val target: Any) extends StaticAnnotation {
  def macroTransform[T](annottees: Any*) = macro MyAnnotationImpl.impl[T]
}
object MyAnnotationImpl {
  def impl[T: c.WeakTypeTag](c: Context)(annottees: c.Expr[Any]*): c.Expr[Any] = {
    ...
  }
}

I get

[error] /Users/imran/other_projs/learn_macros/macros/src/main/scala/com/imranrashid/oleander/macros/MacrosWithReflection.scala:7: macro annotation has wrong shape:
[error]   required: def macroTransform(annottees: Any*) = macro ...
[error]   found   : def macroTransform[T](annottees: Any*) = macro ...
[error] class MyAnnotation[T](val target: Any) extends StaticAnnotation {
[error]       ^

I've also tried to make the type an argument to my annotation, so I would use it like @MyAnnotation(MyTrait) class Foo .... I can extract the name as a String with something like

val targetTrait = c.prefix.tree match {
  case Apply(Select(New(Ident(_)), nme.CONSTRUCTOR), List(Ident(termName))) => termName
}

but, I'm not sure what I can do w/ that String to get back the full type. I've also tried variants like @MyAnnotation(typeOf[MyTrait]) class Foo ..., and then use c.eval on the typeOf inside my macro, but that doesn't compile either.

Was it helpful?

Solution

In macro paradise 2.0.0-SNAPSHOT we have quite a tricky way of accessing type parameters for macro annotations (the situation will improve later on when we have dedicated APIs for that, but right now it's very difficult to introduce new functionality to scala-reflect.jar in macro paradise, so the current API is a bit rough).

For now it's necessary to specify the type parameter on the annotation class and not to declare any type parameters on the macroTransform method. Then, in macro expansion, access c.macroApplication and extract the untyped tree corresponding to the passed type parameter. Afterwards, do c.typeCheck as described in Can't access Parent's Members while dealing with Macro Annotations.

OTHER TIPS

As Eugene points out in his answer it is possible to match on the tree of the whole macro application. Like every Scala method, annotation macro applications can take multiple type argument lists as well as multiple value argument lists.

Consider the macro application of an annotation macro called test:

    @test[A, B][C, D](a, b)(c, d) trait Foo

In the implementation of test we can inspect the macro application by

    println(show(c.macroApplication))

which will result in:

    new test[A, B][C, D](a, b)(c, d).macroTransform(abstract trait Foo extends scala.AnyRef)

To extract the (type/value) parameters from the tree you have to pattern match on the tree. A parser for an arbitrary amount of parameter lists can be found in this project

Using this parser retrieving the first value argument of the macro application is as easy as

    val List(List(arg)) = MacroApp(c.macroApplication).termArgs
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top