Skip to content

Create infrastructure for SemanticDB #5193

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Oct 7, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -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-semanticdb` = Build.`dotty-semanticdb`
val `scala-library` = Build.`scala-library`
val `scala-compiler` = Build.`scala-compiler`
val `scala-reflect` = Build.`scala-reflect`
Expand Down
23 changes: 23 additions & 0 deletions compiler/src/dotty/tools/dotc/consumetasty/ConsumeTasty.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
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

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 = QuoteDriver.currentClasspath
val args = "-from-tasty" +: "-classpath" +: s"$classpath:$currentClasspath" +: classes
(new Consume).process(args.toArray)
}
}
Original file line number Diff line number Diff line change
@@ -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)
}

}
20 changes: 20 additions & 0 deletions compiler/src/dotty/tools/dotc/consumetasty/TastyFromClass.scala
Original file line number Diff line number Diff line change
@@ -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
}
17 changes: 12 additions & 5 deletions compiler/src/dotty/tools/dotc/quoted/QuoteDriver.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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
}

}
26 changes: 26 additions & 0 deletions library/src/scala/tasty/file/ConsumeTasty.scala
Original file line number Diff line number Diff line change
@@ -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
)
}
}
}
7 changes: 7 additions & 0 deletions library/src/scala/tasty/file/TastyConsumer.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package scala.tasty.file

import scala.tasty.Tasty

trait TastyConsumer {
def apply(tasty: Tasty)(root: tasty.Tree): Unit
}
12 changes: 12 additions & 0 deletions project/Build.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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 / "..",
Expand Down Expand Up @@ -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-semanticdb` = project.in(file("semanticdb")).asDottySemanticDB(Bootstrapped)

// Depend on dotty-library so that sbt projects using dotty automatically
// depend on the dotty-library
lazy val `scala-library` = project.
Expand Down Expand Up @@ -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.
Expand Down
27 changes: 27 additions & 0 deletions semanticdb/src/dotty/semanticdb/DBConsumer.scala
Original file line number Diff line number Diff line change
@@ -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)

}
18 changes: 18 additions & 0 deletions semanticdb/src/dotty/semanticdb/Main.scala
Original file line number Diff line number Diff line change
@@ -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)
}
}
}
33 changes: 33 additions & 0 deletions semanticdb/test/dotty/semanticdb/Tests.scala
Original file line number Diff line number Diff line change
@@ -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-semanticdb/scala-0.10/test-classes"

@Test def testMain(): Unit = {
testOutput(
"tests.SimpleClass",
"SimpleClass;<init>;"
)
testOutput(
"tests.SimpleDef",
"SimpleDef;<init>;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())
}
}
3 changes: 3 additions & 0 deletions semanticdb/test/tests/SimpleClass.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package tests

class SimpleClass
5 changes: 5 additions & 0 deletions semanticdb/test/tests/SimpleDef.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package tests

class SimpleDef {
def foo(): Int = 0
}
5 changes: 5 additions & 0 deletions tests/run-with-compiler/tasty-consumer.check
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
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))))))
DefDef("<init>", 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)
5 changes: 5 additions & 0 deletions tests/run-with-compiler/tasty-consumer/Foo.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@

class Foo {
val foo: Int = 2
def bar(i: Int): Int = 3
}
29 changes: 29 additions & 0 deletions tests/run-with-compiler/tasty-consumer/Test.scala
Original file line number Diff line number Diff line change
@@ -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)
}

}