I am trying to create a macro annotation but I need to pass it parameters.
class ToPojo(param1: String, param2: String) extends StaticAnnotation {
inline def apply(defn: Any): Any = meta {
...
}
}
which is used as
@ToPojo("p1", "p2")
case class Entity(a: Int, m: Map[Long, Boolean])
The problem with above code is that the Entity
gets to the apply
as defn
already with the annotation stripped - hence I can not get the parameters from there. Also the param1
and param2
fields are not accessible from the apply
method since it gets inlined.
Could you please point me to the easiest way to overcome this using scala meta? I thought about using two annotations
@ToPojo
@ToPojoParams("p1", "p2")
case class Entity(a: Int, m: Map[Long, Boolean])
But thats hacky and ugly.
Thanks a lot
As described in the ScalaMeta Paradise description under How do I pass an argument to the macro annotation? section
You match on
this
as a Scalameta tree
package scalaworld.macros
import scala.meta._
class Argument(arg: Int) extends scala.annotation.StaticAnnotation {
inline def apply(defn: Any): Any = meta {
// `this` is a scala.meta tree.
println(this.structure)
val arg = this match {
// The argument needs to be a literal like `1` or a string like `"foobar"`.
// You can't pass in a variable name.
case q"new $_(${Lit.Int(arg)})" => arg
// Example if you have more than one argument.
case q"new $_(${Lit.Int(arg)}, ${Lit.String(foo)})" => arg
case _ => ??? // default value
}
println(s"Arg is $arg")
defn.asInstanceOf[Stat]
}
}
Note that this code is a bit naive and doesn't handle named arguments. If you have many arguments, writing all possible combinations of usual and named arguments is boring. So you may try something like this:
package examples
import scala.collection.immutable
import scala.meta._
class MyMacro(p1: String, p2: Int) extends scala.annotation.StaticAnnotation {
inline def apply(defn: Any): Any = meta {
val params = Params.extractParams(this)
//some implementation
...
}
}
case class Params(p1: String, p2: Int) {
def update(name: String, value: Any): Params = name match {
case "p1" => copy(p1 = value.asInstanceOf[String])
case "p2" => copy(p2 = value.asInstanceOf[Int])
case _ => ???
}
}
object Params {
private val paramsNames = List("p1", "p2")
def extractParams(tree: Stat): Params = {
val args: immutable.Seq[Term.Arg] = tree.asInstanceOf[Term.New].templ.parents.head.asInstanceOf[Term.Apply].args
args.zipWithIndex.foldLeft(Params(null, 0))((acc, argAndIndex) => argAndIndex._1 match {
case q"${Lit(value)}" => acc.update(paramsNames(argAndIndex._2), value)
case q"${Term.Arg.Named(name, Lit(value))}" => acc.update(name.value, value)
})
}
}