What's the easiest way to use reify (get an AST of) an expression in Scala?

Daniel C. Sobral picture Daniel C. Sobral · Jun 15, 2012 · Viewed 9.5k times · Source

I'm looking at alternatives to -print or javap as a way of figuring out what the compiler is doing in Scala. With the new reflection/macros library, reify seems a good candidate for that, as shown in retronym's macrocosm's desugar. It even shows how one used to do that, pre-M4.

So the question is, what's the shortest/easiest thing I can type on Scala's REPL to get the AST for an expression, post-Scala 2.10.0-M4?

Answer

kiritsuku picture kiritsuku · Jun 15, 2012

A lot of things previously defined in package scala.reflect.mirror have moved to scala.reflect.runtime.universe:

scala> import scala.reflect.runtime.{universe => u}
import scala.reflect.runtime.{universe=>u}

scala> val expr = u reify { 1 to 3 map (_+1) }
expr: reflect.runtime.universe.Expr[scala.collection.immutable.IndexedSeq[Int]] = Expr[scala.collection.immutable.IndexedSeq[Int]](scala.this.Predef.intWrapper(1).to(3).map(((x$1) => x$1.$plus(1)))(immutable.this.IndexedSeq.canBuildFrom))

scala> u show expr.tree
res57: String = scala.this.Predef.intWrapper(1).to(3).map(((x$1) => x$1.$plus(1)))(immutable.this.IndexedSeq.canBuildFrom)

scala> u showRaw expr.tree
res58: String = Apply(Apply(Select(Apply(Select(Apply(Select(Select(This(newTypeName("scala")), newTermName("Predef")), newTermName("intWrapper")), List(Literal(Constant(1)))), newTermName("to")), List(Literal(Constant(3)))), newTermName("map")), List(Function(List(ValDef(Modifiers(<param> <synthetic>), newTermName("x$1"), TypeTree(), EmptyTree)), Apply(Select(Ident(newTermName("x$1")), newTermName("$plus")), List(Literal(Constant(1))))))), List(Select(Select(This(newTypeName("immutable")), newTermName("IndexedSeq")), newTermName("canBuildFrom"))))

Furthermore it is possible to check if a string containing some Scala code is a valid Scala expression and - even better - do some evaluation:

Edit. In 2.10.0-RC1 some methods of ToolBox have been renamed. parseExpr is now just parse, and runExpr is now called eval.

scala> import scala.tools.reflect.ToolBox
import scala.tools.reflect.ToolBox

scala> import scala.reflect.runtime.{currentMirror => m}
import scala.reflect.runtime.{currentMirror=>m}

scala> val tb = m.mkToolBox()
tb: scala.tools.reflect.ToolBox[reflect.runtime.universe.type] = scala.tools.reflect.ToolBoxFactory$ToolBoxImpl@9293709

scala> val tree = tb.parse("1 to 3 map (_+1)")
tree: tb.u.Tree = 1.to(3).map(((x$1) => x$1.$plus(1)))

scala> val eval = tb.eval(tree)
eval: Any = Vector(2, 3, 4)

The most complicated thing here is the raw tree representation of an expression. When one wants to use macros, the macros have to be defined the same way as shown by showRaw. But with some helper methods it is possible to define some not so ugly looking macro implementations:

object IntMacro {

  import language.experimental.macros
  import scala.reflect.makro.Context
  import scala.reflect.NameTransformer.encode

  def isEven(i: Int): Boolean = macro isEvenImpl

  def isEvenImpl(c: Context)(i: c.Expr[Int]): c.Expr[Boolean] = {
    import c.universe._
    implicit val cc: c.type = c

    val `x = i%2` = Apply(Select(i.tree, op("%")), const(2))
    val `x == 0` = Apply(Select(`x = i%2`, op("==")), const(0))

    c.Expr(`x == 0`)
  }

  def op(s: String)(implicit c: Context): c.universe.TermName =
    c.universe.newTermName(encode(s))

  def const(a: Any)(implicit c: Context): List[c.universe.Literal] =
    List(c.universe.Literal(c.universe.Constant(a)))
}

scala> import IntMacro._
import IntMacro._

scala> isEven(2)
res60: Boolean = true

scala> isEven(3)
res61: Boolean = false

But now we come in problems with path-dependent-types - we have to write their paths explicitly if we want not import them.