简短的回答很简单,你做不到。 false 是 cond in 的运行时值
val cond: Boolean = false
编译器在扩展宏时无法在编译时访问它。
您尝试匹配 Literal 但树/Expr cond 不是文字。
所以Asserts.assert(false, "abc") 可以工作,但Asserts.assert(cond, "abc") 不行。
长答案是有几个技巧。
第一个技巧是你可以将你的项目拆分成三个子项目common、core、macros 并使用c.eval 而不是匹配Literal(Constant)(如果将cond 放在core 而不是common,这将不起作用):
常见
object App {
val cond: Boolean = false
}
核心(取决于宏和common)
import App.cond
object Main {
def main(args: Array[String]): Unit = {
println("Testing assert macro...")
val result = Asserts.assert(cond, "abc")
//scalac: macro expansion has failed: Fix the code, whatever.
}
}
宏(取决于通用)
import scala.language.experimental.macros
import scala.reflect.macros.blackbox
object Asserts {
def assert(cond: Boolean, msg: String): Unit = macro assertImpl
def assertImpl(c: blackbox.Context)(cond: c.Expr[Boolean], msg: c.Expr[String]) : c.Expr[Unit] = {
import c.universe._
val condEvaluated = c.eval(c.Expr[Boolean](c.untypecheck(cond.tree)))
if (!condEvaluated) c.abort(c.enclosingPosition, "Fix the code, whatever.") else c.Expr(q"()")
}
}
Scala: what can code in Context.eval reference?
第二个技巧是,如果你在core中保留cond,那么你可以尝试找到cond的定义树并且得到它的右手边:
核心(取决于宏)
object Main {
def main(args: Array[String]): Unit = {
println("Testing assert macro...")
val cond: Boolean = false
val result = Asserts.assert(cond, "abc")
//scalac: macro expansion has failed: Fix the code, whatever.
}
}
宏
import scala.language.experimental.macros
import scala.reflect.macros.blackbox
object Asserts {
def assert(cond: Boolean, msg: String): Unit = macro assertImpl
def assertImpl(c: blackbox.Context)(cond: c.Expr[Boolean], msg: c.Expr[String]) : c.Expr[Unit] = {
import c.universe._
var condValue: Option[Boolean] = None
val traverser = new Traverser {
override def traverse(tree: Tree): Unit = tree match {
case q"$_ val cond: $_ = $expr" if tree.symbol == cond.tree.symbol =>
expr match {
case Literal(Constant(cond: Boolean)) =>
condValue = Some(cond)
}
case _ => super.traverse(tree)
}
}
c.enclosingRun.units.foreach(unit => traverser.traverse(unit.body))
condValue.map(cond =>
if (!cond) c.abort(c.enclosingPosition, "Fix the code, whatever.") else c.Expr(q"()")
).getOrElse(
c.abort(c.enclosingPosition, "can't find cond")
)
}
}
Creating a method definition tree from a method symbol and a body
Scala macro how to convert a MethodSymbol to DefDef with parameter default values?
How to get the runtime value of parameter passed to a Scala macro?