问题
I have a working macros ie:
object Main extends App {
println("Testing assert macro...")
val result = Asserts.assert(false, "abc")
}
and
import scala.reflect.macros.blackbox.Context
import scala.language.experimental.macros
object Asserts {
val assertionsEnabled: Boolean = true
def assert(cond: Boolean, msg: String): Unit = macro assertImpl
def assertImpl(c: Context)(cond: c.Expr[Boolean], msg: c.Expr[String]) : c.Expr[Unit] = {
import c.universe._
cond.tree match {
case Literal(Constant(cond: Boolean)) =>
if (!cond) c.abort(c.enclosingPosition, "Fix the code, whatever.") else c.Expr(q"()")
}
}
}
but question is: how can I make this working ?:
val cond: Boolean = false
val result = Asserts.assert(cond, "abc")
now I have an error:
Error:(8, 30) exception during macro expansion: scala.MatchError: Main.this.cond (of class scala.reflect.internal.Trees$Select) at io.baku.macrosy.Asserts$.assertImpl(Asserts.scala:13) val result = Asserts.assert(cond, "abc")
回答1:
The short answer is easy, you can't make this work. false
is a runtime value of cond
in
val cond: Boolean = false
Compiler doesn't have access to it at compile time while expanding the macro.
You try to match Literal
but the tree/Expr
cond
is not a literal.
So Asserts.assert(false, "abc")
will work but Asserts.assert(cond, "abc")
won't.
The long answer is that there are a couple of tricks.
The first trick is that you can split your project into three subprojects common, core, macros and use c.eval
instead of matching Literal(Constant)
(if you put cond
to core instead of common, this will not work):
common
object App {
val cond: Boolean = false
}
core (depends on macros and 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.
}
}
macros (depends on common)
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?
The second trick is that if you keep cond
in core then you can try to find definition tree of cond
and get its right hand side:
core (depends on macros)
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.
}
}
macros
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?
来源:https://stackoverflow.com/questions/63132189/def-macro-pass-parameter-from-a-value