From 087fdd0fdb0a2440dfe69c4039b83147e30f44a7 Mon Sep 17 00:00:00 2001 From: Jamie Thompson Date: Fri, 21 Jul 2023 10:29:51 +0200 Subject: [PATCH 1/6] reduce file-io in extractDependencies --- .../tools/dotc/classpath/FileUtils.scala | 4 ++++ .../tools/dotc/sbt/ExtractDependencies.scala | 22 ++++++++++--------- compiler/src/dotty/tools/io/PlainFile.scala | 11 ++++++++-- 3 files changed, 25 insertions(+), 12 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/classpath/FileUtils.scala b/compiler/src/dotty/tools/dotc/classpath/FileUtils.scala index 481419cc87d8..4d34cf3b4351 100644 --- a/compiler/src/dotty/tools/dotc/classpath/FileUtils.scala +++ b/compiler/src/dotty/tools/dotc/classpath/FileUtils.scala @@ -20,6 +20,10 @@ object FileUtils { def isClass: Boolean = !file.isDirectory && file.hasExtension("class") && !file.name.endsWith("$class.class") // FIXME: drop last condition when we stop being compatible with Scala 2.11 + def isClassExtension: Boolean = file.hasExtension("class") + + def isTastyExtension: Boolean = file.hasExtension("tasty") + def isTasty: Boolean = !file.isDirectory && file.hasExtension("tasty") def isScalaBinary: Boolean = file.isClass || file.isTasty diff --git a/compiler/src/dotty/tools/dotc/sbt/ExtractDependencies.scala b/compiler/src/dotty/tools/dotc/sbt/ExtractDependencies.scala index ef9c939c2ea7..8bf3f729336c 100644 --- a/compiler/src/dotty/tools/dotc/sbt/ExtractDependencies.scala +++ b/compiler/src/dotty/tools/dotc/sbt/ExtractDependencies.scala @@ -8,7 +8,7 @@ import java.nio.file.Path import java.util.{Arrays, EnumSet} import dotty.tools.dotc.ast.tpd -import dotty.tools.dotc.classpath.FileUtils.{isTasty, isClass} +import dotty.tools.dotc.classpath.FileUtils.{isTasty, isClassExtension, isTastyExtension} import dotty.tools.dotc.core.Contexts._ import dotty.tools.dotc.core.Decorators._ import dotty.tools.dotc.core.Flags._ @@ -458,21 +458,23 @@ class DependencyRecorder { } val depFile = dep.toClass.associatedFile - if (depFile != null) { + if depFile != null then { // Cannot ignore inheritance relationship coming from the same source (see sbt/zinc#417) def allowLocal = dep.context == DependencyByInheritance || dep.context == LocalDependencyByInheritance - val depClassFile = - if depFile.isClass then depFile - else depFile.resolveSibling(dep.toClass.binaryClassName + ".class") - if (depClassFile != null) { - // Dependency is external -- source is undefined - processExternalDependency(depClassFile, dep.toClass.binaryClassName) - } else if (allowLocal || depFile != sourceFile.file) { + if depFile.isTastyExtension then + val depClassFile = depFile.resolveSibling(depFile.name.stripSuffix(".tasty") + ".class") + if depClassFile != null then + // did not find associated class file, e.g. for a TASTy-only classpath. + // The file that Zinc recieves with binaryDependency is used to lookup any either any + // generated non-local classes or produced xsbti.API associated with the file. + processExternalDependency(depClassFile, dep.toClass.binaryClassName) + else if depFile.isClassExtension then + processExternalDependency(depFile, dep.toClass.binaryClassName) + else if allowLocal || depFile != sourceFile.file then // We cannot ignore dependencies coming from the same source file because // the dependency info needs to propagate. See source-dependencies/trait-trait-211. val toClassName = classNameAsString(dep.toClass) cb.classDependency(toClassName, fromClassName, dep.context) - } } } diff --git a/compiler/src/dotty/tools/io/PlainFile.scala b/compiler/src/dotty/tools/io/PlainFile.scala index 898e037488f7..acef191d3072 100644 --- a/compiler/src/dotty/tools/io/PlainFile.scala +++ b/compiler/src/dotty/tools/io/PlainFile.scala @@ -102,8 +102,15 @@ class PlainFile(val givenPath: Path) extends AbstractFile { */ def lookupName(name: String, directory: Boolean): AbstractFile = { val child = givenPath / name - if ((child.isDirectory && directory) || (child.isFile && !directory)) new PlainFile(child) - else null + if directory then + if child.isDirectory /* IO! */ then + new PlainFile(child) + else + null + else if child.isFile /* IO! */ then + new PlainFile(child) + else + null } /** Does this abstract file denote an existing file? */ From 6f37050d50e7851a39ee78259443af546730da76 Mon Sep 17 00:00:00 2001 From: Jamie Thompson Date: Fri, 21 Jul 2023 16:58:59 +0200 Subject: [PATCH 2/6] cache associated classfile of tasty --- compiler/src/dotty/tools/dotc/core/Contexts.scala | 15 +++++++++++++++ .../src/dotty/tools/dotc/core/SymbolLoaders.scala | 7 +++---- .../tools/dotc/sbt/ExtractDependencies.scala | 6 +++--- compiler/src/dotty/tools/io/AbstractFile.scala | 2 +- compiler/src/dotty/tools/io/ZipArchive.scala | 3 +-- 5 files changed, 23 insertions(+), 10 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/Contexts.scala b/compiler/src/dotty/tools/dotc/core/Contexts.scala index 8a7f2ff4e051..e02f4166f58b 100644 --- a/compiler/src/dotty/tools/dotc/core/Contexts.scala +++ b/compiler/src/dotty/tools/dotc/core/Contexts.scala @@ -272,6 +272,19 @@ object Contexts { /** AbstractFile with given path, memoized */ def getFile(name: String): AbstractFile = getFile(name.toTermName) + def getSiblingClassfile(tastyFile: AbstractFile): AbstractFile = + base.siblingClassfiles.getOrElseUpdate(tastyFile, { + val classfile0 = tastyFile.resolveSibling(tastyFile.name.stripSuffix(".tasty") + ".class") + if classfile0 == null then + val classfile = tastyFile.resolveSibling(tastyFile.name.stripSuffix(".tasty") + "$.class") + if classfile == null then + NoAbstractFile + else + classfile + else + classfile0 + }) + private var related: SimpleIdentityMap[Phase | SourceFile, Context] | Null = null private def lookup(key: Phase | SourceFile): Context | Null = @@ -948,6 +961,7 @@ object Contexts { /** Sources and Files that were loaded */ val sources: util.HashMap[AbstractFile, SourceFile] = util.HashMap[AbstractFile, SourceFile]() val files: util.HashMap[TermName, AbstractFile] = util.HashMap() + val siblingClassfiles: util.HashMap[AbstractFile, AbstractFile] = util.HashMap() // Types state /** A table for hash consing unique types */ @@ -1052,6 +1066,7 @@ object Contexts { errorTypeMsg.clear() sources.clear() files.clear() + siblingClassfiles.clear() comparers.clear() // forces re-evaluation of top and bottom classes in TypeComparer // Test that access is single threaded diff --git a/compiler/src/dotty/tools/dotc/core/SymbolLoaders.scala b/compiler/src/dotty/tools/dotc/core/SymbolLoaders.scala index 734688ea2ec8..ee66ed49abe5 100644 --- a/compiler/src/dotty/tools/dotc/core/SymbolLoaders.scala +++ b/compiler/src/dotty/tools/dotc/core/SymbolLoaders.scala @@ -432,10 +432,9 @@ class TastyLoader(val tastyFile: AbstractFile) extends SymbolLoader { private def checkTastyUUID(tastyFile: AbstractFile, tastyBytes: Array[Byte])(using Context): Unit = - var classfile = tastyFile.resolveSibling(tastyFile.name.stripSuffix(".tasty") + ".class") - if classfile == null then - classfile = tastyFile.resolveSibling(tastyFile.name.stripSuffix(".tasty") + "$.class") - if classfile != null then + import dotty.tools.io.NoAbstractFile + val classfile = ctx.getSiblingClassfile(tastyFile) + if classfile != NoAbstractFile then val tastyUUID = new TastyHeaderUnpickler(tastyBytes).readHeader() new ClassfileTastyUUIDParser(classfile)(ctx).checkTastyUUID(tastyUUID) else diff --git a/compiler/src/dotty/tools/dotc/sbt/ExtractDependencies.scala b/compiler/src/dotty/tools/dotc/sbt/ExtractDependencies.scala index 8bf3f729336c..c2cd66c6b74b 100644 --- a/compiler/src/dotty/tools/dotc/sbt/ExtractDependencies.scala +++ b/compiler/src/dotty/tools/dotc/sbt/ExtractDependencies.scala @@ -21,7 +21,7 @@ import dotty.tools.dotc.core.Types._ import dotty.tools.dotc.transform.SymUtils._ import dotty.tools.dotc.util.{SrcPos, NoSourcePosition} import dotty.tools.io -import dotty.tools.io.{AbstractFile, PlainFile, ZipArchive} +import dotty.tools.io.{AbstractFile, PlainFile, ZipArchive, NoAbstractFile} import xsbti.UseScope import xsbti.api.DependencyContext import xsbti.api.DependencyContext._ @@ -462,8 +462,8 @@ class DependencyRecorder { // Cannot ignore inheritance relationship coming from the same source (see sbt/zinc#417) def allowLocal = dep.context == DependencyByInheritance || dep.context == LocalDependencyByInheritance if depFile.isTastyExtension then - val depClassFile = depFile.resolveSibling(depFile.name.stripSuffix(".tasty") + ".class") - if depClassFile != null then + val depClassFile = ctx.getSiblingClassfile(depFile) + if depClassFile != NoAbstractFile then // did not find associated class file, e.g. for a TASTy-only classpath. // The file that Zinc recieves with binaryDependency is used to lookup any either any // generated non-local classes or produced xsbti.API associated with the file. diff --git a/compiler/src/dotty/tools/io/AbstractFile.scala b/compiler/src/dotty/tools/io/AbstractFile.scala index 09779953fc76..2c20461b5f5c 100644 --- a/compiler/src/dotty/tools/io/AbstractFile.scala +++ b/compiler/src/dotty/tools/io/AbstractFile.scala @@ -253,7 +253,7 @@ abstract class AbstractFile extends Iterable[AbstractFile] { /** Returns the sibling abstract file in the parent of this abstract file or directory. * If there is no such file, returns `null`. */ - def resolveSibling(name: String): AbstractFile | Null = + final def resolveSibling(name: String): AbstractFile | Null = container.lookupName(name, directory = false) private def fileOrSubdirectoryNamed(name: String, isDir: Boolean): AbstractFile = diff --git a/compiler/src/dotty/tools/io/ZipArchive.scala b/compiler/src/dotty/tools/io/ZipArchive.scala index 0fb2a84b0579..e28b7de7983d 100644 --- a/compiler/src/dotty/tools/io/ZipArchive.scala +++ b/compiler/src/dotty/tools/io/ZipArchive.scala @@ -72,8 +72,7 @@ abstract class ZipArchive(override val jpath: JPath, release: Option[String]) ex // have to keep this name for compat with sbt's compiler-interface def getArchive: ZipFile = null override def underlyingSource: Option[ZipArchive] = Some(self) - override def resolveSibling(name: String): AbstractFile = - parent.lookupName(name, directory = false) + override def container: Entry = parent override def toString: String = self.path + "(" + path + ")" } From 1bc36a64f7bb2e88a3dbe973ce3a1c368205c585 Mon Sep 17 00:00:00 2001 From: Jamie Thompson Date: Mon, 24 Jul 2023 11:15:51 +0200 Subject: [PATCH 3/6] compute class name once --- compiler/src/dotty/tools/dotc/core/Contexts.scala | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/Contexts.scala b/compiler/src/dotty/tools/dotc/core/Contexts.scala index e02f4166f58b..a35a74792a7b 100644 --- a/compiler/src/dotty/tools/dotc/core/Contexts.scala +++ b/compiler/src/dotty/tools/dotc/core/Contexts.scala @@ -274,9 +274,10 @@ object Contexts { def getSiblingClassfile(tastyFile: AbstractFile): AbstractFile = base.siblingClassfiles.getOrElseUpdate(tastyFile, { - val classfile0 = tastyFile.resolveSibling(tastyFile.name.stripSuffix(".tasty") + ".class") + val className = tastyFile.name.stripSuffix(".tasty") + val classfile0 = tastyFile.resolveSibling(className + ".class") if classfile0 == null then - val classfile = tastyFile.resolveSibling(tastyFile.name.stripSuffix(".tasty") + "$.class") + val classfile = tastyFile.resolveSibling(className + "$.class") if classfile == null then NoAbstractFile else From 529eaf4ac0460af5e04ffbabb4817f0dff2369cd Mon Sep 17 00:00:00 2001 From: Jamie Thompson Date: Mon, 24 Jul 2023 11:31:07 +0200 Subject: [PATCH 4/6] try only plain classfile --- compiler/src/dotty/tools/dotc/core/Contexts.scala | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/Contexts.scala b/compiler/src/dotty/tools/dotc/core/Contexts.scala index a35a74792a7b..cff7f26fc25c 100644 --- a/compiler/src/dotty/tools/dotc/core/Contexts.scala +++ b/compiler/src/dotty/tools/dotc/core/Contexts.scala @@ -277,11 +277,7 @@ object Contexts { val className = tastyFile.name.stripSuffix(".tasty") val classfile0 = tastyFile.resolveSibling(className + ".class") if classfile0 == null then - val classfile = tastyFile.resolveSibling(className + "$.class") - if classfile == null then - NoAbstractFile - else - classfile + NoAbstractFile else classfile0 }) From ea7583643bd7cf2b547733ec0d395bfafafc4c87 Mon Sep 17 00:00:00 2001 From: Jamie Thompson Date: Mon, 31 Jul 2023 17:59:45 +0200 Subject: [PATCH 5/6] revert context cache, use local cache, always give classfile --- .../tools/dotc/classpath/FileUtils.scala | 8 ++-- .../src/dotty/tools/dotc/core/Contexts.scala | 12 ------ .../dotty/tools/dotc/core/SymbolLoaders.scala | 7 ++-- .../tools/dotc/sbt/ExtractDependencies.scala | 41 ++++++++++++------- compiler/src/dotty/tools/io/PlainFile.scala | 5 +++ 5 files changed, 40 insertions(+), 33 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/classpath/FileUtils.scala b/compiler/src/dotty/tools/dotc/classpath/FileUtils.scala index 4d34cf3b4351..8c31faa43186 100644 --- a/compiler/src/dotty/tools/dotc/classpath/FileUtils.scala +++ b/compiler/src/dotty/tools/dotc/classpath/FileUtils.scala @@ -17,14 +17,14 @@ object FileUtils { extension (file: AbstractFile) { def isPackage: Boolean = file.isDirectory && mayBeValidPackage(file.name) - def isClass: Boolean = !file.isDirectory && file.hasExtension("class") && !file.name.endsWith("$class.class") + def isClass: Boolean = !file.isDirectory && hasClassExtension && !file.name.endsWith("$class.class") // FIXME: drop last condition when we stop being compatible with Scala 2.11 - def isClassExtension: Boolean = file.hasExtension("class") + def hasClassExtension: Boolean = file.hasExtension("class") - def isTastyExtension: Boolean = file.hasExtension("tasty") + def hasTastyExtension: Boolean = file.hasExtension("tasty") - def isTasty: Boolean = !file.isDirectory && file.hasExtension("tasty") + def isTasty: Boolean = !file.isDirectory && hasTastyExtension def isScalaBinary: Boolean = file.isClass || file.isTasty diff --git a/compiler/src/dotty/tools/dotc/core/Contexts.scala b/compiler/src/dotty/tools/dotc/core/Contexts.scala index cff7f26fc25c..8a7f2ff4e051 100644 --- a/compiler/src/dotty/tools/dotc/core/Contexts.scala +++ b/compiler/src/dotty/tools/dotc/core/Contexts.scala @@ -272,16 +272,6 @@ object Contexts { /** AbstractFile with given path, memoized */ def getFile(name: String): AbstractFile = getFile(name.toTermName) - def getSiblingClassfile(tastyFile: AbstractFile): AbstractFile = - base.siblingClassfiles.getOrElseUpdate(tastyFile, { - val className = tastyFile.name.stripSuffix(".tasty") - val classfile0 = tastyFile.resolveSibling(className + ".class") - if classfile0 == null then - NoAbstractFile - else - classfile0 - }) - private var related: SimpleIdentityMap[Phase | SourceFile, Context] | Null = null private def lookup(key: Phase | SourceFile): Context | Null = @@ -958,7 +948,6 @@ object Contexts { /** Sources and Files that were loaded */ val sources: util.HashMap[AbstractFile, SourceFile] = util.HashMap[AbstractFile, SourceFile]() val files: util.HashMap[TermName, AbstractFile] = util.HashMap() - val siblingClassfiles: util.HashMap[AbstractFile, AbstractFile] = util.HashMap() // Types state /** A table for hash consing unique types */ @@ -1063,7 +1052,6 @@ object Contexts { errorTypeMsg.clear() sources.clear() files.clear() - siblingClassfiles.clear() comparers.clear() // forces re-evaluation of top and bottom classes in TypeComparer // Test that access is single threaded diff --git a/compiler/src/dotty/tools/dotc/core/SymbolLoaders.scala b/compiler/src/dotty/tools/dotc/core/SymbolLoaders.scala index ee66ed49abe5..3969a09a69ee 100644 --- a/compiler/src/dotty/tools/dotc/core/SymbolLoaders.scala +++ b/compiler/src/dotty/tools/dotc/core/SymbolLoaders.scala @@ -432,9 +432,10 @@ class TastyLoader(val tastyFile: AbstractFile) extends SymbolLoader { private def checkTastyUUID(tastyFile: AbstractFile, tastyBytes: Array[Byte])(using Context): Unit = - import dotty.tools.io.NoAbstractFile - val classfile = ctx.getSiblingClassfile(tastyFile) - if classfile != NoAbstractFile then + val classfile = + val className = tastyFile.name.stripSuffix(".tasty") + tastyFile.resolveSibling(className + ".class") + if classfile != null then val tastyUUID = new TastyHeaderUnpickler(tastyBytes).readHeader() new ClassfileTastyUUIDParser(classfile)(ctx).checkTastyUUID(tastyUUID) else diff --git a/compiler/src/dotty/tools/dotc/sbt/ExtractDependencies.scala b/compiler/src/dotty/tools/dotc/sbt/ExtractDependencies.scala index c2cd66c6b74b..5417a16a3aeb 100644 --- a/compiler/src/dotty/tools/dotc/sbt/ExtractDependencies.scala +++ b/compiler/src/dotty/tools/dotc/sbt/ExtractDependencies.scala @@ -8,7 +8,7 @@ import java.nio.file.Path import java.util.{Arrays, EnumSet} import dotty.tools.dotc.ast.tpd -import dotty.tools.dotc.classpath.FileUtils.{isTasty, isClassExtension, isTastyExtension} +import dotty.tools.dotc.classpath.FileUtils.{isTasty, hasClassExtension, hasTastyExtension} import dotty.tools.dotc.core.Contexts._ import dotty.tools.dotc.core.Decorators._ import dotty.tools.dotc.core.Flags._ @@ -424,10 +424,28 @@ class DependencyRecorder { classDependencies.foreach(recordClassDependency(cb, _)) clear() + private val _siblingClassfiles = new mutable.HashMap[PlainFile, Path] + + extension (pf: PlainFile) + /**Constructs a sibling class to the `jpath`. + * Does not validate if it exists as a real file. + * The way this works is that by the end of compilation analysis, + * there should be a corresponding NonLocalClass sent to zinc with the same class file name. + * + * FIXME: we still need a way to resolve the correct classfile when we split tasty and classes between + * different outputs (e.g. stdlib-bootstrapped). + */ + private def siblingClass: Path = + _siblingClassfiles.getOrElseUpdate(pf, { + val jpath = pf.jpath + jpath.getParent.resolve(jpath.getFileName.toString.stripSuffix(".tasty") + ".class") + }) + /** Clear all state. */ def clear(): Unit = _usedNames.clear() _classDependencies.clear() + _siblingClassfiles.clear() lastOwner = NoSymbol lastDepSource = NoSymbol _responsibleForImports = NoSymbol @@ -440,10 +458,10 @@ class DependencyRecorder { val fromClassName = classNameAsString(dep.fromClass) val sourceFile = ctx.compilationUnit.source - def binaryDependency(file: Path, binaryClassName: String) = - cb.binaryDependency(file, binaryClassName, fromClassName, sourceFile, dep.context) + def binaryDependency(path: Path, binaryClassName: String) = + cb.binaryDependency(path, binaryClassName, fromClassName, sourceFile, dep.context) - def processExternalDependency(depFile: AbstractFile, binaryClassName: String) = { + def processExternalDependency(depFile: AbstractFile, binaryClassName: String, convertTasty: Boolean) = { depFile match { case ze: ZipArchive#Entry => // The dependency comes from a JAR ze.underlyingSource match @@ -451,7 +469,7 @@ class DependencyRecorder { binaryDependency(zip.jpath, binaryClassName) case _ => case pf: PlainFile => // The dependency comes from a class file, Zinc handles JRT filesystem - binaryDependency(pf.jpath, binaryClassName) + binaryDependency(if convertTasty then pf.siblingClass else pf.jpath, binaryClassName) case _ => internalError(s"Ignoring dependency $depFile of unknown class ${depFile.getClass}}", dep.fromClass.srcPos) } @@ -461,15 +479,10 @@ class DependencyRecorder { if depFile != null then { // Cannot ignore inheritance relationship coming from the same source (see sbt/zinc#417) def allowLocal = dep.context == DependencyByInheritance || dep.context == LocalDependencyByInheritance - if depFile.isTastyExtension then - val depClassFile = ctx.getSiblingClassfile(depFile) - if depClassFile != NoAbstractFile then - // did not find associated class file, e.g. for a TASTy-only classpath. - // The file that Zinc recieves with binaryDependency is used to lookup any either any - // generated non-local classes or produced xsbti.API associated with the file. - processExternalDependency(depClassFile, dep.toClass.binaryClassName) - else if depFile.isClassExtension then - processExternalDependency(depFile, dep.toClass.binaryClassName) + if depFile.hasTastyExtension then + processExternalDependency(depFile, dep.toClass.binaryClassName, convertTasty = true) + else if depFile.hasClassExtension then + processExternalDependency(depFile, dep.toClass.binaryClassName, convertTasty = false) else if allowLocal || depFile != sourceFile.file then // We cannot ignore dependencies coming from the same source file because // the dependency info needs to propagate. See source-dependencies/trait-trait-211. diff --git a/compiler/src/dotty/tools/io/PlainFile.scala b/compiler/src/dotty/tools/io/PlainFile.scala index acef191d3072..1c021bd2f7a5 100644 --- a/compiler/src/dotty/tools/io/PlainFile.scala +++ b/compiler/src/dotty/tools/io/PlainFile.scala @@ -113,6 +113,11 @@ class PlainFile(val givenPath: Path) extends AbstractFile { null } + final def fakeSibling(name: String): AbstractFile = { + val child = givenPath.parent / name + new PlainFile(child) + } + /** Does this abstract file denote an existing file? */ def create(): Unit = if (!exists) givenPath.createFile() From 2dfc28ff27c14e33cf110456a7ddb6b596fb4525 Mon Sep 17 00:00:00 2001 From: Jamie Thompson Date: Wed, 2 Aug 2023 13:42:49 +0200 Subject: [PATCH 6/6] add todo, address comments --- .../tools/dotc/sbt/ExtractDependencies.scala | 96 ++++++++++--------- .../src/dotty/tools/io/AbstractFile.scala | 4 +- compiler/src/dotty/tools/io/PlainFile.scala | 5 - 3 files changed, 55 insertions(+), 50 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/sbt/ExtractDependencies.scala b/compiler/src/dotty/tools/dotc/sbt/ExtractDependencies.scala index 5417a16a3aeb..01a3362c659a 100644 --- a/compiler/src/dotty/tools/dotc/sbt/ExtractDependencies.scala +++ b/compiler/src/dotty/tools/dotc/sbt/ExtractDependencies.scala @@ -421,72 +421,80 @@ class DependencyRecorder { usedNames.names.foreach: case (usedName, scopes) => cb.usedName(className, usedName.toString, scopes) - classDependencies.foreach(recordClassDependency(cb, _)) + val siblingClassfiles = new mutable.HashMap[PlainFile, Path] + classDependencies.foreach(recordClassDependency(cb, _, siblingClassfiles)) clear() - private val _siblingClassfiles = new mutable.HashMap[PlainFile, Path] + /** Clear all state. */ + def clear(): Unit = + _usedNames.clear() + _classDependencies.clear() + lastOwner = NoSymbol + lastDepSource = NoSymbol + _responsibleForImports = NoSymbol + + /** Handles dependency on given symbol by trying to figure out if represents a term + * that is coming from either source code (not necessarily compiled in this compilation + * run) or from class file and calls respective callback method. + */ + private def recordClassDependency(cb: interfaces.IncrementalCallback, dep: ClassDependency, + siblingClassfiles: mutable.Map[PlainFile, Path])(using Context): Unit = { + val fromClassName = classNameAsString(dep.fromClass) + val sourceFile = ctx.compilationUnit.source - extension (pf: PlainFile) - /**Constructs a sibling class to the `jpath`. + /**For a `.tasty` file, constructs a sibling class to the `jpath`. * Does not validate if it exists as a real file. + * + * Because classpath scanning looks for tasty files first, `dep.fromClass` will be + * associated to a `.tasty` file. However Zinc records all dependencies either based on `.jar` or `.class` files, + * where classes are in directories on the filesystem. + * + * So if the dependency comes from an upstream `.tasty` file and it was not packaged in a jar, then + * we need to call this to resolve the classfile that will eventually exist at runtime. + * * The way this works is that by the end of compilation analysis, - * there should be a corresponding NonLocalClass sent to zinc with the same class file name. + * we should have called `cb.generatedNonLocalClass` with the same class file name. * * FIXME: we still need a way to resolve the correct classfile when we split tasty and classes between * different outputs (e.g. stdlib-bootstrapped). */ - private def siblingClass: Path = - _siblingClassfiles.getOrElseUpdate(pf, { + def cachedSiblingClass(pf: PlainFile): Path = + siblingClassfiles.getOrElseUpdate(pf, { val jpath = pf.jpath jpath.getParent.resolve(jpath.getFileName.toString.stripSuffix(".tasty") + ".class") }) - /** Clear all state. */ - def clear(): Unit = - _usedNames.clear() - _classDependencies.clear() - _siblingClassfiles.clear() - lastOwner = NoSymbol - lastDepSource = NoSymbol - _responsibleForImports = NoSymbol - - /** Handles dependency on given symbol by trying to figure out if represents a term - * that is coming from either source code (not necessarily compiled in this compilation - * run) or from class file and calls respective callback method. - */ - private def recordClassDependency(cb: interfaces.IncrementalCallback, dep: ClassDependency)(using Context): Unit = { - val fromClassName = classNameAsString(dep.fromClass) - val sourceFile = ctx.compilationUnit.source - def binaryDependency(path: Path, binaryClassName: String) = cb.binaryDependency(path, binaryClassName, fromClassName, sourceFile, dep.context) - def processExternalDependency(depFile: AbstractFile, binaryClassName: String, convertTasty: Boolean) = { - depFile match { - case ze: ZipArchive#Entry => // The dependency comes from a JAR - ze.underlyingSource match - case Some(zip) if zip.jpath != null => - binaryDependency(zip.jpath, binaryClassName) - case _ => - case pf: PlainFile => // The dependency comes from a class file, Zinc handles JRT filesystem - binaryDependency(if convertTasty then pf.siblingClass else pf.jpath, binaryClassName) - case _ => - internalError(s"Ignoring dependency $depFile of unknown class ${depFile.getClass}}", dep.fromClass.srcPos) - } - } - - val depFile = dep.toClass.associatedFile + val depClass = dep.toClass + val depFile = depClass.associatedFile if depFile != null then { // Cannot ignore inheritance relationship coming from the same source (see sbt/zinc#417) def allowLocal = dep.context == DependencyByInheritance || dep.context == LocalDependencyByInheritance - if depFile.hasTastyExtension then - processExternalDependency(depFile, dep.toClass.binaryClassName, convertTasty = true) - else if depFile.hasClassExtension then - processExternalDependency(depFile, dep.toClass.binaryClassName, convertTasty = false) + val isTasty = depFile.hasTastyExtension + + def processExternalDependency() = { + val binaryClassName = depClass.binaryClassName + depFile match { + case ze: ZipArchive#Entry => // The dependency comes from a JAR + ze.underlyingSource match + case Some(zip) if zip.jpath != null => + binaryDependency(zip.jpath, binaryClassName) + case _ => + case pf: PlainFile => // The dependency comes from a class file, Zinc handles JRT filesystem + binaryDependency(if isTasty then cachedSiblingClass(pf) else pf.jpath, binaryClassName) + case _ => + internalError(s"Ignoring dependency $depFile of unknown class ${depFile.getClass}}", dep.fromClass.srcPos) + } + } + + if isTasty || depFile.hasClassExtension then + processExternalDependency() else if allowLocal || depFile != sourceFile.file then // We cannot ignore dependencies coming from the same source file because // the dependency info needs to propagate. See source-dependencies/trait-trait-211. - val toClassName = classNameAsString(dep.toClass) + val toClassName = classNameAsString(depClass) cb.classDependency(toClassName, fromClassName, dep.context) } } diff --git a/compiler/src/dotty/tools/io/AbstractFile.scala b/compiler/src/dotty/tools/io/AbstractFile.scala index 2c20461b5f5c..a7dcb7d396e8 100644 --- a/compiler/src/dotty/tools/io/AbstractFile.scala +++ b/compiler/src/dotty/tools/io/AbstractFile.scala @@ -97,8 +97,10 @@ abstract class AbstractFile extends Iterable[AbstractFile] { /** Returns the path of this abstract file in a canonical form. */ def canonicalPath: String = if (jpath == null) path else jpath.normalize.toString - /** Checks extension case insensitively. */ + /** Checks extension case insensitively. TODO: change to enum */ def hasExtension(other: String): Boolean = extension == other.toLowerCase + + /** Returns the extension of this abstract file. TODO: store as an enum to avoid costly comparisons */ val extension: String = Path.extension(name) /** The absolute file, if this is a relative file. */ diff --git a/compiler/src/dotty/tools/io/PlainFile.scala b/compiler/src/dotty/tools/io/PlainFile.scala index 1c021bd2f7a5..acef191d3072 100644 --- a/compiler/src/dotty/tools/io/PlainFile.scala +++ b/compiler/src/dotty/tools/io/PlainFile.scala @@ -113,11 +113,6 @@ class PlainFile(val givenPath: Path) extends AbstractFile { null } - final def fakeSibling(name: String): AbstractFile = { - val child = givenPath.parent / name - new PlainFile(child) - } - /** Does this abstract file denote an existing file? */ def create(): Unit = if (!exists) givenPath.createFile()