Skip to content

Commit 3568ecd

Browse files
Merge pull request #10792 from dotty-staging/rework-tasty-inspector-api
Rework TastyInspector API to allow inspection of all files
2 parents 8d3275c + e903941 commit 3568ecd

File tree

23 files changed

+362
-145
lines changed

23 files changed

+362
-145
lines changed

compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2723,7 +2723,11 @@ class QuotesImpl private (using val ctx: Context) extends Quotes, QuoteUnpickler
27232723
type SourceFile = dotc.util.SourceFile
27242724

27252725
object SourceFile extends SourceFileModule {
2726-
def current: SourceFile = ctx.compilationUnit.source
2726+
def current: SourceFile =
2727+
if ctx.compilationUnit == null then
2728+
throw new java.lang.UnsupportedOperationException(
2729+
"`reflect.SourceFile.current` cannot be called within the TASTy ispector")
2730+
ctx.compilationUnit.source
27272731
}
27282732

27292733
given SourceFileMethods: SourceFileMethods with

docs/docs/reference/metaprogramming/tasty-inspect.md

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ libraryDependencies += "org.scala-lang" %% "scala3-tasty-inspector" % scalaVersi
1010
TASTy files contain the full typed tree of a class including source positions
1111
and documentation. This is ideal for tools that analyze or extract semantic
1212
information from the code. To avoid the hassle of working directly with the TASTy
13-
file we provide the `TastyInspector` which loads the contents and exposes it
13+
file we provide the `Inspector` which loads the contents and exposes it
1414
through the TASTy reflect API.
1515

1616
## Inspecting TASTy files
@@ -21,18 +21,22 @@ To inspect the trees of a TASTy file a consumer can be defined in the following
2121
import scala.quoted._
2222
import scala.tasty.inspector._
2323

24-
class MyInspector extends TastyInspector:
25-
protected def processCompilationUnit(using Quotes)(tree: quotes.reflect.Tree): Unit =
24+
class MyInspector extends Inspector:
25+
def inspect(using Quotes)(tastys: List[Tasty[quotes.type]]): Unit =
2626
import quotes.reflect._
27-
// Do something with the tree
27+
for tasty <- tastys do
28+
val tree = tasty.ast
29+
// Do something with the tree
2830
```
2931

3032
Then the consumer can be instantiated with the following code to get the tree of the `foo/Bar.tasty` file.
3133

3234
```scala
3335
object Test:
3436
def main(args: Array[String]): Unit =
35-
new MyInspector().inspectTastyFiles("foo/Bar.tasty")
37+
val tastyFiles = List("foo/Bar.tasty")
38+
TastyInspector.inspectTastyFiles(tastyFiles)(new MyInspector)
39+
3640
```
3741

3842
Note that if we need to run the main (in the example below defined in an object called `Test`) after compilation we need to make the compiler available to the runtime:

sbt-dotty/sbt-test/sbt-dotty/tasty-inspector-example-project/app/Main.scala

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,20 @@
11
package hello
22

33
import scala.quoted._
4-
import scala.tasty.inspector.TastyInspector
4+
import scala.tasty.inspector._
55

66
import scala.jdk.StreamConverters._
77

88
import java.nio.file.{Path, Files, Paths, FileSystems}
99

1010
object Main extends App {
1111

12-
val inspector = new TastyInspector {
13-
protected def processCompilationUnit(using Quotes)(root: quotes.reflect.Tree): Unit = {
14-
val tastyStr = root.show
15-
println(tastyStr)
12+
val inspector = new Inspector {
13+
def inspect(using Quotes)(tastys: List[Tasty[quotes.type]]): Unit = {
14+
for tasty <- tastys do
15+
val tastyStr = tasty.ast.show
16+
println(tastyStr)
17+
1618
}
1719
}
1820

@@ -25,7 +27,7 @@ object Main extends App {
2527

2628
val tastyFiles = for p <- walk(pwd) if `lib/Foo.tasty`.matches(p) yield p.toString
2729

28-
inspector.inspectTastyFiles(List(tastyFiles.head))
30+
TastyInspector.inspectTastyFiles(List(tastyFiles.head))(inspector)
2931

3032
}
3133

scala3doc/src/scala/tasty/inspector/DocTastyInspector.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ package scala.tasty.inspector
22

33
import dotty.tools.dotc.core.Contexts.Context
44

5-
abstract class DocTastyInspector extends TastyInspector:
5+
abstract class DocTastyInspector extends OldTastyInspector:
66
def inspectFilesInDocContext(
77
classpath: List[String],
88
filePaths: List[String])(
Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
package scala.tasty.inspector
2+
3+
import scala.quoted._
4+
import scala.quoted.runtime.impl.QuotesImpl
5+
6+
import dotty.tools.dotc.Compiler
7+
import dotty.tools.dotc.Driver
8+
import dotty.tools.dotc.Run
9+
import dotty.tools.dotc.core.Contexts.Context
10+
import dotty.tools.dotc.core.Mode
11+
import dotty.tools.dotc.core.Phases.Phase
12+
import dotty.tools.dotc.fromtasty._
13+
import dotty.tools.dotc.util.ClasspathFromClassloader
14+
import dotty.tools.dotc.CompilationUnit
15+
import dotty.tools.unsupported
16+
import dotty.tools.dotc.report
17+
18+
import java.io.File.pathSeparator
19+
20+
// COPY OF OLD IMPLEMENTATION
21+
// TODO: update to new implementation
22+
trait OldTastyInspector:
23+
self =>
24+
25+
/** Process a TASTy file using TASTy reflect */
26+
protected def processCompilationUnit(using Quotes)(root: quotes.reflect.Tree): Unit
27+
28+
/** Called after all compilation units are processed */
29+
protected def postProcess(using Quotes): Unit = ()
30+
31+
/** Load and process TASTy files using TASTy reflect
32+
*
33+
* @param tastyFiles List of paths of `.tasty` files
34+
*/
35+
def inspectTastyFiles(tastyFiles: List[String]): Boolean =
36+
inspectAllTastyFiles(tastyFiles, Nil, Nil)
37+
38+
/** Load and process TASTy files in a `jar` file using TASTy reflect
39+
*
40+
* @param jars Path of `.jar` file
41+
*/
42+
def inspectTastyFilesInJar(jar: String): Boolean =
43+
inspectAllTastyFiles(Nil, List(jar), Nil)
44+
45+
/** Load and process TASTy files using TASTy reflect
46+
*
47+
* @param tastyFiles List of paths of `.tasty` files
48+
* @param jars List of path of `.jar` files
49+
* @param dependenciesClasspath Classpath with extra dependencies needed to load class in the `.tasty` files
50+
*/
51+
def inspectAllTastyFiles(tastyFiles: List[String], jars: List[String], dependenciesClasspath: List[String]): Boolean =
52+
def checkFile(fileName: String, ext: String): Unit =
53+
val file = dotty.tools.io.Path(fileName)
54+
if file.extension != ext then
55+
throw new IllegalArgumentException(s"File extension is not `.$ext`: $file")
56+
else if !file.exists then
57+
throw new IllegalArgumentException(s"File not found: ${file.toAbsolute}")
58+
tastyFiles.foreach(checkFile(_, "tasty"))
59+
jars.foreach(checkFile(_, "jar"))
60+
val files = tastyFiles ::: jars
61+
files.nonEmpty && inspectFiles(dependenciesClasspath, files)
62+
63+
/** Load and process TASTy files using TASTy reflect and provided context
64+
*
65+
* Used in doctool to reuse reporter and setup provided by sbt
66+
*
67+
* @param classes List of paths of `.tasty` and `.jar` files (no validation is performed)
68+
* @param classpath Classpath with extra dependencies needed to load class in the `.tasty` files
69+
*/
70+
protected[inspector] def inspectFilesInContext(classpath: List[String], classes: List[String])(using Context): Unit =
71+
if (classes.isEmpty) report.error("Parameter classes should no be empty")
72+
inspectorDriver().process(inspectorArgs(classpath, classes), summon[Context])
73+
74+
75+
private def inspectorDriver() =
76+
class InspectorDriver extends Driver:
77+
override protected def newCompiler(implicit ctx: Context): Compiler = new TastyFromClass
78+
79+
class TastyInspectorPhase extends Phase:
80+
override def phaseName: String = "tastyInspector"
81+
82+
override def run(implicit ctx: Context): Unit =
83+
val qctx = QuotesImpl()
84+
self.processCompilationUnit(using qctx)(ctx.compilationUnit.tpdTree.asInstanceOf[qctx.reflect.Tree])
85+
86+
class TastyInspectorFinishPhase extends Phase:
87+
override def phaseName: String = "tastyInspectorFinish"
88+
89+
override def runOn(units: List[CompilationUnit])(using Context): List[CompilationUnit] =
90+
val qctx = QuotesImpl()
91+
self.postProcess(using qctx)
92+
units
93+
94+
override def run(implicit ctx: Context): Unit = unsupported("run")
95+
96+
class TastyFromClass extends TASTYCompiler:
97+
98+
override protected def frontendPhases: List[List[Phase]] =
99+
List(new ReadTasty) :: // Load classes from tasty
100+
Nil
101+
102+
override protected def picklerPhases: List[List[Phase]] = Nil
103+
104+
override protected def transformPhases: List[List[Phase]] = Nil
105+
106+
override protected def backendPhases: List[List[Phase]] =
107+
List(new TastyInspectorPhase) :: // Perform a callback for each compilation unit
108+
List(new TastyInspectorFinishPhase) :: // Perform a final callback
109+
Nil
110+
111+
override def newRun(implicit ctx: Context): Run =
112+
reset()
113+
new TASTYRun(this, ctx.fresh.addMode(Mode.ReadPositions).addMode(Mode.ReadComments))
114+
115+
new InspectorDriver
116+
117+
private def inspectorArgs(classpath: List[String], classes: List[String]): Array[String] =
118+
val currentClasspath = ClasspathFromClassloader(getClass.getClassLoader)
119+
val fullClasspath = (classpath :+ currentClasspath).mkString(pathSeparator)
120+
("-from-tasty" :: "-Yretain-trees" :: "-classpath" :: fullClasspath :: classes).toArray
121+
122+
123+
private def inspectFiles(classpath: List[String], classes: List[String]): Boolean =
124+
if (classes.isEmpty)
125+
throw new IllegalArgumentException("Parameter classes should no be empty")
126+
127+
val reporter = inspectorDriver().process(inspectorArgs(classpath, classes))
128+
reporter.hasErrors
129+
130+
end inspectFiles
131+
132+
133+
end OldTastyInspector

scala3doc/test/dotty/dokka/tasty/comments/CommentExpanderTests.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,8 @@ class CommentExpanderTests {
3737

3838
@Test
3939
def test(): Unit = {
40-
import scala.tasty.inspector.TastyInspector
41-
class Inspector extends TastyInspector:
40+
import scala.tasty.inspector.OldTastyInspector
41+
class Inspector extends OldTastyInspector:
4242

4343
def processCompilationUnit(using quoted.Quotes)(root: quotes.reflect.Tree): Unit = ()
4444

scala3doc/test/dotty/dokka/tasty/comments/MemberLookupTests.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -101,8 +101,8 @@ class MemberLookupTests {
101101

102102
@Test
103103
def test(): Unit = {
104-
import scala.tasty.inspector.TastyInspector
105-
class Inspector extends TastyInspector:
104+
import scala.tasty.inspector.OldTastyInspector
105+
class Inspector extends OldTastyInspector:
106106
var alreadyRan: Boolean = false
107107

108108
override def processCompilationUnit(using ctx: quoted.Quotes)(root: ctx.reflect.Tree): Unit =

stdlib-bootstrapped-tasty-tests/test/BootstrappedStdLibTASYyTest.scala

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import dotty.tools.io._
88
import dotty.tools.dotc.util.ClasspathFromClassloader
99

1010
import scala.quoted._
11+
import scala.tasty.inspector._
1112

1213
import java.io.File.pathSeparator
1314
import java.io.File.separator
@@ -101,13 +102,14 @@ object BootstrappedStdLibTASYyTest:
101102
.toList
102103

103104
def loadWithTastyInspector(blacklisted: Set[String]): Unit =
104-
val inspector = new scala.tasty.inspector.TastyInspector {
105-
def processCompilationUnit(using Quotes)(root: quotes.reflect.Tree): Unit =
106-
root.show(using quotes.reflect.Printer.TreeStructure) // Check that we can traverse the full tree
105+
val inspector = new scala.tasty.inspector.Inspector {
106+
def inspect(using Quotes)(tastys: List[Tasty[quotes.type]]): Unit =
107+
for tasty <- tastys do
108+
tasty.ast.show(using quotes.reflect.Printer.TreeStructure) // Check that we can traverse the full tree
107109
()
108110
}
109111
val tastyFiles = scalaLibTastyPaths.filterNot(blacklisted)
110-
val hasErrors = inspector.inspectTastyFiles(tastyFiles.map(x => scalaLibClassesPath.resolve(x).toString))
112+
val hasErrors = TastyInspector.inspectTastyFiles(tastyFiles.map(x => scalaLibClassesPath.resolve(x).toString))(inspector)
111113
assert(!hasErrors, "Errors reported while loading from TASTy")
112114

113115
def compileFromTastyInJar(blacklisted: Set[String]): Unit = {
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
package scala.tasty.inspector
2+
3+
import scala.quoted._
4+
import scala.quoted.runtime.impl.QuotesImpl
5+
6+
import dotty.tools.dotc.Compiler
7+
import dotty.tools.dotc.Driver
8+
import dotty.tools.dotc.Run
9+
import dotty.tools.dotc.core.Contexts.Context
10+
import dotty.tools.dotc.core.Mode
11+
import dotty.tools.dotc.core.Phases.Phase
12+
import dotty.tools.dotc.fromtasty._
13+
import dotty.tools.dotc.util.ClasspathFromClassloader
14+
import dotty.tools.dotc.CompilationUnit
15+
import dotty.tools.unsupported
16+
import dotty.tools.dotc.report
17+
18+
import java.io.File.pathSeparator
19+
20+
trait Inspector:
21+
22+
/** Inspect all TASTy files using `Quotes` reflect API.
23+
*
24+
* Note: Within this method `quotes.reflect.SourceFile.current` will not work, hence the explicit source paths.
25+
*
26+
* @param tastys List of `Tasty` containing `.tasty`file path and AST
27+
*/
28+
def inspect(using Quotes)(tastys: List[Tasty[quotes.type]]): Unit
29+
30+
end Inspector
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package scala.tasty.inspector
2+
3+
import scala.quoted._
4+
5+
/** `.tasty` file representation containing file path and the AST */
6+
trait Tasty[Q <: Quotes & Singleton]:
7+
8+
/** Instance of `Quotes` used to load the AST */
9+
val quotes: Q
10+
11+
/** Path to the `.tasty` file */
12+
def path: String
13+
14+
/** Abstract Syntax Tree contained in the `.tasty` file */
15+
def ast: quotes.reflect.Tree
16+
17+
end Tasty

0 commit comments

Comments
 (0)