-
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.X
when used in a case statement. To avoid that, we couldimport global._
. -
Type names (like Int, Map, etc) either belong to a class called
TypeName_S
orTypeName_R
. But these are private members of the classNames
so we cannot pattern match against them. However, any AST node that is a type name has the flagisTypeName
set 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.
Scala type | AST case class |
---|---|
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,
AppliedTypeTree
takes 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. -
Select
is 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
Ident
s 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 (that is, it should be something like what is defined in the Types section above.
Scala expression | AST case class |
---|---|
type Foo1 = Int |
TypeDef(Modifiers(), newTypeName("Foo1"), List(), Ident(newTypeName("Int"))) |
type Foo3 = Set[String] |
TypeDef(Modifiers(), newTypeName("Foo3"), List(), AppliedTypeTree(Ident(newTypeName("Set")), List(Ident(newTypeName("String"))))) |
type Foo8 = Foo |
TypeDef(Modifiers(), newTypeName("Foo8"), List(), Ident(newTypeName("Foo"))) |
type Foo12[T] = Set[T] |
TypeDef(Modifiers(), newTypeName("Foo12"), List(TypeDef(Modifiers(PARAM), newTypeName("T"), List(), TypeBoundsTree(Select(Select(Ident(nme.ROOTPKG), scala), newTypeName("Nothing")), Select(Select(Ident(nme.ROOTPKG), scala), newTypeName("Any"))))), AppliedTypeTree(Ident(newTypeName("Set")), List(Ident(newTypeName("T"))))) |
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")))) |
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 :: xs
construction 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 aBlock
object 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.
- for comprehensions
- lambda expressions