Retrieving parameters from Scala Macro annotation

So, I have an annotation for a function (DefDef). This annotation has options. However, I am confused about how to get the parameters from the constructor.

Usage example:

class TestMacro { @Foo(true) def foo(): String = "" foo } 

Here's the code for the annotation:

 class Foo(b: Boolean) extends StaticAnnotation { def macroTransform(annottees: Any*) = macro Foo.impl } object Foo { def impl(c: whitebox.Context)(annottees: c.Tree*): c.Expr[Any] = { import c.universe._ //how do I get value of `b` here??? c.abort(c.enclosingPosition, "message") } } 
+7
scala annotations scala-macros
source share
2 answers

How about this:

 val b: Boolean = c.prefix.tree match { case q"new Foo($b)" => c.eval[Boolean](c.Expr(b)) } 

For completeness, this is the complete source:

 import scala.reflect.macros.Context import scala.language.experimental.macros import scala.annotation.StaticAnnotation import scala.annotation.compileTimeOnly import scala.reflect.api.Trees import scala.reflect.runtime.universe._ class Foo(b: Boolean) extends StaticAnnotation { def macroTransform(annottees: Any*) :Any = macro FooMacro.impl } object FooMacro { def impl(c: Context)(annottees: c.Expr[Any]*): c.Expr[Any] = { import c.universe._ val b: Boolean = c.prefix.tree match { case q"new Foo($b)" => c.eval[Boolean](c.Expr(b)) } c.abort(c.enclosingPosition, "message") } } 
+7
source share

This is an answer that shows a variation in the Federico technique if you want to use static annotation with optional named arguments. In this case, you need to consider possible call expressions in the case matching statement. An optional argument may be explicitly specified, it may be given without a name, or it may be absent. Each of them is displayed at compile time as a separate template in c.prefix.tree , as shown below.

 @compileTimeOnly("Must enable the Scala macro paradise compiler plugin to expand static annotations") class noop(arg1: Int, arg2: Int = 0) extends StaticAnnotation { def macroTransform(annottees: Any*): Any = macro AnnotationMacros.noop } class AnnotationMacros(val c: whitebox.Context) { import c.universe._ // an annotation that doesn't do anything: def noop(annottees: c.Expr[Any]*): c.Expr[Any] = { // cases for handling optional arguments val (arg1q, arg2q) = c.prefix.tree match { case q"new noop($arg1, arg2 = $arg2)" => (arg1, arg2) // user gave named arg2 case q"new noop($arg1, $arg2)" => (arg1, arg2) // arg2 without name case q"new noop($arg1)" => (arg1, q"0") // arg2 defaulted case _ => c.abort(c.enclosingPosition, "unexpected annotation pattern!") } // print out the values println(s"arg1= ${evalTree[Int](arg1q)} arg2= ${evalTree[Int](arg2q)}") // just return the original annotee: annottees.length match { case 1 => c.Expr(q"{ ${annottees(0)} }") case _ => c.abort(c.enclosingPosition, "Only one annottee!") } } def evalTree[T](tree: Tree) = c.eval(c.Expr[T(c.untypecheck(tree.duplicate))) } 

Here is an example of a call that arg2 calls, and therefore it will match the first pattern - case q"new noop($arg1, arg2 = $arg2)" - above:

 object demo { // I will match this pattern: case q"new noop($arg1, arg2 = $arg2)" @noop(1, arg2 = 2) trait someDeclarationToAnnotate } 

Please also note that due to the way these templates work, you must explicitly specify the default argument value inside the macro code, which, unfortunately, is a bit hacky, but the final evaluated class is not available to you.

As an experiment, I tried to create a class by calling evalTree[scope.of.class.noop](c.prefix.tree) , but the Scala compiler throws an error because I thought that the link to the annotation inside the annotation macro, which is illegal.

+1
source share

All Articles