how to see the trees using the Scala compilers
Here’s a memo on how to show trees using the Scala compilers. While this likely won’t be relevant for normal usage of Scala, having the direct knowledge of the tree can be useful when developing tooling or during metaprogramming.
Scala 2.13.14
Let’s use the following example:
package example
object Main extends App {
println("hello")
}
Here’s how you can show the AST using Scala 2.13.14:
$ sdk install scala 2.13.14
$ sdk use scala 2.13.14
$ scalac --version
Scala compiler version 2.13.14 -- Copyright 2002-2024, LAMP/EPFL and Lightbend, Inc.
$ scalac -Vprint:parser,typer -Ystop-after:typer -Yprint-trees:format Hello.scala
The output looks like this:
[[syntax trees at end of parser]]// Scala source: Hello.scala
PackageDef(
"example"
ModuleDef(
0
"Main"
Template(
"App" // parents
ValDef(
private
"_"
<tpt>
<empty>
)
// 2 statements
DefDef(
0
"<init>"
[]
List(Nil)
<tpt>
Block(
Apply(
super."<init>"
Nil
)
()
)
)
Apply(
"println"
"hello"
)
)
)
)
[[syntax trees at end of typer]]// Scala source: Hello.scala
PackageDef(
"example" // final package example, tree.tpe=example.type
ModuleDef( // object Main in package example
<module>
"Main"
Template( // val <local Main>: <notype> in object Main, tree.tpe=example.Main.type
"java.lang.Object", "scala.App" // parents
ValDef(
private
"_"
<tpt>
<empty>
)
// 2 statements
DefDef( // def <init>(): example.Main.type in object Main
<method>
"<init>"
[]
List(Nil)
<tpt> // tree.tpe=example.Main.type
Block( // tree.tpe=Unit
Apply( // def <init>(): Object in class Object, tree.tpe=Object
Main.super."<init>" // def <init>(): Object in class Object, tree.tpe=(): Object
Nil
)
()
)
)
Apply( // def println(x: Any): Unit in object Predef, tree.tpe=Unit
"scala"."Predef"."println" // def println(x: Any): Unit in object Predef, tree.tpe=(x: Any): Unit
"hello"
)
)
)
)
There are lots of interesting flags covered under -V
and -Y
, which are documented as Verbose settings and Private settings.
-Vprint-types
For example, here’s how to print the inferred types:
$ scalac -Vprint:parser,typer -Ystop-after:typer -Yprint-trees:text -Vprint-types Hello.scala
[[syntax trees at end of parser]] // Hello.scala
package example{<null>} {
object Main extends App {
def <init>() = {
super{<null>}.<init>{<null>}();
(){<null>}
}{<null>};
println{<null>}("hello"{<null>}){<null>}
}
}
[[syntax trees at end of typer]] // Hello.scala
package example{example.type} {
object Main extends AnyRef with App {
def <init>(): example.Main.type = {
Main.super{example.Main.type}.<init>{(): Object}(){Object};
(){Unit}
}{Unit};
scala.Predef.println{(x: Any): Unit}("hello"{String("hello")}){Unit}
}
}
-Yprint-trees:diff
Here’s how to show the difference between the parser phase and the typer phase:
$ scalac -Vprint:parser,typer -Ystop-after:typer -Yprint-trees:diff Hello.scala
[[syntax trees at end of typer]] // Hello.scala
--- parser
+++ typer
@@ -1,8 +1,8 @@
package example {
- object Main extends App {
- def <init>() = {
- super.<init>();
+ object Main extends AnyRef with App {
+ def <init>(): example.Main.type = {
+ Main.super.<init>();
()
};
- println("hello")
+ scala.Predef.println("hello")
}
-Vprint-pos
Here’s how to print the positions:
$ scalac -Vprint:parser,typer -Ystop-after:typer -Yprint-trees:text -Vprint-pos Hello.scala
[[syntax trees at end of parser]] // Hello.scala
[0:63]package [8:15]example {
[17:63]object Main extends [29:63][37:40]App {
[29]def <init>() = [29]{
[NoPosition][NoPosition][NoPosition]super.<init>();
[29]()
};
[45:61][45:52]println([53:60]"hello")
}
}
[[syntax trees at end of typer]] // Hello.scala
[0:63]package [8:15]example {
[17:63]object Main extends [29:63][37]AnyRef with [37:40]<type: [37:40]scala.App> {
[37]def <init>(): [29]example.Main.type = [37]{
[37][37][37]Main.super.<init>();
[29]()
};
[45:61][45:52]scala.Predef.println([53:60]"hello")
}
}
Scala 3.4.2
Let’s also try this with Scala 3.x:
package example
@main def hello() = println("Hello")
$ sdk install scala 3.4.2
$ sdk use scala 3.4.2
$ scalac --version
Scala compiler version 3.4.2 -- Copyright 2002-2024, LAMP/EPFL
$ scalac -Vprint:parser,typer -Ystop-after:typer -Yplain-printer Hello.scala
Here’s the output:
[[syntax trees at end of parser]] // Hello.scala
PackageDef(Ident(example), List(
DefDef(hello, List(List()), TypeTree(),
Apply(Ident(println), List(Literal("Hello"))))
))
[[syntax trees at end of typer]] // Hello.scala
PackageDef(Ident(example), List(
ValDef(Hello$package, Ident(Hello$package$),
Apply(Select(New(Ident(Hello$package$)), <init>), List())),
TypeDef(Hello$package$,
Template(DefDef(<init>, List(List()), TypeTree(), Thicket(List())),
List(Apply(Select(New(TypeTree()), <init>), List())),
ValDef(_, SingletonTypeTree(Ident(Hello$package)), Thicket(List())), List(
DefDef(hello, List(List()), TypeTree(),
Apply(Ident(println), List(Literal("Hello"))))
))
),
TypeDef(hello,
Template(DefDef(<init>, List(List()), TypeTree(), Thicket(List())),
List(Apply(Select(New(TypeTree()), <init>), List())),
ValDef(_, Thicket(List()), Thicket(List())), List(
DefDef(main, List(List(ValDef(args, TypeTree(), Thicket(List())))),
TypeTree(),
Try(Apply(Ident(hello), List()),
List(
CaseDef(Bind(error, Typed(Ident(_), TypeTree())), Thicket(List()),
Apply(Ident(showError), List(Ident(error))))
),
Thicket(List()))
)
))
)
))
Here are the -Y
flags related to printing:
$ scalac -Y 2>&1 | rg "print|show"
....
-Ycc-print-setup Used in conjunction with captureChecking language
import, print trees after cc.Setup phase
-Yexplain-lowlevel When explaining type errors, show types at a lower
-Yplain-printer Pretty-print using a plain printer.
-Yprint-debug When printing trees, print some extra information
-Yprint-debug-owners When printing trees, print owners of definitions.
-Yprint-level print nesting levels of symbols and type variables.
-Yprint-pos Show tree positions.
-Yprint-pos-syms Show symbol definitions positions.
-Yprint-syms When printing trees print info in symbols instead of
-Yprint-tasty Prints the generated TASTY to stdout.
-Yshow-print-errors Don't suppress exceptions thrown during tree
printing.
-Yshow-suppressed-errors Also show follow-on errors and warnings that are
-Yshow-tree-ids Uniquely tag all tree nodes in debugging output.
-Yshow-var-bounds Print type variables with their bounds.
-Ytest-pickler-check Self-test for pickling -print-tasty output; should
-Ypring-debug
Here’s how to print the inferred types in Scala 3.x:
$ scalac -Vprint:parser,typer -Ystop-after:typer -Yprint-debug Hello.scala
$ scalac -Vprint:typer -Ystop-after:typer -Yprint-debug Hello.scala
[[syntax trees at end of typer]] // Hello.scala
package <root>.this.example {
final lazy module val Hello$package: example.this.Hello$package$ =
new example.this.Hello$package$.<init>()
final module class Hello$package$() extends Object.<init>() {
this: example.this.Hello$package.type =>
@main def hello(): scala.this.Unit(inf) = scala.this.Predef.println("Hello")
}
final class hello() extends Object.<init>() {
<static> def main(args: scala.this.Array[String]): scala.this.Unit =
try Hello$package$.this.hello() catch
{
case error @ _:CommandLineParser$.this.ParseError =>
CommandLineParser$.this.showError(error)
}
}
}
-Yprint-pos
Here’s how to print the positions in Scala 3.x:
$ scalac -Vprint:parser,typer -Ystop-after:typer -Yprint-pos Hello.scala
[[syntax trees at end of parser]] // Hello.scala
package example@<Hello.scala:1> {
@main@<Hello.scala:3> def hello() =
println@<Hello.scala:3>("Hello"@<Hello.scala:3>)@<Hello.scala:3>@<
Hello.scala:3>
}@<Hello.scala:1>
[[syntax trees at end of typer]] // Hello.scala
package example@<Hello.scala:1> {
final lazy module val Hello$package: example.Hello$package@<Hello.scala:3> =
new example.Hello$package@<Hello.scala:3>@<Hello.scala:3>@<Hello.scala:3>()@
<Hello.scala:3>
@<Hello.scala:3>
final module class Hello$package() extends Object@<Hello.scala:3>@<
Hello.scala:3>()@<Hello.scala:3> {
this: example.Hello$package@<Hello.scala:3>.type@<Hello.scala:3> =>
@main def hello(): Unit =
println@<Hello.scala:3>("Hello"@<Hello.scala:3>)@<Hello.scala:3>@<
Hello.scala:3>
}@<Hello.scala:3>
final class hello() extends Object@<Hello.scala:3>@<Hello.scala:3>()@<
Hello.scala:3> {
<static> def main(args: Array[String]@<Hello.scala:3>): Unit =
try example.hello@<Hello.scala:3>()@<Hello.scala:3> catch
{
case
error @
_@<Hello.scala:3> :scala.util.CommandLineParser.ParseError@<
Hello.scala:3>
@<Hello.scala:3>
=>
scala.util.CommandLineParser.showError@<Hello.scala:3>(
error@<Hello.scala:3>)@<Hello.scala:3>
@<Hello.scala:3>
}
@<Hello.scala:3>
@<Hello.scala:3>
}@<Hello.scala:3>
}@<Hello.scala:1>
Printer
We can show the tree programmatically using Scala 3 macros as well. Here’s build.sbt
:
ThisBuild / scalaVersion := "3.4.2"
then:
$ sbt console
scala> import scala.quoted.*
scala> def showTreeImpl[A: Type](a: Expr[A])(using Quotes): Expr[String] =
import quotes.reflect.*
Expr(Printer.TreeStructure.show(a.asTerm))
scala> inline def showTree[A](inline a: A): String = ${showTreeImpl[A]('{ a })}
scala> showTree(List(1).map(x => x + 1))
val res0: String = Inlined(None, Nil, Apply(TypeApply(Select(Apply(TypeApply(Select(Ident("List"), "apply"), List(Inferred())), List(Typed(Repeated(List(Literal(IntConstant(1))), Inferred()), Inferred()))), "map"), List(Inferred())), List(Block(List(DefDef("$anonfun", List(TermParamClause(List(ValDef("x", Inferred(), None)))), Inferred(), Some(Apply(Select(Ident("x"), "+"), List(Literal(IntConstant(1))))))), Closure(Ident("$anonfun"), None)))))
bonus: Scalameta
Yoshida-san created scalameta-ast, which can show the tree for Scalameta, which could be helpful when creating a Scalafix rule.
For example, the tree for the the following Scala 3.x code:
package example
@main def hello() = println("Hello")
is:
Source(
List(
Pkg(
Term.Name("example"),
List(
Defn.Def(
List(
Mod.Annot(
Init(
Type.Name("main"),
Name(""),
Nil
)
)
),
Term.Name("hello"),
Nil,
List(List()),
None,
Term.Apply(
Term.Name("println"),
List(Lit.String("Hello"))
)
)
)
)
)
)
summary
There are compiler flags on Scala 2.x and 3.x to print out the abstract syntax tree of the Scala source code under compilation. This shows the direct interpretation of how the compiler is processing the source code, and additionally it could show useful information such as the type info and positions.
Generally speaking current Scala 2.13.14 tends to use -V
flags, while Scala 3.4.2 tends to use -Y
flags, however these are private flags that are subject to change from one release to the other.