Getting Parameters from Scala Macro Annotation

后端 未结 2 763
灰色年华
灰色年华 2020-12-06 06:38

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

Usage exa

相关标签:
2条回答
  • 2020-12-06 07:05

    This is an answer that shows a variation on Federico's technique, if you want to use a static annotation that has optional named arguments. In that case, you need to consider the possible invocation expressions in the case matching statement. An optional argument might be explicitly named, it might be given without a name, or it might be not present. Each of these shows up at compile time as a separate pattern 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 invocation that names arg2, and so 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
    }
    

    Note also that because of the way these patterns work, you have to explicitly supply the default argument value inside the macro code, which is unfortunately a bit hacky, but the final evaluated class is not available to you.

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

    0 讨论(0)
  • 2020-12-06 07:14

    What about this:

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

    For sake of completeness this is the full 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")
      }
    }
    
    0 讨论(0)
提交回复
热议问题