Skip to content

Commit 978d01c

Browse files
Merge pull request #5193 from dotty-staging/semanticdb-infrastructure
Create infrastructure for SemanticDB
2 parents 5aa2f9c + 8593d69 commit 978d01c

File tree

16 files changed

+244
-5
lines changed

16 files changed

+244
-5
lines changed

build.sbt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ val `dotty-sbt-bridge-bootstrapped` = Build.`dotty-sbt-bridge-bootstrapped`
1212
val `dotty-language-server` = Build.`dotty-language-server`
1313
val `dotty-bench` = Build.`dotty-bench`
1414
val `dotty-bench-bootstrapped` = Build.`dotty-bench-bootstrapped`
15+
val `dotty-semanticdb` = Build.`dotty-semanticdb`
1516
val `scala-library` = Build.`scala-library`
1617
val `scala-compiler` = Build.`scala-compiler`
1718
val `scala-reflect` = Build.`scala-reflect`
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package dotty.tools.dotc.consumetasty
2+
3+
import dotty.tools.dotc
4+
import dotty.tools.dotc.core.Contexts._
5+
import dotty.tools.dotc.quoted.QuoteDriver
6+
7+
import scala.tasty.file.TastyConsumer
8+
9+
object ConsumeTasty {
10+
def apply(classpath: String, classes: List[String], tastyConsumer: TastyConsumer): Unit = {
11+
if (classes.isEmpty)
12+
throw new IllegalArgumentException("Parameter classes should no be empty")
13+
14+
class Consume extends dotc.Driver {
15+
override protected def newCompiler(implicit ctx: Context): dotc.Compiler =
16+
new TastyFromClass(tastyConsumer)
17+
}
18+
19+
val currentClasspath = QuoteDriver.currentClasspath
20+
val args = "-from-tasty" +: "-classpath" +: s"$classpath:$currentClasspath" +: classes
21+
(new Consume).process(args.toArray)
22+
}
23+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
package dotty.tools.dotc.consumetasty
2+
3+
import dotty.tools.dotc.core.Contexts._
4+
import dotty.tools.dotc.core.Phases.Phase
5+
import dotty.tools.dotc.tastyreflect.TastyImpl
6+
7+
import scala.tasty.file.TastyConsumer
8+
9+
class TastyConsumerPhase(consumer: TastyConsumer) extends Phase {
10+
11+
override def phaseName: String = "tastyConsumer"
12+
13+
override def run(implicit ctx: Context): Unit = {
14+
val tasty = new TastyImpl(ctx)
15+
consumer(tasty)(ctx.compilationUnit.tpdTree)
16+
}
17+
18+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package dotty.tools.dotc.consumetasty
2+
3+
import dotty.tools.dotc.core.Phases.Phase
4+
import dotty.tools.dotc.fromtasty._
5+
6+
import scala.tasty.file.TastyConsumer
7+
8+
class TastyFromClass(consumer: TastyConsumer) extends TASTYCompiler {
9+
10+
override protected def frontendPhases: List[List[Phase]] =
11+
List(new ReadTastyTreesFromClasses) :: // Load classes from tasty
12+
Nil
13+
14+
override protected def picklerPhases: List[List[Phase]] = Nil
15+
override protected def transformPhases: List[List[Phase]] = Nil
16+
17+
override protected def backendPhases: List[List[Phase]] =
18+
List(new TastyConsumerPhase(consumer)) :: // Print all loaded classes
19+
Nil
20+
}

compiler/src/dotty/tools/dotc/quoted/QuoteDriver.scala

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -75,17 +75,24 @@ class QuoteDriver extends Driver {
7575

7676
override def initCtx: Context = {
7777
val ictx = contextBase.initialCtx
78-
var classpath = System.getProperty("java.class.path")
78+
ictx.settings.classpath.update(QuoteDriver.currentClasspath)(ictx)
79+
ictx
80+
}
81+
82+
}
83+
84+
object QuoteDriver {
85+
86+
def currentClasspath: String = {
87+
val classpath0 = System.getProperty("java.class.path")
7988
this.getClass.getClassLoader match {
8089
case cl: URLClassLoader =>
8190
// Loads the classes loaded by this class loader
8291
// When executing `run` or `test` in sbt the classpath is not in the property java.class.path
8392
val newClasspath = cl.getURLs.map(_.getFile())
84-
classpath = newClasspath.mkString("", java.io.File.pathSeparator, if (classpath == "") "" else java.io.File.pathSeparator + classpath)
85-
case _ =>
93+
newClasspath.mkString("", java.io.File.pathSeparator, if (classpath0 == "") "" else java.io.File.pathSeparator + classpath0)
94+
case _ => classpath0
8695
}
87-
ictx.settings.classpath.update(classpath)(ictx)
88-
ictx
8996
}
9097

9198
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package scala.tasty.file
2+
3+
object ConsumeTasty {
4+
5+
/** Load and process TASTy files using TASTy reflect
6+
*
7+
* @param classpath Classpath where the classes are located
8+
* @param classes classes to be consumed
9+
* @param tastyConsumer consumer that will process the tasty trees
10+
*/
11+
def apply(classpath: String, classes: List[String], tastyConsumer: TastyConsumer): Unit = {
12+
val cl = getClass.getClassLoader
13+
try {
14+
val dottyConsumeTastyCls = cl.loadClass("dotty.tools.dotc.consumetasty.ConsumeTasty")
15+
val makeMeth = dottyConsumeTastyCls.getMethod("apply", classOf[String], classOf[List[_]], classOf[TastyConsumer])
16+
makeMeth.invoke(null, classpath, classes, tastyConsumer)
17+
}
18+
catch {
19+
case ex: ClassNotFoundException =>
20+
throw new Exception(
21+
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.""",
22+
ex
23+
)
24+
}
25+
}
26+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package scala.tasty.file
2+
3+
import scala.tasty.Tasty
4+
5+
trait TastyConsumer {
6+
def apply(tasty: Tasty)(root: tasty.Tree): Unit
7+
}

project/Build.scala

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -381,6 +381,12 @@ object Build {
381381
dottyLib + File.pathSeparator + dottyInterfaces + File.pathSeparator + otherDeps
382382
}
383383

384+
lazy val semanticDBSettings = Seq(
385+
baseDirectory in (Compile, run) := baseDirectory.value / "..",
386+
baseDirectory in Test := baseDirectory.value / "..",
387+
libraryDependencies += "com.novocode" % "junit-interface" % "0.11" % Test
388+
)
389+
384390
// Settings shared between dotty-doc and dotty-doc-bootstrapped
385391
lazy val dottyDocSettings = Seq(
386392
baseDirectory in (Compile, run) := baseDirectory.value / "..",
@@ -904,6 +910,8 @@ object Build {
904910
lazy val `dotty-bench` = project.in(file("bench")).asDottyBench(NonBootstrapped)
905911
lazy val `dotty-bench-bootstrapped` = project.in(file("bench")).asDottyBench(Bootstrapped)
906912

913+
lazy val `dotty-semanticdb` = project.in(file("semanticdb")).asDottySemanticDB(Bootstrapped)
914+
907915
// Depend on dotty-library so that sbt projects using dotty automatically
908916
// depend on the dotty-library
909917
lazy val `scala-library` = project.
@@ -1297,6 +1305,10 @@ object Build {
12971305
settings(commonBenchmarkSettings).
12981306
enablePlugins(JmhPlugin)
12991307

1308+
def asDottySemanticDB(implicit mode: Mode): Project = project.withCommonSettings.
1309+
dependsOn(dottyCompiler).
1310+
settings(semanticDBSettings)
1311+
13001312
def asDist(implicit mode: Mode): Project = project.
13011313
enablePlugins(PackPlugin).
13021314
withCommonSettings.
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
package dotty.semanticdb
2+
3+
import scala.tasty.Tasty
4+
import scala.tasty.file.TastyConsumer
5+
import scala.tasty.util.TreeTraverser
6+
7+
class DBConsumer extends TastyConsumer {
8+
9+
final def apply(tasty: Tasty)(root: tasty.Tree): Unit = {
10+
import tasty._
11+
object Traverser extends TreeTraverser[tasty.type](tasty) {
12+
13+
override def traverseTree(tree: Tree)(implicit ctx: Context): Unit = tree match {
14+
case IsDefinition(tree) =>
15+
println(tree.name)
16+
super.traverseTree(tree)
17+
case tree =>
18+
super.traverseTree(tree)
19+
}
20+
21+
}
22+
Traverser.traverseTree(root)(tasty.rootContext)
23+
}
24+
25+
def println(x: Any): Unit = Predef.println(x)
26+
27+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
package dotty.semanticdb
2+
3+
import scala.tasty.Tasty
4+
import scala.tasty.util.TreeTraverser
5+
import scala.tasty.file._
6+
7+
object Main {
8+
def main(args: Array[String]): Unit = {
9+
val extraClasspath = "." // TODO allow to set it from the args with -classpath XYZ
10+
val classes = args.toList
11+
if (args.isEmpty) {
12+
println("Dotty Semantic DB: No classes where passed as argument")
13+
} else {
14+
println("Running Dotty Semantic DB on: " + args.mkString(" "))
15+
ConsumeTasty(extraClasspath, classes, new DBConsumer)
16+
}
17+
}
18+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
package dotty.semanticdb
2+
3+
import scala.tasty.Tasty
4+
import scala.tasty.util.TreeTraverser
5+
import scala.tasty.file._
6+
7+
import org.junit.Test
8+
import org.junit.Assert._
9+
10+
class Tests {
11+
12+
// TODO: update scala-0.10 on version change (or resolve automatically)
13+
final def testClasspath = "out/bootstrap/dotty-semanticdb/scala-0.10/test-classes"
14+
15+
@Test def testMain(): Unit = {
16+
testOutput(
17+
"tests.SimpleClass",
18+
"SimpleClass;<init>;"
19+
)
20+
testOutput(
21+
"tests.SimpleDef",
22+
"SimpleDef;<init>;foo;"
23+
)
24+
}
25+
26+
def testOutput(className: String, expected: String): Unit = {
27+
val out = new StringBuilder
28+
ConsumeTasty(testClasspath, List(className), new DBConsumer {
29+
override def println(x: Any): Unit = out.append(x).append(";")
30+
})
31+
assertEquals(expected, out.result())
32+
}
33+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
package tests
2+
3+
class SimpleClass

semanticdb/test/tests/SimpleDef.scala

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
package tests
2+
3+
class SimpleDef {
4+
def foo(): Int = 0
5+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
ClassDef("Foo", DefDef("<init>", Nil, List(Nil), TypeTree.Synthetic(), None), List(Term.Apply(Term.Select(Term.New(TypeTree.Synthetic()), "<init>", 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))))))
2+
DefDef("<init>", Nil, List(Nil), TypeTree.Synthetic(), None)
3+
ValDef("foo", TypeTree.TypeIdent("Int"), Some(Term.Literal(Constant.Int(2))))
4+
DefDef("bar", Nil, List(List(ValDef("i", TypeTree.TypeIdent("Int"), None))), TypeTree.TypeIdent("Int"), Some(Term.Literal(Constant.Int(3))))
5+
ValDef("i", TypeTree.TypeIdent("Int"), None)
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
2+
class Foo {
3+
val foo: Int = 2
4+
def bar(i: Int): Int = 3
5+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import scala.tasty.Tasty
2+
import scala.tasty.util.TreeTraverser
3+
import scala.tasty.file._
4+
5+
object Test {
6+
def main(args: Array[String]): Unit = {
7+
ConsumeTasty("", List("Foo"), new DBConsumer)
8+
}
9+
}
10+
11+
class DBConsumer extends TastyConsumer {
12+
13+
final def apply(tasty: Tasty)(root: tasty.Tree): Unit = {
14+
import tasty._
15+
object Traverser extends TreeTraverser[tasty.type](tasty) {
16+
17+
override def traverseTree(tree: Tree)(implicit ctx: Context): Unit = tree match {
18+
case IsDefinition(tree) =>
19+
println(tree.show)
20+
super.traverseTree(tree)
21+
case tree =>
22+
super.traverseTree(tree)
23+
}
24+
25+
}
26+
Traverser.traverseTree(root)(tasty.rootContext)
27+
}
28+
29+
}

0 commit comments

Comments
 (0)