diff --git a/compiler/src/dotty/tools/backend/jvm/opt/BCodeRepository.scala b/compiler/src/dotty/tools/backend/jvm/opt/BCodeRepository.scala index 44174e17da87..b8b0eec25ad4 100644 --- a/compiler/src/dotty/tools/backend/jvm/opt/BCodeRepository.scala +++ b/compiler/src/dotty/tools/backend/jvm/opt/BCodeRepository.scala @@ -17,9 +17,8 @@ import dotty.tools.backend.jvm.BTypes.InternalName import dotty.tools.backend.jvm.opt.* import dotty.tools.backend.jvm.ClassNode1 import dotty.tools.backend.jvm.analysis.AnalysisUtils.LambdaMetaFactoryCall -import dotty.tools.dotc.classpath.{AggregateClassPath, CtSymClassPath, JrtClassPath} +import dotty.tools.dotc.classpath.{AggregateClassPath, ClassPath, CtSymClassPath, JrtClassPath} import dotty.tools.io -import dotty.tools.io.ClassPath import scala.collection.{concurrent, mutable} import scala.jdk.CollectionConverters.* diff --git a/compiler/src/dotty/tools/dotc/classpath/AggregateClassPath.scala b/compiler/src/dotty/tools/dotc/classpath/AggregateClassPath.scala index 3dd874245813..0a60f674cf4f 100644 --- a/compiler/src/dotty/tools/dotc/classpath/AggregateClassPath.scala +++ b/compiler/src/dotty/tools/dotc/classpath/AggregateClassPath.scala @@ -5,10 +5,9 @@ package dotty.tools package dotc.classpath import java.net.URL -import scala.collection.mutable.ArrayBuffer import dotc.util -import dotty.tools.io.{ AbstractFile, ClassPath, ClassRepresentation } +import dotty.tools.io.AbstractFile /** * A classpath unifying multiple class- and sourcepath entries. @@ -32,7 +31,7 @@ case class AggregateClassPath(aggregates: Seq[ClassPath]) extends ClassPath { override def asURLs: Seq[URL] = aggregates.flatMap(_.asURLs) - override def packages(inPackage: String): Iterable[PackageEntry] = + override def packages(inPackage: String): Iterable[String] = aggregates.flatMap(_.packages(inPackage)).distinct override def classes(inPackage: String): Iterable[BinaryFileEntry] = @@ -43,61 +42,7 @@ case class AggregateClassPath(aggregates: Seq[ClassPath]) extends ClassPath { override def hasPackage(pkg: String): Boolean = aggregates.exists(_.hasPackage(pkg)) - /** Returns only one entry for each name. - * - * If there's both a source and a class entry, it - * creates an entry containing both of them. If there would be more than one class or source - * entries for the same class it always would use the first entry of each type found on a classpath. - * - * A TASTy file with no class file entry will be chosen over a class file entry. This can happen if we load - * the Scala 2 library as it has one JAR containing the class files and one JAR containing the TASTy files. - * As classpath orders are not guaranteed to be deterministic we might end up having the TASTy in a later classpath entry. - */ - private def mergeClassesAndSources(entries: scala.collection.Seq[ClassRepresentation]): Seq[ClassRepresentation] = { - // based on the implementation from MergedClassPath - var count = 0 - val indices = util.HashMap[String, Int]() - val mergedEntries = new ArrayBuffer[ClassRepresentation](entries.size) - for { - entry <- entries - } { - val name = entry.name - if (indices.contains(name)) { - val index = indices(name) - val existing = mergedEntries(index) - (entry, existing) match - case (entry: SourceFileEntry, existing: BinaryFileEntry) => - mergedEntries(index) = BinaryAndSourceFilesEntry(existing, entry) - case (entry: BinaryFileEntry, existing: SourceFileEntry) => - mergedEntries(index) = BinaryAndSourceFilesEntry(entry, existing) - case (entry: StandaloneTastyFileEntry, _: ClassFileEntry) => - // Here we do not create a TastyWithClassFileEntry because the TASTy and the classfile - // come from different classpaths. These may not have the same TASTy UUID. - mergedEntries(index) = entry - case (entry: StandaloneTastyFileEntry, BinaryAndSourceFilesEntry(_: ClassFileEntry, sourceEntry)) => - mergedEntries(index) = BinaryAndSourceFilesEntry(entry, sourceEntry) - case _ => - } - else { - indices(name) = count - mergedEntries += entry - count += 1 - } - } - if (mergedEntries.isEmpty) Nil else mergedEntries.toIndexedSeq - } - - private def getDistinctEntries[EntryType <: ClassRepresentation](getEntries: ClassPath => Iterable[EntryType]): Iterable[EntryType] = { + private def getDistinctEntries[EntryType <: ClassRepresentation](getEntries: ClassPath => Iterable[EntryType]): Iterable[EntryType] = val seenNames = util.HashSet[String]() - val entriesBuffer = new ArrayBuffer[EntryType](1024) - for { - cp <- aggregates - entry <- getEntries(cp) if !seenNames.contains(entry.name) - } - { - entriesBuffer += entry - seenNames += entry.name - } - entriesBuffer.toIndexedSeq - } + aggregates.flatMap(getEntries).filter(e => seenNames.add(e.name)) } diff --git a/compiler/src/dotty/tools/dotc/classpath/ClassPath.scala b/compiler/src/dotty/tools/dotc/classpath/ClassPath.scala index e5bad464f6d9..dbd7a8243341 100644 --- a/compiler/src/dotty/tools/dotc/classpath/ClassPath.scala +++ b/compiler/src/dotty/tools/dotc/classpath/ClassPath.scala @@ -3,40 +3,81 @@ */ package dotty.tools.dotc.classpath -import dotty.tools.io.{AbstractFile, ClassRepresentation, FileExtension} +import dotty.tools.dotc +import dotty.tools.io.File.pathSeparator +import dotty.tools.io.{AbstractFile, Directory, File, FileExtension} -case class PackageEntry(name: String) +import java.net.URL +import java.util.regex.PatternSyntaxException -/** A TASTy file or classfile */ -sealed trait BinaryFileEntry extends ClassRepresentation { - def file: AbstractFile - final def fileName: String = file.name - final def name: String = FileUtils.stripExtension(file.name) // class name - final def source: Option[AbstractFile] = None -} +/** + * A representation of the compiler's class- or sourcepath. + */ +trait ClassPath { + def asURLs: Seq[URL] = Seq.empty + def hasPackage(pkg: String): Boolean = false + def packages(inPackage: String): Iterable[String] = Seq.empty + def classes(inPackage: String): Iterable[BinaryFileEntry] = Seq.empty + def sources(inPackage: String): Iterable[SourceFileEntry] = Seq.empty -object BinaryFileEntry { - def apply(file: AbstractFile): BinaryFileEntry = - if file.exists && file.ext.isTasty then - if file.resolveSiblingWithExtension(FileExtension.Class) != null then TastyWithClassFileEntry(file) - else StandaloneTastyFileEntry(file) - else - ClassFileEntry(file) + /** + * Returns *only* the classfile for an external name, e.g., "java.lang.String". This method does not + * return source files or tasty files. + * + * This method is used by the classfile parser. When parsing a Java class, its own inner classes + * are entered with a `ClassfileLoader` that parses the classfile returned by this method. + * It is also used in the backend, by the inliner, to obtain the bytecode when inlining from the + * classpath. It's also used by scalap. + */ + def findClassFile(className: String): Option[AbstractFile] = None } -/** A classfile or .sig that does not have an associated TASTy file */ -private[dotty] final case class ClassFileEntry(file: AbstractFile) extends BinaryFileEntry { - def binary: Option[AbstractFile] = Some(file) +object ClassPath { + val RootPackage: String = "" + + /** Expand single path entry */ + private def expandS(pattern: String): List[String] = { + val wildSuffix = File.separator + "*" + + /* Get all subdirectories, jars, zips out of a directory. */ + def lsDir(dir: Directory, filt: String => Boolean = _ => true) = + dir.list.filter(x => filt(x.name) && (x.isDirectory || x.ext.isJarOrZip)).map(_.path).toList + + if (pattern == "*") lsDir(Directory(".")) + // On Windows the JDK supports forward slash or backslash in classpath entries + else if (pattern.endsWith(wildSuffix) || pattern.endsWith("/*")) lsDir(Directory(pattern dropRight 2)) + else if (pattern.contains('*')) { + try { + val regexp = ("^" + pattern.replace("""\*""", """.*""") + "$").r + lsDir(Directory(pattern).parent, regexp.findFirstIn(_).isDefined) + } + catch { case _: PatternSyntaxException => List(pattern) } + } + else List(pattern) + } + + /** Split classpath using platform-dependent path separator */ + def split(path: String): List[String] = path.split(pathSeparator).toList.filterNot(_ == "").distinct + + /** Expand path and possibly expanding stars */ + def expandPath(path: String, expandStar: Boolean = true): List[String] = + if (expandStar) split(path).flatMap(expandS) + else split(path) } -/** A TASTy file that has an associated class file */ -private[dotty] final case class TastyWithClassFileEntry(file: AbstractFile) extends BinaryFileEntry { - def binary: Option[AbstractFile] = Some(file) +trait ClassRepresentation { + def fileName: String + def name: String + def binary: Option[AbstractFile] + def source: Option[AbstractFile] } -/** A TASTy file that does not have an associated class file */ -private[dotty] final case class StandaloneTastyFileEntry(file: AbstractFile) extends BinaryFileEntry { +/** A TASTy file or classfile */ +private[dotty] final case class BinaryFileEntry(file: AbstractFile) extends ClassRepresentation { + def fileName: String = file.name + def name: String = FileUtils.stripExtension(file.name) // class name def binary: Option[AbstractFile] = Some(file) + def source: Option[AbstractFile] = None } private[dotty] final case class SourceFileEntry(file: AbstractFile) extends ClassRepresentation { @@ -45,10 +86,3 @@ private[dotty] final case class SourceFileEntry(file: AbstractFile) extends Clas def binary: Option[AbstractFile] = None def source: Option[AbstractFile] = Some(file) } - -private[dotty] final case class BinaryAndSourceFilesEntry(binaryEntry: BinaryFileEntry, sourceEntry: SourceFileEntry) extends ClassRepresentation { - def fileName: String = binaryEntry.fileName - def name: String = binaryEntry.name - def binary: Option[AbstractFile] = binaryEntry.binary - def source: Option[AbstractFile] = sourceEntry.source -} diff --git a/compiler/src/dotty/tools/dotc/classpath/ClassPathFactory.scala b/compiler/src/dotty/tools/dotc/classpath/ClassPathFactory.scala index ca2a5d2cd2e9..06c0f48bd04c 100644 --- a/compiler/src/dotty/tools/dotc/classpath/ClassPathFactory.scala +++ b/compiler/src/dotty/tools/dotc/classpath/ClassPathFactory.scala @@ -3,7 +3,7 @@ */ package dotty.tools.dotc.classpath -import dotty.tools.io.{AbstractFile, ClassPath, Directory, File, Path, VirtualDirectory} +import dotty.tools.io.{AbstractFile, Directory, File, Path, VirtualDirectory} import dotty.tools.dotc.classpath.FileUtils.isClassContainer import dotty.tools.dotc.core.Contexts.* import dotty.tools.dotc.interactive.LogicalSourcePath @@ -34,7 +34,7 @@ class ClassPathFactory(precomputedSourcePackages: Option[LogicalPackage] = None) yield ClassPathFactory.newSourcePath(dir) } - def expandPath(path: String, expandStar: Boolean = true): List[String] = dotty.tools.io.ClassPath.expandPath(path, expandStar) + def expandPath(path: String, expandStar: Boolean = true): List[String] = ClassPath.expandPath(path, expandStar) /** Expand dir out to contents, a la extdir */ private def expandDir(extdir: String)(using Context): List[String] = diff --git a/compiler/src/dotty/tools/dotc/classpath/DirectoryClassPath.scala b/compiler/src/dotty/tools/dotc/classpath/DirectoryClassPath.scala index 1aa34726a237..59d39d808123 100644 --- a/compiler/src/dotty/tools/dotc/classpath/DirectoryClassPath.scala +++ b/compiler/src/dotty/tools/dotc/classpath/DirectoryClassPath.scala @@ -3,16 +3,15 @@ */ package dotty.tools.dotc.classpath -import java.io.{File as JFile} +import java.io.File as JFile import java.net.{URI, URL} import java.nio.file.{FileSystems, Files} import dotty.tools.dotc.classpath.PackageNameUtils.{packageContains, separatePkgAndClassNames} -import dotty.tools.io.{AbstractFile, ClassPath, PlainFile} +import dotty.tools.io.{AbstractFile, PlainFile} import FileUtils.* import PlainFile.toPlainFile import scala.jdk.CollectionConverters.* -import scala.collection.immutable.ArraySeq import scala.collection.mutable /** @@ -27,9 +26,8 @@ trait DirectoryLookup[FileEntryType] extends ClassPath { val dir: F - protected def emptyFiles: Array[F] // avoids reifying ClassTag[F] protected def getSubDir(dirName: String): Option[F] - protected def listChildren(dir: F, filter: Option[F => Boolean] = None): Array[F] + protected def listChildren(dir: F, filter: Option[F => Boolean] = None): Iterable[F] protected def getName(f: F): String protected def toAbstractFile(f: F): AbstractFile protected def isPackage(f: F): Boolean @@ -45,35 +43,32 @@ trait DirectoryLookup[FileEntryType] extends ClassPath { override def hasPackage(pkg: String): Boolean = getDirectory(pkg).isDefined - override def packages(inPackage: String): Seq[PackageEntry] = { + override def packages(inPackage: String): Iterable[String] = { val dirForPackage = getDirectory(inPackage) - val nestedDirs: Array[F] = dirForPackage match { - case None => emptyFiles - case Some(directory) => listChildren(directory, Some(isPackage)) + dirForPackage match { + case None => Iterable.empty + case Some(directory) => listChildren(directory, Some(isPackage)).map(f => PackageNameUtils.entryName(inPackage, getName(f))) } - ArraySeq.unsafeWrapArray(nestedDirs).map(f => PackageEntry(PackageNameUtils.entryName(inPackage, getName(f)))) } - protected def files(inPackage: String): Seq[FileEntryType] = { + protected def files(inPackage: String): Iterable[FileEntryType] = { val dirForPackage = getDirectory(inPackage) - val files: Array[F] = dirForPackage match { - case None => emptyFiles - case Some(directory) => listChildren(directory, Some(isMatchingFile)) + dirForPackage match { + case None => Iterable.empty + case Some(directory) => listChildren(directory, Some(isMatchingFile)).map(f => createFileEntry(toAbstractFile(f))) } - files.iterator.map(f => createFileEntry(toAbstractFile(f))).toSeq } } trait JFileDirectoryLookup[FileEntryType] extends DirectoryLookup[FileEntryType] { type F = JFile - protected def emptyFiles: Array[JFile] = Array.empty protected def getSubDir(packageDirName: String): Option[JFile] = { val packageDir = new JFile(dir, packageDirName) if (packageDir.exists && packageDir.isDirectory) Some(packageDir) else None } - protected def listChildren(dir: JFile, filter: Option[JFile => Boolean]): Array[JFile] = { + protected def listChildren(dir: JFile, filter: Option[JFile => Boolean]): Iterable[JFile] = { val listing = filter match { case Some(f) => dir.listFiles(file => f(file)) case None => dir.listFiles() @@ -96,14 +91,12 @@ trait JFileDirectoryLookup[FileEntryType] extends DirectoryLookup[FileEntryType] }) listing } - else Array() + else Iterable.empty } protected def getName(f: JFile): String = f.getName protected def toAbstractFile(f: JFile): AbstractFile = f.toPath.toPlainFile protected def isPackage(f: JFile): Boolean = f.isPackage - assert(dir.asInstanceOf[JFile | Null] != null, "Directory file in DirectoryFileLookup cannot be null") - override def asURLs: Seq[URL] = Seq(dir.toURI.toURL) } @@ -134,11 +127,14 @@ object JrtClassPath { * The implementation assumes that no classes exist in the empty package. */ final class JrtClassPath(fs: java.nio.file.FileSystem) extends ClassPath { - import java.nio.file.Path, java.nio.file.* - private val dirName = "/packages/" - private val dir: Path = fs.getPath(dirName) + import java.nio.file.Path + private val dir: Path = fs.getPath("/packages") + // Right now the compiler always asks for those at the root package anyway (inPackage == ""), + // and we have no way to query the file system for "entries without a dot in their name", + // so might as well cache them + private val allPackages = listFiles(dir).map(f => f.getFileName.toString) - private def listFiles(dir: Path, glob: String = "*"): Iterable[Path] = + private def listFiles(dir: Path, glob: String = "*"): Seq[Path] = val stream = Files.newDirectoryStream(dir, glob) try stream.asScala.toSeq finally stream.close() @@ -152,11 +148,12 @@ final class JrtClassPath(fs: java.nio.file.FileSystem) extends ClassPath { cachedPackageToModuleBases.get(pkg) match case Some(ps) => ps case None => - val pkgDir = dir.resolve(pkg) - // no TOCTOU here with Files.exists, we're inside JRT so nothing will get modified (unless something has gone horribly wrong) + // Files.exists inside JRT uses exceptions internally which is slow (according to a profiling trace), + // since we have `allPackages` cached anyway, use it. + // No TOCTOU bug here, we're inside JRT so nothing will get modified (unless something has gone horribly wrong). val moduleFiles = - if Files.exists(pkgDir) - then listFiles(pkgDir).map(_.toRealPath()) // toRealPath to follow symlinks + if allPackages.contains(pkg) + then listFiles(dir.resolve(pkg)).map(_.toRealPath()) // toRealPath to follow symlinks else Iterable.empty cachedPackageToModuleBases(pkg) = moduleFiles moduleFiles @@ -164,41 +161,35 @@ final class JrtClassPath(fs: java.nio.file.FileSystem) extends ClassPath { override def hasPackage(pkg: String): Boolean = packageToModuleBases(pkg).nonEmpty - // Right now the compiler always asks for those at the root package anyway (inPackage == ""), - // and we have no way to query the file system for "entries without a dot in their name", - // so might as well cache them - private val allPackages = listFiles(dir).map(f => PackageEntry(f.toString.stripPrefix(dirName))) - override def packages(inPackage: String): Iterable[PackageEntry] = + override def packages(inPackage: String): Iterable[String] = if inPackage == "" then - allPackages.filter(p => !p.name.contains('.')) + allPackages.filter(p => !p.contains('.')) else val start = inPackage + "." - allPackages.filter(p => p.name.startsWith(start) && p.name.lastIndexOf('.') == inPackage.length) + allPackages.filter(p => p.startsWith(start) && p.lastIndexOf('.') == inPackage.length) - private val cachedClasses = mutable.Map.empty[String, Iterable[BinaryFileEntry]] - override def classes(inPackage: String): Iterable[BinaryFileEntry] = cachedClasses.get(inPackage) match { + private val cachedClasses = mutable.Map.empty[String, Map[String, BinaryFileEntry]] + private def classesByName(inPackage: String): Map[String, BinaryFileEntry] = cachedClasses.get(inPackage) match { case Some(cs) => cs case None => val cs = packageToModuleBases(inPackage) .flatMap(pkg => listFiles(pkg.resolve(inPackage.replace('.', JFile.separatorChar)), "*.class")) - .map(x => ClassFileEntry(x.toPlainFile)) + .map(x => (x.getFileName.toString, BinaryFileEntry(x.toPlainFile))) + .toMap cachedClasses(inPackage) = cs cs } + override def classes(inPackage: String): Iterable[BinaryFileEntry] = + classesByName(inPackage).values + override def asURLs: Seq[URL] = Seq(new URI("jrt:/").toURL) override def findClassFile(className: String): Option[AbstractFile] = - if (!className.contains(".")) None - else { - val (inPackage, _) = separatePkgAndClassNames(className) - packageToModuleBases(inPackage).flatMap{ x => - val file = x.resolve(FileUtils.dirPath(className) + ".class") - if (Files.exists(file)) { - file.toPlainFile :: Nil - } else Nil - }.take(1).toList.headOption - } + val (pkg, cls) = separatePkgAndClassNames(className) + // Because the compiler asks about `classes` first and then requests classfiles, + // this will in practice be cached + classesByName(pkg).get(cls + ".class").map(_.file) } /** @@ -232,15 +223,15 @@ final class CtSymClassPath(ctSym: java.nio.file.Path, release: Int) extends Clas /** Empty string represents root package */ override def hasPackage(pkg: String) = packageIndex.contains(pkg) - override def packages(inPackage: String): Seq[PackageEntry] = { - packageIndex.keysIterator.filter(pack => packageContains(inPackage, pack)).map(PackageEntry(_)).toVector + override def packages(inPackage: String): Iterable[String] = { + packageIndex.keys.filter(pack => packageContains(inPackage, pack)) } - override def classes(inPackage: String): Seq[BinaryFileEntry] = { + override def classes(inPackage: String): Iterable[BinaryFileEntry] = { if (inPackage == ClassPath.RootPackage) Nil else { - val sigFiles = packageIndex.getOrElse(inPackage, Nil).iterator.flatMap(p => + val sigFiles = packageIndex.getOrElse(inPackage, Nil).flatMap(p => Files.list(p).iterator.asScala.filter(_.getFileName.toString.endsWith(".sig"))) - sigFiles.map(f => ClassFileEntry(f.toPlainFile)).toVector + sigFiles.map(f => BinaryFileEntry(f.toPlainFile)) } } @@ -271,12 +262,12 @@ case class DirectoryClassPath(dir: JFile) extends JFileDirectoryLookup[BinaryFil protected def isMatchingFile(f: JFile): Boolean = f.isTasty || f.isBestEffortTasty || (f.isClass && !f.hasSiblingTasty) - override def classes(inPackage: String): Seq[BinaryFileEntry] = files(inPackage) + override def classes(inPackage: String): Iterable[BinaryFileEntry] = files(inPackage) } case class DirectorySourcePath(dir: JFile) extends JFileDirectoryLookup[SourceFileEntry] { protected def createFileEntry(file: AbstractFile): SourceFileEntry = SourceFileEntry(file) protected def isMatchingFile(f: JFile): Boolean = endsSourceExtension(f.getName) - override def sources(inPackage: String): Seq[SourceFileEntry] = files(inPackage) + override def sources(inPackage: String): Iterable[SourceFileEntry] = files(inPackage) } diff --git a/compiler/src/dotty/tools/dotc/classpath/PackageNameUtils.scala b/compiler/src/dotty/tools/dotc/classpath/PackageNameUtils.scala index f6a6377b8d1a..ab8d421b1c20 100644 --- a/compiler/src/dotty/tools/dotc/classpath/PackageNameUtils.scala +++ b/compiler/src/dotty/tools/dotc/classpath/PackageNameUtils.scala @@ -3,9 +3,6 @@ */ package dotty.tools.dotc.classpath -import dotty.tools.io.ClassPath -import dotty.tools.io.ClassPath.RootPackage - /** * Common methods related to package names represented as String */ @@ -18,12 +15,12 @@ object PackageNameUtils { inline def separatePkgAndClassNames(fullClassName: String): (String, String) = { val lastDotIndex = fullClassName.lastIndexOf('.') if (lastDotIndex == -1) - (RootPackage, fullClassName) + (ClassPath.RootPackage, fullClassName) else (fullClassName.substring(0, lastDotIndex).nn, fullClassName.substring(lastDotIndex + 1).nn) } - def packagePrefix(inPackage: String): String = if (inPackage == RootPackage) "" else inPackage + "." + def packagePrefix(inPackage: String): String = if (inPackage == ClassPath.RootPackage) "" else inPackage + "." /** * `true` if `packageDottedName` is a package directly nested in `inPackage`, for example: diff --git a/compiler/src/dotty/tools/dotc/classpath/VirtualDirectoryClassPath.scala b/compiler/src/dotty/tools/dotc/classpath/VirtualDirectoryClassPath.scala index ed038db11275..8aa39d2ab86d 100644 --- a/compiler/src/dotty/tools/dotc/classpath/VirtualDirectoryClassPath.scala +++ b/compiler/src/dotty/tools/dotc/classpath/VirtualDirectoryClassPath.scala @@ -1,6 +1,6 @@ package dotty.tools.dotc.classpath -import dotty.tools.io.{AbstractFile, ClassPath, FileExtension, VirtualDirectory} +import dotty.tools.io.{AbstractFile, FileExtension} import FileUtils.* import java.net.{URI, URL} @@ -8,14 +8,12 @@ import java.net.{URI, URL} case class VirtualDirectoryClassPath(dir: AbstractFile) extends ClassPath with DirectoryLookup[BinaryFileEntry] { type F = AbstractFile - protected def emptyFiles: Array[AbstractFile] = Array.empty - protected def getSubDir(packageDirName: String): Option[AbstractFile] = dir.lookupPath(packageDirName, java.io.File.separatorChar, directory = true) - protected def listChildren(dir: AbstractFile, filter: Option[AbstractFile => Boolean] = None): Array[F] = filter match { - case Some(f) => dir.iterator.filter(f).toArray - case _ => dir.iterator.toArray + protected def listChildren(dir: AbstractFile, filter: Option[AbstractFile => Boolean] = None): Iterable[F] = filter match { + case Some(f) => dir.iterator.filter(f).toSeq + case _ => dir.iterator.toSeq } def getName(f: AbstractFile): String = f.name def toAbstractFile(f: AbstractFile): AbstractFile = f @@ -27,7 +25,7 @@ case class VirtualDirectoryClassPath(dir: AbstractFile) extends ClassPath with D dir.lookupPath(className, '.', lastSuffix = FileExtension.Class.withDot, directory = false) } - override def classes(inPackage: String): Seq[BinaryFileEntry] = files(inPackage) + override def classes(inPackage: String): Iterable[BinaryFileEntry] = files(inPackage) protected def createFileEntry(file: AbstractFile): BinaryFileEntry = BinaryFileEntry(file) diff --git a/compiler/src/dotty/tools/dotc/classpath/ZipAndJarFileLookupFactory.scala b/compiler/src/dotty/tools/dotc/classpath/ZipAndJarFileLookupFactory.scala index 72c2d063574a..97038a4a5848 100644 --- a/compiler/src/dotty/tools/dotc/classpath/ZipAndJarFileLookupFactory.scala +++ b/compiler/src/dotty/tools/dotc/classpath/ZipAndJarFileLookupFactory.scala @@ -8,7 +8,7 @@ import java.io.File import java.nio.file.Files import java.nio.file.attribute.{BasicFileAttributes, FileTime} -import dotty.tools.io.{AbstractFile, ClassPath, FileZipArchive} +import dotty.tools.io.{AbstractFile, FileZipArchive} import dotty.tools.dotc.core.Contexts.* import FileUtils.* @@ -43,7 +43,7 @@ object ZipAndJarClassPathFactory extends ZipAndJarFileLookupFactory { override def findClassFile(className: String): Option[AbstractFile] = file(className).map(_.file) - override def classes(inPackage: String): Seq[BinaryFileEntry] = files(inPackage) + override def classes(inPackage: String): Iterable[BinaryFileEntry] = files(inPackage) override protected def createFileEntry(file: AbstractFile): BinaryFileEntry = BinaryFileEntry(file) @@ -62,7 +62,7 @@ object ZipAndJarClassPathFactory extends ZipAndJarFileLookupFactory { */ object ZipAndJarSourcePathFactory extends ZipAndJarFileLookupFactory { private case class ZipArchiveSourcePath(zipFile: File, override val release: String) extends ZipArchiveFileLookup[SourceFileEntry] { - override def sources(inPackage: String): Seq[SourceFileEntry] = files(inPackage) + override def sources(inPackage: String): Iterable[SourceFileEntry] = files(inPackage) override protected def createFileEntry(file: AbstractFile): SourceFileEntry = SourceFileEntry(file) override protected def isRequiredFileType(file: AbstractFile): Boolean = file.ext.isSourceExtension diff --git a/compiler/src/dotty/tools/dotc/classpath/ZipArchiveFileLookup.scala b/compiler/src/dotty/tools/dotc/classpath/ZipArchiveFileLookup.scala index 8aad1188c151..57d886e25a88 100644 --- a/compiler/src/dotty/tools/dotc/classpath/ZipArchiveFileLookup.scala +++ b/compiler/src/dotty/tools/dotc/classpath/ZipArchiveFileLookup.scala @@ -7,9 +7,8 @@ package dotty.tools.dotc.classpath import java.io.File import java.net.URL -import dotty.tools.io.{ AbstractFile, FileZipArchive } +import dotty.tools.io.AbstractFile import FileUtils.* -import dotty.tools.io.ClassPath /** * A trait allowing to look for classpath entries of given type in zip and jar files. @@ -24,15 +23,15 @@ trait ZipArchiveFileLookup[FileEntryType] extends ClassPath { private val archive = AbstractFile.getDirectory(zipFile.toPath, release).nn - override def packages(inPackage: String): Seq[PackageEntry] = + override def packages(inPackage: String): Iterable[String] = findDirEntry(inPackage) match { case None => Seq.empty case Some(dirEntry) => - dirEntry.iterator.filter(_.isPackage).map(e => PackageEntry(PackageNameUtils.entryName(inPackage, e.name))).toSeq + dirEntry.iterator.filter(_.isPackage).map(e => PackageNameUtils.entryName(inPackage, e.name)).toSeq } - protected def files(inPackage: String): Seq[FileEntryType] = + protected def files(inPackage: String): Iterable[FileEntryType] = findDirEntry(inPackage) match { case None => Seq.empty diff --git a/compiler/src/dotty/tools/dotc/config/JavaPlatform.scala b/compiler/src/dotty/tools/dotc/config/JavaPlatform.scala index 026f9cffdaa0..9830450f82f7 100644 --- a/compiler/src/dotty/tools/dotc/config/JavaPlatform.scala +++ b/compiler/src/dotty/tools/dotc/config/JavaPlatform.scala @@ -3,7 +3,7 @@ package dotc package config import io.* -import classpath.AggregateClassPath +import classpath.{AggregateClassPath, ClassPath} import core.* import Symbols.*, Types.*, Contexts.*, StdNames.* import Flags.* diff --git a/compiler/src/dotty/tools/dotc/config/PathResolver.scala b/compiler/src/dotty/tools/dotc/config/PathResolver.scala index 118568b74ee6..ea16d393314c 100644 --- a/compiler/src/dotty/tools/dotc/config/PathResolver.scala +++ b/compiler/src/dotty/tools/dotc/config/PathResolver.scala @@ -3,9 +3,8 @@ package dotc package config import WrappedProperties.AccessControl -import io.{ClassPath, Directory, Path} -import classpath.{AggregateClassPath, ClassPathFactory, JrtClassPath} -import ClassPath.split +import io.{Directory, Path} +import classpath.{AggregateClassPath, ClassPath, ClassPathFactory, JrtClassPath} import PartialFunction.condOpt import core.Contexts.* import Settings.* @@ -20,7 +19,7 @@ object PathResolver { /** pretty print class path */ - def ppcp(s: String): String = split(s) match { + def ppcp(s: String): String = ClassPath.split(s) match { case Nil => "" case Seq(x) => x case xs => xs.map("\n" + _).mkString diff --git a/compiler/src/dotty/tools/dotc/config/Platform.scala b/compiler/src/dotty/tools/dotc/config/Platform.scala index 824ca2cf47bf..3568eb1f1361 100644 --- a/compiler/src/dotty/tools/dotc/config/Platform.scala +++ b/compiler/src/dotty/tools/dotc/config/Platform.scala @@ -2,7 +2,8 @@ package dotty.tools package dotc package config -import io.{ClassPath, AbstractFile} +import classpath.ClassPath +import io.AbstractFile import core.Contexts.*, core.Symbols.* import core.SymbolLoader import core.StdNames.nme diff --git a/compiler/src/dotty/tools/dotc/core/MacroClassLoader.scala b/compiler/src/dotty/tools/dotc/core/MacroClassLoader.scala index 6e31717c6824..fd10df55d4ad 100644 --- a/compiler/src/dotty/tools/dotc/core/MacroClassLoader.scala +++ b/compiler/src/dotty/tools/dotc/core/MacroClassLoader.scala @@ -4,7 +4,7 @@ import dotty.tools.dotc.core.Contexts.* import dotty.tools.dotc.core.Mode import dotty.tools.dotc.util.Property import dotty.tools.dotc.reporting.trace -import dotty.tools.io.ClassPath +import dotty.tools.dotc.classpath.ClassPath object MacroClassLoader { diff --git a/compiler/src/dotty/tools/dotc/core/SymbolLoaders.scala b/compiler/src/dotty/tools/dotc/core/SymbolLoaders.scala index 08b9e0c15e50..2f0cb23c626b 100644 --- a/compiler/src/dotty/tools/dotc/core/SymbolLoaders.scala +++ b/compiler/src/dotty/tools/dotc/core/SymbolLoaders.scala @@ -5,8 +5,8 @@ package core import java.io.{IOException, File} import java.nio.channels.ClosedByInterruptException -import dotty.tools.dotc.classpath.PackageNameUtils -import dotty.tools.io.{ ClassPath, ClassRepresentation, AbstractFile } +import dotty.tools.dotc.classpath.{ClassPath, ClassRepresentation, PackageNameUtils} +import dotty.tools.io.AbstractFile import Contexts.*, Symbols.*, Flags.*, SymDenotations.*, Types.*, Scopes.*, Names.* import NameOps.* @@ -331,8 +331,7 @@ object SymbolLoaders { } enterClasses(root, packageName, flat = false) if (!root.isEmptyPackage) - for (pkg <- classPath.packages(packageName)) { - val fullName = pkg.name + for (fullName <- classPath.packages(packageName)) { val name = if (packageName.isEmpty) fullName else fullName.substring(packageName.length + 1).nn @@ -383,11 +382,11 @@ object SymbolLoaders { // e.g. scala-parallel-collections adds both classes to scala.collection // and the new scala.collection.parallel sub-package. for p <- jarClasspath.packages(fullPackageName) do - val subPackageName = PackageNameUtils.separatePkgAndClassNames(p.name)._2.toTermName + val subPackageName = PackageNameUtils.separatePkgAndClassNames(p)._2.toTermName val subPackage = packageClass.info.decl(subPackageName).orElse: // package does not exist in symbol table, create a new symbol enterPackage(packageClass, subPackageName, (module, modcls) => new PackageLoader(module, fullClasspath)) - mergeNewEntries(subPackage.asSymDenotation.moduleClass.asClass, p.name, jarClasspath, fullClasspath) + mergeNewEntries(subPackage.asSymDenotation.moduleClass.asClass, p, jarClasspath, fullClasspath) end mergeNewEntries } diff --git a/compiler/src/dotty/tools/dotc/interactive/InteractiveDriver.scala b/compiler/src/dotty/tools/dotc/interactive/InteractiveDriver.scala index ca845c49582d..d5209079b240 100644 --- a/compiler/src/dotty/tools/dotc/interactive/InteractiveDriver.scala +++ b/compiler/src/dotty/tools/dotc/interactive/InteractiveDriver.scala @@ -44,7 +44,7 @@ class InteractiveDriver( override def sourcesRequired: Boolean = false - private var myProgressCallback: ProgressCallback = new ProgressCallback: + private val myProgressCallback: ProgressCallback = new ProgressCallback: override def isCancelled(): Boolean = Thread.interrupted() private val myInitCtx: Context = { diff --git a/compiler/src/dotty/tools/dotc/interactive/LogicalPackagesProvider.scala b/compiler/src/dotty/tools/dotc/interactive/LogicalPackagesProvider.scala index 4d7ea40233f4..d18b1ecc0da3 100644 --- a/compiler/src/dotty/tools/dotc/interactive/LogicalPackagesProvider.scala +++ b/compiler/src/dotty/tools/dotc/interactive/LogicalPackagesProvider.scala @@ -7,9 +7,9 @@ import dotty.tools.dotc.parsing.JavaParsers import dotty.tools.dotc.parsing.Parsers import dotty.tools.dotc.util.SourceFile import dotty.tools.io.AbstractFile -import dotty.tools.io.ClassPath import dotty.tools.io.FileExtension import dotty.tools.io.Path +import dotty.tools.dotc.classpath.ClassPath /** * A compiler component that adds support for parsing Scala and Java source files and finding out diff --git a/compiler/src/dotty/tools/dotc/interactive/LogicalSourcePath.scala b/compiler/src/dotty/tools/dotc/interactive/LogicalSourcePath.scala index f7a7df1b3c8c..3a6b38d59923 100644 --- a/compiler/src/dotty/tools/dotc/interactive/LogicalSourcePath.scala +++ b/compiler/src/dotty/tools/dotc/interactive/LogicalSourcePath.scala @@ -1,8 +1,7 @@ package dotty.tools.dotc.interactive -import dotty.tools.dotc.classpath.PackageEntry import dotty.tools.dotc.classpath.SourceFileEntry -import dotty.tools.io.ClassPath +import dotty.tools.dotc.classpath.ClassPath import java.io.File import java.net.URL @@ -17,25 +16,25 @@ class LogicalSourcePath(val sourcepath: String, rootPackage: LogicalPackage) findPackage(inPackage).isDefined /** Return all packages contained inside `inPackage`. Package entries contain the *full name* of the package. */ - override def packages(inPackage: String): Seq[PackageEntry] = + override def packages(inPackage: String): Iterable[String] = findPackage(inPackage) match case Some(pkg) => packagesIn(pkg, inPackage) - case None => Seq.empty[PackageEntry] + case None => Iterable.empty /** Return all sources contained directly inside `inPackage` */ - override def sources(inPackage: String): Seq[SourceFileEntry] = + override def sources(inPackage: String): Iterable[SourceFileEntry] = findPackage(inPackage) match case Some(pkg) => sourcesIn(pkg) - case None => Seq.empty[SourceFileEntry] + case None => Iterable.empty private def sourcesIn(pkg: LogicalPackage) = pkg.sources.map(p => SourceFileEntry(p)) private def packagesIn(pkg: LogicalPackage, prefix: String) = val pre = if (prefix.isEmpty) prefix else s"$prefix." - pkg.packages.map(p => PackageEntry(pre + p.name)) + pkg.packages.map(p => pre + p.name) override def asURLs: Seq[URL] = sourcepath.split(File.pathSeparator).toIndexedSeq.map(new File(_)).map(_.toURI.toURL) diff --git a/compiler/src/dotty/tools/dotc/plugins/Plugins.scala b/compiler/src/dotty/tools/dotc/plugins/Plugins.scala index 5f4dbfd6e7a1..a82ade03aa81 100644 --- a/compiler/src/dotty/tools/dotc/plugins/Plugins.scala +++ b/compiler/src/dotty/tools/dotc/plugins/Plugins.scala @@ -6,6 +6,7 @@ import Contexts.* import Decorators.em import config.PathResolver import dotty.tools.io.* +import dotty.tools.dotc.classpath.ClassPath import Phases.* import config.Printers.plugins.{ println => debug } import config.Properties diff --git a/compiler/src/dotty/tools/io/ClassPath.scala b/compiler/src/dotty/tools/io/ClassPath.scala deleted file mode 100644 index ab1906de80d0..000000000000 --- a/compiler/src/dotty/tools/io/ClassPath.scala +++ /dev/null @@ -1,75 +0,0 @@ -/* NSC -- new Scala compiler - * Copyright 2006-2013 LAMP/EPFL - * @author Martin Odersky - */ - - -package dotty.tools -package io - -import java.net.{MalformedURLException, URI, URISyntaxException, URL} -import java.util.regex.PatternSyntaxException -import File.pathSeparator -import dotc.classpath.{BinaryFileEntry, PackageEntry, SourceFileEntry} - -/** - * A representation of the compiler's class- or sourcepath. - */ -trait ClassPath { - def asURLs: Seq[URL] = Seq.empty - def hasPackage(pkg: String): Boolean = false - def packages(inPackage: String): Iterable[PackageEntry] = Seq.empty - def classes(inPackage: String): Iterable[BinaryFileEntry] = Seq.empty - def sources(inPackage: String): Iterable[SourceFileEntry] = Seq.empty - - /** - * Returns *only* the classfile for an external name, e.g., "java.lang.String". This method does not - * return source files or tasty files. - * - * This method is used by the classfile parser. When parsing a Java class, its own inner classes - * are entered with a `ClassfileLoader` that parses the classfile returned by this method. - * It is also used in the backend, by the inliner, to obtain the bytecode when inlining from the - * classpath. It's also used by scalap. - */ - def findClassFile(className: String): Option[AbstractFile] = None -} - -object ClassPath { - val RootPackage: String = "" - - /** Expand single path entry */ - private def expandS(pattern: String): List[String] = { - val wildSuffix = File.separator + "*" - - /* Get all subdirectories, jars, zips out of a directory. */ - def lsDir(dir: Directory, filt: String => Boolean = _ => true) = - dir.list.filter(x => filt(x.name) && (x.isDirectory || x.ext.isJarOrZip)).map(_.path).toList - - if (pattern == "*") lsDir(Directory(".")) - // On Windows the JDK supports forward slash or backslash in classpath entries - else if (pattern.endsWith(wildSuffix) || pattern.endsWith("/*")) lsDir(Directory(pattern dropRight 2)) - else if (pattern.contains('*')) { - try { - val regexp = ("^" + pattern.replace("""\*""", """.*""") + "$").r - lsDir(Directory(pattern).parent, regexp.findFirstIn(_).isDefined) - } - catch { case _: PatternSyntaxException => List(pattern) } - } - else List(pattern) - } - - /** Split classpath using platform-dependent path separator */ - def split(path: String): List[String] = path.split(pathSeparator).toList.filterNot(_ == "").distinct - - /** Expand path and possibly expanding stars */ - def expandPath(path: String, expandStar: Boolean = true): List[String] = - if (expandStar) split(path).flatMap(expandS) - else split(path) -} - -trait ClassRepresentation { - def fileName: String - def name: String - def binary: Option[AbstractFile] - def source: Option[AbstractFile] -} diff --git a/compiler/src/dotty/tools/scripting/ScriptingDriver.scala b/compiler/src/dotty/tools/scripting/ScriptingDriver.scala index 4198d71d4abe..4df47794bf08 100755 --- a/compiler/src/dotty/tools/scripting/ScriptingDriver.scala +++ b/compiler/src/dotty/tools/scripting/ScriptingDriver.scala @@ -5,7 +5,8 @@ import java.io.File import dotty.tools.dotc.Driver import dotty.tools.dotc.core.Contexts, Contexts.{ Context, ctx } -import dotty.tools.io.{ PlainDirectory, Directory, ClassPath } +import dotty.tools.io.{ PlainDirectory, Directory } +import dotty.tools.dotc.classpath.ClassPath import Util.* class ScriptingDriver(compilerArgs: Array[String], scriptFile: File, scriptArgs: Array[String]) extends Driver: diff --git a/compiler/src/dotty/tools/scripting/StringDriver.scala b/compiler/src/dotty/tools/scripting/StringDriver.scala index 6d4fac719be2..ab23b0074665 100755 --- a/compiler/src/dotty/tools/scripting/StringDriver.scala +++ b/compiler/src/dotty/tools/scripting/StringDriver.scala @@ -4,7 +4,8 @@ import java.nio.file.{ Files, Paths, Path } import dotty.tools.dotc.Driver import dotty.tools.dotc.core.Contexts, Contexts.{ Context, ctx } -import dotty.tools.io.{ PlainDirectory, Directory, ClassPath } +import dotty.tools.io.{ PlainDirectory, Directory } +import dotty.tools.dotc.classpath.ClassPath import Util.* import dotty.tools.dotc.util.SourceFile diff --git a/compiler/test/dotty/tools/io/ClasspathTest.scala b/compiler/test/dotty/tools/dotc/classpath/ClasspathTest.scala similarity index 96% rename from compiler/test/dotty/tools/io/ClasspathTest.scala rename to compiler/test/dotty/tools/dotc/classpath/ClasspathTest.scala index fcbe08b6ea6d..53c45bd15ec9 100755 --- a/compiler/test/dotty/tools/io/ClasspathTest.scala +++ b/compiler/test/dotty/tools/dotc/classpath/ClasspathTest.scala @@ -1,10 +1,10 @@ -package dotty.tools.io +package dotty.tools.dotc.classpath +import dotty.tools.dotc.classpath.ClassPath import org.junit.Test import java.io.File import java.nio.file.{Files, Paths} -import dotty.tools.io.ClassPath class ClasspathTest { diff --git a/compiler/test/dotty/tools/dotc/classpath/JrtClassPathTest.scala b/compiler/test/dotty/tools/dotc/classpath/JrtClassPathTest.scala index 9097cbe9d14b..146d1582d029 100644 --- a/compiler/test/dotty/tools/dotc/classpath/JrtClassPathTest.scala +++ b/compiler/test/dotty/tools/dotc/classpath/JrtClassPathTest.scala @@ -1,38 +1,18 @@ -/* - * Copyright (c) 2014 Contributor. All rights reserved. - */ - package dotty.tools.dotc.classpath -import dotty.tools.io.ClassPath import org.junit.Assert.* import org.junit.Test -import org.junit.runner.RunWith -import org.junit.runners.JUnit4 -import dotty.tools.dotc.config.PathResolver -import dotty.tools.dotc.core.Contexts.{Context, ContextBase} -import dotty.tools.dotc.classpath.ClassPathFactory import scala.tools.asm.ClassReader import scala.tools.asm.tree.ClassNode -@RunWith(classOf[JUnit4]) class JrtClassPathTest { @Test def lookupJavaClasses(): Unit = { - given Context = new ContextBase().initialCtx - val specVersion = scala.util.Properties.javaSpecVersion - // Run the test using the JDK8 or 9 provider for rt.jar depending on the platform the test is running on. - val cp: ClassPath = - if (specVersion == "" || specVersion == "1.8") { - val resolver = new PathResolver - val elements = (new ClassPathFactory).classesInPath(resolver.Calculated.javaBootClassPath) - AggregateClassPath(elements) - } - else JrtClassPath(None).get + val cp: ClassPath = JrtClassPath(None).get - assertEquals(Nil, cp.classes("")) - assertTrue(cp.packages("java").toString, cp.packages("java").exists(_.name == "java.lang")) + assertTrue(cp.classes("").isEmpty) + assertTrue(cp.packages("java").toString, cp.packages("java").exists(_ == "java.lang")) assertTrue(cp.classes("java.lang").exists(_.name == "Object")) val jl_Object = cp.classes("java.lang").find(_.name == "Object").get val jl_Class = { @@ -41,7 +21,7 @@ class JrtClassPathTest { node } assertEquals("java/lang/Object", jl_Class.name) - assertTrue(cp.packages("java.lang").exists(_.name == "java.lang.annotation")) + assertTrue(cp.packages("java.lang").exists(_ == "java.lang.annotation")) assertTrue(cp.findClassFile("java.lang.Object").isDefined) } } diff --git a/repl/src/dotty/tools/repl/DependencyResolver.scala b/repl/src/dotty/tools/repl/DependencyResolver.scala index 33e3da1d20fb..f44e8cf1918a 100644 --- a/repl/src/dotty/tools/repl/DependencyResolver.scala +++ b/repl/src/dotty/tools/repl/DependencyResolver.scala @@ -86,10 +86,10 @@ object DependencyResolver: prevClassLoader: ClassLoader, prevOutputDir: dotty.tools.io.AbstractFile )(using ctx: dotty.tools.dotc.core.Contexts.Context): AbstractFileClassLoader = - import dotty.tools.dotc.classpath.ClassPathFactory + import dotty.tools.dotc.classpath.{ClassPath, ClassPathFactory} import dotty.tools.dotc.core.SymbolLoaders import dotty.tools.dotc.core.Symbols.defn - import dotty.tools.io.{AbstractFile, ClassPath} + import dotty.tools.io.AbstractFile import dotty.tools.repl.ScalaClassLoader.fromURLsParallelCapable // Create a classloader with all the resolved JAR files diff --git a/repl/src/dotty/tools/repl/ReplDriver.scala b/repl/src/dotty/tools/repl/ReplDriver.scala index 998e9380d7a1..d0c8cb57e7f9 100644 --- a/repl/src/dotty/tools/repl/ReplDriver.scala +++ b/repl/src/dotty/tools/repl/ReplDriver.scala @@ -11,7 +11,7 @@ import java.util.regex.Pattern import dotc.ast.Trees.* import dotc.ast.{tpd, untpd} -import dotc.classpath.ClassPathFactory +import dotc.classpath.{ClassPath, ClassPathFactory} import dotc.config.CommandLineParser.tokenize import dotc.config.Properties.{javaVersion, javaVmName, simpleVersionString} import dotc.core.Contexts.*