diff --git a/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala b/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala index 1be24a46e0d9..263fc51f8ba7 100644 --- a/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala +++ b/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala @@ -2723,7 +2723,11 @@ class QuotesImpl private (using val ctx: Context) extends Quotes, QuoteUnpickler type SourceFile = dotc.util.SourceFile object SourceFile extends SourceFileModule { - def current: SourceFile = ctx.compilationUnit.source + def current: SourceFile = + if ctx.compilationUnit == null then + throw new java.lang.UnsupportedOperationException( + "`reflect.SourceFile.current` cannot be called within the TASTy ispector") + ctx.compilationUnit.source } given SourceFileMethods: SourceFileMethods with diff --git a/docs/docs/reference/metaprogramming/tasty-inspect.md b/docs/docs/reference/metaprogramming/tasty-inspect.md index 3547b8939a3d..4324f7a63339 100644 --- a/docs/docs/reference/metaprogramming/tasty-inspect.md +++ b/docs/docs/reference/metaprogramming/tasty-inspect.md @@ -10,7 +10,7 @@ libraryDependencies += "org.scala-lang" %% "scala3-tasty-inspector" % scalaVersi TASTy files contain the full typed tree of a class including source positions and documentation. This is ideal for tools that analyze or extract semantic information from the code. To avoid the hassle of working directly with the TASTy -file we provide the `TastyInspector` which loads the contents and exposes it +file we provide the `Inspector` which loads the contents and exposes it through the TASTy reflect API. ## Inspecting TASTy files @@ -21,10 +21,12 @@ To inspect the trees of a TASTy file a consumer can be defined in the following import scala.quoted._ import scala.tasty.inspector._ -class MyInspector extends TastyInspector: - protected def processCompilationUnit(using Quotes)(tree: quotes.reflect.Tree): Unit = +class MyInspector extends Inspector: + def inspect(using Quotes)(tastys: List[Tasty[quotes.type]]): Unit = import quotes.reflect._ - // Do something with the tree + for tasty <- tastys do + val tree = tasty.ast + // Do something with the tree ``` Then the consumer can be instantiated with the following code to get the tree of the `foo/Bar.tasty` file. @@ -32,7 +34,9 @@ Then the consumer can be instantiated with the following code to get the tree of ```scala object Test: def main(args: Array[String]): Unit = - new MyInspector().inspectTastyFiles("foo/Bar.tasty") + val tastyFiles = List("foo/Bar.tasty") + TastyInspector.inspectTastyFiles(tastyFiles)(new MyInspector) + ``` 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: diff --git a/sbt-dotty/sbt-test/sbt-dotty/tasty-inspector-example-project/app/Main.scala b/sbt-dotty/sbt-test/sbt-dotty/tasty-inspector-example-project/app/Main.scala index f4e945ae2667..536cf25122f6 100644 --- a/sbt-dotty/sbt-test/sbt-dotty/tasty-inspector-example-project/app/Main.scala +++ b/sbt-dotty/sbt-test/sbt-dotty/tasty-inspector-example-project/app/Main.scala @@ -1,7 +1,7 @@ package hello import scala.quoted._ -import scala.tasty.inspector.TastyInspector +import scala.tasty.inspector._ import scala.jdk.StreamConverters._ @@ -9,10 +9,12 @@ import java.nio.file.{Path, Files, Paths, FileSystems} object Main extends App { - val inspector = new TastyInspector { - protected def processCompilationUnit(using Quotes)(root: quotes.reflect.Tree): Unit = { - val tastyStr = root.show - println(tastyStr) + val inspector = new Inspector { + def inspect(using Quotes)(tastys: List[Tasty[quotes.type]]): Unit = { + for tasty <- tastys do + val tastyStr = tasty.ast.show + println(tastyStr) + } } @@ -25,7 +27,7 @@ object Main extends App { val tastyFiles = for p <- walk(pwd) if `lib/Foo.tasty`.matches(p) yield p.toString - inspector.inspectTastyFiles(List(tastyFiles.head)) + TastyInspector.inspectTastyFiles(List(tastyFiles.head))(inspector) } diff --git a/scala3doc/src/scala/tasty/inspector/DocTastyInspector.scala b/scala3doc/src/scala/tasty/inspector/DocTastyInspector.scala index f652551443c5..963c1dfc3550 100644 --- a/scala3doc/src/scala/tasty/inspector/DocTastyInspector.scala +++ b/scala3doc/src/scala/tasty/inspector/DocTastyInspector.scala @@ -2,7 +2,7 @@ package scala.tasty.inspector import dotty.tools.dotc.core.Contexts.Context -abstract class DocTastyInspector extends TastyInspector: +abstract class DocTastyInspector extends OldTastyInspector: def inspectFilesInDocContext( classpath: List[String], filePaths: List[String])( diff --git a/scala3doc/src/scala/tasty/inspector/OldTastyInspector.scala b/scala3doc/src/scala/tasty/inspector/OldTastyInspector.scala new file mode 100644 index 000000000000..91311b1ff9bb --- /dev/null +++ b/scala3doc/src/scala/tasty/inspector/OldTastyInspector.scala @@ -0,0 +1,133 @@ +package scala.tasty.inspector + +import scala.quoted._ +import scala.quoted.runtime.impl.QuotesImpl + +import dotty.tools.dotc.Compiler +import dotty.tools.dotc.Driver +import dotty.tools.dotc.Run +import dotty.tools.dotc.core.Contexts.Context +import dotty.tools.dotc.core.Mode +import dotty.tools.dotc.core.Phases.Phase +import dotty.tools.dotc.fromtasty._ +import dotty.tools.dotc.util.ClasspathFromClassloader +import dotty.tools.dotc.CompilationUnit +import dotty.tools.unsupported +import dotty.tools.dotc.report + +import java.io.File.pathSeparator + +// COPY OF OLD IMPLEMENTATION +// TODO: update to new implementation +trait OldTastyInspector: + self => + + /** Process a TASTy file using TASTy reflect */ + protected def processCompilationUnit(using Quotes)(root: quotes.reflect.Tree): Unit + + /** Called after all compilation units are processed */ + protected def postProcess(using Quotes): Unit = () + + /** Load and process TASTy files using TASTy reflect + * + * @param tastyFiles List of paths of `.tasty` files + */ + def inspectTastyFiles(tastyFiles: List[String]): Boolean = + inspectAllTastyFiles(tastyFiles, Nil, Nil) + + /** Load and process TASTy files in a `jar` file using TASTy reflect + * + * @param jars Path of `.jar` file + */ + def inspectTastyFilesInJar(jar: String): Boolean = + inspectAllTastyFiles(Nil, List(jar), Nil) + + /** Load and process TASTy files using TASTy reflect + * + * @param tastyFiles List of paths of `.tasty` files + * @param jars List of path of `.jar` files + * @param dependenciesClasspath Classpath with extra dependencies needed to load class in the `.tasty` files + */ + def inspectAllTastyFiles(tastyFiles: List[String], jars: List[String], dependenciesClasspath: List[String]): Boolean = + def checkFile(fileName: String, ext: String): Unit = + val file = dotty.tools.io.Path(fileName) + if file.extension != ext then + throw new IllegalArgumentException(s"File extension is not `.$ext`: $file") + else if !file.exists then + throw new IllegalArgumentException(s"File not found: ${file.toAbsolute}") + tastyFiles.foreach(checkFile(_, "tasty")) + jars.foreach(checkFile(_, "jar")) + val files = tastyFiles ::: jars + files.nonEmpty && inspectFiles(dependenciesClasspath, files) + + /** Load and process TASTy files using TASTy reflect and provided context + * + * Used in doctool to reuse reporter and setup provided by sbt + * + * @param classes List of paths of `.tasty` and `.jar` files (no validation is performed) + * @param classpath Classpath with extra dependencies needed to load class in the `.tasty` files + */ + protected[inspector] def inspectFilesInContext(classpath: List[String], classes: List[String])(using Context): Unit = + if (classes.isEmpty) report.error("Parameter classes should no be empty") + inspectorDriver().process(inspectorArgs(classpath, classes), summon[Context]) + + + private def inspectorDriver() = + class InspectorDriver extends Driver: + override protected def newCompiler(implicit ctx: Context): Compiler = new TastyFromClass + + class TastyInspectorPhase extends Phase: + override def phaseName: String = "tastyInspector" + + override def run(implicit ctx: Context): Unit = + val qctx = QuotesImpl() + self.processCompilationUnit(using qctx)(ctx.compilationUnit.tpdTree.asInstanceOf[qctx.reflect.Tree]) + + class TastyInspectorFinishPhase extends Phase: + override def phaseName: String = "tastyInspectorFinish" + + override def runOn(units: List[CompilationUnit])(using Context): List[CompilationUnit] = + val qctx = QuotesImpl() + self.postProcess(using qctx) + units + + override def run(implicit ctx: Context): Unit = unsupported("run") + + class TastyFromClass extends TASTYCompiler: + + override protected def frontendPhases: List[List[Phase]] = + List(new ReadTasty) :: // Load classes from tasty + Nil + + override protected def picklerPhases: List[List[Phase]] = Nil + + override protected def transformPhases: List[List[Phase]] = Nil + + override protected def backendPhases: List[List[Phase]] = + List(new TastyInspectorPhase) :: // Perform a callback for each compilation unit + List(new TastyInspectorFinishPhase) :: // Perform a final callback + Nil + + override def newRun(implicit ctx: Context): Run = + reset() + new TASTYRun(this, ctx.fresh.addMode(Mode.ReadPositions).addMode(Mode.ReadComments)) + + new InspectorDriver + + private def inspectorArgs(classpath: List[String], classes: List[String]): Array[String] = + val currentClasspath = ClasspathFromClassloader(getClass.getClassLoader) + val fullClasspath = (classpath :+ currentClasspath).mkString(pathSeparator) + ("-from-tasty" :: "-Yretain-trees" :: "-classpath" :: fullClasspath :: classes).toArray + + + private def inspectFiles(classpath: List[String], classes: List[String]): Boolean = + if (classes.isEmpty) + throw new IllegalArgumentException("Parameter classes should no be empty") + + val reporter = inspectorDriver().process(inspectorArgs(classpath, classes)) + reporter.hasErrors + + end inspectFiles + + +end OldTastyInspector diff --git a/scala3doc/test/dotty/dokka/tasty/comments/CommentExpanderTests.scala b/scala3doc/test/dotty/dokka/tasty/comments/CommentExpanderTests.scala index d86b3e743c1f..667633538116 100644 --- a/scala3doc/test/dotty/dokka/tasty/comments/CommentExpanderTests.scala +++ b/scala3doc/test/dotty/dokka/tasty/comments/CommentExpanderTests.scala @@ -37,8 +37,8 @@ class CommentExpanderTests { @Test def test(): Unit = { - import scala.tasty.inspector.TastyInspector - class Inspector extends TastyInspector: + import scala.tasty.inspector.OldTastyInspector + class Inspector extends OldTastyInspector: def processCompilationUnit(using quoted.Quotes)(root: quotes.reflect.Tree): Unit = () diff --git a/scala3doc/test/dotty/dokka/tasty/comments/MemberLookupTests.scala b/scala3doc/test/dotty/dokka/tasty/comments/MemberLookupTests.scala index 99ec26c0779d..154e7456d120 100644 --- a/scala3doc/test/dotty/dokka/tasty/comments/MemberLookupTests.scala +++ b/scala3doc/test/dotty/dokka/tasty/comments/MemberLookupTests.scala @@ -101,8 +101,8 @@ class MemberLookupTests { @Test def test(): Unit = { - import scala.tasty.inspector.TastyInspector - class Inspector extends TastyInspector: + import scala.tasty.inspector.OldTastyInspector + class Inspector extends OldTastyInspector: var alreadyRan: Boolean = false override def processCompilationUnit(using ctx: quoted.Quotes)(root: ctx.reflect.Tree): Unit = diff --git a/stdlib-bootstrapped-tasty-tests/test/BootstrappedStdLibTASYyTest.scala b/stdlib-bootstrapped-tasty-tests/test/BootstrappedStdLibTASYyTest.scala index ef004baa191a..54b89b1f5e3b 100644 --- a/stdlib-bootstrapped-tasty-tests/test/BootstrappedStdLibTASYyTest.scala +++ b/stdlib-bootstrapped-tasty-tests/test/BootstrappedStdLibTASYyTest.scala @@ -8,6 +8,7 @@ import dotty.tools.io._ import dotty.tools.dotc.util.ClasspathFromClassloader import scala.quoted._ +import scala.tasty.inspector._ import java.io.File.pathSeparator import java.io.File.separator @@ -101,13 +102,14 @@ object BootstrappedStdLibTASYyTest: .toList def loadWithTastyInspector(blacklisted: Set[String]): Unit = - val inspector = new scala.tasty.inspector.TastyInspector { - def processCompilationUnit(using Quotes)(root: quotes.reflect.Tree): Unit = - root.show(using quotes.reflect.Printer.TreeStructure) // Check that we can traverse the full tree + val inspector = new scala.tasty.inspector.Inspector { + def inspect(using Quotes)(tastys: List[Tasty[quotes.type]]): Unit = + for tasty <- tastys do + tasty.ast.show(using quotes.reflect.Printer.TreeStructure) // Check that we can traverse the full tree () } val tastyFiles = scalaLibTastyPaths.filterNot(blacklisted) - val hasErrors = inspector.inspectTastyFiles(tastyFiles.map(x => scalaLibClassesPath.resolve(x).toString)) + val hasErrors = TastyInspector.inspectTastyFiles(tastyFiles.map(x => scalaLibClassesPath.resolve(x).toString))(inspector) assert(!hasErrors, "Errors reported while loading from TASTy") def compileFromTastyInJar(blacklisted: Set[String]): Unit = { diff --git a/tasty-inspector/src/scala/tasty/inspector/Inspector.scala b/tasty-inspector/src/scala/tasty/inspector/Inspector.scala new file mode 100644 index 000000000000..0cb07a1a4c46 --- /dev/null +++ b/tasty-inspector/src/scala/tasty/inspector/Inspector.scala @@ -0,0 +1,30 @@ +package scala.tasty.inspector + +import scala.quoted._ +import scala.quoted.runtime.impl.QuotesImpl + +import dotty.tools.dotc.Compiler +import dotty.tools.dotc.Driver +import dotty.tools.dotc.Run +import dotty.tools.dotc.core.Contexts.Context +import dotty.tools.dotc.core.Mode +import dotty.tools.dotc.core.Phases.Phase +import dotty.tools.dotc.fromtasty._ +import dotty.tools.dotc.util.ClasspathFromClassloader +import dotty.tools.dotc.CompilationUnit +import dotty.tools.unsupported +import dotty.tools.dotc.report + +import java.io.File.pathSeparator + +trait Inspector: + + /** Inspect all TASTy files using `Quotes` reflect API. + * + * Note: Within this method `quotes.reflect.SourceFile.current` will not work, hence the explicit source paths. + * + * @param tastys List of `Tasty` containing `.tasty`file path and AST + */ + def inspect(using Quotes)(tastys: List[Tasty[quotes.type]]): Unit + +end Inspector diff --git a/tasty-inspector/src/scala/tasty/inspector/Tasty.scala b/tasty-inspector/src/scala/tasty/inspector/Tasty.scala new file mode 100644 index 000000000000..1f7eea14bc83 --- /dev/null +++ b/tasty-inspector/src/scala/tasty/inspector/Tasty.scala @@ -0,0 +1,17 @@ +package scala.tasty.inspector + +import scala.quoted._ + +/** `.tasty` file representation containing file path and the AST */ +trait Tasty[Q <: Quotes & Singleton]: + + /** Instance of `Quotes` used to load the AST */ + val quotes: Q + + /** Path to the `.tasty` file */ + def path: String + + /** Abstract Syntax Tree contained in the `.tasty` file */ + def ast: quotes.reflect.Tree + +end Tasty diff --git a/tasty-inspector/src/scala/tasty/inspector/TastyInspector.scala b/tasty-inspector/src/scala/tasty/inspector/TastyInspector.scala index c6f11e76d177..f2ea76e30b7b 100644 --- a/tasty-inspector/src/scala/tasty/inspector/TastyInspector.scala +++ b/tasty-inspector/src/scala/tasty/inspector/TastyInspector.scala @@ -17,28 +17,21 @@ import dotty.tools.dotc.report import java.io.File.pathSeparator -trait TastyInspector: - self => - - /** Process a TASTy file using TASTy reflect */ - protected def processCompilationUnit(using Quotes)(root: quotes.reflect.Tree): Unit - - /** Called after all compilation units are processed */ - protected def postProcess(using Quotes): Unit = () +object TastyInspector: /** Load and process TASTy files using TASTy reflect * * @param tastyFiles List of paths of `.tasty` files */ - def inspectTastyFiles(tastyFiles: List[String]): Boolean = - inspectAllTastyFiles(tastyFiles, Nil, Nil) + def inspectTastyFiles(tastyFiles: List[String])(inspector: Inspector): Boolean = + inspectAllTastyFiles(tastyFiles, Nil, Nil)(inspector) /** Load and process TASTy files in a `jar` file using TASTy reflect * * @param jars Path of `.jar` file */ - def inspectTastyFilesInJar(jar: String): Boolean = - inspectAllTastyFiles(Nil, List(jar), Nil) + def inspectTastyFilesInJar(jar: String)(inspector: Inspector): Boolean = + inspectAllTastyFiles(Nil, List(jar), Nil)(inspector) /** Load and process TASTy files using TASTy reflect * @@ -46,7 +39,7 @@ trait TastyInspector: * @param jars List of path of `.jar` files * @param dependenciesClasspath Classpath with extra dependencies needed to load class in the `.tasty` files */ - def inspectAllTastyFiles(tastyFiles: List[String], jars: List[String], dependenciesClasspath: List[String]): Boolean = + def inspectAllTastyFiles(tastyFiles: List[String], jars: List[String], dependenciesClasspath: List[String])(inspector: Inspector): Boolean = def checkFile(fileName: String, ext: String): Unit = val file = dotty.tools.io.Path(fileName) if file.extension != ext then @@ -56,40 +49,26 @@ trait TastyInspector: tastyFiles.foreach(checkFile(_, "tasty")) jars.foreach(checkFile(_, "jar")) val files = tastyFiles ::: jars - files.nonEmpty && inspectFiles(dependenciesClasspath, files) + files.nonEmpty && inspectFiles(dependenciesClasspath, files)(inspector) - /** Load and process TASTy files using TASTy reflect and provided context - * - * Used in doctool to reuse reporter and setup provided by sbt - * - * @param classes List of paths of `.tasty` and `.jar` files (no validation is performed) - * @param classpath Classpath with extra dependencies needed to load class in the `.tasty` files - */ - protected[inspector] def inspectFilesInContext(classpath: List[String], classes: List[String])(using Context): Unit = - if (classes.isEmpty) report.error("Parameter classes should no be empty") - inspectorDriver().process(inspectorArgs(classpath, classes), summon[Context]) - - - private def inspectorDriver() = + private def inspectorDriver(inspector: Inspector) = class InspectorDriver extends Driver: override protected def newCompiler(implicit ctx: Context): Compiler = new TastyFromClass class TastyInspectorPhase extends Phase: override def phaseName: String = "tastyInspector" - override def run(implicit ctx: Context): Unit = - val qctx = QuotesImpl() - self.processCompilationUnit(using qctx)(ctx.compilationUnit.tpdTree.asInstanceOf[qctx.reflect.Tree]) - - class TastyInspectorFinishPhase extends Phase: - override def phaseName: String = "tastyInspectorFinish" - override def runOn(units: List[CompilationUnit])(using Context): List[CompilationUnit] = - val qctx = QuotesImpl() - self.postProcess(using qctx) + val quotesImpl = QuotesImpl() + class TastyImpl(val path: String, val ast: quotesImpl.reflect.Tree) extends Tasty[quotesImpl.type] { + val quotes = quotesImpl + } + val tastys = units.map(unit => new TastyImpl(unit.source.path , unit.tpdTree.asInstanceOf[quotesImpl.reflect.Tree])) + inspector.inspect(using quotesImpl)(tastys) units override def run(implicit ctx: Context): Unit = unsupported("run") + end TastyInspectorPhase class TastyFromClass extends TASTYCompiler: @@ -103,7 +82,6 @@ trait TastyInspector: override protected def backendPhases: List[List[Phase]] = List(new TastyInspectorPhase) :: // Perform a callback for each compilation unit - List(new TastyInspectorFinishPhase) :: // Perform a final callback Nil override def newRun(implicit ctx: Context): Run = @@ -118,11 +96,11 @@ trait TastyInspector: ("-from-tasty" :: "-Yretain-trees" :: "-classpath" :: fullClasspath :: classes).toArray - private def inspectFiles(classpath: List[String], classes: List[String]): Boolean = + private def inspectFiles(classpath: List[String], classes: List[String])(inspector: Inspector): Boolean = if (classes.isEmpty) throw new IllegalArgumentException("Parameter classes should no be empty") - val reporter = inspectorDriver().process(inspectorArgs(classpath, classes)) + val reporter = inspectorDriver(inspector).process(inspectorArgs(classpath, classes)) reporter.hasErrors end inspectFiles diff --git a/tests/run-custom-args/tasty-inspector/i10359.scala b/tests/run-custom-args/tasty-inspector/i10359.scala index b724d0431b4a..f8f8f8c971f1 100644 --- a/tests/run-custom-args/tasty-inspector/i10359.scala +++ b/tests/run-custom-args/tasty-inspector/i10359.scala @@ -29,18 +29,19 @@ object Test { val tastyFiles = allTastyFiles.filter(_.contains("TraitParams")) val inspect = new TestInspector() - inspect.inspectTastyFiles(allTastyFiles.filter(_.contains("Bar"))) + TastyInspector.inspectTastyFiles(allTastyFiles.filter(_.contains("Bar")))(inspect) } } -class TestInspector() extends TastyInspector: +class TestInspector() extends Inspector: - protected def processCompilationUnit(using Quotes)(root: quotes.reflect.Tree): Unit = + def inspect(using Quotes)(tastys: List[Tasty[quotes.type]]): Unit = import quotes.reflect._ - val code = root.show - assert(code.contains("import Foo.this.g.{given}"), code) - assert(code.contains("import Foo.this.g.{given scala.Int}"), code) + for tasty <- tastys do + val code = tasty.ast.show + assert(code.contains("import Foo.this.g.{given}"), code) + assert(code.contains("import Foo.this.g.{given scala.Int}"), code) - val extractors = root.show(using Printer.TreeStructure) - assert(extractors.contains("GivenSelector"), extractors) + val extractors = tasty.ast.show(using Printer.TreeStructure) + assert(extractors.contains("GivenSelector"), extractors) diff --git a/tests/run-custom-args/tasty-inspector/i8163.scala b/tests/run-custom-args/tasty-inspector/i8163.scala index 2ac769b321f1..4c06c317d41f 100644 --- a/tests/run-custom-args/tasty-inspector/i8163.scala +++ b/tests/run-custom-args/tasty-inspector/i8163.scala @@ -16,14 +16,15 @@ object Test { val allTastyFiles = dotty.tools.io.Path(classpath).walkFilter(_.extension == "tasty").map(_.toString).toList val tastyFiles = allTastyFiles.filter(_.contains("I8163")) - new TestInspector().inspectTastyFiles(tastyFiles) + TastyInspector.inspectTastyFiles(tastyFiles)(new TestInspector()) } } -class TestInspector() extends TastyInspector: +class TestInspector() extends Inspector: - protected def processCompilationUnit(using Quotes)(root: quotes.reflect.Tree): Unit = - inspectClass(root) + def inspect(using Quotes)(tastys: List[Tasty[quotes.type]]): Unit = + for tasty <- tastys do + inspectClass(tasty.ast) private def inspectClass(using Quotes)(tree: quotes.reflect.Tree): Unit = import quotes.reflect._ diff --git a/tests/run-custom-args/tasty-inspector/i8364.scala b/tests/run-custom-args/tasty-inspector/i8364.scala index 59775110d46d..bd808ac38a20 100644 --- a/tests/run-custom-args/tasty-inspector/i8364.scala +++ b/tests/run-custom-args/tasty-inspector/i8364.scala @@ -2,9 +2,10 @@ import scala.quoted._ import scala.tasty.inspector._ @main def Test = { - val inspector = new TastyInspector { - protected def processCompilationUnit(using Quotes)(tree: quotes.reflect.Tree): Unit = { - tree.show(using quotes.reflect.Printer.TreeStructure) // Make sure that tree is loaded and can be traveresed + val inspector = new Inspector { + def inspect(using Quotes)(tastys: List[Tasty[quotes.type]]): Unit = { + for tasty <- tastys do + tasty.ast.show(using quotes.reflect.Printer.TreeStructure) // Make sure that tree is loaded and can be traveresed } } @@ -12,5 +13,5 @@ import scala.tasty.inspector._ // TODO improve infrastructure to avoid needing this code on each test val libJarClasspath = dotty.tools.dotc.util.ClasspathFromClassloader(this.getClass.getClassLoader).split(java.io.File.pathSeparator).find(x => x.contains("scala3-library-bootstrapped") && x.endsWith(".jar")).get - inspector.inspectTastyFilesInJar(libJarClasspath) + TastyInspector.inspectTastyFilesInJar(libJarClasspath)(inspector) } diff --git a/tests/run-custom-args/tasty-inspector/i8389.scala b/tests/run-custom-args/tasty-inspector/i8389.scala index 10a76388c309..aef85a24d22c 100644 --- a/tests/run-custom-args/tasty-inspector/i8389.scala +++ b/tests/run-custom-args/tasty-inspector/i8389.scala @@ -9,12 +9,13 @@ import scala.tasty.inspector._ val tastyFiles = allTastyFiles.filter(_.contains("TraitParams")) // in dotty-example-project - val inspector = new TastyInspector { - protected def processCompilationUnit(using Quotes)(tree: quotes.reflect.Tree): Unit = { - println(tree.show) + val inspector = new Inspector { + def inspect(using Quotes)(tastys: List[Tasty[quotes.type]]): Unit = { + for tasty <- tastys do + println(tasty.ast.show) } } - inspector.inspectTastyFiles(tastyFiles) + TastyInspector.inspectTastyFiles(tastyFiles)(inspector) } object TraitParams { diff --git a/tests/run-custom-args/tasty-inspector/i8460.scala b/tests/run-custom-args/tasty-inspector/i8460.scala index 4f2d9ba6df44..2ed9b59168bc 100644 --- a/tests/run-custom-args/tasty-inspector/i8460.scala +++ b/tests/run-custom-args/tasty-inspector/i8460.scala @@ -23,23 +23,24 @@ object Test { // Tasty Scala Class val inspect1 = new TestInspector_Children() - inspect1.inspectTastyFiles(allTastyFiles.filter(_.contains("Vehicle"))) + TastyInspector.inspectTastyFiles(allTastyFiles.filter(_.contains("Vehicle")))(inspect1) assert(inspect1.kids == List("Truck","Car","Plane")) // Java Class val inspect2 = new TestInspector_Children() - inspect2.inspectTastyFiles(allTastyFiles.filter(_.contains("Flavor"))) + TastyInspector.inspectTastyFiles(allTastyFiles.filter(_.contains("Flavor")))(inspect2) assert(inspect2.kids == List("Vanilla","Chocolate","Bourbon")) } } -class TestInspector_Children() extends TastyInspector: +class TestInspector_Children() extends Inspector: var kids: List[String] = Nil - protected def processCompilationUnit(using Quotes)(root: quotes.reflect.Tree): Unit = - import quotes.reflect._ - inspectClass(root) + def inspect(using Quotes)(tastys: List[Tasty[quotes.type]]): Unit = { + for tasty <- tastys do + inspectClass(tasty.ast) + } private def inspectClass(using Quotes)(tree: quotes.reflect.Tree): Unit = import quotes.reflect._ diff --git a/tests/run-custom-args/tasty-inspector/i9970.scala b/tests/run-custom-args/tasty-inspector/i9970.scala index fc76ea5e0fb5..afc1cd25352e 100644 --- a/tests/run-custom-args/tasty-inspector/i9970.scala +++ b/tests/run-custom-args/tasty-inspector/i9970.scala @@ -40,50 +40,52 @@ object Test { val allTastyFiles = dotty.tools.io.Path(classpath).walkFilter(_.extension == "tasty").map(_.toString).toList val tastyFiles = allTastyFiles.filter(_.contains("I9970")) - new TestInspector().inspectTastyFiles(tastyFiles) + TastyInspector.inspectTastyFiles(tastyFiles)(new TestInspector()) } } // Inspector that performs the actual tests -class TestInspector() extends TastyInspector: - - private var foundIOApp: Boolean = false - private var foundSimple: Boolean = false - - protected def processCompilationUnit(using Quotes)(root: quotes.reflect.Tree): Unit = - foundIOApp = false - foundSimple = false - inspectClass(root) - // Sanity check to make sure that our pattern matches are not simply glossing over the things we want to test - assert(foundIOApp, "the inspector did not encounter IOApp") - assert(foundSimple, "the inspect did not encounter IOApp.Simple") - - private def inspectClass(using Quotes)(tree: quotes.reflect.Tree): Unit = - import quotes.reflect._ - tree match - case t: PackageClause => - t.stats.foreach(inspectClass(_)) - - case t: ClassDef if t.name.endsWith("$") => - t.body.foreach(inspectClass(_)) - - case t: ClassDef => - t.name match - case "I9970IOApp" => - foundIOApp = true - // Cannot test the following because NoInits is not part of the quotes API - //assert(!t.symbol.flags.is(Flags.NoInits)) - assert(!t.constructor.symbol.flags.is(Flags.StableRealizable)) - - case "Simple" => - foundSimple = true - // Cannot test the following because NoInits is not part of the quotes API - //assert(t.symbol.flags.is(Flags.NoInits)) - assert(t.constructor.symbol.flags.is(Flags.StableRealizable)) - - case _ => - assert(false, s"unexpected ClassDef '${t.name}'") - - case _ => - end inspectClass +class TestInspector() extends Inspector: + + def inspect(using Quotes)(tastys: List[Tasty[quotes.type]]): Unit = + var foundIOApp: Boolean = false + var foundSimple: Boolean = false + + def inspectClass(using Quotes)(tree: quotes.reflect.Tree): Unit = + import quotes.reflect._ + tree match + case t: PackageClause => + t.stats.foreach(inspectClass(_)) + + case t: ClassDef if t.name.endsWith("$") => + t.body.foreach(inspectClass(_)) + + case t: ClassDef => + t.name match + case "I9970IOApp" => + foundIOApp = true + // Cannot test the following because NoInits is not part of the quotes API + //assert(!t.symbol.flags.is(Flags.NoInits)) + assert(!t.constructor.symbol.flags.is(Flags.StableRealizable)) + + case "Simple" => + foundSimple = true + // Cannot test the following because NoInits is not part of the quotes API + //assert(t.symbol.flags.is(Flags.NoInits)) + assert(t.constructor.symbol.flags.is(Flags.StableRealizable)) + + case _ => + assert(false, s"unexpected ClassDef '${t.name}'") + + case _ => + + for tasty <- tastys do + foundIOApp = false + foundSimple = false + inspectClass(tasty.ast) + // Sanity check to make sure that our pattern matches are not simply glossing over the things we want to test + assert(foundIOApp, "the inspector did not encounter IOApp") + assert(foundSimple, "the inspect did not encounter IOApp.Simple") + + end inspect diff --git a/tests/run-custom-args/tasty-inspector/tasty-documentation-inspector/Test.scala b/tests/run-custom-args/tasty-inspector/tasty-documentation-inspector/Test.scala index 019537b19144..0e03bab7107b 100644 --- a/tests/run-custom-args/tasty-inspector/tasty-documentation-inspector/Test.scala +++ b/tests/run-custom-args/tasty-inspector/tasty-documentation-inspector/Test.scala @@ -9,13 +9,14 @@ object Test { val allTastyFiles = dotty.tools.io.Path(classpath).walkFilter(_.extension == "tasty").map(_.toString).toList val tastyFiles = allTastyFiles.filter(_.contains("Foo")) - new DocumentationInspector().inspectTastyFiles(tastyFiles) + TastyInspector.inspectTastyFiles(tastyFiles)(new DocumentationInspector()) } } -class DocumentationInspector extends TastyInspector { +class DocumentationInspector extends Inspector { + + def inspect(using Quotes)(tastys: List[Tasty[quotes.type]]): Unit = { - protected def processCompilationUnit(using Quotes)(root: quotes.reflect.Tree): Unit = { import quotes.reflect._ object Traverser extends TreeTraverser { @@ -31,7 +32,8 @@ class DocumentationInspector extends TastyInspector { } } - Traverser.traverseTree(root) + for tasty <- tastys do + Traverser.traverseTree(tasty.ast) } } diff --git a/tests/run-custom-args/tasty-inspector/tasty-inspector/Test.scala b/tests/run-custom-args/tasty-inspector/tasty-inspector/Test.scala index 017bb7c2f8d5..d6ddec2c9a29 100644 --- a/tests/run-custom-args/tasty-inspector/tasty-inspector/Test.scala +++ b/tests/run-custom-args/tasty-inspector/tasty-inspector/Test.scala @@ -9,13 +9,13 @@ object Test { val allTastyFiles = dotty.tools.io.Path(classpath).walkFilter(_.extension == "tasty").map(_.toString).toList val tastyFiles = allTastyFiles.filter(_.contains("Foo")) - new DBInspector().inspectTastyFiles(tastyFiles) + TastyInspector.inspectTastyFiles(tastyFiles)(new DBInspector()) } } -class DBInspector extends TastyInspector { +class DBInspector extends Inspector { - protected def processCompilationUnit(using Quotes)(root: quotes.reflect.Tree): Unit = { + def inspect(using Quotes)(tastys: List[Tasty[quotes.type]]): Unit = { import quotes.reflect._ object Traverser extends TreeTraverser { @@ -28,7 +28,8 @@ class DBInspector extends TastyInspector { } } - Traverser.traverseTree(root) + for tasty <- tastys do + Traverser.traverseTree(tasty.ast) } } diff --git a/tests/run-custom-args/tasty-inspector/tastyPaths.check b/tests/run-custom-args/tasty-inspector/tastyPaths.check new file mode 100644 index 000000000000..edc6e3ee2b64 --- /dev/null +++ b/tests/run-custom-args/tasty-inspector/tastyPaths.check @@ -0,0 +1,2 @@ +List(tastyPaths/I8163.class) +`reflect.SourceFile.current` cannot be called within the TASTy ispector diff --git a/tests/run-custom-args/tasty-inspector/tastyPaths.scala b/tests/run-custom-args/tasty-inspector/tastyPaths.scala new file mode 100644 index 000000000000..7977c5fc667b --- /dev/null +++ b/tests/run-custom-args/tasty-inspector/tastyPaths.scala @@ -0,0 +1,31 @@ +import scala.quoted._ +import scala.tasty.inspector._ + +opaque type PhoneNumber = String + +case class I8163() { + val phone: PhoneNumber = "555-555-5555".asInstanceOf[PhoneNumber] + val other: String = "not a phone" +} + +object Test { + def main(args: Array[String]): Unit = { + // Artefact of the current test infrastructure + // TODO improve infrastructure to avoid needing this code on each test + val classpath = dotty.tools.dotc.util.ClasspathFromClassloader(this.getClass.getClassLoader).split(java.io.File.pathSeparator).find(_.contains("runWithCompiler")).get + val allTastyFiles = dotty.tools.io.Path(classpath).walkFilter(_.extension == "tasty").map(_.toString).toList + val tastyFiles = allTastyFiles.filter(_.contains("I8163")) + + TastyInspector.inspectTastyFiles(tastyFiles)(new TestInspector()) + } +} + +class TestInspector() extends Inspector: + + def inspect(using Quotes)(tastys: List[Tasty[quotes.type]]): Unit = + println(tastys.map(_.path.split("/tasty-inspector/").last)) + try + quotes.reflect.SourceFile.current + assert(false) + catch case ex: java.lang.UnsupportedOperationException => + println(ex.getMessage) // ok \ No newline at end of file diff --git a/tests/run-custom-args/tasty-interpreter/Test.scala b/tests/run-custom-args/tasty-interpreter/Test.scala index 0b5b0a125ecf..e9f9ad5cb7ca 100644 --- a/tests/run-custom-args/tasty-interpreter/Test.scala +++ b/tests/run-custom-args/tasty-interpreter/Test.scala @@ -9,6 +9,7 @@ import dotty.tools.io.Path import scala.io.Source import scala.util.Using import scala.tasty.interpreter.TastyInterpreter +import scala.tasty.inspector.TastyInspector object Test { @@ -18,7 +19,7 @@ object Test { val classpath = dotty.tools.dotc.util.ClasspathFromClassloader(this.getClass.getClassLoader).split(java.io.File.pathSeparator).find(_.contains("runWithCompiler")).get val allTastyFiles = dotty.tools.io.Path(classpath).walkFilter(_.extension == "tasty").map(_.toString).toList - val actualOutput = interpret("")(allTastyFiles.filter(x => x.contains("IntepretedMain") || x.contains("InterpretedBar"))) + val actualOutput = interpret(allTastyFiles.filter(x => x.contains("IntepretedMain") || x.contains("InterpretedBar"))) val expectedOutput = """42 | @@ -97,7 +98,7 @@ object Test { // TODO improve infrastructure to avoid needing this code on each test val allTastyFiles = dotty.tools.io.Path(out).walkFilter(_.extension == "tasty").map(_.toString).toList - val actualOutput = interpret(out.toString)(allTastyFiles.filter(_.endsWith("Test.tasty"))) + val actualOutput = interpret(allTastyFiles.filter(_.endsWith("Test.tasty"))) val checkFile = java.nio.file.Paths.get("tests/run/" + testFileName.stripSuffix(".scala") + ".check") if (java.nio.file.Files.exists(checkFile)) { @@ -111,10 +112,10 @@ object Test { } } - def interpret(classpath: String*)(interpretedClasses: List[String]): String = { + def interpret(interpretedClasses: List[String]): String = { val ps = new ByteArrayOutputStream() try scala.Console.withOut(ps) { - new TastyInterpreter().inspectAllTastyFiles(interpretedClasses.toList, Nil, classpath.toList) + TastyInspector.inspectTastyFiles(interpretedClasses.toList)(new TastyInterpreter) } catch { case e: Throwable => throw new Exception(ps.toString, e) } diff --git a/tests/run-custom-args/tasty-interpreter/interpreter/TastyInterpreter.scala b/tests/run-custom-args/tasty-interpreter/interpreter/TastyInterpreter.scala index 73f71a0f7dd4..a5ed8048de3a 100644 --- a/tests/run-custom-args/tasty-interpreter/interpreter/TastyInterpreter.scala +++ b/tests/run-custom-args/tasty-interpreter/interpreter/TastyInterpreter.scala @@ -1,14 +1,14 @@ package scala.tasty.interpreter import scala.quoted._ -import scala.tasty.inspector.TastyInspector +import scala.tasty.inspector._ -class TastyInterpreter extends TastyInspector { +class TastyInterpreter extends Inspector { - protected def processCompilationUnit(using Quotes)(root: quotes.reflect.Tree): Unit = { + def inspect(using Quotes)(tastys: List[Tasty[quotes.type]]): Unit = { import quotes.reflect._ - object Traverser extends TreeTraverser { + object Traverser extends TreeTraverser { override def traverseTree(tree: Tree)(owner: Symbol): Unit = tree match { // TODO: check the correct sig and object enclosement for main case DefDef("main", _, _, Some(rhs)) => @@ -20,6 +20,9 @@ class TastyInterpreter extends TastyInspector { super.traverseTree(tree)(owner) } } - Traverser.traverseTree(root)(Symbol.spliceOwner) + + for tasty <- tastys do + Traverser.traverseTree(tasty.ast)(Symbol.spliceOwner) } + }