Accessing a defined val from inside a macro

Example project at

I want to be able to add a debugging statement after every val definition in a block:

object Main {
  def main(args: Array[String]): Unit = {
    val str = concatStrings("hello", "world")
    println(s"concatStrings = $str")
  }

  def concatStrings(s1: String, s2: String): String = {
    dumpVars {
      val result = s1 + s2
      result
    }(s => println(s.toString))
  }
}

And I ALMOST have it working with this:


case class DumpTerm(name: String, value: Any)

class MacroBundle(val c: blackbox.Context) {
  import c.universe._

  def dumpVars(expr: c.Tree)(output: c.Expr[DumpTerm => Unit]): c.Tree = {
    val q"..$stats" = expr
    val loggedStats = stats.flatMap { stat =>
      stat match {
        case ValDef(_, termName, _, rhs) =>
          //val ident = Ident(TermName("s1")) // works fine on parameter
          //val ident = rhs // can cheat by using the rvalue
          val ident = Ident(termName) // doesn't work on ident of a val def defined inside :-/

          List(stat, q"$output(DumpTerm(${termName.encodedName.toString}, ${ident}))")
        case _ =>
          List(stat)
      }
    }
    println(showRaw(q"..$loggedStats"))
    q"..$loggedStats"
  }
}

Somehow the debug statement doesn’t have access to the val that was just defined in the previous line:

[error] /home/wsargent/work/debug-after-valdef-macro/example/src/main/scala/example/Main.scala:16:6: not found: value result
[error]     }(s => println(s.toString))

I’ve printed out the AST and generated code in the README. It looks like it should work. I am kind of flummoxed.

1 Like

After playing with this a little it seems that you need to call c.untypecheck(q"..$loggedStats") and return the value from that.

My guess is that you need to tell the compiler that it needs to redo the typechecking.

1 Like

Ah thank you!

After some tweaking (and making the names clearer) I managed to get it down to this, which works:

case class DebugVal(name: String, value: Any)

class DebugValMacros(val c: blackbox.Context) {
  import c.universe._

  def debugVals[A](expr: c.Expr[A])(output: c.Expr[DebugVal => Unit]): c.Expr[A] = {
    val loggedStats = expr.tree.children.flatMap {
      case valdef@ValDef(_, termName, _, _) =>
        List(valdef, q"$output(DebugVal(${termName.encodedName.toString}, $termName))")
      case stat =>
        List(stat)
    }
    val outputExpr: c.Expr[A] = c.Expr[A](c.untypecheck(q"..$loggedStats"))
    outputExpr
  }
}

I’ve updated the project.

2 Likes