-
Notifications
You must be signed in to change notification settings - Fork 17
Scala AST reference
This document is not a formal reference for the AST created by the Scala compiler. It provides several examples for the kinds of nodes in the AST that we may encounter.
-
All the case classes here (say
X) should be prefixed byglobal.Xwhen used in a case statement. To avoid that, we couldimport global._. -
Type names (like Int, Map, etc) either belong to a class called
TypeName_SorTypeName_R. But these are private members of the classNamesso we cannot pattern match against them. However, any AST node that is a type name has the flagisTypeNameset totrue. This could be used to identify type names. Below, all type names are represented by the stringnewTypeName("<some-name>").
Types are identified by either Ident, Select or AppliedTypeTree. See notes on Select below. Here are some examples.
| Original code | AST after type inference |
|---|---|
Int |
Ident(newTypeName("Int")) |
String |
Ident(newTypeName("String")) |
Foo |
Ident(newTypeName("Foo")) |
java.util.Random |
Select(Select(Ident(newTermName("java")), newTermName("util")), newTypeName("Random")) |
scala.util.Random |
Select(Select(Ident(newTermName("scala")), newTermName("util")), newTypeName("Random")) |
Set[String] |
AppliedTypeTree(Ident(newTypeName("Set")),List(Ident(newTypeName("String")))) |
Map[String, Int] |
AppliedTypeTree(Ident(newTypeName("Map")), List(Ident(newTypeName("String")), Ident(newTypeName("Int")))) |
(String, Int) |
AppliedTypeTree(Select(Ident(scala), newTypeName("Tuple2")), List(Ident(newTypeName("String")), Ident(newTypeName("Int")))) |
(String, Int, Double) |
AppliedTypeTree(Select(Ident(scala), newTypeName("Tuple3")), List(Ident(newTypeName("String")), Ident(newTypeName("Int")), Ident(newTypeName("Double")))) |
String => Double |
AppliedTypeTree(Select(Select(Ident(nme.ROOTPKG), scala), newTypeName("Function1")), List(Ident(newTypeName("String")), Ident(newTypeName("Double")))) |
(String, Int) => Double |
AppliedTypeTree(Select(Select(Ident(nme.ROOTPKG), scala), newTypeName("Function2")), List(Ident(newTypeName("String")), Ident(newTypeName("Int")), Ident(newTypeName("Double"))) |
(String, Int, Boolean) => Double |
AppliedTypeTree(Select(Select(Ident(nme.ROOTPKG), scala), newTypeName("Function3")), List(Ident(newTypeName("String")), Ident(newTypeName("Int")), Ident(newTypeName("Boolean")), Ident(newTypeName("Double")))) |
String => Int => Double |
AppliedTypeTree(Select(Select(Ident(nme.ROOTPKG), scala), newTypeName("Function1")), List(Ident(newTypeName("String")), AppliedTypeTree(Select(Select(Ident(nme.ROOTPKG), scala), newTypeName("Function1")), List(Ident(newTypeName("Int")), Ident(newTypeName("Double")))))) |
Notes
- In general,
AppliedTypeTreetakes two arguments. The first argument tells us about the type and the second argument is a list that gives information about the types of parameters used to construct the types. -
Selectis used to pick a member of a type or an object asSelect(identifier, member). In the examples above, note that this is nested repeatedly till we drill down to the member we need. We will see more of this below too. - Some
Idents take the word scala as a parameter. Most likely, we won't be using this. So, for now, let's ignore them. Ditto fornme.ROOTPKG.
New types defined using the type construction are represented by TypeDef nodes in the AST. TypeDef takes four arguments:
- Modifiers, which indicates whether the new type is public/private/etc. We won't be needing this for now.
- The name of the new type
- A list of parameters for the type (eg, for cases like
type Foo[T, S] = Map[T, S]. Not sure if we will need this. - The RHS which should be a valid type expression. Since we will be looking at ASTs after many compiler phases, in all cases, we will be looking at an object called TypeTree(). This object should have a member called original which will give us a type expressionthat looks like what is defined in the Types section above.
| Original code | AST after type inference |
|---|---|
type Foo1 = Int |
TypeDef(Modifiers(), newTypeName("Foo1"), List(), TypeTree().setOriginal(Select(Ident(scala), scala.Int))) |
type Foo2 = String |
TypeDef(Modifiers(), newTypeName("Foo2"), List(), TypeTree().setOriginal(Select(Select(This(newTypeName("scala")), scala.Predef), newTypeName("String")))) |
type Foo3 = Set[String] |
TypeDef(Modifiers(), newTypeName("Foo3"), List(), TypeTree().setOriginal(AppliedTypeTree(Select(Select(This(newTypeName("scala")), scala.Predef), newTypeName("Set")), List(TypeTree().setOriginal(Select(Select(This(newTypeName("scala")), scala.Predef), newTypeName("String"))))))) |
type Foo4 = Map[String, Int] |
TypeDef(Modifiers(), newTypeName("Foo4"), List(), TypeTree().setOriginal(AppliedTypeTree(Select(Select(This(newTypeName("scala")), scala.Predef), newTypeName("Map")), List(TypeTree().setOriginal(Select(Select(This(newTypeName("scala")), scala.Predef), newTypeName("String"))), TypeTree().setOriginal(Select(Ident(scala), scala.Int)))))) |
type Foo5 = (String, Int) |
TypeDef(Modifiers(), newTypeName("Foo5"), List(), TypeTree().setOriginal(AppliedTypeTree(Select(Ident(scala), scala.Tuple2), List(TypeTree().setOriginal(Select(Select(This(newTypeName("scala")), scala.Predef), newTypeName("String"))), TypeTree().setOriginal(Select(Ident(scala), scala.Int)))))) |
type Foo6 = scala.util.Random |
TypeDef(Modifiers(), newTypeName("Foo6"), List(), TypeTree().setOriginal(Select(Select(Ident(scala), scala.util), scala.util.Random))) |
type Foo7 = (String, Int, Double) |
TypeDef(Modifiers(), newTypeName("Foo7"), List(), TypeTree().setOriginal(AppliedTypeTree(Select(Ident(scala), scala.Tuple3), List(TypeTree().setOriginal(Select(Select(This(newTypeName("scala")), scala.Predef), newTypeName("String"))), TypeTree().setOriginal(Select(Ident(scala), scala.Int)), TypeTree().setOriginal(Select(Ident(scala), scala.Double)))))) |
type Foo9 = String => Double |
TypeDef(Modifiers(), newTypeName("Foo9"), List(), TypeTree().setOriginal(AppliedTypeTree(Select(Select(Ident(_root_), scala), scala.Function1), List(TypeTree().setOriginal(Select(Select(This(newTypeName("scala")), scala.Predef), newTypeName("String"))), TypeTree().setOriginal(Select(Ident(scala), scala.Double)))))) |
type Foo10 = (String, Int, Boolean) => Double |
TypeDef(Modifiers(), newTypeName("Foo10"), List(), TypeTree().setOriginal(AppliedTypeTree(Select(Select(Ident(_root_), scala), scala.Function3), List(TypeTree().setOriginal(Select(Select(This(newTypeName("scala")), scala.Predef), newTypeName("String"))), TypeTree().setOriginal(Select(Ident(scala), scala.Int)), TypeTree().setOriginal(Select(Ident(scala), scala.Boolean)), TypeTree().setOriginal(Select(Ident(scala), scala.Double)))))) |
type Foo11 = String => Int => Double |
TypeDef(Modifiers(), newTypeName("Foo11"), List(), TypeTree().setOriginal(AppliedTypeTree(Select(Select(Ident(_root_), scala), scala.Function1), List(TypeTree().setOriginal(Select(Select(This(newTypeName("scala")), scala.Predef), newTypeName("String"))), TypeTree().setOriginal(AppliedTypeTree(Select(Select(Ident(_root_), scala), scala.Function1), List(TypeTree().setOriginal(Select(Ident(scala), scala.Int)), TypeTree().setOriginal(Select(Ident(scala), scala.Double))))))))) |
type Foo12[T] = Set[T] |
TypeDef(Modifiers(), newTypeName("Foo12"), List(TypeDef(Modifiers(PARAM), newTypeName("T"), List(), TypeTree().setOriginal(TypeBoundsTree(TypeTree().setOriginal(Select(Select(Ident(_root_), scala), scala.Nothing)), TypeTree().setOriginal(Select(Select(Ident(_root_), scala), scala.Any)))))), TypeTree().setOriginal(AppliedTypeTree(Select(Select(This(newTypeName("scala")), scala.Predef), newTypeName("Set")), List(TypeTree().setOriginal(Ident(newTypeName("T"))))))) |
type Foo13[T, S] = Map[T, S] |
TypeDef(Modifiers(), newTypeName("Foo13"), List(TypeDef(Modifiers(PARAM), newTypeName("T"), List(), TypeTree().setOriginal(TypeBoundsTree(TypeTree().setOriginal(Select(Select(Ident(_root_), scala), scala.Nothing)), TypeTree().setOriginal(Select(Select(Ident(_root_), scala), scala.Any))))), TypeDef(Modifiers(PARAM), newTypeName("S"), List(), TypeTree().setOriginal(TypeBoundsTree(TypeTree().setOriginal(Select(Select(Ident(_root_), scala), scala.Nothing)), TypeTree().setOriginal(Select(Select(Ident(_root_), scala), scala.Any)))))), TypeTree().setOriginal(AppliedTypeTree(Select(Select(This(newTypeName("scala")), scala.Predef), newTypeName("Map")), List(TypeTree().setOriginal(Ident(newTypeName("T"))), TypeTree().setOriginal(Ident(newTypeName("S"))))))) |
Scala objects are either literals (integers, strings, etc) or are created by applying a constructor. Symbols look like a combination of both.
| Scala object | AST case class |
|---|---|
1 |
Literal(Constant(1)) |
"wolfe" |
Literal(Constant("wolfe")) |
1.3 |
Literal(Constant(1.3)) |
true |
Literal(Constant(true)) |
(1, 2) |
Apply(Select(Ident(scala), newTermName("Tuple2")), List(Literal(Constant(1)), Literal(Constant(2)))) |
'Anna |
Apply(Select(Ident(scala), newTermName("Symbol")), List(Literal(Constant("Anna")))) |
new Foo |
Apply(Select(New(Ident(newTypeName("Foo"))), nme.CONSTRUCTOR), List()) |
new Foo(a, b) |
Apply(Select(New(Ident(newTypeName("Foo"))), nme.CONSTRUCTOR), List(Ident(newTermName("a")), Ident(newTermName("b")))) |
new some.where.Foo |
Apply(Select(New(Select(Select(Ident(newTermName("some")), newTermName("where")), newTypeName("Foo"))), nme.CONSTRUCTOR), List()) |
new some.where.Foo(a,b) |
Apply(Select(New(Select(Select(Ident(newTermName("some")), newTermName("where")), newTypeName("Foo"))), nme.CONSTRUCTOR), List(Ident(newTermName("a")), Ident(newTermName("b")))) |
After typer:
| new String | Apply(Select(New(Ident(newTypeName("String"))), nme.CONSTRUCTOR), List()) |
After refchecks:
| new String | Apply(Select(New(TypeTree()), nme.CONSTRUCTOR), List())|
Assignment to a val is represented using the ValDef case class, which takes four arguments:
- Modifier, representing private/public/etc, which we won't use for now
- The name of the new term
- The type of the new term (if explicitly stated), as a type node (see section on Types)
- The right hand side (which could represent any expression)
Some examples below
| Scala statement | AST case class |
|---|---|
val num = 1 |
ValDef(Modifiers(), newTermName("num"), TypeTree(), Literal(Constant(1))) |
private val dbl: Double = 1.3 |
ValDef(Modifiers(PRIVATE), newTermName("dbl"), Ident(newTypeName("Double")), Literal(Constant(1.3))) |
val foo = new Foo |
ValDef(Modifiers(), newTermName("foo"), TypeTree(), Apply(Select(New(Ident(newTypeName("Foo"))), nme.CONSTRUCTOR), List())) |
val fooab = new Foo(a, b) |
ValDef(Modifiers(), newTermName("fooab"), TypeTree(), Apply(Select(New(Ident(newTypeName("Foo"))), nme.CONSTRUCTOR), List(Ident(newTermName("a")), Ident(newTermName("b"))))) |
Function calls are translated to the Apply case class. Apply takes two arguments -- a Select or an Ident for the function and a list of arguments. Each element in the argument list is an AST representing it.
Examples:
| Scala statement | AST case class |
|---|---|
f(10) |
Apply(Ident(newTermName("f")), List(Literal(Constant(10)))) |
val a = foo(10) |
ValDef(Modifiers(), newTermName("a"), TypeTree(), Apply(Ident(newTermName("f")), List(Literal(Constant(10))))) |
foo.call(1, 3) |
Apply(Select(Ident(newTermName("foo")), newTermName("call")), List(Literal(Constant(1)), Literal(Constant(3)))) |
1 + 3 |
Apply(Select(Literal(Constant(1)), newTermName("$plus")), List(Literal(Constant(3)))) |
bar.execute("mary", "lamb") |
Apply(Select(Ident(newTermName("bar")), newTermName("execute")), List(Literal(Constant("mary")), Literal(Constant("lamb")))) |
a + 3 |
Apply(Select(Ident(newTermName("a")), newTermName("$plus")), List(Literal(Constant(3)))) |
f1 + f2 |
Apply(Select(Ident(newTermName("f1")), newTermName("$plus")), List(Ident(newTermName("f2")))) |
(f1 + f2) dot weights |
Apply(Select(Apply(Select(Ident(newTermName("f1")), newTermName("$plus")), List(Ident(newTermName("f2")))), newTermName("dot")), List(Ident(newTermName("weights")))) |
Notes
- Operators are just functions, selected from the previous identifier. This is consistent with the way we define operators.
- One weird case is the
x :: xsconstruction used to create a list. The::is really a function ofxs. To handle this, this is translated toval x$1 = x; xs.$colon$colon(x$1). This is then parsed as aBlockobject containing a list of two statements: a val assignment followed by a function call. - Note that as far as the AST is concerned, we don't distinguish between calling a function with a dot or with a space.
Functions are defined using the DefDef construction. DefDef takes five arguments:
- Modifiers: public/private/etc
- Name of the function
- Type parameters: Any generic type parameters associated with the function
- Parameters: A list of list of ValDefs. Each inner list is one set of parameters.
- A type tree that may contain information about the function's return type
- The body of the function, as a tree
Examples
| Function definition | AST case class |
|---|---|
def f = 10 |
DefDef(Modifiers(), newTermName("f"), List(), List(), TypeTree(), Literal(Constant(10))) |
def f: Int = 10 |
DefDef(Modifiers(), newTermName("f"), List(), List(), Ident(newTypeName("Int")), Literal(Constant(10))) |
def f(a: Int) = a + 3 |
DefDef(Modifiers(), newTermName("f"), List(), List(List(ValDef(Modifiers(PARAM), newTermName("a"), Ident(newTypeName("Int")), EmptyTree))), TypeTree(), Apply(Select(Ident(newTermName("a")), newTermName("$plus")), List(Literal(Constant(3))))) |
def f[T](a: T) = a.toString |
DefDef(Modifiers(), newTermName("f"), List(TypeDef(Modifiers(PARAM), newTypeName("T"), List(), TypeBoundsTree(Select(Select(Ident(nme.ROOTPKG), scala), newTypeName("Nothing")), Select(Select(Ident(nme.ROOTPKG), scala), newTypeName("Any"))))), List(List(ValDef(Modifiers(PARAM), newTermName("a"), Ident(newTypeName("T")), EmptyTree))), TypeTree(), Select(Ident(newTermName("a")), newTermName("toString"))) |
def f[T, S](a: T) = createS(a) |
DefDef(Modifiers(), newTermName("f"), List(TypeDef(Modifiers(PARAM), newTypeName("T"), List(), TypeBoundsTree(Select(Select(Ident(nme.ROOTPKG), scala), newTypeName("Nothing")), Select(Select(Ident(nme.ROOTPKG), scala), newTypeName("Any")))), TypeDef(Modifiers(PARAM), newTypeName("S"), List(), TypeBoundsTree(Select(Select(Ident(nme.ROOTPKG), scala), newTypeName("Nothing")), Select(Select(Ident(nme.ROOTPKG), scala), newTypeName("Any"))))), List(List(ValDef(Modifiers(PARAM), newTermName("a"), Ident(newTypeName("T")), EmptyTree))), TypeTree(), Apply(Ident(newTermName("createS")), List(Ident(newTermName("a"))))) |
def f(a: Int) = { a + 3 } |
DefDef(Modifiers(), newTermName("f"), List(), List(List(ValDef(Modifiers(PARAM), newTermName("a"), Ident(newTypeName("Int")), EmptyTree))), TypeTree(), Apply(Select(Ident(newTermName("a")), newTermName("$plus")), List(Literal(Constant(3))))) |
def f(a: Int = 2) = a + 3 |
DefDef(Modifiers(), newTermName("f"), List(), List(List(ValDef(Modifiers(PARAM | DEFAULTPARAM/TRAIT), newTermName("a"), Ident(newTypeName("Int")), Literal(Constant(2))))), TypeTree(), Apply(Select(Ident(newTermName("a")), newTermName("$plus")), List(Literal(Constant(3))))) |
def f(a: Int = 2): Int = a + 3 |
DefDef(Modifiers(), newTermName("f"), List(), List(List(ValDef(Modifiers(PARAM | DEFAULTPARAM/TRAIT), newTermName("a"), Ident(newTypeName("Int")), Literal(Constant(2))))), Ident(newTypeName("Int")), Apply(Select(Ident(newTermName("a")), newTermName("$plus")), List(Literal(Constant(3))))) |
def f(a: Int) = { println(a); a + 3 } |
DefDef(Modifiers(), newTermName("f"), List(), List(List(ValDef(Modifiers(PARAM), newTermName("a"), Ident(newTypeName("Int")), EmptyTree))), TypeTree(), Block(List(Apply(Ident(newTermName("println")), List(Ident(newTermName("a"))))), Apply(Select(Ident(newTermName("a")), newTermName("$plus")), List(Literal(Constant(3)))))) |
def f(a: Int, b: String) = a + b |
DefDef(Modifiers(), newTermName("f"), List(), List(List(ValDef(Modifiers(PARAM), newTermName("a"), Ident(newTypeName("Int")), EmptyTree), ValDef(Modifiers(PARAM), newTermName("b"), Ident(newTypeName("String")), EmptyTree))), TypeTree(), Apply(Select(Ident(newTermName("a")), newTermName("$plus")), List(Ident(newTermName("b"))))) |
def f(a: Int)(b: String) = a + b |
DefDef(Modifiers(), newTermName("f"), List(), List(List(ValDef(Modifiers(PARAM), newTermName("a"), Ident(newTypeName("Int")), EmptyTree)), List(ValDef(Modifiers(PARAM), newTermName("b"), Ident(newTypeName("String")), EmptyTree))), TypeTree(), Apply(Select(Ident(newTermName("a")), newTermName("$plus")), List(Ident(newTermName("b"))))) |
Lambda expressions are defined using the case class Function, which takes two arguments
- The parameters, as a list of
ValDefs - The body of function as a
Tree
Examples:
| Lambda expression | AST case class |
|---|---|
{x: Int => x} |
Function(List(ValDef(Modifiers(PARAM), newTermName("x"), Ident(newTypeName("Int")), EmptyTree)), Ident(newTermName("x"))) |
{p: (Int, Int) => p._1 + p._2} |
Function(List(ValDef(Modifiers(PARAM), newTermName("p"), AppliedTypeTree(Select(Ident(scala), newTypeName("Tuple2")), List(Ident(newTypeName("Int")), Ident(newTypeName("Int")))), EmptyTree)), Apply(Select(Select(Ident(newTermName("p")), newTermName("_1")), newTermName("$plus")), List(Select(Ident(newTermName("p")), newTermName("_2"))))) |
{x: Int => y: Int => x + y} |
Function(List(ValDef(Modifiers(PARAM), newTermName("x"), Ident(newTypeName("Int")), EmptyTree)), Function(List(ValDef(Modifiers(PARAM), newTermName("y"), Ident(newTypeName("Int")), EmptyTree)), Apply(Select(Ident(newTermName("x")), newTermName("$plus")), List(Ident(newTermName("y")))))) |
These are huge. Not sure if we need this.
Annotations are part of the Modifiers argument of the ValDef/DefDef type definitions. If there are any annotations, then the modifiers is populated. The case class Modifiers has three arguments:
- A Long indicating flags (like PARAM, etc) which we probably don't care for at this point
- A qualifier for public/private/etc
- A list of Trees, each of which contains an annotation
For example, if a function/field is marked as @deprecated, its AST will contain the following modifier
Modifiers(NoFlags, tpnme.EMPTY, List(Apply(Select(New(Ident(newTypeName("deprecated"))), nme.CONSTRUCTOR).
Note that the annotation is created by calling the constructor of the class deprecated.
Important note: The scala doc for the Modifier class says
the typechecker drops these annotations, use the AnnotationInfo's (Symbol.annotations) in later phases.
Not sure what this means, but we should watch out.
-
for comprehensions: For comprehensions are syntactically translated into expressions that use map, filter, etc. So we will only be dealing with such expressions. To see examples of the translations see http://docs.scala-lang.org/tutorials/FAQ/yield.html
-
Blocks: Blocks are enclosed in a
Blockobject, which contains a list of trees.