diff --git a/.drone.yml b/.drone.yml index 56f49929a59b..5218a7957bd3 100644 --- a/.drone.yml +++ b/.drone.yml @@ -40,7 +40,7 @@ steps: depends_on: [ clone ] commands: - cp -R . /tmp/2/ && cd /tmp/2/ - - ./project/scripts/sbt ";dotty-bootstrapped/compile ;dotty-bootstrapped/test ;dotty-staging/test ;sjsSandbox/run;sjsSandbox/test;sjsJUnitTests/test ;configureIDE" + - ./project/scripts/sbt ";dotty-bootstrapped/compile ;dotty-bootstrapped/test;sjsSandbox/run;sjsSandbox/test;sjsJUnitTests/test ;configureIDE" - ./project/scripts/bootstrapCmdTests - name: community_build diff --git a/build.sbt b/build.sbt index 54f8fe6bd63a..067eee0b0969 100644 --- a/build.sbt +++ b/build.sbt @@ -11,6 +11,7 @@ val `dotty-library-bootstrappedJS` = Build.`dotty-library-bootstrappedJS` val `dotty-sbt-bridge` = Build.`dotty-sbt-bridge` val `dotty-sbt-bridge-tests` = Build.`dotty-sbt-bridge-tests` val `dotty-staging` = Build.`dotty-staging` +val `dotty-tasty-inspector` = Build.`dotty-tasty-inspector` val `dotty-language-server` = Build.`dotty-language-server` val `dotty-bench` = Build.`dotty-bench` val `dotty-bench-bootstrapped` = Build.`dotty-bench-bootstrapped` diff --git a/compiler/src/dotty/tools/dotc/consumetasty/ConsumeTasty.scala b/compiler/src/dotty/tools/dotc/consumetasty/ConsumeTasty.scala deleted file mode 100644 index 59f3b9464ed0..000000000000 --- a/compiler/src/dotty/tools/dotc/consumetasty/ConsumeTasty.scala +++ /dev/null @@ -1,55 +0,0 @@ -package dotty.tools.dotc.consumetasty - -import java.net.URLClassLoader - -import dotty.tools.dotc -import dotty.tools.dotc.core.Contexts._ - -import java.nio.file.Paths -import java.io.File -import java.io.File.{ pathSeparator => sep } -import scala.annotation.tailrec - -import scala.tasty.file.TastyConsumer - -object ConsumeTasty { - - def apply(classpath: String, classes: List[String], tastyConsumer: TastyConsumer): Unit = { - if (classes.isEmpty) - throw new IllegalArgumentException("Parameter classes should no be empty") - - class Consume extends dotc.Driver { - override protected def newCompiler(implicit ctx: Context): dotc.Compiler = - new TastyFromClass(tastyConsumer) - } - - val currentClasspath = classpathFromClassloader(getClass.getClassLoader) - val args = "-from-tasty" :: "-Yretain-trees" :: "-classpath" :: s"$classpath$sep$currentClasspath" :: classes - (new Consume).process(args.toArray) - } - - /** Attempt to recreate a classpath from a classloader. - * - * BEWARE: with exotic enough classloaders, this may not work at all or do - * the wrong thing. - */ - private def classpathFromClassloader(cl: ClassLoader): String = { - @tailrec - def loop(cl: ClassLoader, suffixClasspath: String): String = - cl match { - case cl: URLClassLoader => - val updatedClasspath = cl.getURLs - .map(url => Paths.get(url.toURI).toAbsolutePath.toString) - .mkString( - "", - File.pathSeparator, - if (suffixClasspath.isEmpty) "" else File.pathSeparator + suffixClasspath - ) - loop(cl.getParent, updatedClasspath) - case _ => - suffixClasspath - } - - loop(cl, "") - } -} \ No newline at end of file diff --git a/compiler/src/dotty/tools/dotc/consumetasty/TastyConsumerPhase.scala b/compiler/src/dotty/tools/dotc/consumetasty/TastyConsumerPhase.scala deleted file mode 100644 index 938043ffd7ab..000000000000 --- a/compiler/src/dotty/tools/dotc/consumetasty/TastyConsumerPhase.scala +++ /dev/null @@ -1,18 +0,0 @@ -package dotty.tools.dotc.consumetasty - -import dotty.tools.dotc.core.Contexts._ -import dotty.tools.dotc.core.Phases.Phase -import dotty.tools.dotc.tastyreflect.ReflectionImpl - -import scala.tasty.file.TastyConsumer - -class TastyConsumerPhase(consumer: TastyConsumer) extends Phase { - - override def phaseName: String = "tastyConsumer" - - override def run(implicit ctx: Context): Unit = { - val reflect = ReflectionImpl(ctx) - consumer(reflect)(ctx.compilationUnit.tpdTree.asInstanceOf[reflect.Tree]) - } -} - diff --git a/compiler/src/dotty/tools/dotc/consumetasty/TastyFromClass.scala b/compiler/src/dotty/tools/dotc/consumetasty/TastyFromClass.scala deleted file mode 100644 index afc811e54cc1..000000000000 --- a/compiler/src/dotty/tools/dotc/consumetasty/TastyFromClass.scala +++ /dev/null @@ -1,28 +0,0 @@ -package dotty.tools.dotc.consumetasty - -import dotty.tools.dotc.Run -import dotty.tools.dotc.core.Mode -import dotty.tools.dotc.core.Contexts.Context -import dotty.tools.dotc.core.Phases.Phase -import dotty.tools.dotc.fromtasty._ - -import scala.tasty.file.TastyConsumer - -class TastyFromClass(consumer: TastyConsumer) 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 TastyConsumerPhase(consumer)) :: // Print all loaded classes - Nil - - override def newRun(implicit ctx: Context): Run = { - reset() - new TASTYRun(this, ctx.fresh.addMode(Mode.ReadPositions).addMode(Mode.ReadComments)) - } -} diff --git a/compiler/src/dotty/tools/dotc/util/ClasspathFromClassloader.scala b/compiler/src/dotty/tools/dotc/util/ClasspathFromClassloader.scala new file mode 100644 index 000000000000..de7edd804d85 --- /dev/null +++ b/compiler/src/dotty/tools/dotc/util/ClasspathFromClassloader.scala @@ -0,0 +1,47 @@ +package dotty.tools.dotc.util + +import java.net.URLClassLoader +import java.nio.file.Paths + +import dotty.tools.repl.AbstractFileClassLoader + +object ClasspathFromClassloader { + + /** Attempt to recreate a classpath from a classloader. + * + * BEWARE: with exotic enough classloaders, this may not work at all or do + * the wrong thing. + */ + def apply(cl: ClassLoader): String = { + val classpathBuff = List.newBuilder[String] + def collectClassLoaderPaths(cl: ClassLoader): Unit = { + if (cl != null) { + cl match { + case cl: URLClassLoader => + // This is wrong if we're in a subclass of URLClassLoader + // that filters loading classes from its parent ¯\_(ツ)_/¯ + collectClassLoaderPaths(cl.getParent) + // Parent classloaders are searched before their child, so the part of + // the classpath coming from the child is added at the _end_ of the + // classpath. + classpathBuff ++= + cl.getURLs.iterator.map(url => Paths.get(url.toURI).toAbsolutePath.toString) + case _ => + // HACK: We can't just collect the classpath from arbitrary parent + // classloaders since the current classloader might intentionally + // filter loading classes from its parent (for example + // BootFilteredLoader in the sbt launcher does this and we really + // don't want to include the scala-library that sbt depends on + // here), but we do need to look at the parent of the REPL + // classloader, so we special case it. We can't do this using a type + // test since the REPL classloader class itself is normally loaded + // with a different classloader. + if (cl.getClass.getName == classOf[AbstractFileClassLoader].getName) + collectClassLoaderPaths(cl.getParent) + } + } + } + collectClassLoaderPaths(cl) + classpathBuff.result().mkString(java.io.File.pathSeparator) + } +} diff --git a/compiler/test/dotty/Properties.scala b/compiler/test/dotty/Properties.scala index ed377859a047..cf9a92d58832 100644 --- a/compiler/test/dotty/Properties.scala +++ b/compiler/test/dotty/Properties.scala @@ -53,6 +53,9 @@ object Properties { /** dotty-staging jar */ def dottyStaging: String = sys.props("dotty.tests.classes.dottyStaging") + /** dotty-tasty-inspector jar */ + def dottyTastyInspector: String = sys.props("dotty.tests.classes.dottyTastyInspector") + /** tasty-core jar */ def tastyCore: String = sys.props("dotty.tests.classes.tastyCore") diff --git a/compiler/test/dotty/tools/dotc/BootstrappedOnlyCompilationTests.scala b/compiler/test/dotty/tools/dotc/BootstrappedOnlyCompilationTests.scala index 104783d9c867..6f8d4c162b88 100644 --- a/compiler/test/dotty/tools/dotc/BootstrappedOnlyCompilationTests.scala +++ b/compiler/test/dotty/tools/dotc/BootstrappedOnlyCompilationTests.scala @@ -127,7 +127,8 @@ class BootstrappedOnlyCompilationTests extends ParallelTesting { aggregateTests( compileFilesInDir("tests/run-with-compiler", withCompilerOptions), compileFilesInDir("tests/run-staging", withStagingOptions), - compileDir("tests/run-with-compiler-custom-args/tasty-interpreter", withCompilerOptions) + compileFilesInDir("tests/run-custom-args/tasty-inspector", withTastyInspectorOptions), + compileDir("tests/run-custom-args/tasty-interpreter", withTastyInspectorOptions), ).checkRuns() } diff --git a/compiler/test/dotty/tools/vulpix/TestConfiguration.scala b/compiler/test/dotty/tools/vulpix/TestConfiguration.scala index fdcd4f83f44f..cdea285eceb0 100644 --- a/compiler/test/dotty/tools/vulpix/TestConfiguration.scala +++ b/compiler/test/dotty/tools/vulpix/TestConfiguration.scala @@ -39,6 +39,9 @@ object TestConfiguration { lazy val withStagingClasspath = withCompilerClasspath + File.pathSeparator + mkClasspath(List(Properties.dottyStaging)) + lazy val withTastyInspectorClasspath = + withCompilerClasspath + File.pathSeparator + mkClasspath(List(Properties.dottyTastyInspector)) + def mkClasspath(classpaths: List[String]): String = classpaths.map({ p => val file = new java.io.File(p) @@ -54,6 +57,8 @@ object TestConfiguration { defaultOptions.withClasspath(withCompilerClasspath).withRunClasspath(withCompilerClasspath) lazy val withStagingOptions = defaultOptions.withClasspath(withStagingClasspath).withRunClasspath(withStagingClasspath) + lazy val withTastyInspectorOptions = + defaultOptions.withClasspath(withTastyInspectorClasspath).withRunClasspath(withTastyInspectorClasspath) val allowDeepSubtypes = defaultOptions without "-Yno-deep-subtypes" val allowDoubleBindings = defaultOptions without "-Yno-double-bindings" val picklingOptions = defaultOptions and ( diff --git a/dist/bin/common b/dist/bin/common index 0c7822f6c953..13097da97419 100755 --- a/dist/bin/common +++ b/dist/bin/common @@ -108,6 +108,7 @@ DOTTY_COMP=$(find_lib "*dotty-compiler*") DOTTY_INTF=$(find_lib "*dotty-interfaces*") DOTTY_LIB=$(find_lib "*dotty-library*") DOTTY_STAGING=$(find_lib "*dotty-staging*") +DOTTY_CONSUME_TASTY=$(find_lib "*dotty-consume-tasty*") TASTY_CORE=$(find_lib "*tasty-core*") SCALA_ASM=$(find_lib "*scala-asm*") SCALA_LIB=$(find_lib "*scala-library*") diff --git a/dist/bin/dotr b/dist/bin/dotr index 029a0aee97af..78f18a910dfa 100755 --- a/dist/bin/dotr +++ b/dist/bin/dotr @@ -102,7 +102,7 @@ elif [ $execute_repl == true ] || [ ${#residual_args[@]} -ne 0 ]; then echo "warning: multiple classpaths are found, dotr only use the last one." fi if [ $with_compiler == true ]; then - cp_arg+="$PSEP$DOTTY_COMP$PSEP$TASTY_CORE$PSEP$DOTTY_INTF$PSEP$SCALA_ASM$PSEP$DOTTY_STAGING" + cp_arg+="$PSEP$DOTTY_COMP$PSEP$TASTY_CORE$PSEP$DOTTY_INTF$PSEP$SCALA_ASM$PSEP$DOTTY_STAGING$PSEP$DOTTY_CONSUME_TASTY" fi eval exec "\"$JAVACMD\"" "$DEBUG" "-classpath \"$cp_arg\"" "${jvm_options[@]}" "${residual_args[@]}" else diff --git a/docs/docs/reference/metaprogramming/tasty-inspect.md b/docs/docs/reference/metaprogramming/tasty-inspect.md index ca6503f3525a..01e988470d72 100644 --- a/docs/docs/reference/metaprogramming/tasty-inspect.md +++ b/docs/docs/reference/metaprogramming/tasty-inspect.md @@ -3,10 +3,14 @@ layout: doc-page title: "TASTy Inspection" --- +```scala +libraryDependencies += "ch.epfl.lamp" %% "dotty-tasty-inspector" % scalaVersion.value +``` + 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 of the code. To avoid the hassle of working directly with the TASTy -file we provide the `TastyConsumer` which loads the contents and exposes it +file we provide the `TastyInspector` which loads the contents and exposes it through the TASTy reflect API. @@ -16,7 +20,10 @@ To inspect the TASTy Reflect trees of a TASTy file a consumer can be defined in the following way. ```scala -class Consumer extends TastyConsumer { +import scala.tasty.Reflection +import scala.tasty.file._ + +class Consumer extends TastyInspector { final def apply(reflect: Reflection)(root: reflect.Tree): Unit = { import reflect._ // Do something with the tree @@ -30,7 +37,15 @@ the class `foo.Bar` for a foo in the classpath. ```scala object Test { def main(args: Array[String]): Unit = { - ConsumeTasty("", List("foo.Bar"), new Consumer) + InspectTasty("", List("foo.Bar"), new Consumer) } } -``` \ No newline at end of file +``` + +Note that if we need to run the main (in an object called `Test`) after +compilation we need make available the compiler to the runtime: + +```shell +dotc -with-compiler -d out Test.scala +dotr -with-compiler -classpath out Test +``` diff --git a/library/src/scala/tasty/file/ConsumeTasty.scala b/library/src/scala/tasty/file/ConsumeTasty.scala deleted file mode 100644 index 5a2892242d99..000000000000 --- a/library/src/scala/tasty/file/ConsumeTasty.scala +++ /dev/null @@ -1,26 +0,0 @@ -package scala.tasty.file - -object ConsumeTasty { - - /** Load and process TASTy files using TASTy reflect - * - * @param classpath Classpath where the classes are located - * @param classes classes to be consumed - * @param tastyConsumer consumer that will process the tasty trees - */ - def apply(classpath: String, classes: List[String], tastyConsumer: TastyConsumer): Unit = { - val cl = getClass.getClassLoader - try { - val dottyConsumeTastyCls = cl.loadClass("dotty.tools.dotc.consumetasty.ConsumeTasty") - val makeMeth = dottyConsumeTastyCls.getMethod("apply", classOf[String], classOf[List[_]], classOf[TastyConsumer]) - makeMeth.invoke(null, classpath, classes, tastyConsumer) - } - catch { - case ex: ClassNotFoundException => - throw new Exception( - s"""Could not load the dotty.tools.dotc.consumetasty.ConsumeTasty class `${ex.getMessage}` from the JVM classpath. Make sure that the compiler is on the JVM classpath.""", - ex - ) - } - } -} diff --git a/library/src/scala/tasty/file/TastyConsumer.scala b/library/src/scala/tasty/file/TastyConsumer.scala deleted file mode 100644 index 50ab240436f2..000000000000 --- a/library/src/scala/tasty/file/TastyConsumer.scala +++ /dev/null @@ -1,7 +0,0 @@ -package scala.tasty.file - -import scala.tasty.Reflection - -trait TastyConsumer { - def apply(reflect: Reflection)(root: reflect.Tree): Unit -} diff --git a/project/Build.scala b/project/Build.scala index e972d58f0ef7..9257e880aaae 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -595,9 +595,10 @@ object Build { val asm = findArtifactPath(externalDeps, "scala-asm") val dottyCompiler = jars("dotty-compiler") val dottyStaging = jars("dotty-staging") + val dottyTastyInspector = jars("dotty-tasty-inspector") val dottyInterfaces = jars("dotty-interfaces") val tastyCore = jars("tasty-core") - run(insertClasspathInArgs(args1, List(dottyCompiler, dottyInterfaces, asm, dottyStaging, tastyCore).mkString(File.pathSeparator))) + run(insertClasspathInArgs(args1, List(dottyCompiler, dottyInterfaces, asm, dottyStaging, dottyTastyInspector, tastyCore).mkString(File.pathSeparator))) } else run(args) }, @@ -671,9 +672,10 @@ object Build { } val dottyInterfaces = jars("dotty-interfaces") val dottyStaging = jars("dotty-staging") + val dottyTastyInspector = jars("dotty-tasty-inspector") val tastyCore = jars("tasty-core") val asm = findArtifactPath(externalDeps, "scala-asm") - extraClasspath ++= Seq(dottyCompiler, dottyInterfaces, asm, dottyStaging, tastyCore) + extraClasspath ++= Seq(dottyCompiler, dottyInterfaces, asm, dottyStaging, dottyTastyInspector, tastyCore) } val fullArgs = main :: insertClasspathInArgs(args, extraClasspath.mkString(File.pathSeparator)) @@ -715,14 +717,18 @@ object Build { ) lazy val bootstrapedDottyCompilerSettings = commonDottyCompilerSettings ++ Seq( - javaOptions += { + javaOptions ++= { val jars = packageAll.value - "-Ddotty.tests.classes.dottyStaging=" + jars("dotty-staging") + Seq( + "-Ddotty.tests.classes.dottyStaging=" + jars("dotty-staging"), + "-Ddotty.tests.classes.dottyTastyInspector=" + jars("dotty-tasty-inspector"), + ) }, packageAll := { packageAll.in(`dotty-compiler`).value ++ Seq( "dotty-compiler" -> packageBin.in(Compile).value.getAbsolutePath, "dotty-staging" -> packageBin.in(LocalProject("dotty-staging"), Compile).value.getAbsolutePath, + "dotty-tasty-inspector" -> packageBin.in(LocalProject("dotty-tasty-inspector"), Compile).value.getAbsolutePath, "tasty-core" -> packageBin.in(LocalProject("tasty-core-bootstrapped"), Compile).value.getAbsolutePath, ) } @@ -795,9 +801,18 @@ object Build { // We want the compiler to be present in the compiler classpath when compiling this project but not // when compiling a project that depends on dotty-staging (see sbt-dotty/sbt-test/sbt-dotty/quoted-example-project), // but we always need it to be present on the JVM classpath at runtime. - dependsOn(dottyCompiler(Bootstrapped) % "provided"). - dependsOn(dottyCompiler(Bootstrapped) % "compile->runtime"). - dependsOn(dottyCompiler(Bootstrapped) % "test->test"). + dependsOn(dottyCompiler(Bootstrapped) % "provided; compile->runtime; test->test"). + settings(commonBootstrappedSettings). + settings( + javaOptions := (javaOptions in `dotty-compiler-bootstrapped`).value + ) + + lazy val `dotty-tasty-inspector` = project.in(file("tasty-inspector")). + withCommonSettings(Bootstrapped). + // We want the compiler to be present in the compiler classpath when compiling this project but not + // when compiling a project that depends on dotty-tasty-inspector (see sbt-dotty/sbt-test/sbt-dotty/tasty-inspector-example-project), + // but we always need it to be present on the JVM classpath at runtime. + dependsOn(dottyCompiler(Bootstrapped) % "provided; compile->runtime; test->test"). settings(commonBootstrappedSettings). settings( javaOptions := (javaOptions in `dotty-compiler-bootstrapped`).value @@ -1106,6 +1121,7 @@ object Build { publishLocal in `dotty-library-bootstrapped`, publishLocal in `tasty-core-bootstrapped`, publishLocal in `dotty-staging`, + publishLocal in `dotty-tasty-inspector`, publishLocal in `scala-library`, publishLocal in `scala-reflect`, publishLocal in `dotty-doc-bootstrapped`, @@ -1315,7 +1331,8 @@ object Build { // FIXME: we do not aggregate `bin` because its tests delete jars, thus breaking other tests def asDottyRoot(implicit mode: Mode): Project = project.withCommonSettings. aggregate(`dotty-interfaces`, dottyLibrary, dottyCompiler, tastyCore, dottyDoc, `dotty-sbt-bridge`). - bootstrappedAggregate(`scala-library`, `scala-compiler`, `scala-reflect`, scalap, `dotty-language-server`, `dotty-staging`). + bootstrappedAggregate(`scala-library`, `scala-compiler`, `scala-reflect`, scalap, + `dotty-language-server`, `dotty-staging`, `dotty-tasty-inspector`, `dotty-tastydoc`). dependsOn(tastyCore). dependsOn(dottyCompiler). dependsOn(dottyLibrary). @@ -1354,6 +1371,7 @@ object Build { def asDottyTastydoc(implicit mode: Mode): Project = project.withCommonSettings. aggregate(`dotty-tastydoc-input`). dependsOn(dottyCompiler). + dependsOn(`dotty-tasty-inspector`). settings(commonDocSettings) def asDottyTastydocInput(implicit mode: Mode): Project = project.withCommonSettings. diff --git a/sbt-dotty/sbt-test/sbt-dotty/tasty-consumer-example-project/app/Main.scala b/sbt-dotty/sbt-test/sbt-dotty/tasty-consumer-example-project/app/Main.scala new file mode 100644 index 000000000000..be465eefd946 --- /dev/null +++ b/sbt-dotty/sbt-test/sbt-dotty/tasty-consumer-example-project/app/Main.scala @@ -0,0 +1,19 @@ +package hello + +import scala.tasty.Reflection +import scala.tasty.file._ + +object Main extends App { + + + class Consumer extends TastyInspector { + final def apply(reflect: Reflection)(root: reflect.Tree): Unit = { + import reflect._ + val tastyStr = root.show + println(tastyStr) + } + } + + InspectTasty("", List("lib.Foo"), new Consumer) + +} diff --git a/sbt-dotty/sbt-test/sbt-dotty/tasty-consumer-example-project/build.sbt b/sbt-dotty/sbt-test/sbt-dotty/tasty-consumer-example-project/build.sbt new file mode 100644 index 000000000000..9169f4809813 --- /dev/null +++ b/sbt-dotty/sbt-test/sbt-dotty/tasty-consumer-example-project/build.sbt @@ -0,0 +1,15 @@ +lazy val dottyVersion = sys.props("plugin.scalaVersion") + +lazy val lib = project + .in(file("lib")) + .settings( + scalaVersion := dottyVersion + ) + +lazy val app = project + .in(file("app")) + .settings( + scalaVersion := dottyVersion, + libraryDependencies += "ch.epfl.lamp" %% "dotty-tasty-inspector" % scalaVersion.value, + ) + .dependsOn(lib) diff --git a/sbt-dotty/sbt-test/sbt-dotty/tasty-consumer-example-project/lib/Foo.scala b/sbt-dotty/sbt-test/sbt-dotty/tasty-consumer-example-project/lib/Foo.scala new file mode 100644 index 000000000000..d3d2d13c2c5a --- /dev/null +++ b/sbt-dotty/sbt-test/sbt-dotty/tasty-consumer-example-project/lib/Foo.scala @@ -0,0 +1,5 @@ +package lib + +class Foo { + def foo(x: Int) = x +} diff --git a/sbt-dotty/sbt-test/sbt-dotty/tasty-consumer-example-project/project/plugins.sbt b/sbt-dotty/sbt-test/sbt-dotty/tasty-consumer-example-project/project/plugins.sbt new file mode 100644 index 000000000000..c17caab2d98c --- /dev/null +++ b/sbt-dotty/sbt-test/sbt-dotty/tasty-consumer-example-project/project/plugins.sbt @@ -0,0 +1 @@ +addSbtPlugin("ch.epfl.lamp" % "sbt-dotty" % sys.props("plugin.version")) diff --git a/sbt-dotty/sbt-test/sbt-dotty/tasty-consumer-example-project/test b/sbt-dotty/sbt-test/sbt-dotty/tasty-consumer-example-project/test new file mode 100644 index 000000000000..63092ffa4a03 --- /dev/null +++ b/sbt-dotty/sbt-test/sbt-dotty/tasty-consumer-example-project/test @@ -0,0 +1 @@ +> app/run diff --git a/staging/src/scala/quoted/staging/QuoteDriver.scala b/staging/src/scala/quoted/staging/QuoteDriver.scala index fe4410bafeb6..f9b0c6ed6736 100644 --- a/staging/src/scala/quoted/staging/QuoteDriver.scala +++ b/staging/src/scala/quoted/staging/QuoteDriver.scala @@ -8,10 +8,9 @@ import dotty.tools.dotc.tastyreflect.ReflectionImpl import dotty.tools.io.{AbstractFile, Directory, PlainDirectory, VirtualDirectory} import dotty.tools.repl.AbstractFileClassLoader import dotty.tools.dotc.reporting._ +import dotty.tools.dotc.util.ClasspathFromClassloader import scala.quoted._ import scala.quoted.staging.Toolbox -import java.net.URLClassLoader -import java.nio.file.Paths import java.io.File import scala.annotation.tailrec @@ -56,7 +55,7 @@ private class QuoteDriver(appClassloader: ClassLoader) extends Driver { override def initCtx: Context = { val ictx = contextBase.initialCtx - ictx.settings.classpath.update(classpathFromClassloader(appClassloader))(ictx) + ictx.settings.classpath.update(ClasspathFromClassloader(appClassloader))(ictx) ictx } @@ -67,42 +66,5 @@ private class QuoteDriver(appClassloader: ClassLoader) extends Driver { ctx.setReporter(new ThrowingReporter(ctx.reporter)) } - /** Attempt to recreate a classpath from a classloader. - * - * BEWARE: with exotic enough classloaders, this may not work at all or do - * the wrong thing. - */ - private def classpathFromClassloader(cl: ClassLoader): String = { - val classpathBuff = List.newBuilder[String] - def collectClassLoaderPaths(cl: ClassLoader): Unit = { - if (cl != null) { - cl match { - case cl: URLClassLoader => - // This is wrong if we're in a subclass of URLClassLoader - // that filters loading classes from its parent ¯\_(ツ)_/¯ - collectClassLoaderPaths(cl.getParent) - // Parent classloaders are searched before their child, so the part of - // the classpath coming from the child is added at the _end_ of the - // classpath. - classpathBuff ++= - cl.getURLs.iterator.map(url => Paths.get(url.toURI).toAbsolutePath.toString) - case _ => - // HACK: We can't just collect the classpath from arbitrary parent - // classloaders since the current classloader might intentionally - // filter loading classes from its parent (for example - // BootFilteredLoader in the sbt launcher does this and we really - // don't want to include the scala-library that sbt depends on - // here), but we do need to look at the parent of the REPL - // classloader, so we special case it. We can't do this using a type - // test since the REPL classloader class itself is normally loaded - // with a different classloader. - if (cl.getClass.getName == classOf[AbstractFileClassLoader].getName) - collectClassLoaderPaths(cl.getParent) - } - } - } - collectClassLoaderPaths(cl) - classpathBuff.result().mkString(java.io.File.pathSeparator) - } } diff --git a/tasty-inspector/src/scala/tasty/inspector/TastyInspector.scala b/tasty-inspector/src/scala/tasty/inspector/TastyInspector.scala new file mode 100644 index 000000000000..e4a0a0514777 --- /dev/null +++ b/tasty-inspector/src/scala/tasty/inspector/TastyInspector.scala @@ -0,0 +1,67 @@ +package scala.tasty.inspector + +import scala.tasty.Reflection + +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.tastyreflect.ReflectionImpl +import dotty.tools.dotc.util.ClasspathFromClassloader + +import java.io.File.pathSeparator + +trait TastyInspector { self => + + /** Process a TASTy file using TASTy reflect */ + protected def processCompilationUnit(reflect: Reflection)(root: reflect.Tree): Unit + + /** Load and process TASTy files using TASTy reflect + * + * @param classpath Classpath where the classes are located + * @param classes classes to be inspected + */ + def inspect(classpath: String, classes: List[String]): Unit = { + if (classes.isEmpty) + throw new IllegalArgumentException("Parameter classes should no be empty") + + class InspectorDriver extends Driver { + override protected def newCompiler(implicit ctx: Context): Compiler = new TastyFromClass + } + + 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) :: // Print all loaded classes + Nil + + override def newRun(implicit ctx: Context): Run = { + reset() + new TASTYRun(this, ctx.fresh.addMode(Mode.ReadPositions).addMode(Mode.ReadComments)) + } + } + + class TastyInspectorPhase extends Phase { + override def phaseName: String = "tastyInspector" + + override def run(implicit ctx: Context): Unit = { + val reflect = ReflectionImpl(ctx) + self.processCompilationUnit(reflect)(ctx.compilationUnit.tpdTree.asInstanceOf[reflect.Tree]) + } + } + + val currentClasspath = ClasspathFromClassloader(getClass.getClassLoader) + val args = "-from-tasty" :: "-Yretain-trees" :: "-classpath" :: s"$classpath$pathSeparator$currentClasspath" :: classes + (new InspectorDriver).process(args.toArray) + } + +} diff --git a/tastydoc/src/dotty/tastydoc/Main.scala b/tastydoc/src/dotty/tastydoc/Main.scala index 01af8156975c..0c85a0d0694a 100644 --- a/tastydoc/src/dotty/tastydoc/Main.scala +++ b/tastydoc/src/dotty/tastydoc/Main.scala @@ -1,6 +1,6 @@ package dotty.tastydoc -import scala.tasty.file._ +import scala.tasty.inspector._ import dotty.tastydoc.representations._ @@ -92,8 +92,8 @@ object Main { } else { println("Running Dotty Tastydoc on: " + classes.mkString(" ")) val mutablePackagesMap: scala.collection.mutable.HashMap[String, EmulatedPackageRepresentation] = new scala.collection.mutable.HashMap[String, EmulatedPackageRepresentation]() - val tc = new TastydocConsumer(mutablePackagesMap) - ConsumeTasty(extraClasspath, classes, tc) + val tc = new TastydocInspector(mutablePackagesMap) + tc.inspect(extraClasspath, classes) representations.setSubClasses(mutablePackagesMap) diff --git a/tastydoc/src/dotty/tastydoc/TastydocConsumer.scala b/tastydoc/src/dotty/tastydoc/TastydocConsumer.scala deleted file mode 100644 index 9e78f8b59c0c..000000000000 --- a/tastydoc/src/dotty/tastydoc/TastydocConsumer.scala +++ /dev/null @@ -1,19 +0,0 @@ -package dotty.tastydoc - -import scala.tasty.Reflection -import scala.tasty.file.TastyConsumer - -import dotty.tastydoc.representations._ - -/* Extends TastyConsumer and consumes Tasty Files to produce Representations - * - * @param mutablePackagesMap A mutable HashMap where seen packages are added - */ -class TastydocConsumer(mutablePackagesMap: scala.collection.mutable.HashMap[String, EmulatedPackageRepresentation]) extends TastyConsumer { - - final def apply(reflect: Reflection)(root: reflect.Tree): Unit = { - import reflect._ - - representations.convertToRepresentation(reflect)(root, None)(given mutablePackagesMap) - } -} diff --git a/tastydoc/src/dotty/tastydoc/TastydocInspector.scala b/tastydoc/src/dotty/tastydoc/TastydocInspector.scala new file mode 100644 index 000000000000..610e67c5f1c1 --- /dev/null +++ b/tastydoc/src/dotty/tastydoc/TastydocInspector.scala @@ -0,0 +1,19 @@ +package dotty.tastydoc + +import scala.tasty.Reflection +import scala.tasty.inspector.TastyInspector + +import dotty.tastydoc.representations._ + +/* Extends TastyInspector and consumes Tasty Files to produce Representations + * + * @param mutablePackagesMap A mutable HashMap where seen packages are added + */ +class TastydocInspector(mutablePackagesMap: scala.collection.mutable.HashMap[String, EmulatedPackageRepresentation]) extends TastyInspector { + + protected def processCompilationUnit(reflect: Reflection)(root: reflect.Tree): Unit = { + import reflect._ + + representations.convertToRepresentation(reflect)(root, None)(given mutablePackagesMap) + } +} diff --git a/tastydoc/test/dotty/tastydoc/Tests.scala b/tastydoc/test/dotty/tastydoc/Tests.scala index 2783565d41a5..e05499ee1547 100644 --- a/tastydoc/test/dotty/tastydoc/Tests.scala +++ b/tastydoc/test/dotty/tastydoc/Tests.scala @@ -1,7 +1,6 @@ package dotty.tastydoc import scala.tasty.Reflection -import scala.tasty.file._ import scala.collection.mutable.HashMap import org.junit.Test @@ -10,7 +9,6 @@ import java.nio.file._ import scala.collection.JavaConverters._ import java.io.File import scala.tasty.Reflection -import scala.tasty.file.TastyConsumer import java.lang.reflect.InvocationTargetException class Tests { diff --git a/tests/run-with-compiler/tasty-comment-consumer/Foo.scala b/tests/run-custom-args/tasty-inspector/tasty-comment-inspector/Foo.scala similarity index 100% rename from tests/run-with-compiler/tasty-comment-consumer/Foo.scala rename to tests/run-custom-args/tasty-inspector/tasty-comment-inspector/Foo.scala diff --git a/tests/run-with-compiler/tasty-comment-consumer/Test.scala b/tests/run-custom-args/tasty-inspector/tasty-comment-inspector/Test.scala similarity index 73% rename from tests/run-with-compiler/tasty-comment-consumer/Test.scala rename to tests/run-custom-args/tasty-inspector/tasty-comment-inspector/Test.scala index 0980dc5d6a32..71dbf6518c1a 100644 --- a/tests/run-with-compiler/tasty-comment-consumer/Test.scala +++ b/tests/run-custom-args/tasty-inspector/tasty-comment-inspector/Test.scala @@ -1,15 +1,15 @@ import scala.tasty.Reflection -import scala.tasty.file._ +import scala.tasty.inspector._ object Test { def main(args: Array[String]): Unit = { - ConsumeTasty("", List("Foo"), new CommentConsumer) + new CommentInspector().inspect("", List("Foo")) } } -class CommentConsumer extends TastyConsumer { +class CommentInspector extends TastyInspector { - final def apply(reflect: Reflection)(root: reflect.Tree): Unit = { + def processCompilationUnit(reflect: Reflection)(root: reflect.Tree): Unit = { import reflect.{_, given _} object Traverser extends TreeTraverser { diff --git a/tests/run-with-compiler/tasty-consumer/Foo.scala b/tests/run-custom-args/tasty-inspector/tasty-inspector/Foo.scala similarity index 100% rename from tests/run-with-compiler/tasty-consumer/Foo.scala rename to tests/run-custom-args/tasty-inspector/tasty-inspector/Foo.scala diff --git a/tests/run-with-compiler/tasty-consumer/Test.scala b/tests/run-custom-args/tasty-inspector/tasty-inspector/Test.scala similarity index 70% rename from tests/run-with-compiler/tasty-consumer/Test.scala rename to tests/run-custom-args/tasty-inspector/tasty-inspector/Test.scala index 87e4aab461e7..1d64ffde07f2 100644 --- a/tests/run-with-compiler/tasty-consumer/Test.scala +++ b/tests/run-custom-args/tasty-inspector/tasty-inspector/Test.scala @@ -1,15 +1,15 @@ import scala.tasty.Reflection -import scala.tasty.file._ +import scala.tasty.inspector._ object Test { def main(args: Array[String]): Unit = { - ConsumeTasty("", List("Foo"), new DBConsumer) + new DBInspector().inspect("", List("Foo")) } } -class DBConsumer extends TastyConsumer { +class DBInspector extends TastyInspector { - final def apply(reflect: Reflection)(root: reflect.Tree): Unit = { + protected def processCompilationUnit(reflect: Reflection)(root: reflect.Tree): Unit = { import reflect.{_, given _} object Traverser extends TreeTraverser { diff --git a/tests/run-with-compiler-custom-args/tasty-interpreter/InterpretedMain.scala b/tests/run-custom-args/tasty-interpreter/InterpretedMain.scala similarity index 100% rename from tests/run-with-compiler-custom-args/tasty-interpreter/InterpretedMain.scala rename to tests/run-custom-args/tasty-interpreter/InterpretedMain.scala diff --git a/tests/run-with-compiler-custom-args/tasty-interpreter/Precompiled.scala b/tests/run-custom-args/tasty-interpreter/Precompiled.scala similarity index 100% rename from tests/run-with-compiler-custom-args/tasty-interpreter/Precompiled.scala rename to tests/run-custom-args/tasty-interpreter/Precompiled.scala diff --git a/tests/run-with-compiler-custom-args/tasty-interpreter/Test.scala b/tests/run-custom-args/tasty-interpreter/Test.scala similarity index 95% rename from tests/run-with-compiler-custom-args/tasty-interpreter/Test.scala rename to tests/run-custom-args/tasty-interpreter/Test.scala index 9134c304cc7f..7feff7c01300 100644 --- a/tests/run-with-compiler-custom-args/tasty-interpreter/Test.scala +++ b/tests/run-custom-args/tasty-interpreter/Test.scala @@ -7,7 +7,6 @@ import dotty.tools.dotc.util.DiffUtil import dotty.tools.io.Path import scala.io.Source -import scala.tasty.file._ import scala.tasty.interpreter.TastyInterpreter object Test { @@ -103,7 +102,7 @@ object Test { def interpret(classpath: String*)(interpretedClasses: String*): String = { val ps = new ByteArrayOutputStream() try scala.Console.withOut(ps) { - ConsumeTasty(classpath.mkString(java.io.File.pathSeparatorChar.toString), interpretedClasses.toList, new TastyInterpreter) + new TastyInterpreter().inspect(classpath.mkString(java.io.File.pathSeparatorChar.toString), interpretedClasses.toList) } catch { case e: Throwable => throw new Exception(ps.toString, e) } diff --git a/tests/run-with-compiler-custom-args/tasty-interpreter/interpreter/TastyInterpreter.scala b/tests/run-custom-args/tasty-interpreter/interpreter/TastyInterpreter.scala similarity index 77% rename from tests/run-with-compiler-custom-args/tasty-interpreter/interpreter/TastyInterpreter.scala rename to tests/run-custom-args/tasty-interpreter/interpreter/TastyInterpreter.scala index e73846f25780..7c627eba989a 100644 --- a/tests/run-with-compiler-custom-args/tasty-interpreter/interpreter/TastyInterpreter.scala +++ b/tests/run-custom-args/tasty-interpreter/interpreter/TastyInterpreter.scala @@ -1,11 +1,11 @@ package scala.tasty.interpreter import scala.tasty.Reflection -import scala.tasty.file.TastyConsumer +import scala.tasty.inspector.TastyInspector -class TastyInterpreter extends TastyConsumer { +class TastyInterpreter extends TastyInspector { - final def apply(reflect: Reflection)(root: reflect.Tree): Unit = { + protected def processCompilationUnit(reflect: Reflection)(root: reflect.Tree): Unit = { import reflect.{_, given _} object Traverser extends TreeTraverser { diff --git a/tests/run-with-compiler-custom-args/tasty-interpreter/interpreter/TreeInterpreter.scala b/tests/run-custom-args/tasty-interpreter/interpreter/TreeInterpreter.scala similarity index 100% rename from tests/run-with-compiler-custom-args/tasty-interpreter/interpreter/TreeInterpreter.scala rename to tests/run-custom-args/tasty-interpreter/interpreter/TreeInterpreter.scala diff --git a/tests/run-with-compiler-custom-args/tasty-interpreter/interpreter/jvm/Interpreter.scala b/tests/run-custom-args/tasty-interpreter/interpreter/jvm/Interpreter.scala similarity index 100% rename from tests/run-with-compiler-custom-args/tasty-interpreter/interpreter/jvm/Interpreter.scala rename to tests/run-custom-args/tasty-interpreter/interpreter/jvm/Interpreter.scala diff --git a/tests/run-with-compiler-custom-args/tasty-interpreter/interpreter/jvm/JVMReflection.scala b/tests/run-custom-args/tasty-interpreter/interpreter/jvm/JVMReflection.scala similarity index 100% rename from tests/run-with-compiler-custom-args/tasty-interpreter/interpreter/jvm/JVMReflection.scala rename to tests/run-custom-args/tasty-interpreter/interpreter/jvm/JVMReflection.scala diff --git a/tests/run-with-compiler-custom-args/tasty-interpreter/notes.md b/tests/run-custom-args/tasty-interpreter/notes.md similarity index 100% rename from tests/run-with-compiler-custom-args/tasty-interpreter/notes.md rename to tests/run-custom-args/tasty-interpreter/notes.md