From d90e8c39f890362da1dcfb8ce046b3511987a84f Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Tue, 2 Oct 2018 19:47:41 +0200 Subject: [PATCH 1/3] Create infrastructure for semantic db tests/run-with-compiler/tasty-consumer/Test.scala shows an example of how to consume tasty files. --- .../dotc/consumetasty/ConsumeTasty.scala | 22 ++++++++++++++ .../consumetasty/TastyConsumerPhase.scala | 18 ++++++++++++ .../dotc/consumetasty/TastyFromClass.scala | 20 +++++++++++++ .../src/scala/tasty/file/ConsumeTasty.scala | 26 +++++++++++++++++ .../src/scala/tasty/file/TastyConsumer.scala | 7 +++++ tests/run-with-compiler/tasty-consumer.check | 5 ++++ .../tasty-consumer/Foo.scala | 5 ++++ .../tasty-consumer/Test.scala | 29 +++++++++++++++++++ 8 files changed, 132 insertions(+) create mode 100644 compiler/src/dotty/tools/dotc/consumetasty/ConsumeTasty.scala create mode 100644 compiler/src/dotty/tools/dotc/consumetasty/TastyConsumerPhase.scala create mode 100644 compiler/src/dotty/tools/dotc/consumetasty/TastyFromClass.scala create mode 100644 library/src/scala/tasty/file/ConsumeTasty.scala create mode 100644 library/src/scala/tasty/file/TastyConsumer.scala create mode 100644 tests/run-with-compiler/tasty-consumer.check create mode 100644 tests/run-with-compiler/tasty-consumer/Foo.scala create mode 100644 tests/run-with-compiler/tasty-consumer/Test.scala diff --git a/compiler/src/dotty/tools/dotc/consumetasty/ConsumeTasty.scala b/compiler/src/dotty/tools/dotc/consumetasty/ConsumeTasty.scala new file mode 100644 index 000000000000..b09f6ef2b8a5 --- /dev/null +++ b/compiler/src/dotty/tools/dotc/consumetasty/ConsumeTasty.scala @@ -0,0 +1,22 @@ +package dotty.tools.dotc.consumetasty + +import dotty.tools.dotc +import dotty.tools.dotc.core.Contexts._ + +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 = System.getProperty("java.class.path") + val args = "-from-tasty" +: "-classpath" +: s"$classpath:$currentClasspath" +: classes + (new Consume).process(args.toArray) + } +} diff --git a/compiler/src/dotty/tools/dotc/consumetasty/TastyConsumerPhase.scala b/compiler/src/dotty/tools/dotc/consumetasty/TastyConsumerPhase.scala new file mode 100644 index 000000000000..9bab9b4361ce --- /dev/null +++ b/compiler/src/dotty/tools/dotc/consumetasty/TastyConsumerPhase.scala @@ -0,0 +1,18 @@ +package dotty.tools.dotc.consumetasty + +import dotty.tools.dotc.core.Contexts._ +import dotty.tools.dotc.core.Phases.Phase +import dotty.tools.dotc.tastyreflect.TastyImpl + +import scala.tasty.file.TastyConsumer + +class TastyConsumerPhase(consumer: TastyConsumer) extends Phase { + + override def phaseName: String = "tastyConsumer" + + override def run(implicit ctx: Context): Unit = { + val tasty = new TastyImpl(ctx) + consumer(tasty)(ctx.compilationUnit.tpdTree) + } + +} diff --git a/compiler/src/dotty/tools/dotc/consumetasty/TastyFromClass.scala b/compiler/src/dotty/tools/dotc/consumetasty/TastyFromClass.scala new file mode 100644 index 000000000000..fdbc6d4d79fc --- /dev/null +++ b/compiler/src/dotty/tools/dotc/consumetasty/TastyFromClass.scala @@ -0,0 +1,20 @@ +package dotty.tools.dotc.consumetasty + +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 ReadTastyTreesFromClasses) :: // 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 +} diff --git a/library/src/scala/tasty/file/ConsumeTasty.scala b/library/src/scala/tasty/file/ConsumeTasty.scala new file mode 100644 index 000000000000..5a2892242d99 --- /dev/null +++ b/library/src/scala/tasty/file/ConsumeTasty.scala @@ -0,0 +1,26 @@ +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 new file mode 100644 index 000000000000..89baabd6868f --- /dev/null +++ b/library/src/scala/tasty/file/TastyConsumer.scala @@ -0,0 +1,7 @@ +package scala.tasty.file + +import scala.tasty.Tasty + +trait TastyConsumer { + def apply(tasty: Tasty)(root: tasty.Tree): Unit +} diff --git a/tests/run-with-compiler/tasty-consumer.check b/tests/run-with-compiler/tasty-consumer.check new file mode 100644 index 000000000000..4ac0c4a0488c --- /dev/null +++ b/tests/run-with-compiler/tasty-consumer.check @@ -0,0 +1,5 @@ +ClassDef("Foo", DefDef("", Nil, List(Nil), TypeTree.Synthetic(), None), List(Term.Apply(Term.Select(Term.New(TypeTree.Synthetic()), "", Some(Signature(Nil, java.lang.Object))), Nil)), None, List(ValDef("foo", TypeTree.TypeIdent("Int"), Some(Term.Literal(Constant.Int(2)))), DefDef("bar", Nil, List(List(ValDef("i", TypeTree.TypeIdent("Int"), None))), TypeTree.TypeIdent("Int"), Some(Term.Literal(Constant.Int(3)))))) +DefDef("", Nil, List(Nil), TypeTree.Synthetic(), None) +ValDef("foo", TypeTree.TypeIdent("Int"), Some(Term.Literal(Constant.Int(2)))) +DefDef("bar", Nil, List(List(ValDef("i", TypeTree.TypeIdent("Int"), None))), TypeTree.TypeIdent("Int"), Some(Term.Literal(Constant.Int(3)))) +ValDef("i", TypeTree.TypeIdent("Int"), None) diff --git a/tests/run-with-compiler/tasty-consumer/Foo.scala b/tests/run-with-compiler/tasty-consumer/Foo.scala new file mode 100644 index 000000000000..82476d584d55 --- /dev/null +++ b/tests/run-with-compiler/tasty-consumer/Foo.scala @@ -0,0 +1,5 @@ + +class Foo { + val foo: Int = 2 + def bar(i: Int): Int = 3 +} diff --git a/tests/run-with-compiler/tasty-consumer/Test.scala b/tests/run-with-compiler/tasty-consumer/Test.scala new file mode 100644 index 000000000000..3cbb57aa07e3 --- /dev/null +++ b/tests/run-with-compiler/tasty-consumer/Test.scala @@ -0,0 +1,29 @@ +import scala.tasty.Tasty +import scala.tasty.util.TreeTraverser +import scala.tasty.file._ + +object Test { + def main(args: Array[String]): Unit = { + ConsumeTasty("", List("Foo"), new DBConsumer) + } +} + +class DBConsumer extends TastyConsumer { + + final def apply(tasty: Tasty)(root: tasty.Tree): Unit = { + import tasty._ + object Traverser extends TreeTraverser[tasty.type](tasty) { + + override def traverseTree(tree: Tree)(implicit ctx: Context): Unit = tree match { + case IsDefinition(tree) => + println(tree.show) + super.traverseTree(tree) + case tree => + super.traverseTree(tree) + } + + } + Traverser.traverseTree(root)(tasty.rootContext) + } + +} From 7ba35b61e543bf5b4c365e14859cea6f51fb3454 Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Wed, 3 Oct 2018 22:35:02 +0200 Subject: [PATCH 2/3] Add semantic-db project --- build.sbt | 1 + .../dotc/consumetasty/ConsumeTasty.scala | 3 +- .../dotty/tools/dotc/quoted/QuoteDriver.scala | 17 +++++++--- project/Build.scala | 12 +++++++ .../src/dotty/semanticdb/DBConsumer.scala | 27 +++++++++++++++ semantic-db/src/dotty/semanticdb/Main.scala | 18 ++++++++++ semantic-db/test/dotty/semanticdb/Tests.scala | 33 +++++++++++++++++++ semantic-db/test/tests/SimpleClass.scala | 3 ++ semantic-db/test/tests/SimpleDef.scala | 5 +++ 9 files changed, 113 insertions(+), 6 deletions(-) create mode 100644 semantic-db/src/dotty/semanticdb/DBConsumer.scala create mode 100644 semantic-db/src/dotty/semanticdb/Main.scala create mode 100644 semantic-db/test/dotty/semanticdb/Tests.scala create mode 100644 semantic-db/test/tests/SimpleClass.scala create mode 100644 semantic-db/test/tests/SimpleDef.scala diff --git a/build.sbt b/build.sbt index d0758ebb5625..6518859637f0 100644 --- a/build.sbt +++ b/build.sbt @@ -12,6 +12,7 @@ val `dotty-sbt-bridge-bootstrapped` = Build.`dotty-sbt-bridge-bootstrapped` val `dotty-language-server` = Build.`dotty-language-server` val `dotty-bench` = Build.`dotty-bench` val `dotty-bench-bootstrapped` = Build.`dotty-bench-bootstrapped` +val `dotty-semantic-db` = Build.`dotty-semantic-db` val `scala-library` = Build.`scala-library` val `scala-compiler` = Build.`scala-compiler` val `scala-reflect` = Build.`scala-reflect` diff --git a/compiler/src/dotty/tools/dotc/consumetasty/ConsumeTasty.scala b/compiler/src/dotty/tools/dotc/consumetasty/ConsumeTasty.scala index b09f6ef2b8a5..6fd80cbe50e2 100644 --- a/compiler/src/dotty/tools/dotc/consumetasty/ConsumeTasty.scala +++ b/compiler/src/dotty/tools/dotc/consumetasty/ConsumeTasty.scala @@ -2,6 +2,7 @@ package dotty.tools.dotc.consumetasty import dotty.tools.dotc import dotty.tools.dotc.core.Contexts._ +import dotty.tools.dotc.quoted.QuoteDriver import scala.tasty.file.TastyConsumer @@ -15,7 +16,7 @@ object ConsumeTasty { new TastyFromClass(tastyConsumer) } - val currentClasspath = System.getProperty("java.class.path") + val currentClasspath = QuoteDriver.currentClasspath val args = "-from-tasty" +: "-classpath" +: s"$classpath:$currentClasspath" +: classes (new Consume).process(args.toArray) } diff --git a/compiler/src/dotty/tools/dotc/quoted/QuoteDriver.scala b/compiler/src/dotty/tools/dotc/quoted/QuoteDriver.scala index 38aa8942bce8..ede4c72c9a1a 100644 --- a/compiler/src/dotty/tools/dotc/quoted/QuoteDriver.scala +++ b/compiler/src/dotty/tools/dotc/quoted/QuoteDriver.scala @@ -75,17 +75,24 @@ class QuoteDriver extends Driver { override def initCtx: Context = { val ictx = contextBase.initialCtx - var classpath = System.getProperty("java.class.path") + ictx.settings.classpath.update(QuoteDriver.currentClasspath)(ictx) + ictx + } + +} + +object QuoteDriver { + + def currentClasspath: String = { + val classpath0 = System.getProperty("java.class.path") this.getClass.getClassLoader match { case cl: URLClassLoader => // Loads the classes loaded by this class loader // When executing `run` or `test` in sbt the classpath is not in the property java.class.path val newClasspath = cl.getURLs.map(_.getFile()) - classpath = newClasspath.mkString("", java.io.File.pathSeparator, if (classpath == "") "" else java.io.File.pathSeparator + classpath) - case _ => + newClasspath.mkString("", java.io.File.pathSeparator, if (classpath0 == "") "" else java.io.File.pathSeparator + classpath0) + case _ => classpath0 } - ictx.settings.classpath.update(classpath)(ictx) - ictx } } diff --git a/project/Build.scala b/project/Build.scala index 98fcc7f476e0..85e850368469 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -381,6 +381,12 @@ object Build { dottyLib + File.pathSeparator + dottyInterfaces + File.pathSeparator + otherDeps } + lazy val semanticDBSettings = Seq( + baseDirectory in (Compile, run) := baseDirectory.value / "..", + baseDirectory in Test := baseDirectory.value / "..", + libraryDependencies += "com.novocode" % "junit-interface" % "0.11" % Test + ) + // Settings shared between dotty-doc and dotty-doc-bootstrapped lazy val dottyDocSettings = Seq( baseDirectory in (Compile, run) := baseDirectory.value / "..", @@ -904,6 +910,8 @@ object Build { lazy val `dotty-bench` = project.in(file("bench")).asDottyBench(NonBootstrapped) lazy val `dotty-bench-bootstrapped` = project.in(file("bench")).asDottyBench(Bootstrapped) + lazy val `dotty-semantic-db` = project.in(file("semantic-db")).asDottySemanticDB(Bootstrapped) + // Depend on dotty-library so that sbt projects using dotty automatically // depend on the dotty-library lazy val `scala-library` = project. @@ -1297,6 +1305,10 @@ object Build { settings(commonBenchmarkSettings). enablePlugins(JmhPlugin) + def asDottySemanticDB(implicit mode: Mode): Project = project.withCommonSettings. + dependsOn(dottyCompiler). + settings(semanticDBSettings) + def asDist(implicit mode: Mode): Project = project. enablePlugins(PackPlugin). withCommonSettings. diff --git a/semantic-db/src/dotty/semanticdb/DBConsumer.scala b/semantic-db/src/dotty/semanticdb/DBConsumer.scala new file mode 100644 index 000000000000..cc7f5a4f3268 --- /dev/null +++ b/semantic-db/src/dotty/semanticdb/DBConsumer.scala @@ -0,0 +1,27 @@ +package dotty.semanticdb + +import scala.tasty.Tasty +import scala.tasty.file.TastyConsumer +import scala.tasty.util.TreeTraverser + +class DBConsumer extends TastyConsumer { + + final def apply(tasty: Tasty)(root: tasty.Tree): Unit = { + import tasty._ + object Traverser extends TreeTraverser[tasty.type](tasty) { + + override def traverseTree(tree: Tree)(implicit ctx: Context): Unit = tree match { + case IsDefinition(tree) => + println(tree.name) + super.traverseTree(tree) + case tree => + super.traverseTree(tree) + } + + } + Traverser.traverseTree(root)(tasty.rootContext) + } + + def println(x: Any): Unit = Predef.println(x) + +} diff --git a/semantic-db/src/dotty/semanticdb/Main.scala b/semantic-db/src/dotty/semanticdb/Main.scala new file mode 100644 index 000000000000..6f3607b2c9c2 --- /dev/null +++ b/semantic-db/src/dotty/semanticdb/Main.scala @@ -0,0 +1,18 @@ +package dotty.semanticdb + +import scala.tasty.Tasty +import scala.tasty.util.TreeTraverser +import scala.tasty.file._ + +object Main { + def main(args: Array[String]): Unit = { + val extraClasspath = "." // TODO allow to set it from the args with -classpath XYZ + val classes = args.toList + if (args.isEmpty) { + println("Dotty Semantic DB: No classes where passed as argument") + } else { + println("Running Dotty Semantic DB on: " + args.mkString(" ")) + ConsumeTasty(extraClasspath, classes, new DBConsumer) + } + } +} diff --git a/semantic-db/test/dotty/semanticdb/Tests.scala b/semantic-db/test/dotty/semanticdb/Tests.scala new file mode 100644 index 000000000000..91759d2f009a --- /dev/null +++ b/semantic-db/test/dotty/semanticdb/Tests.scala @@ -0,0 +1,33 @@ +package dotty.semanticdb + +import scala.tasty.Tasty +import scala.tasty.util.TreeTraverser +import scala.tasty.file._ + +import org.junit.Test +import org.junit.Assert._ + +class Tests { + + // TODO: update scala-0.10 on version change (or resolve automatically) + final def testClasspath = "out/bootstrap/dotty-semantic-db/scala-0.10/test-classes" + + @Test def testMain(): Unit = { + testOutput( + "tests.SimpleClass", + "SimpleClass;;" + ) + testOutput( + "tests.SimpleDef", + "SimpleDef;;foo;" + ) + } + + def testOutput(className: String, expected: String): Unit = { + val out = new StringBuilder + ConsumeTasty(testClasspath, List(className), new DBConsumer { + override def println(x: Any): Unit = out.append(x).append(";") + }) + assertEquals(expected, out.result()) + } +} diff --git a/semantic-db/test/tests/SimpleClass.scala b/semantic-db/test/tests/SimpleClass.scala new file mode 100644 index 000000000000..6827e19bf8d6 --- /dev/null +++ b/semantic-db/test/tests/SimpleClass.scala @@ -0,0 +1,3 @@ +package tests + +class SimpleClass diff --git a/semantic-db/test/tests/SimpleDef.scala b/semantic-db/test/tests/SimpleDef.scala new file mode 100644 index 000000000000..15afc0d77be6 --- /dev/null +++ b/semantic-db/test/tests/SimpleDef.scala @@ -0,0 +1,5 @@ +package tests + +class SimpleDef { + def foo(): Int = 0 +} From 8593d690654b84f468bf5356ad941aa6f09391a0 Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Thu, 4 Oct 2018 15:47:42 +0200 Subject: [PATCH 3/3] semantic-db -> semanticdb --- build.sbt | 2 +- project/Build.scala | 2 +- .../src/dotty/semanticdb/DBConsumer.scala | 0 {semantic-db => semanticdb}/src/dotty/semanticdb/Main.scala | 0 {semantic-db => semanticdb}/test/dotty/semanticdb/Tests.scala | 2 +- {semantic-db => semanticdb}/test/tests/SimpleClass.scala | 0 {semantic-db => semanticdb}/test/tests/SimpleDef.scala | 0 7 files changed, 3 insertions(+), 3 deletions(-) rename {semantic-db => semanticdb}/src/dotty/semanticdb/DBConsumer.scala (100%) rename {semantic-db => semanticdb}/src/dotty/semanticdb/Main.scala (100%) rename {semantic-db => semanticdb}/test/dotty/semanticdb/Tests.scala (89%) rename {semantic-db => semanticdb}/test/tests/SimpleClass.scala (100%) rename {semantic-db => semanticdb}/test/tests/SimpleDef.scala (100%) diff --git a/build.sbt b/build.sbt index 6518859637f0..11804a2ccec1 100644 --- a/build.sbt +++ b/build.sbt @@ -12,7 +12,7 @@ val `dotty-sbt-bridge-bootstrapped` = Build.`dotty-sbt-bridge-bootstrapped` val `dotty-language-server` = Build.`dotty-language-server` val `dotty-bench` = Build.`dotty-bench` val `dotty-bench-bootstrapped` = Build.`dotty-bench-bootstrapped` -val `dotty-semantic-db` = Build.`dotty-semantic-db` +val `dotty-semanticdb` = Build.`dotty-semanticdb` val `scala-library` = Build.`scala-library` val `scala-compiler` = Build.`scala-compiler` val `scala-reflect` = Build.`scala-reflect` diff --git a/project/Build.scala b/project/Build.scala index 85e850368469..a57534bd1d18 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -910,7 +910,7 @@ object Build { lazy val `dotty-bench` = project.in(file("bench")).asDottyBench(NonBootstrapped) lazy val `dotty-bench-bootstrapped` = project.in(file("bench")).asDottyBench(Bootstrapped) - lazy val `dotty-semantic-db` = project.in(file("semantic-db")).asDottySemanticDB(Bootstrapped) + lazy val `dotty-semanticdb` = project.in(file("semanticdb")).asDottySemanticDB(Bootstrapped) // Depend on dotty-library so that sbt projects using dotty automatically // depend on the dotty-library diff --git a/semantic-db/src/dotty/semanticdb/DBConsumer.scala b/semanticdb/src/dotty/semanticdb/DBConsumer.scala similarity index 100% rename from semantic-db/src/dotty/semanticdb/DBConsumer.scala rename to semanticdb/src/dotty/semanticdb/DBConsumer.scala diff --git a/semantic-db/src/dotty/semanticdb/Main.scala b/semanticdb/src/dotty/semanticdb/Main.scala similarity index 100% rename from semantic-db/src/dotty/semanticdb/Main.scala rename to semanticdb/src/dotty/semanticdb/Main.scala diff --git a/semantic-db/test/dotty/semanticdb/Tests.scala b/semanticdb/test/dotty/semanticdb/Tests.scala similarity index 89% rename from semantic-db/test/dotty/semanticdb/Tests.scala rename to semanticdb/test/dotty/semanticdb/Tests.scala index 91759d2f009a..b497af89c482 100644 --- a/semantic-db/test/dotty/semanticdb/Tests.scala +++ b/semanticdb/test/dotty/semanticdb/Tests.scala @@ -10,7 +10,7 @@ import org.junit.Assert._ class Tests { // TODO: update scala-0.10 on version change (or resolve automatically) - final def testClasspath = "out/bootstrap/dotty-semantic-db/scala-0.10/test-classes" + final def testClasspath = "out/bootstrap/dotty-semanticdb/scala-0.10/test-classes" @Test def testMain(): Unit = { testOutput( diff --git a/semantic-db/test/tests/SimpleClass.scala b/semanticdb/test/tests/SimpleClass.scala similarity index 100% rename from semantic-db/test/tests/SimpleClass.scala rename to semanticdb/test/tests/SimpleClass.scala diff --git a/semantic-db/test/tests/SimpleDef.scala b/semanticdb/test/tests/SimpleDef.scala similarity index 100% rename from semantic-db/test/tests/SimpleDef.scala rename to semanticdb/test/tests/SimpleDef.scala