Получение параметров из аннотации макроса Scala

Итак, у меня есть аннотация к функции (DefDef). Эта аннотация имеет параметры. Однако я не понимаю, как получить параметры из конструктора.

Пример использования:

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

Вот код аннотации:

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

person ilinum    schedule 17.09.2015    source источник


Ответы (2)


Что насчет этого:

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

Для полноты это полный источник:

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")
  }
}
person Federico Pellegatta    schedule 17.09.2015
comment
Это решение интересное, но мне оно почему-то кажется хакерским. Кроме того, я хочу, чтобы b имел тип Boolean, а не тип Any. Например, кто-то может сказать @Foo("AAA") и подумать, что его код скомпилируется, глядя на сигнатуру конструктора. Кроме того, это станет еще более запутанным, если я решу добавить больше аргументов. Но это решение работает, так что спасибо! - person ilinum; 17.09.2015
comment
Этот код сейчас не компилируется. При смене типа параметра с Boolean на Any все нормально. Сообщение об ошибке: an expression of type Null is ineligible for implicit conversion - person ilinum; 18.09.2015

Это ответ, который показывает вариант метода Федерико, если вы хотите использовать статическую аннотацию с необязательными именованными аргументами. В этом случае вам необходимо рассмотреть возможные выражения вызова в операторе сопоставления case. Необязательный аргумент может быть явно назван, он может быть задан без имени или может отсутствовать. Каждый из них отображается во время компиляции как отдельный шаблон в c.prefix.tree, как показано ниже.

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

Вот пример вызова с именем arg2, поэтому он будет соответствовать первому шаблону — case q"new noop($arg1, arg2 = $arg2)" — выше:

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

Также обратите внимание, что из-за того, как работают эти шаблоны, вы должны явно указать значение аргумента по умолчанию внутри кода макроса, что, к сожалению, немного хакерское, но окончательный оцененный класс вам недоступен.

В качестве эксперимента я попытался на самом деле создать класс, вызвав evalTree[scope.of.class.noop](c.prefix.tree), но компилятор Scala выдает ошибку, поскольку считает, что ссылка на аннотацию находится внутри кода макроса аннотации, что является незаконным.

person eje    schedule 22.03.2017