Def Macro, pass parameter from a value

若如初见. 提交于 2021-01-29 13:52:21

问题


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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!