Skip to content

Commit 854d5ef

Browse files
committed
Rework TastyInspector API to allow inspection of all files
1 parent 26398f4 commit 854d5ef

File tree

21 files changed

+333
-140
lines changed

21 files changed

+333
-140
lines changed

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

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

27232723
object SourceFile extends SourceFileModule {
2724-
def current: SourceFile = ctx.compilationUnit.source
2724+
def current: SourceFile =
2725+
if ctx.compilationUnit == null then
2726+
throw new java.lang.UnsupportedOperationException(
2727+
"`reflect.SourceFile.current` cannot be called within the TASTy ispector")
2728+
ctx.compilationUnit.source
27252729
}
27262730

27272731
given SourceFileMethods: SourceFileMethods with

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[(String, quotes.reflect.Tree)]): Unit = {
14+
for (_, tree) <- tastys do
15+
val tastyStr = tree.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 with
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[(String, quotes.reflect.Tree)]): Unit =
107+
for (_, tree) <- tastys do
108+
tree.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 tuples containing the path of the `.tasty` and the AST within the file.
27+
*/
28+
def inspect(using Quotes)(tastys: List[(String, quotes.reflect.Tree)]): Unit
29+
30+
end Inspector

tasty-inspector/src/scala/tasty/inspector/TastyInspector.scala

Lines changed: 14 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -17,36 +17,29 @@ import dotty.tools.dotc.report
1717

1818
import java.io.File.pathSeparator
1919

20-
trait TastyInspector:
21-
self =>
22-
23-
/** Process a TASTy file using TASTy reflect */
24-
protected def processCompilationUnit(using Quotes)(root: quotes.reflect.Tree): Unit
25-
26-
/** Called after all compilation units are processed */
27-
protected def postProcess(using Quotes): Unit = ()
20+
object TastyInspector:
2821

2922
/** Load and process TASTy files using TASTy reflect
3023
*
3124
* @param tastyFiles List of paths of `.tasty` files
3225
*/
33-
def inspectTastyFiles(tastyFiles: List[String]): Boolean =
34-
inspectAllTastyFiles(tastyFiles, Nil, Nil)
26+
def inspectTastyFiles(tastyFiles: List[String])(inspector: Inspector): Boolean =
27+
inspectAllTastyFiles(tastyFiles, Nil, Nil)(inspector)
3528

3629
/** Load and process TASTy files in a `jar` file using TASTy reflect
3730
*
3831
* @param jars Path of `.jar` file
3932
*/
40-
def inspectTastyFilesInJar(jar: String): Boolean =
41-
inspectAllTastyFiles(Nil, List(jar), Nil)
33+
def inspectTastyFilesInJar(jar: String)(inspector: Inspector): Boolean =
34+
inspectAllTastyFiles(Nil, List(jar), Nil)(inspector)
4235

4336
/** Load and process TASTy files using TASTy reflect
4437
*
4538
* @param tastyFiles List of paths of `.tasty` files
4639
* @param jars List of path of `.jar` files
4740
* @param dependenciesClasspath Classpath with extra dependencies needed to load class in the `.tasty` files
4841
*/
49-
def inspectAllTastyFiles(tastyFiles: List[String], jars: List[String], dependenciesClasspath: List[String]): Boolean =
42+
def inspectAllTastyFiles(tastyFiles: List[String], jars: List[String], dependenciesClasspath: List[String])(inspector: Inspector): Boolean =
5043
def checkFile(fileName: String, ext: String): Unit =
5144
val file = dotty.tools.io.Path(fileName)
5245
if file.extension != ext then
@@ -56,40 +49,23 @@ trait TastyInspector:
5649
tastyFiles.foreach(checkFile(_, "tasty"))
5750
jars.foreach(checkFile(_, "jar"))
5851
val files = tastyFiles ::: jars
59-
files.nonEmpty && inspectFiles(dependenciesClasspath, files)
52+
files.nonEmpty && inspectFiles(dependenciesClasspath, files)(inspector)
6053

61-
/** Load and process TASTy files using TASTy reflect and provided context
62-
*
63-
* Used in doctool to reuse reporter and setup provided by sbt
64-
*
65-
* @param classes List of paths of `.tasty` and `.jar` files (no validation is performed)
66-
* @param classpath Classpath with extra dependencies needed to load class in the `.tasty` files
67-
*/
68-
protected[inspector] def inspectFilesInContext(classpath: List[String], classes: List[String])(using Context): Unit =
69-
if (classes.isEmpty) report.error("Parameter classes should no be empty")
70-
inspectorDriver().process(inspectorArgs(classpath, classes), summon[Context])
71-
72-
73-
private def inspectorDriver() =
54+
private def inspectorDriver(inspector: Inspector) =
7455
class InspectorDriver extends Driver:
7556
override protected def newCompiler(implicit ctx: Context): Compiler = new TastyFromClass
7657

7758
class TastyInspectorPhase extends Phase:
7859
override def phaseName: String = "tastyInspector"
7960

80-
override def run(implicit ctx: Context): Unit =
81-
val qctx = QuotesImpl()
82-
self.processCompilationUnit(using qctx)(ctx.compilationUnit.tpdTree.asInstanceOf[qctx.reflect.Tree])
83-
84-
class TastyInspectorFinishPhase extends Phase:
85-
override def phaseName: String = "tastyInspectorFinish"
86-
8761
override def runOn(units: List[CompilationUnit])(using Context): List[CompilationUnit] =
88-
val qctx = QuotesImpl()
89-
self.postProcess(using qctx)
62+
val quotesImpl = QuotesImpl()
63+
val tastys = units.map(unit => (unit.source.path , unit.tpdTree.asInstanceOf[quotesImpl.reflect.Tree]))
64+
inspector.inspect(using quotesImpl)(tastys)
9065
units
9166

9267
override def run(implicit ctx: Context): Unit = unsupported("run")
68+
end TastyInspectorPhase
9369

9470
class TastyFromClass extends TASTYCompiler:
9571

@@ -103,7 +79,6 @@ trait TastyInspector:
10379

10480
override protected def backendPhases: List[List[Phase]] =
10581
List(new TastyInspectorPhase) :: // Perform a callback for each compilation unit
106-
List(new TastyInspectorFinishPhase) :: // Perform a final callback
10782
Nil
10883

10984
override def newRun(implicit ctx: Context): Run =
@@ -118,11 +93,11 @@ trait TastyInspector:
11893
("-from-tasty" :: "-Yretain-trees" :: "-classpath" :: fullClasspath :: classes).toArray
11994

12095

121-
private def inspectFiles(classpath: List[String], classes: List[String]): Boolean =
96+
private def inspectFiles(classpath: List[String], classes: List[String])(inspector: Inspector): Boolean =
12297
if (classes.isEmpty)
12398
throw new IllegalArgumentException("Parameter classes should no be empty")
12499

125-
val reporter = inspectorDriver().process(inspectorArgs(classpath, classes))
100+
val reporter = inspectorDriver(inspector).process(inspectorArgs(classpath, classes))
126101
reporter.hasErrors
127102

128103
end inspectFiles

0 commit comments

Comments
 (0)