Getting Parameters from Scala Macro Annotation

梦想与她 提交于 2020-01-19 12:42:30

问题


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 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")
  }
}

回答1:


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")
  }
}



回答2:


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.



来源:https://stackoverflow.com/questions/32631372/getting-parameters-from-scala-macro-annotation

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!