This doc page is specific to Scala 3, and may cover new concepts not available in Scala 2. Unless otherwise stated, all the code examples in this page assume you are using Scala 3.
If you create a Scala 3 source code file named Hello.scala:
@main def hello = println("Hello, world")
and then compile that file with scalac
:
$ scalac Hello.scala
you’ll notice that amongst other resulting files, scalac
creates files with the .tasty extension:
$ ls -1
Hello$package$.class
Hello$package.class
Hello$package.tasty
Hello.scala
hello.class
hello.tasty
This leads to the natural question, “What is tasty?”
What is TASTy?
TASTy is an acronym that comes from the term, Typed Abstract Syntax Trees. It’s a high-level interchange format for Scala 3, and in this document we’ll refer to it as Tasty.
A first important thing to know is that Tasty files are generated by the scalac
compiler, and contain all the information about your source code, including the syntactic structure of your program, and complete information about types, positions, and even documentation. Tasty files contain much more information than .class files, which are generated to run on the JVM. (More on this shortly.)
In Scala 3, the compilation process looks like this:
+-------------+ +-------------+ +-------------+
$ scalac | Hello.scala | -> | Hello.tasty | -> | Hello.class |
+-------------+ +-------------+ +-------------+
^ ^ ^
| | |
Your source TASTy file Class file
code for scalac for the JVM
(contains (incomplete
complete information)
information)
You can view the contents of a .tasty file in a human-readable form by running the compiler on it with the -print-tasty
flag.
You can also view the contents decompiled in a form similar to Scala source code using the -decompile
flag.
$ scalac -print-tasty hello.tasty
$ scalac -decompile hello.tasty
The issue with .class files
Because of issues such as type erasure, .class files are actually an incomplete representation of your code.
A simple way to demonstrate this is with a List
example.
Type erasure means that when you write Scala code like this that’s supposed to run on the JVM:
val xs: List[Int] = List(1, 2, 3)
that code is compiled to a .class file that needs to be compatible with the JVM. As a result of that compatibility requirement, the code inside that class file — which you can see with a javap
command — ends up looking like this:
public scala.collection.immutable.List<java.lang.Object> xs();
That javap
command output shows a Java representation of what’s contained in the class file. Notice in this output that xs
is not defined as a List[Int]
anymore; it’s essentially represented as a List[java.lang.Object]
. For your Scala code to work with the JVM, the Int
type has been erased.
Later, when you access an element of your List[Int]
in your Scala code, like this:
val x = xs(0)
the resulting class file will have a casting operation for this line of code, which you can think of being like this:
int x = (Int) xs.get(0) // Java-ish
val x = xs.get(0).asInstanceOf[Int] // more Scala-like
Again, this is done for compatibility, so your Scala code can run on the JVM. However, the information that we already had a list of integers is lost in the class files. This poses problems when trying to compile a Scala program against an already compiled library. For this, we need more information than usually available in class files.
And this discussion only covers the topic of type erasure. There are similar issues for every other Scala construct that the JVM isn’t aware of, including constructs like unions, intersections, traits with parameters, and many more Scala 3 features.
TASTy to the Rescue
So, instead of having no information about the original types in .class files, or only the public API (as with the Scala 2.13 “Pickle” format), the TASTy format stores the complete abstract syntax tree (AST), after type checking. Storing the whole AST has a lot of advantages: it enables separate compilation, recompilation for a different JVM version, static analysis of programs, and many more.
Key points
So that’s the first takeaway from this section: The types you specify in your Scala code aren’t represented completely accurately in .class files.
A second key point is to understand that there are differences between the information that’s available at compile time and run time:
- At compile time, as
scalac
reads and analyzes your code, it knows thatxs
is aList[Int]
- When the compiler writes your code to a class file, it writes that
xs
is aList[Object]
, and it adds casting information everywhere elsexs
is accessed - Then at run time — with your code running inside the JVM — the JVM doesn’t know that your list is a
List[Int]
With Scala 3 and Tasty, here’s another important note about compile time:
- When you write Scala 3 code that uses other Scala 3 libraries,
scalac
doesn’t have to read their .class files anymore; it can read their .tasty files, which, as mentioned, are an exact representation of your code. This is important to enable separate compilation and compatibility between Scala 2.13 and Scala 3.
Tasty benefits
As you can imagine, having a complete representation of your code has many benefits:
- The compiler uses it to support separate compilation.
- The Scala Language Server Protocol-based language server uses it to support hyperlinking, command completion, documentation, and also for global operations such as find-references and renaming.
- Tasty makes an excellent foundation for a new generation of reflection-based macros.
- Optimizers and analyzers can use it for deep code analysis and advanced code generation.
In a related note, Scala 2.13.6 has a TASTy reader, and the Scala 3 compiler can also read the 2.13 “Pickle” format. The Classpath Compatibility Page in the Scala 3 Migration Guide explains the benefits of this cross-compiling capability.
More information
In summary, Tasty is a high-level interchange format for Scala 3, and .tasty files contain a complete representation of your source code, leading to the benefits outlined in the previous section.
For more details, see these resources:
- In this video, Jamie Thompson of the Scala Center provides a thorough discussion of how Tasty works, and its benefits
- Binary Compatibility for library authors discusses binary compatibility, source compatibility, and the JVM execution model
- Forward Compatibility for the Scala 3 Transition demonstrates techniques for using Scala 2.13 and Scala 3 in the same project
These articles provide more information about Scala 3 macros:
- Scala Macro Libraries
- Macros: The Plan for Scala 3
- The reference documentation on Quotes Reflect
- The reference documentation on macros
TASTyViz is a tool to inspect TASTy files visually. At the time of writing, it is still in the early stages of developement, therefore you can expect missing functionality and less-than-ideal user experience, but it could still prove useful when debugging. You can check it out here.