Bak*_*aku 0 scala scala-macros
我有一个工作宏,即:
object Main extends App {
println("Testing assert macro...")
val result = Asserts.assert(false, "abc")
}
Run Code Online (Sandbox Code Playgroud)
和
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"()")
}
}
}
Run Code Online (Sandbox Code Playgroud)
但问题是:我怎样才能做到这一点?:
val cond: Boolean = false
val result = Asserts.assert(cond, "abc")
Run Code Online (Sandbox Code Playgroud)
现在我有一个错误:
Error:(8, 30) 宏扩展期间的异常:scala.MatchError: Main.this.cond (of class scala.reflect.internal.Trees$Select) at io.baku.macrosy.Asserts$.assertImpl(Asserts.scala: 13) val 结果 = Asserts.assert(cond, "abc")
简短的回答很简单,您无法完成这项工作。false是condin的运行时值
val cond: Boolean = false
Run Code Online (Sandbox Code Playgroud)
编译器在扩展宏时无法在编译时访问它。
您尝试匹配,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
}
Run Code Online (Sandbox Code Playgroud)
核心(取决于宏和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.
}
}
Run Code Online (Sandbox Code Playgroud)
宏(取决于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"()")
}
}
Run Code Online (Sandbox Code Playgroud)
Scala:什么可以在 Context.eval 参考中编码?
第二个技巧是,如果您保持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.
}
}
Run Code Online (Sandbox Code Playgroud)
宏
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")
)
}
}
Run Code Online (Sandbox Code Playgroud)
Scala 宏如何使用参数默认值将 MethodSymbol 转换为 DefDef?
| 归档时间: |
|
| 查看次数: |
106 次 |
| 最近记录: |