Def 宏,从一个值传递参数

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")

Dmy*_*tin 5

简短的回答很简单,您无法完成这项工作。falsecondin的运行时值

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?

如何获取传递给 Scala 宏的参数的运行时值?