How can I find the statements in a Scala program from within a compiler plugin?
compiler-construction, plugins, scala
Solution
Ok, assuming the file `TwoStatements.scala` below:
class TwoStatements {
def f {
println("first statement")
println("second statement")
}
}
Try these commands:
scalac -Yshow-trees -Xprint:typer TwoStatements.scala
scalac -Yshow-trees-compact -Xprint:typer TwoStatements.scala
scalac -Yshow-trees-stringified -Xprint:typer TwoStatements.scala
scalac -Ybrowse:typer TwoStatements.scala
Note that `typer` is the phase after which it will produce the output. So, select the phase as appropriate for the phase your own plugin runs in.
The `-Yshow-trees-compact` is the one producing an output exactly (or pretty close) to what you'll use in your own code, but it is very difficult to read.
The others are easier to read, but may be confusing to translate into your own code. The `-Yshow-trees-stringified` will probably be more useful than `-Yshow-trees`, since it display the most information. On the other hand, `-Ybrowse:typer` is interactive, and shows code for the selected tree node, which might be helpful, particularly if you look at larger programs.
If you try `-Yshow-trees-compact` with the example in the linked blog, you'll see this snippet:
Apply(
Select(
Select(This(newTypeName("Test")), newTermName("five")), // assigned to rcvr
newTermName("$div") // compared to nme.DIV
),
List(Literal(Constant(0)))) // as is
)
So what I'm suggesting is that you look at how the code you want to process is translated into at the phase your plugin is working, and then just grab the snippet, and replace whatever uninteresting parts exist with variables.
You'll note that every `DefDef` has the body as its sixth element. It might be a `Block` with a `List` of multiple statements, it might be a method call (`Apply`), a getter (`Select`), an assigment (`Assign`), an if statement (`If`) and so on. Other kinds of declaration, such as `ValDef`, also have code associated with it.
If you were looking for something like `tree @ Statement(...)`, it does not exist. You could use `TermTree` (or, better yet, the method `isTerm`) to identify things that represent code, but that will not let you tell apart statements from parts of an expression or full blocks.
Look at the AST of actual code, and learn how it works.
EDIT
Watching a presentation just clued me in to this comment at the end of the `scala/reflecti/api/Trees.scala` file:
// A standard pattern match
case EmptyTree =>
case PackageDef(pid, stats) =>
// package pid { stats }
case ClassDef(mods, name, tparams, impl) =>
// mods class name [tparams] impl where impl = extends parents { defs }
case ModuleDef(mods, name, impl) => (eliminated by refcheck)
// mods object name impl where impl = extends parents { defs }
case ValDef(mods, name, tpt, rhs) =>
// mods val name: tpt = rhs
// note missing type information is expressed by tpt = TypeTree()
case DefDef(mods, name, tparams, vparamss, tpt, rhs) =>
// mods def name[tparams](vparams_1)...(vparams_n): tpt = rhs
// note missing type information is expressed by tpt = TypeTree()
case TypeDef(mods, name, tparams, rhs) => (eliminated by erasure)
// mods type name[tparams] = rhs
// mods type name[tparams] >: lo <: hi, where lo, hi are in a TypeBoundsTree,
and DEFERRED is set in mods
case LabelDef(name, params, rhs) =>
// used for tailcalls and like
// while/do are desugared to label defs as follows:
// while (cond) body ==> LabelDef($L, List(), if (cond) { body; L$() } else ())
// do body while (cond) ==> LabelDef($L, List(), body; if (cond) L$() else ())
case Import(expr, selectors) => (eliminated by typecheck)
// import expr.{selectors}
// Selectors are a list of pairs of names (from, to).
// The last (and maybe only name) may be a nme.WILDCARD
// for instance
// import qual.{x, y => z, _} would be represented as
// Import(qual, List(("x", "x"), ("y", "z"), (WILDCARD, null)))
case Template(parents, self, body) =>
// extends parents { self => body }
// if self is missing it is represented as emptyValDef
case Block(stats, expr) =>
// { stats; expr }
case CaseDef(pat, guard, body) => (eliminated by transmatch/explicitouter)
// case pat if guard => body
case Alternative(trees) => (eliminated by transmatch/explicitouter)
// pat1 | ... | patn
case Star(elem) => (eliminated by transmatch/explicitouter)
// pat*
case Bind(name, body) => (eliminated by transmatch/explicitouter)
// name @ pat
case UnApply(fun: Tree, args) (introduced by typer, eliminated by transmatch/explicitouter)
// used for unapply's
case ArrayValue(elemtpt, trees) => (introduced by uncurry)
// used to pass arguments to vararg arguments
// for instance, printf("%s%d", foo, 42) is translated to after uncurry to:
// Apply(
// Ident("printf"),
// Literal("%s%d"),
// ArrayValue(<Any>, List(Ident("foo"), Literal(42))))
case Function(vparams, body) => (eliminated by lambdaLift)
// vparams => body where vparams:List[ValDef]
case Assign(lhs, rhs) =>
// lhs = rhs
case AssignOrNamedArg(lhs, rhs) => (eliminated by typer, resurrected by reifier)
// @annotation(lhs = rhs)
case If(cond, thenp, elsep) =>
// if (cond) thenp else elsep
case Match(selector, cases) =>
// selector match { cases }
case Return(expr) =>
// return expr
case Try(block, catches, finalizer) =>
// try block catch { catches } finally finalizer where catches: List[CaseDef]
case Throw(expr) =>
// throw expr
case New(tpt) =>
// new tpt always in the context: (new tpt).<init>[targs](args)
case Typed(expr, tpt) => (eliminated by erasure)
// expr: tpt
case TypeApply(fun, args) =>
// fun[args]
case Apply(fun, args) =>
// fun(args)
// for instance fun[targs](args) is expressed as Apply(TypeApply(fun, targs), args)
case ApplyDynamic(qual, args) (introduced by erasure, eliminated by cleanup)
// fun(args)
case Super(qual, mix) =>
// qual.super[mix] qual is always This(something), if mix is empty, it is tpnme.EMPTY
case This(qual) =>
// qual.this
case Select(qualifier, selector) =>
// qualifier.selector
case Ident(name) =>
// name
// note: type checker converts idents that refer to enclosing fields or methods
// to selects; name ==> this.name
case ReferenceToBoxed(ident) => (created by typer, eliminated by lambdalift)
// synthetic node emitted by macros to reference capture vars directly without going through ``elem''
// var x = ...; fun { x } will emit Ident(x), which gets transformed to Select(Ident(x), "elem")
// if ReferenceToBoxed were used instead of Ident, no transformation would be performed
case Literal(value) =>
// value
case TypeTree() => (introduced by refcheck)
// a type that's not written out, but given in the tpe attribute
case Annotated(annot, arg) => (eliminated by typer)
// arg @annot for types, arg: @annot for exprs
case SingletonTypeTree(ref) => (eliminated by uncurry)
// ref.type
case SelectFromTypeTree(qualifier, selector) => (eliminated by uncurry)
// qualifier # selector, a path-dependent type p.T is expressed as p.type # T
case CompoundTypeTree(templ: Template) => (eliminated by uncurry)
// parent1 with ... with parentN { refinement }
case AppliedTypeTree(tpt, args) => (eliminated by uncurry)
// tpt[args]
case TypeBoundsTree(lo, hi) => (eliminated by uncurry)
// >: lo <: hi
case ExistentialTypeTree(tpt, whereClauses) => (eliminated by uncurry)
// tpt forSome { whereClauses }
Problem
I'm writing a Scala compiler plugin and have gotten to the point of writing the "apply(unit:CompilationUnit" method. The syntax within that method is beyond me however. The following code is from http://www.scala-lang.org/node/140 ``` for ( tree @ Apply(Select(rcvr, nme.DIV), List(Literal(Constant(0)))) <- unit.body; if rcvr.tpe <:< definitions.IntClass.tpe) { unit.error(tree.pos, "definitely division by zero") } ``` This expression finds all division by zero. I can't figure out how to do something similar to find all executable statements (TermTrees??).