Skip to content

Support for multi-project find all references #5209

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 11 commits into from
Nov 13, 2018
29 changes: 29 additions & 0 deletions compiler/src/dotty/tools/dotc/interactive/Interactive.scala
Original file line number Diff line number Diff line change
Expand Up @@ -490,4 +490,33 @@ object Interactive {
}
}

/**
* Given `sym`, originating from `sourceDriver`, find its representation in
* `targetDriver`.
*
* @param symbol The symbol to expression in the new driver.
* @param sourceDriver The driver from which `symbol` originates.
* @param targetDriver The driver in which we want to get a representation of `symbol`.
* @return A representation of `symbol` in `targetDriver`.
*/
def localize(symbol: Symbol, sourceDriver: InteractiveDriver, targetDriver: InteractiveDriver): Symbol = {

def in[T](driver: InteractiveDriver)(fn: Context => T): T =
fn(driver.currentCtx)

if (sourceDriver == targetDriver) symbol
else {
val owners = in(sourceDriver) { implicit ctx =>
symbol.ownersIterator.toList.reverse.map(_.name)
}
in(targetDriver) { implicit ctx =>
val base: Symbol = ctx.definitions.RootClass
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this work for symbols in the empty package ? Definition#staticRef has a special case to handle that.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

owners.tail.foldLeft(base) { (prefix, symbolName) =>
if (prefix.exists) prefix.info.member(symbolName).symbol
else NoSymbol
}
}
}
}

}
290 changes: 170 additions & 120 deletions compiler/src/dotty/tools/dotc/interactive/InteractiveDriver.scala
Original file line number Diff line number Diff line change
Expand Up @@ -36,78 +36,27 @@ class InteractiveDriver(val settings: List[String]) extends Driver {
}

private[this] var myCtx: Context = myInitCtx

def currentCtx: Context = myCtx

private val compiler: Compiler = new InteractiveCompiler

private val myOpenedFiles = new mutable.LinkedHashMap[URI, SourceFile] {
override def default(key: URI) = NoSource
}
def openedFiles: Map[URI, SourceFile] = myOpenedFiles

private val myOpenedTrees = new mutable.LinkedHashMap[URI, List[SourceTree]] {
override def default(key: URI) = Nil
}
def openedTrees: Map[URI, List[SourceTree]] = myOpenedTrees

private val myCompilationUnits = new mutable.LinkedHashMap[URI, CompilationUnit]

def openedFiles: Map[URI, SourceFile] = myOpenedFiles
def openedTrees: Map[URI, List[SourceTree]] = myOpenedTrees
def compilationUnits: Map[URI, CompilationUnit] = myCompilationUnits

def allTrees(implicit ctx: Context): List[SourceTree] = allTreesContaining("")

def allTreesContaining(id: String)(implicit ctx: Context): List[SourceTree] = {
val fromSource = openedTrees.values.flatten.toList
val fromClassPath = (dirClassPathClasses ++ zipClassPathClasses).flatMap { cls =>
val className = cls.toTypeName
List(tree(className, id), tree(className.moduleClassName, id)).flatten
}
(fromSource ++ fromClassPath).distinct
}

private def tree(className: TypeName, id: String)(implicit ctx: Context): Option[SourceTree] = {
val clsd = ctx.base.staticRef(className)
clsd match {
case clsd: ClassDenotation =>
clsd.ensureCompleted()
SourceTree.fromSymbol(clsd.symbol.asClass, id)
case _ =>
None
}
}

// Presence of a file with one of these suffixes indicates that the
// corresponding class has been pickled with TASTY.
private val tastySuffixes = List(".hasTasty", ".tasty")

private def classNames(cp: ClassPath, packageName: String): List[String] = {
def className(classSegments: List[String]) =
classSegments.mkString(".").stripSuffix(".class")

val ClassPathEntries(pkgs, classReps) = cp.list(packageName)

classReps
.filter((classRep: ClassRepresentation) => classRep.binary match {
case None =>
true
case Some(binFile) =>
val prefix =
if (binFile.name.endsWith(".class"))
binFile.name.stripSuffix(".class")
else
null
prefix != null && {
binFile match {
case pf: PlainFile =>
tastySuffixes.map(suffix => pf.givenPath.parent / (prefix + suffix)).exists(_.exists)
case _ =>
sys.error(s"Unhandled file type: $binFile [getClass = ${binFile.getClass}]")
}
}
})
.map(classRep => (packageName ++ (if (packageName != "") "." else "") ++ classRep.name)).toList ++
pkgs.flatMap(pkg => classNames(cp, pkg.name))
}

// FIXME: All the code doing classpath handling is very fragile and ugly,
// improving this requires changing the dotty classpath APIs to handle our usecases.
// We also need something like sbt server-mode to be informed of changes on
Expand All @@ -128,46 +77,173 @@ class InteractiveDriver(val settings: List[String]) extends Driver {
}

// Like in `ZipArchiveFileLookup` we assume that zips are immutable
private val zipClassPathClasses: Seq[String] = zipClassPaths.flatMap { zipCp =>
val zipFile = new ZipFile(zipCp.zipFile)
private val zipClassPathClasses: Seq[TypeName] = {
val names = new mutable.ListBuffer[TypeName]
zipClassPaths.foreach { zipCp =>
val zipFile = new ZipFile(zipCp.zipFile)
classesFromZip(zipFile, names)
}
names
}

initialize()

/**
* The trees for all the source files in this project.
*
* This includes the trees for the buffers that are presently open in the IDE, and the trees
* from the target directory.
*/
def sourceTrees(implicit ctx: Context): List[SourceTree] = sourceTreesContaining("")

/**
* The trees for all the source files in this project that contain `id`.
*
* This includes the trees for the buffers that are presently open in the IDE, and the trees
* from the target directory.
*/
def sourceTreesContaining(id: String)(implicit ctx: Context): List[SourceTree] = {
val fromBuffers = openedTrees.values.flatten.toList
val fromCompilationOutput = {
val classNames = new mutable.ListBuffer[TypeName]
val output = ctx.settings.outputDir.value
if (output.isDirectory) {
classesFromDir(output.jpath, classNames)
} else {
val zipFile = new ZipFile(output.file)
classesFromZip(zipFile, classNames)
}
classNames.flatMap { cls =>
treesFromClassName(cls, id)
}
}
(fromBuffers ++ fromCompilationOutput).distinct
}

/**
* All the trees for this project.
*
* This includes the trees of the sources of this project, along with the trees that are found
* on this project's classpath.
*/
def allTrees(implicit ctx: Context): List[SourceTree] = allTreesContaining("")

/**
* All the trees for this project that contain `id`.
*
* This includes the trees of the sources of this project, along with the trees that are found
* on this project's classpath.
*/
def allTreesContaining(id: String)(implicit ctx: Context): List[SourceTree] = {
val fromSource = openedTrees.values.flatten.toList
val fromClassPath = (dirClassPathClasses ++ zipClassPathClasses).flatMap { cls =>
treesFromClassName(cls, id)
}
(fromSource ++ fromClassPath).distinct
}

def run(uri: URI, sourceCode: String): List[MessageContainer] = run(uri, toSource(uri, sourceCode))

def run(uri: URI, source: SourceFile): List[MessageContainer] = {
val previousCtx = myCtx
try {
for {
entry <- zipFile.stream.toArray((size: Int) => new Array[ZipEntry](size))
name = entry.getName
tastySuffix <- tastySuffixes.find(name.endsWith)
} yield name.replace("/", ".").stripSuffix(tastySuffix)
val reporter =
new StoreReporter(null) with UniqueMessagePositions with HideNonSensicalMessages

val run = compiler.newRun(myInitCtx.fresh.setReporter(reporter))
myCtx = run.runContext

implicit val ctx = myCtx

myOpenedFiles(uri) = source

run.compileSources(List(source))
run.printSummary()
val unit = ctx.run.units.head
val t = unit.tpdTree
cleanup(t)
myOpenedTrees(uri) = topLevelClassTrees(t, source)
myCompilationUnits(uri) = unit

reporter.removeBufferedMessages
}
catch {
case ex: FatalError =>
myCtx = previousCtx
close(uri)
Nil
}
}

def close(uri: URI): Unit = {
myOpenedFiles.remove(uri)
myOpenedTrees.remove(uri)
myCompilationUnits.remove(uri)
}

/**
* The `SourceTree`s that define the class `className` and/or module `className`.
*
* @see SourceTree.fromSymbol
*/
private def treesFromClassName(className: TypeName, id: String)(implicit ctx: Context): List[SourceTree] = {
def tree(className: TypeName, id: String): Option[SourceTree] = {
val clsd = ctx.base.staticRef(className)
clsd match {
case clsd: ClassDenotation =>
clsd.ensureCompleted()
SourceTree.fromSymbol(clsd.symbol.asClass, id)
case _ =>
None
}
}
finally zipFile.close()
List(tree(className, id), tree(className.moduleClassName, id)).flatten
}

// FIXME: classfiles in directories may change at any point, so we retraverse
// the directories each time, if we knew when classfiles changed (sbt
// server-mode might help here), we could do cache invalidation instead.
private def dirClassPathClasses: Seq[String] = {
val names = new mutable.ListBuffer[String]
private def dirClassPathClasses: Seq[TypeName] = {
val names = new mutable.ListBuffer[TypeName]
dirClassPaths.foreach { dirCp =>
val root = dirCp.dir.toPath
try
Files.walkFileTree(root, new SimpleFileVisitor[Path] {
override def visitFile(path: Path, attrs: BasicFileAttributes) = {
if (!attrs.isDirectory) {
val name = path.getFileName.toString
for {
tastySuffix <- tastySuffixes
if name.endsWith(tastySuffix)
} {
names += root.relativize(path).toString.replace("/", ".").stripSuffix(tastySuffix)
}
classesFromDir(root, names)
}
names
}

/** Adds the names of the classes that are defined in `zipFile` to `buffer`. */
private def classesFromZip(zipFile: ZipFile, buffer: mutable.ListBuffer[TypeName]): Unit = {
try {
for {
entry <- zipFile.stream.toArray((size: Int) => new Array[ZipEntry](size))
name = entry.getName
tastySuffix <- tastySuffixes.find(name.endsWith)
} buffer += name.replace("/", ".").stripSuffix(tastySuffix).toTypeName
}
finally zipFile.close()
}

/** Adds the names of the classes that are defined in `dir` to `buffer`. */
private def classesFromDir(dir: Path, buffer: mutable.ListBuffer[TypeName]): Unit = {
try
Files.walkFileTree(dir, new SimpleFileVisitor[Path] {
override def visitFile(path: Path, attrs: BasicFileAttributes) = {
if (!attrs.isDirectory) {
val name = path.getFileName.toString
for {
tastySuffix <- tastySuffixes
if name.endsWith(tastySuffix)
} {
buffer += dir.relativize(path).toString.replace("/", ".").stripSuffix(tastySuffix).toTypeName
}
FileVisitResult.CONTINUE
}
})
catch {
case _: NoSuchFileException =>
}
FileVisitResult.CONTINUE
}
})
catch {
case _: NoSuchFileException =>
}
names.toList
}

private def topLevelClassTrees(topTree: Tree, source: SourceFile): List[SourceTree] = {
Expand All @@ -185,8 +261,6 @@ class InteractiveDriver(val settings: List[String]) extends Driver {
trees.toList
}

private val compiler: Compiler = new InteractiveCompiler

/** Remove attachments and error out completers. The goal is to avoid
* having a completer hanging in a typed tree which can capture the context
* of a previous run. Note that typed trees can have untyped or partially
Expand Down Expand Up @@ -224,44 +298,20 @@ class InteractiveDriver(val settings: List[String]) extends Driver {
new SourceFile(virtualFile, Codec.UTF8)
}

def run(uri: URI, sourceCode: String): List[MessageContainer] = run(uri, toSource(uri, sourceCode))

def run(uri: URI, source: SourceFile): List[MessageContainer] = {
val previousCtx = myCtx
try {
val reporter =
new StoreReporter(null) with UniqueMessagePositions with HideNonSensicalMessages

val run = compiler.newRun(myInitCtx.fresh.setReporter(reporter))
myCtx = run.runContext

implicit val ctx = myCtx

myOpenedFiles(uri) = source

run.compileSources(List(source))
run.printSummary()
val unit = ctx.run.units.head
val t = unit.tpdTree
cleanup(t)
myOpenedTrees(uri) = topLevelClassTrees(t, source)
myCompilationUnits(uri) = unit

reporter.removeBufferedMessages
}
catch {
case ex: FatalError =>
myCtx = previousCtx
close(uri)
Nil
}
/**
* Initialize this driver and compiler.
*
* This is necessary because an `InteractiveDriver` can be put to work without having
* compiled anything (for instance, resolving a symbol coming from a different compiler in
* this compiler). In those cases, an un-initialized compiler may crash (for instance if
* late-compilation is needed).
*/
private[this] def initialize(): Unit = {
val run = compiler.newRun(myInitCtx.fresh)
myCtx = run.runContext
run.compileUnits(Nil, myCtx)
}

def close(uri: URI): Unit = {
myOpenedFiles.remove(uri)
myOpenedTrees.remove(uri)
myCompilationUnits.remove(uri)
}
}

object InteractiveDriver {
Expand Down
Loading