From 06d988a5bf9457f3095e0147ecbd537e3bc88fb7 Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Thu, 22 Oct 2020 14:28:21 +0200 Subject: [PATCH] Make tasty inspector receive tasty files directly We add ways to load a list of tasty files and all tasty files in a jar. This change is aligned with the changes in progress on the `-from-tasty` compilation. We remove the old way to load tasty file by passing the class name. Loading by name had some unintended consecuences that made this API fragile. - Loaded Scala 2 classes by mistake (no tasty) - Loaded java classes by mistake (no tasty) - Load the wrong version of the tasty file because the classpath contains an older/newer version of that class (different tasty) --- .../test/BootstrappedStdLibTASYyTest.scala | 2 +- .../tasty/inspector/TastyInspector.scala | 39 +++++++++++++++--- .../tasty-inspector/i8163.scala | 8 +++- .../tasty-inspector/i8215.scala | 41 ------------------- .../tasty-inspector/i8364.scala | 9 +++- .../tasty-inspector/i8389.scala | 8 +++- .../tasty-inspector/i8460.scala | 9 +++- .../tasty-inspector/i8558.scala | 22 ---------- .../tasty-documentation-inspector/Test.scala | 8 +++- .../tasty-inspector/Test.scala | 8 +++- .../tasty-interpreter/Test.scala | 17 ++++++-- 11 files changed, 89 insertions(+), 82 deletions(-) delete mode 100644 tests/run-custom-args/tasty-inspector/i8215.scala delete mode 100644 tests/run-custom-args/tasty-inspector/i8558.scala diff --git a/stdlib-bootstrapped-tasty-tests/test/BootstrappedStdLibTASYyTest.scala b/stdlib-bootstrapped-tasty-tests/test/BootstrappedStdLibTASYyTest.scala index 93b6e97f41bb..4df5909036a5 100644 --- a/stdlib-bootstrapped-tasty-tests/test/BootstrappedStdLibTASYyTest.scala +++ b/stdlib-bootstrapped-tasty-tests/test/BootstrappedStdLibTASYyTest.scala @@ -108,7 +108,7 @@ object BootstrappedStdLibTASYyTest: () } val classNames = scalaLibJarTastyClassNames.filterNot(blacklisted) - val hasErrors = inspector.inspect(scalaLibJarPath, classNames) + val hasErrors = inspector.inspectTastyFilesInJar(scalaLibJarPath) assert(!hasErrors, "Errors reported while loading from TASTy") def compileFromTasty(blacklisted: String => Boolean): Unit = { diff --git a/tasty-inspector/src/scala/tasty/inspector/TastyInspector.scala b/tasty-inspector/src/scala/tasty/inspector/TastyInspector.scala index 9d336f4fbff5..f8cfe8a9f1c7 100644 --- a/tasty-inspector/src/scala/tasty/inspector/TastyInspector.scala +++ b/tasty-inspector/src/scala/tasty/inspector/TastyInspector.scala @@ -22,11 +22,37 @@ trait TastyInspector: /** Load and process TASTy files using TASTy reflect * - * @param classpath Classpath where the classes are located - * @param classes classes to be inspected - * @return if an error was reported + * @param tastyFiles List of paths of `.tasty` files */ - def inspect(classpath: String, classes: List[String]): Boolean = + 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) + + private def inspectFiles(classpath: List[String], classes: List[String]): Boolean = if (classes.isEmpty) throw new IllegalArgumentException("Parameter classes should no be empty") @@ -64,11 +90,12 @@ trait TastyInspector: end TastyInspectorPhase val currentClasspath = ClasspathFromClassloader(getClass.getClassLoader) - val args = "-from-tasty" :: "-Yretain-trees" :: "-classpath" :: s"$classpath$pathSeparator$currentClasspath" :: classes + val fullClasspath = (classpath :+ currentClasspath).mkString(pathSeparator) + val args = "-from-tasty" :: "-Yretain-trees" :: "-classpath" :: fullClasspath :: classes val reporter = (new InspectorDriver).process(args.toArray) reporter.hasErrors - end inspect + end inspectFiles end TastyInspector diff --git a/tests/run-custom-args/tasty-inspector/i8163.scala b/tests/run-custom-args/tasty-inspector/i8163.scala index 34bedacdd630..111907cb7895 100644 --- a/tests/run-custom-args/tasty-inspector/i8163.scala +++ b/tests/run-custom-args/tasty-inspector/i8163.scala @@ -10,7 +10,13 @@ case class I8163() { object Test { def main(args: Array[String]): Unit = { - new TestInspector().inspect("", List("I8163")) + // 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")) + + new TestInspector().inspectTastyFiles(tastyFiles) } } diff --git a/tests/run-custom-args/tasty-inspector/i8215.scala b/tests/run-custom-args/tasty-inspector/i8215.scala deleted file mode 100644 index 0aa6c3823924..000000000000 --- a/tests/run-custom-args/tasty-inspector/i8215.scala +++ /dev/null @@ -1,41 +0,0 @@ -import scala.quoted._ -import scala.tasty.inspector._ - -case class I8215(id: String) - -object Test { - def main(args: Array[String]): Unit = { - - // Tasty Scala Class - val inspect1 = new TestInspector_NonTasty() - inspect1.inspect("", List("I8215")) - assert(inspect1.isJava == false) - assert(inspect1.isScala2 == false) - assert(inspect1.className == "") - - // Java Class - val inspect2 = new TestInspector_NonTasty() - inspect2.inspect("", List("java.util.UUID")) - assert(inspect2.isJava == true) - assert(inspect2.isScala2 == false) - assert(inspect2.className == "java.util.UUID") - - // Legacy non-Tasty Scala class - val inspect3 = new TestInspector_NonTasty() - inspect3.inspect("", List("scala.collection.immutable.RedBlackTree")) - assert(inspect3.isJava == false) - assert(inspect3.isScala2 == true) - assert(inspect3.className == "scala.collection.immutable.RedBlackTree") - } -} - -class TestInspector_NonTasty() extends TastyInspector: - - var isJava: Boolean = false - var isScala2: Boolean = false - var className: String = "" - - protected def processCompilationUnit(using QuoteContext)(root: qctx.reflect.Tree): Unit = - isJava = qctx.reflect.Source.isJavaCompilationUnit - isScala2 = qctx.reflect.Source.isScala2CompilationUnit - className = qctx.reflect.Source.compilationUnitClassname diff --git a/tests/run-custom-args/tasty-inspector/i8364.scala b/tests/run-custom-args/tasty-inspector/i8364.scala index ea1a951f8968..041b7f420c27 100644 --- a/tests/run-custom-args/tasty-inspector/i8364.scala +++ b/tests/run-custom-args/tasty-inspector/i8364.scala @@ -4,8 +4,13 @@ import scala.tasty.inspector._ @main def Test = { val inspector = new TastyInspector { protected def processCompilationUnit(using QuoteContext)(tree: qctx.reflect.Tree): Unit = { - println(tree.show) + tree.showExtractors // Make sure that tree is loaded and can be traveresed } } - inspector.inspect("", List("scala.tasty.Reflection")) + + // Artefact of the current test infrastructure + // 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) } diff --git a/tests/run-custom-args/tasty-inspector/i8389.scala b/tests/run-custom-args/tasty-inspector/i8389.scala index 69a2f4e79b9c..4fb2df69a1e7 100644 --- a/tests/run-custom-args/tasty-inspector/i8389.scala +++ b/tests/run-custom-args/tasty-inspector/i8389.scala @@ -2,13 +2,19 @@ import scala.quoted._ import scala.tasty.inspector._ @main def Test = { + // 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("TraitParams")) + // in dotty-example-project val inspector = new TastyInspector { protected def processCompilationUnit(using QuoteContext)(tree: qctx.reflect.Tree): Unit = { println(tree.show) } } - inspector.inspect("", List("TraitParams")) + inspector.inspectTastyFiles(tastyFiles) } object TraitParams { diff --git a/tests/run-custom-args/tasty-inspector/i8460.scala b/tests/run-custom-args/tasty-inspector/i8460.scala index 5af6649189f8..5324aa9601b0 100644 --- a/tests/run-custom-args/tasty-inspector/i8460.scala +++ b/tests/run-custom-args/tasty-inspector/i8460.scala @@ -15,15 +15,20 @@ case object Bourbon extends Flavor 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("TraitParams")) // Tasty Scala Class val inspect1 = new TestInspector_Children() - inspect1.inspect("", List("Vehicle")) + inspect1.inspectTastyFiles(allTastyFiles.filter(_.contains("Vehicle"))) assert(inspect1.kids == List("Truck","Car","Plane")) // Java Class val inspect2 = new TestInspector_Children() - inspect2.inspect("", List("Flavor")) + inspect2.inspectTastyFiles(allTastyFiles.filter(_.contains("Flavor"))) assert(inspect2.kids == List("Vanilla","Chocolate","Bourbon")) } } diff --git a/tests/run-custom-args/tasty-inspector/i8558.scala b/tests/run-custom-args/tasty-inspector/i8558.scala deleted file mode 100644 index 820f3106bc09..000000000000 --- a/tests/run-custom-args/tasty-inspector/i8558.scala +++ /dev/null @@ -1,22 +0,0 @@ -import scala.quoted._ -import scala.tasty.inspector._ - -object Test { - def main(args: Array[String]): Unit = { - - // Tasty Scala Class - val inspector = new TestInspector_NonTasty() - inspector.inspect("", List("scala.Option")) - assert(inspector.isAlreadyLoaded) - assert(inspector.className == "scala.Option") - } -} - -class TestInspector_NonTasty() extends TastyInspector: - - var isAlreadyLoaded: Boolean = false - var className: String = "" - - protected def processCompilationUnit(using QuoteContext)(root: qctx.reflect.Tree): Unit = - isAlreadyLoaded = qctx.reflect.Source.isAlreadyLoadedCompilationUnit - className = qctx.reflect.Source.compilationUnitClassname 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 724c850440ee..8074e11480c3 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 @@ -3,7 +3,13 @@ import scala.tasty.inspector._ object Test { def main(args: Array[String]): Unit = { - new DocumentationInspector().inspect("", List("Foo")) + // 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("Foo")) + + new DocumentationInspector().inspectTastyFiles(tastyFiles) } } 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 a236b7a9f72f..b3c608b9241d 100644 --- a/tests/run-custom-args/tasty-inspector/tasty-inspector/Test.scala +++ b/tests/run-custom-args/tasty-inspector/tasty-inspector/Test.scala @@ -3,7 +3,13 @@ import scala.tasty.inspector._ object Test { def main(args: Array[String]): Unit = { - new DBInspector().inspect("", List("Foo")) + // 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("Foo")) + + new DBInspector().inspectTastyFiles(tastyFiles) } } diff --git a/tests/run-custom-args/tasty-interpreter/Test.scala b/tests/run-custom-args/tasty-interpreter/Test.scala index dbbd1b461855..0b5b0a125ecf 100644 --- a/tests/run-custom-args/tasty-interpreter/Test.scala +++ b/tests/run-custom-args/tasty-interpreter/Test.scala @@ -11,9 +11,14 @@ import scala.util.Using import scala.tasty.interpreter.TastyInterpreter 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 actualOutput = interpret("")("IntepretedMain", "InterpretedBar") + val actualOutput = interpret("")(allTastyFiles.filter(x => x.contains("IntepretedMain") || x.contains("InterpretedBar"))) val expectedOutput = """42 | @@ -88,7 +93,11 @@ object Test { val filePath = "tests" + File.separator + "run" + File.separator + testFileName dotty.tools.dotc.Main.process(Array("-classpath", System.getProperty("java.class.path"), "-d", out.toString, filePath), reproter) - val actualOutput = interpret(out.toString)("Test") + // Artefact of the current test infrastructure + // 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 checkFile = java.nio.file.Paths.get("tests/run/" + testFileName.stripSuffix(".scala") + ".check") if (java.nio.file.Files.exists(checkFile)) { @@ -102,10 +111,10 @@ object Test { } } - def interpret(classpath: String*)(interpretedClasses: String*): String = { + def interpret(classpath: String*)(interpretedClasses: List[String]): String = { val ps = new ByteArrayOutputStream() try scala.Console.withOut(ps) { - new TastyInterpreter().inspect(classpath.mkString(java.io.File.pathSeparatorChar.toString), interpretedClasses.toList) + new TastyInterpreter().inspectAllTastyFiles(interpretedClasses.toList, Nil, classpath.toList) } catch { case e: Throwable => throw new Exception(ps.toString, e) }