Discover the future impact of TASTy Query: a single, cross-platform API to answer all semantic questions about Scala programs, no compiler required. Already used in Metals’ debugger, TASTy Query will validate APIs do not break compatibility. Learn to build an analysis to improve your own code.
5. TASTy Query Elevator Pitch
● Cross platform (JS, JVM)
● Reason about and analyse APIs defined in Scala 3, Java, and Scala 2.13
● Extract full information from TASTy files.
● Simple to use
● Independent of compiler
5
9. 9
Why is TASTy useful as a format?
● Stores the whole program
● Stable with respect to dependencies
○ Method overloads are already resolved
○ Implicits are already resolved
Foo.tasty
��
23. 23
So, What’s Happening?
1. Recompile my::app:2.7.3 against new dependency org::utils:1.0.1 .
2. def app calls inline def Macros.foo from foo::macros:3.3.5 .
○ ⚠ foo::macros:3.3.5 still depends on older org::utils:1.0.0 .
3. Compiler substitutes Macros.foo call by its inline body from TASTy.
4. Macros.foo body calls Utils.asList , which resolves to newer org::utils:1.0.1 .
5. Seq(1, 2, 3) argument not passed as varargs and so no longer type-checks!
○ org::utils:1.0.0 and org::utils:1.0.1 are not TASTy compatible!
24. 24
TASTy Query Use Cases
● Validate API compatibility between releases ✅
● Metals Debugger ✅
● TASTy interpreter (for research) ✅
● Custom Analyses
27. 27
First Step: Building the Classpath
● Ordered sequence of entries ✅
● Each entry is a .jar file or directory ✅
● Inside each entry is .class/.tasty files, organised by package
✅
[0] my-library/target/scala-3.2.1/classes
[1]
maven2/org/scala-lang/scala3-library_3/3.2.1/scala3-library_3-3.2.1.jar
[2] maven2/org/scala-lang/scala-library/2.13.10/scala-library-2.13.10.jar
[3] jrt:/modules/java.base
import java.nio.file.*
val cpPaths = List(
Paths.get("my-library/target/scala-3.2.1/classes"),
Paths.get(".../scala3-library_3-3.2.1.jar"),
Paths.get(".../scala-library-2.13.10.jar"),
jrtFileSystem.getPath("modules/java.base")
)
28. 28
First Step: Read the Classpath
import tastyquery.jdk.ClasspathLoaders
import tastyquery.Classpaths.Classpath
val classpath: Classpath = ClasspathLoaders.read(cpPaths)
val lookup: Map[Path, Classpath.Entry] =
cpPaths.zip(classpath.entries).toMap
import java.nio.file.*
val cpPaths = List(...) // from previous slide
29. 29
First Step: Read the Classpath
● Pure, in-memory representation of classpath ✅
● Each Entry stores .class/.tasty files found in its source path ✅
● Platform agnostic (JS/JVM) ✅
Classpath.Entry
Classpath
30. 30
● A Context wraps a Classpath ✅
● Entry point to inspect definitions ✅
● Summon a Context with ctx ✅
Second Step: Context
import tastyquery.Contexts
import tastyquery.Contexts.*
Context
given Context = Contexts.init(classpath)
31. 31
Third Step: Query
🤔 how to find only the classes from scala-library ?
val scalaLib: Classpath.Entry =
lookup.filterKeys(_.toString.contains("scala-library")).values.head
val scalaLibRoots: Iterable[Symbol] =
ctx.findSymbolsByClasspathEntry(scalaLib) // top-level classes
val lookup: Map[Path, Classpath.Entry] = … // from earlier slide
import tastyquery.Symbols.*
32. 32
Core Concepts: Data
Symbols
● Unique identity for a definition ✅
● Store all information about the definition ✅
● Specialised for each kind of definition ✅
import tastyquery.Symbols.*
34. 34
ClassSymbol Methods
cls.parentClasses List[ClassSymbol] of the parents of the class.
cls.fullName FullyQualifiedName of the class.
cls.owner What symbol was cls declared in?
cls.getDecls(name) Find all overloads in cls matching a Name .
cls.declarations Find all symbols declared in cls .
ClassSymbol
35. 35
Comparison to the Compiler API
● Inspired by compiler ✅
● Single “temporal” view ✅
● Favor specialisation over abstraction ✅
TermSymbol
declaredType
Type
TypeMemberSymbol
bounds
TypeBounds
ClassSymbol
parents
List[Type]
typeParams
List[CTPSymbol]
36. 36
Comparison to the Compiler API
dotty.tools.dotc API
Symbol
info
Type | TypeBounds | ClassInfo
❌
● Inspired by compiler ✅
● Single “temporal” view ✅
● Favor specialisation over abstraction ✅
37. 37
Third Step: Query
🤔 how to find only the classes in scala.collection.mutable ?
val mutablePackage = ctx.findPackage("scala.collection.mutable")
val mutableClasses: Iterable[ClassSymbol] =
scalaLibRoots.collect {
case cls: ClassSymbol if cls.owner == mutablePackage => cls
}
val scalaLibRoots: Iterable[Symbol] = … // from earlier slide
39. 39
Computing inheritance graph of class A
1. Links := []; explore := {class A}
2. While explore is non-empty:
a. remove class A from explore
b. For each parent class P of A:
i. add a link class A -> class P
ii. add class P to explore
3. Return Links
48. 48
Types
tp1.isSubType(tp2) Boolean is tp1 <:< tp2 by Scala’s type rules
tp1.isSameType(tp2) Boolean is tp1 =:= tp2 by Scala’s type rules
tp1.member(name) Find a (possibly inherited) member of tp1 called name .
tp1.isRef(sym) Does tp1 reduce to a reference of sym ?
val tp1: Type
val tp2: Type
val name: Name
val sym: Symbol
49. 49
Overrides
f.overridenSymbol(cls) Which term/type in cls is overridden by f ?
f.overridingSymbol(cls) Which term/type in cls overrides f ?
f.allOverriddenSymbols
Find all the terms/types overridden by f in parent
classes.
f.nextOverriddenSymbol
Find the first term/type overridden by f in parent
classes.
val f: TermOrTypeSymbol
val cls: ClassSymbol
53. Summary
● TASTy Query will be critical for Scala ecosystem’s infrastructure
● Simple API to inspect definitions from all the classpath
● Cross platform
● Accessible for many kinds of user.
53