Skip to content
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