将参数传递给 scalameta paradise 宏
Passing parameters to scalameta paradise macro
我正在尝试创建一个宏注释,但我需要向它传递参数。
class ToPojo(param1: String, param2: String) extends StaticAnnotation {
inline def apply(defn: Any): Any = meta {
...
}
}
用作
@ToPojo("p1", "p2")
case class Entity(a: Int, m: Map[Long, Boolean])
上面代码的问题是 Entity
到达 apply
时 defn
已经删除了注释 - 因此我无法从那里获取参数。此外,param1
和 param2
字段无法从 apply
方法访问,因为它是内联的。
你能告诉我使用 scala meta 克服这个问题的最简单方法吗?我考虑过使用两个注释
@ToPojo
@ToPojoParams("p1", "p2")
case class Entity(a: Int, m: Map[Long, Boolean])
但那太古怪和丑陋了。
非常感谢
如 How do I pass an argument to the macro annotation? 部分
下的 ScalaMeta Paradise 描述中所述
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]
}
}
请注意,这段代码有点天真,不处理命名参数。如果你有很多参数,写出常用参数和命名参数的所有可能组合是很无聊的。所以你可以尝试这样的事情:
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)
})
}
}
我正在尝试创建一个宏注释,但我需要向它传递参数。
class ToPojo(param1: String, param2: String) extends StaticAnnotation {
inline def apply(defn: Any): Any = meta {
...
}
}
用作
@ToPojo("p1", "p2")
case class Entity(a: Int, m: Map[Long, Boolean])
上面代码的问题是 Entity
到达 apply
时 defn
已经删除了注释 - 因此我无法从那里获取参数。此外,param1
和 param2
字段无法从 apply
方法访问,因为它是内联的。
你能告诉我使用 scala meta 克服这个问题的最简单方法吗?我考虑过使用两个注释
@ToPojo
@ToPojoParams("p1", "p2")
case class Entity(a: Int, m: Map[Long, Boolean])
但那太古怪和丑陋了。
非常感谢
如 How do I pass an argument to the macro annotation? 部分
下的 ScalaMeta Paradise 描述中所述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]
}
}
请注意,这段代码有点天真,不处理命名参数。如果你有很多参数,写出常用参数和命名参数的所有可能组合是很无聊的。所以你可以尝试这样的事情:
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)
})
}
}