doc/internal/tastyreader.md
The TASTy Reader For Scala 2, included in the Scala 2.x Compiler will enable usage in Scala 2.13.x of dependencies that have been compiled with dotc, the reference compiler of Scala 3.0.
TASTy is an intermediate representation of a Scala program after type checking and term elaboration, such as inference of implicit parameters. When compiling code with Scala 3, a single TASTy document is associated with each pair of root class and companion object. Within a TASTy document, the public API of those roots and any inner classes can be read, in a similar way to pickles in the Scala 2.x series.
-Ytasty-reader enables the support for reading Scala 3's TASTy files.
-Ydebug-tasty enables rich output when traversing tasty files, important for tracing the history of events when diagnosing errors.
-Ytasty-no-annotations ignores all annotations on tasty symbols, may be useful for ignoring complex annotations that are unsupported, but will prevent safety checks for pattern matching.
A classfile is assumed to have an associated TASTy file if it has a TASTY classfile attribute (not available through
Java reflection). This attribute contains a UUID that matches a UUID in the header of a sibling .tasty file of the
same directory as the classfile. This file is then found and the UUIDs are compared in
scala.tools.nsc.symtab.classfile.ClassfileParser.
After validation of the header, the tasty file is traversed in scala.tools.nsc.tasty.TastyUnpickler.unpickle, which
reads any definitions into the symbol table of the compiler.
A TASTy document is composed of a header, which contains a magic number 0x5CA1AB1F, a version number and a UUID.
The TASTy document then is composed of a list of names, followed by customisable "sections". The section we are
interested in for Scala 2 is the "ASTs" section. The ASTs section contains a package definition for the root class and
companion of the tasty file. In TASTy, both terms and types are made of trees, and sometimes trees can be reused in
either term or type position, for example path selections. There are five main concepts in TASTy:
A typical workflow for experimenting with the TASTy reader is to:
$issue, e.g. sandbox/issue$out, e.g. $issue/bin$src3, e.g. $issue/FancyColours.scala$out:
dotc -d $out $src3$src2, that uses some symbols from $src3, e.g. $issue/TestFancyColours.scala$src3 to the classpath:
scalac -Ydebug-tasty -d $out -classpath $out $src2Here are some example source files from the above scenario:
// FancyColours.scala - compile with Scala 3
trait Pretty:
self: Colour =>
trait Dull:
self: Colour =>
enum Colour:
case Pink extends Colour with Pretty
case Red extends Colour with Dull
// TestFancyColours.scala - compile with Scala 2
object TestFancyColours {
def describe(c: Colour) = c match {
case Colour.Pink => "Amazing!"
case Colour.Red => "Yawn..."
}
def describePretty(c: Pretty) = c match {
case Colour.Pink => "Amazing!"
}
def describeDull(c: Dull) = c match {
case Colour.Red => "Yawn..."
}
}
The Script Runner section describes some commands that support this workflow and can be run from sbt; which also handles providing the supported version of dotc on the classpath.
Below is an example of using the Script Runner to simplify iterative development of the scenario above:
tasty/test:runMain scala.tools.tastytest.Scripted dotc $out $issue/FancyColours.scala.tasty/test:runMain scala.tools.tastytest.Scripted scalac $out $issue/TestFancyColours.scala -Ydebug-tasty, which will also put the contents of $out on the classpath.Colour with tasty/test:runMain scala.tools.tastytest.Scripted dotcd $out/Colour.tasty -print-tastyIn the above, relative paths will be calculated from the working directory of tasty/test.
Because these commands are run from sbt, incremental changes can be made to the code for the TASTy reader and then step 2 can be immediately re-run to observe new behaviour of the compiler.
In the output of the above step 2, you will see the following snippet, showing progress in traversing TASTy and understanding the definition of trait Dull:
#[trait Dull]: Addr(4) completing Symbol(trait Dull, #6286):
#[trait Dull]: Addr(7) No symbol found at current address, ensuring one exists:
#[trait Dull]: Addr(7) registered Symbol(value <local Dull>, #7240) in trait Dull
#[trait Dull]: Addr(9) Template: reading parameters of trait Dull:
#[trait Dull]: Addr(9) Template: indexing members of trait Dull:
#[trait Dull]: Addr(22) No symbol found at current address, ensuring one exists:
#[trait Dull]: Addr(22) ::: => create DEFDEF <init>
#[trait Dull]: Addr(22) parsed flags Stable | Method
#[trait Dull]: Addr(22) registered Symbol(constructor Dull, #7241) in trait Dull
#[trait Dull]: Addr(9) Template: adding parents of trait Dull:
#[trait Dull]: Addr(9) reading type TYPEREF:
#[trait Dull]: Addr(11) reading type TERMREFpkg:
#[trait Dull]: Addr(13) Template: adding self-type of trait Dull:
#[trait Dull]: Addr(15) reading term IDENTtpt:
#[trait Dull]: Addr(17) reading type TYPEREF:
#[trait Dull]: Addr(19) reading type THIS:
#[trait Dull]: Addr(20) reading type TYPEREFpkg:
#[trait Dull]: Addr(22) Template: self-type is Colour
#[trait Dull]: Addr(22) Template: Updated info of trait Dull extends AnyRef
#[trait Dull]: Addr(4) typeOf(Symbol(trait Dull, #6286)) =:= Dull; owned by package <empty>
Comments beginning with TODO [tasty]: express concerns specific to the implementation of the TASTy reader. These should be considered carefully because of either the disruptive changes they make to the rest of the code base, or as a note that there may be a more correct solution, or as a placeholder to outline missing features of Scala 3 that are not yet backported to Scala 2.x.
The framework for testing the TASTy reader is contained in the tastytest subproject.
The tasty project is an example subproject depending on tastytest, used to test the functionality of the TASTy
reader. Test sources are placed in the test/tasty directory of this repository and tested with the sbt task
tasty/test. Several suites exist that build upon primitives in tastytest:
run: test that classes can depend on Scala 3 classes and execute without runtime errors.neg: assert that scala 2 test sources depending on Scala 3 classes do not compileneg-isolated: assert that code depending on symbols not on the classpath fails correctly.pos: The same as run except with no runtime checking, useful for validating types while waiting for bytecode to align.pos-false-noannotations: the same as pos but asserting code falsely compiles without warnings or errors when annotations are ignored.A key tool for working with the tasty reader on individual test cases is scala.tools.tastytest.Scripted. It provides several sub commands which share a common implementation with the core of tastytest, meaning that the behaviour is identical.
Each sub command is executed with the Dotty standard library and tooling on the classpath, with the version determined by
TastySupport.dottyCompiler in the build definition. All relative paths will use the working directory tasty/test:
In the sbt shell the scripted runner can be executed by tasty/test:runMain scala.tools.tastytest.Scripted, and provides several sub-commands:
dotc <out: Directory> <src: File>: compile a Scala 3 source file, which may depend on classes already compiled in out.dotcd <tasty: File> <args: String*>: decompile a tasty file, pass -print-tasty to see the structure of the ASTs.scalac <out: Directory> <src: File> <args: String*>: compile a Scala 2 source file, which may depend on classes already compiled in out, including those compiled by Scala 3. args can be used to pass additional scalac flags, such as -Ydebug-tastyrunDotty <classpath: Paths> <classname: String>: execute the static main method of the given class, and providing no arguments.javac <out: Directory> <src: File>: compile a Java source file matching, which may depend on classes already compiled in out.tastytest is a testing library for validating that Scala 2 code can correctly depend on classes compiled with dotc, the Scala 3 compiler, which outputs TASTy trees. The framework has several suites for testing different scenarios. In each suite kind, the Scala 3 standard library is available to all test sources. tastytest does not implement the TestInterface so it is recommended to call its entry points from JUnit, like in test/tasty/test/scala/tools/tastytest/TastyTestJUnit.scala.
A run suite tests the runtime behaviour of Scala 2 code that may extend or call into code compiled with dotc, and is specified as follows:
$src is declared, e.g. "run"$src/pre/**/ with the Scala 2 compiler, this step may be used to create helper methods available to Scala 2 and 3 sources, or embedded test runners.$src/src-3/**/ with the Dotty compiler. Classes compiled in (2) are now on the classpath.$src/src-2/**/ with the Scala 2 compiler. Classes compiled in (2) and (3) are now on the classpath.(.*Test)\.class, and have a corresponding source file in $src/src-2/**/ matching $1.scala, where $1 is the substituted name of the class file. The remaining classes are executed sequentially as tests:
main and with descriptor ([Ljava.lang.String;)V.out and err print streams of scala.Console are intercepted before executing the main method.main.A pos suite tests the compilation of Scala 2 code that may extend or call into code compiled with dotc, and is specified the same as run, except that step (5) is skipped. If step (4) succeeds then the suite succeeds.
A neg suite asserts which Scala 2 code is not compatible with code compiled with dotc, and is specified as follows:
$src is declared, e.g. "neg"$src/src-3/**/ with the Dotty compiler.$src/src-2/**/ are filtered for those with names that match the regex (.*)_fail.scala, and an optional check file that matches $1.check where $1 is the substituted test name, and the check file is in the same directory. These are sources expected to fail compilation.$src/src-2/**/ with the Scala 2 compiler.
(2) are now on the classpath.A neg-isolated suite tests the effect of missing transitive dependencies on the classpath that are available to Scala 3 dependencies of Scala 2 sources, but not to those downstream Scala 2 sources, and is specified as follows:
$src is declared, e.g. "neg-isolated"$src/src-3-A/**/ with the Dotty compiler.$src/src-3-B/**/ with the Dotty compiler. Classes compiled in (2) are now on the classpath.neg step (3)$src/src-2/**/ with the Scala 2 compiler. Test validation behaviour matches neg step (4), except for the following caveats:
(3) will be on the classpath. Classes compiled in (2) are deliberately hidden.(3) that reference symbols in compiled in (2) should trigger missing symbol errors due to the missing transitive dependency.