diff options
Diffstat (limited to 'compiler/src/dotty/tools/dotc')
21 files changed, 1054 insertions, 83 deletions
diff --git a/compiler/src/dotty/tools/dotc/classpath/AggregateClassPath.scala b/compiler/src/dotty/tools/dotc/classpath/AggregateClassPath.scala new file mode 100644 index 000000000..ec3e8fdf4 --- /dev/null +++ b/compiler/src/dotty/tools/dotc/classpath/AggregateClassPath.scala @@ -0,0 +1,149 @@ +/* + * Copyright (c) 2014 Contributor. All rights reserved. + */ +package dotty.tools.dotc.classpath + +import java.net.URL +import scala.annotation.tailrec +import scala.collection.mutable.ArrayBuffer +import scala.reflect.internal.FatalError +import scala.reflect.io.AbstractFile +import dotty.tools.io.ClassPath +import dotty.tools.io.ClassRepresentation + +/** + * A classpath unifying multiple class- and sourcepath entries. + * The Classpath can obtain entries for classes and sources independently + * so it tries to do operations quite optimally - iterating only these collections + * which are needed in the given moment and only as far as it's necessary. + * + * @param aggregates classpath instances containing entries which this class processes + */ +case class AggregateClassPath(aggregates: Seq[ClassPath]) extends ClassPath { + override def findClassFile(className: String): Option[AbstractFile] = { + @tailrec + def find(aggregates: Seq[ClassPath]): Option[AbstractFile] = + if (aggregates.nonEmpty) { + val classFile = aggregates.head.findClassFile(className) + if (classFile.isDefined) classFile + else find(aggregates.tail) + } else None + + find(aggregates) + } + + override def findClass(className: String): Option[ClassRepresentation] = { + @tailrec + def findEntry(aggregates: Seq[ClassPath], isSource: Boolean): Option[ClassRepresentation] = + if (aggregates.nonEmpty) { + val entry = aggregates.head.findClass(className) match { + case s @ Some(_: SourceFileEntry) if isSource => s + case s @ Some(_: ClassFileEntry) if !isSource => s + case _ => None + } + if (entry.isDefined) entry + else findEntry(aggregates.tail, isSource) + } else None + + val classEntry = findEntry(aggregates, isSource = false) + val sourceEntry = findEntry(aggregates, isSource = true) + + (classEntry, sourceEntry) match { + case (Some(c: ClassFileEntry), Some(s: SourceFileEntry)) => Some(ClassAndSourceFilesEntry(c.file, s.file)) + case (c @ Some(_), _) => c + case (_, s) => s + } + } + + override def asURLs: Seq[URL] = aggregates.flatMap(_.asURLs) + + override def asClassPathStrings: Seq[String] = aggregates.map(_.asClassPathString).distinct + + override def asSourcePathString: String = ClassPath.join(aggregates map (_.asSourcePathString): _*) + + override private[dotty] def packages(inPackage: String): Seq[PackageEntry] = { + val aggregatedPackages = aggregates.flatMap(_.packages(inPackage)).distinct + aggregatedPackages + } + + override private[dotty] def classes(inPackage: String): Seq[ClassFileEntry] = + getDistinctEntries(_.classes(inPackage)) + + override private[dotty] def sources(inPackage: String): Seq[SourceFileEntry] = + getDistinctEntries(_.sources(inPackage)) + + override private[dotty] def list(inPackage: String): ClassPathEntries = { + val (packages, classesAndSources) = aggregates.map { cp => + try { + cp.list(inPackage) + } catch { + case ex: java.io.IOException => + val e = new FatalError(ex.getMessage) + e.initCause(ex) + throw e + } + }.unzip + val distinctPackages = packages.flatten.distinct + val distinctClassesAndSources = mergeClassesAndSources(classesAndSources: _*) + ClassPathEntries(distinctPackages, distinctClassesAndSources) + } + + /** + * 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. + */ + private def mergeClassesAndSources(entries: Seq[ClassRepresentation]*): Seq[ClassRepresentation] = { + // based on the implementation from MergedClassPath + var count = 0 + val indices = collection.mutable.HashMap[String, Int]() + val mergedEntries = new ArrayBuffer[ClassRepresentation](1024) + + for { + partOfEntries <- entries + entry <- partOfEntries + } { + val name = entry.name + if (indices contains name) { + val index = indices(name) + val existing = mergedEntries(index) + + if (existing.binary.isEmpty && entry.binary.isDefined) + mergedEntries(index) = ClassAndSourceFilesEntry(entry.binary.get, existing.source.get) + if (existing.source.isEmpty && entry.source.isDefined) + mergedEntries(index) = ClassAndSourceFilesEntry(existing.binary.get, entry.source.get) + } + else { + indices(name) = count + mergedEntries += entry + count += 1 + } + } + mergedEntries.toIndexedSeq + } + + private def getDistinctEntries[EntryType <: ClassRepresentation](getEntries: ClassPath => Seq[EntryType]): Seq[EntryType] = { + val seenNames = collection.mutable.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 + } +} + +object AggregateClassPath { + def createAggregate(parts: ClassPath*): ClassPath = { + val elems = new ArrayBuffer[ClassPath]() + parts foreach { + case AggregateClassPath(ps) => elems ++= ps + case p => elems += p + } + if (elems.size == 1) elems.head + else AggregateClassPath(elems.toIndexedSeq) + } +} diff --git a/compiler/src/dotty/tools/dotc/classpath/ClassPath.scala b/compiler/src/dotty/tools/dotc/classpath/ClassPath.scala new file mode 100644 index 000000000..129c6b9fe --- /dev/null +++ b/compiler/src/dotty/tools/dotc/classpath/ClassPath.scala @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2014 Contributor. All rights reserved. + */ +package dotty.tools.dotc.classpath + +import scala.reflect.io.AbstractFile +import dotty.tools.io.ClassRepresentation + +case class ClassPathEntries(packages: Seq[PackageEntry], classesAndSources: Seq[ClassRepresentation]) + +object ClassPathEntries { + import scala.language.implicitConversions + // to have working unzip method + implicit def entry2Tuple(entry: ClassPathEntries): (Seq[PackageEntry], Seq[ClassRepresentation]) = (entry.packages, entry.classesAndSources) +} + +trait ClassFileEntry extends ClassRepresentation { + def file: AbstractFile +} + +trait SourceFileEntry extends ClassRepresentation { + def file: AbstractFile +} + +trait PackageEntry { + def name: String +} + +private[dotty] case class ClassFileEntryImpl(file: AbstractFile) extends ClassFileEntry { + override def name = FileUtils.stripClassExtension(file.name) // class name + + override def binary: Option[AbstractFile] = Some(file) + override def source: Option[AbstractFile] = None +} + +private[dotty] case class SourceFileEntryImpl(file: AbstractFile) extends SourceFileEntry { + override def name = FileUtils.stripSourceExtension(file.name) + + override def binary: Option[AbstractFile] = None + override def source: Option[AbstractFile] = Some(file) +} + +private[dotty] case class ClassAndSourceFilesEntry(classFile: AbstractFile, srcFile: AbstractFile) extends ClassRepresentation { + override def name = FileUtils.stripClassExtension(classFile.name) + + override def binary: Option[AbstractFile] = Some(classFile) + override def source: Option[AbstractFile] = Some(srcFile) +} + +private[dotty] case class PackageEntryImpl(name: String) extends PackageEntry + +private[dotty] trait NoSourcePaths { + def asSourcePathString: String = "" + private[dotty] def sources(inPackage: String): Seq[SourceFileEntry] = Seq.empty +} + +private[dotty] trait NoClassPaths { + def findClassFile(className: String): Option[AbstractFile] = None + private[dotty] def classes(inPackage: String): Seq[ClassFileEntry] = Seq.empty +} diff --git a/compiler/src/dotty/tools/dotc/classpath/ClassPathFactory.scala b/compiler/src/dotty/tools/dotc/classpath/ClassPathFactory.scala new file mode 100644 index 000000000..ac8fc633f --- /dev/null +++ b/compiler/src/dotty/tools/dotc/classpath/ClassPathFactory.scala @@ -0,0 +1,83 @@ +/* + * Copyright (c) 2014 Contributor. All rights reserved. + */ +package dotty.tools.dotc.classpath + +import scala.reflect.io.{AbstractFile, VirtualDirectory} +import scala.reflect.io.Path.string2path +import dotty.tools.dotc.config.Settings +import FileUtils.AbstractFileOps +import dotty.tools.io.ClassPath +import dotty.tools.dotc.core.Contexts.Context + +/** + * Provides factory methods for classpath. When creating classpath instances for a given path, + * it uses proper type of classpath depending on a types of particular files containing sources or classes. + */ +class ClassPathFactory { + /** + * Create a new classpath based on the abstract file. + */ + def newClassPath(file: AbstractFile)(implicit ctx: Context): ClassPath = ClassPathFactory.newClassPath(file) + + /** + * Creators for sub classpaths which preserve this context. + */ + def sourcesInPath(path: String)(implicit ctx: Context): List[ClassPath] = + for { + file <- expandPath(path, expandStar = false) + dir <- Option(AbstractFile getDirectory file) + } yield createSourcePath(dir) + + + def expandPath(path: String, expandStar: Boolean = true): List[String] = dotty.tools.io.ClassPath.expandPath(path, expandStar) + + def expandDir(extdir: String): List[String] = dotty.tools.io.ClassPath.expandDir(extdir) + + def contentsOfDirsInPath(path: String)(implicit ctx: Context): List[ClassPath] = + for { + dir <- expandPath(path, expandStar = false) + name <- expandDir(dir) + entry <- Option(AbstractFile.getDirectory(name)) + } yield newClassPath(entry) + + def classesInExpandedPath(path: String)(implicit ctx: Context): IndexedSeq[ClassPath] = + classesInPathImpl(path, expand = true).toIndexedSeq + + def classesInPath(path: String)(implicit ctx: Context) = classesInPathImpl(path, expand = false) + + def classesInManifest(useManifestClassPath: Boolean)(implicit ctx: Context) = + if (useManifestClassPath) dotty.tools.io.ClassPath.manifests.map(url => newClassPath(AbstractFile getResources url)) + else Nil + + // Internal + protected def classesInPathImpl(path: String, expand: Boolean)(implicit ctx: Context) = + for { + file <- expandPath(path, expand) + dir <- { + def asImage = if (file.endsWith(".jimage")) Some(AbstractFile.getFile(file)) else None + Option(AbstractFile.getDirectory(file)).orElse(asImage) + } + } yield newClassPath(dir) + + private def createSourcePath(file: AbstractFile)(implicit ctx: Context): ClassPath = + if (file.isJarOrZip) + ZipAndJarSourcePathFactory.create(file) + else if (file.isDirectory) + new DirectorySourcePath(file.file) + else + sys.error(s"Unsupported sourcepath element: $file") +} + +object ClassPathFactory { + def newClassPath(file: AbstractFile)(implicit ctx: Context): ClassPath = file match { + case vd: VirtualDirectory => VirtualDirectoryClassPath(vd) + case _ => + if (file.isJarOrZip) + ZipAndJarClassPathFactory.create(file) + else if (file.isDirectory) + new DirectoryClassPath(file.file) + else + sys.error(s"Unsupported classpath element: $file") + } +} diff --git a/compiler/src/dotty/tools/dotc/classpath/DirectoryClassPath.scala b/compiler/src/dotty/tools/dotc/classpath/DirectoryClassPath.scala new file mode 100644 index 000000000..1ed233ed7 --- /dev/null +++ b/compiler/src/dotty/tools/dotc/classpath/DirectoryClassPath.scala @@ -0,0 +1,245 @@ +/* + * Copyright (c) 2014 Contributor. All rights reserved. + */ +package dotty.tools.dotc.classpath + +import java.io.File +import java.net.{URI, URL} +import java.nio.file.{FileSystems, Files, SimpleFileVisitor} +import java.util.function.IntFunction +import java.util +import java.util.Comparator + +import scala.reflect.io.{AbstractFile, PlainFile} +import dotty.tools.io.{ClassPath, ClassRepresentation, PlainNioFile} +import FileUtils._ +import scala.collection.JavaConverters._ + +/** + * A trait allowing to look for classpath entries in directories. It provides common logic for + * classes handling class and source files. + * It makes use of the fact that in the case of nested directories it's easy to find a file + * when we have a name of a package. + * It abstracts over the file representation to work with both JFile and AbstractFile. + */ +trait DirectoryLookup[FileEntryType <: ClassRepresentation] extends ClassPath { + type F + + 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 getName(f: F): String + protected def toAbstractFile(f: F): AbstractFile + protected def isPackage(f: F): Boolean + + protected def createFileEntry(file: AbstractFile): FileEntryType + protected def isMatchingFile(f: F): Boolean + + private def getDirectory(forPackage: String): Option[F] = { + if (forPackage == ClassPath.RootPackage) { + Some(dir) + } else { + val packageDirName = FileUtils.dirPath(forPackage) + getSubDir(packageDirName) + } + } + + private[dotty] def packages(inPackage: String): Seq[PackageEntry] = { + val dirForPackage = getDirectory(inPackage) + val nestedDirs: Array[F] = dirForPackage match { + case None => emptyFiles + case Some(directory) => listChildren(directory, Some(isPackage)) + } + val prefix = PackageNameUtils.packagePrefix(inPackage) + nestedDirs.map(f => PackageEntryImpl(prefix + getName(f))) + } + + protected def files(inPackage: String): Seq[FileEntryType] = { + val dirForPackage = getDirectory(inPackage) + val files: Array[F] = dirForPackage match { + case None => emptyFiles + case Some(directory) => listChildren(directory, Some(isMatchingFile)) + } + files.map(f => createFileEntry(toAbstractFile(f))) + } + + private[dotty] def list(inPackage: String): ClassPathEntries = { + val dirForPackage = getDirectory(inPackage) + val files: Array[F] = dirForPackage match { + case None => emptyFiles + case Some(directory) => listChildren(directory) + } + val packagePrefix = PackageNameUtils.packagePrefix(inPackage) + val packageBuf = collection.mutable.ArrayBuffer.empty[PackageEntry] + val fileBuf = collection.mutable.ArrayBuffer.empty[FileEntryType] + for (file <- files) { + if (isPackage(file)) + packageBuf += PackageEntryImpl(packagePrefix + getName(file)) + else if (isMatchingFile(file)) + fileBuf += createFileEntry(toAbstractFile(file)) + } + ClassPathEntries(packageBuf, fileBuf) + } +} + +trait JFileDirectoryLookup[FileEntryType <: ClassRepresentation] extends DirectoryLookup[FileEntryType] { + type F = File + + protected def emptyFiles: Array[File] = Array.empty + protected def getSubDir(packageDirName: String): Option[File] = { + val packageDir = new File(dir, packageDirName) + if (packageDir.exists && packageDir.isDirectory) Some(packageDir) + else None + } + protected def listChildren(dir: File, filter: Option[File => Boolean]): Array[File] = { + val listing = filter match { + case Some(f) => dir.listFiles(mkFileFilter(f)) + case None => dir.listFiles() + } + + if (listing != null) { + // Sort by file name for stable order of directory .class entries in package scope. + // This gives stable results ordering of base type sequences for unrelated classes + // with the same base type depth. + // + // Notably, this will stably infer`Product with Serializable` + // as the type of `case class C(); case class D(); List(C(), D()).head`, rather than the opposite order. + // On Mac, the HFS performs this sorting transparently, but on Linux the order is unspecified. + // + // Note this behaviour can be enabled in javac with `javac -XDsortfiles`, but that's only + // intended to improve determinism of the compiler for compiler hackers. + java.util.Arrays.sort(listing, + new java.util.Comparator[File] { + def compare(o1: File, o2: File) = o1.getName.compareTo(o2.getName) + }) + listing + } else Array() + } + protected def getName(f: File): String = f.getName + protected def toAbstractFile(f: File): AbstractFile = new PlainFile(new scala.reflect.io.File(f)) + protected def isPackage(f: File): Boolean = f.isPackage + + assert(dir != null, "Directory file in DirectoryFileLookup cannot be null") + + def asURLs: Seq[URL] = Seq(dir.toURI.toURL) + def asClassPathStrings: Seq[String] = Seq(dir.getPath) +} + +object JrtClassPath { + import java.nio.file._, java.net.URI + def apply(): Option[ClassPath] = { + try { + val fs = FileSystems.getFileSystem(URI.create("jrt:/")) + Some(new JrtClassPath(fs)) + } catch { + case _: ProviderNotFoundException | _: FileSystemNotFoundException => + None + } + } +} + +/** + * Implementation `ClassPath` based on the JDK 9 encapsulated runtime modules (JEP-220) + * + * https://bugs.openjdk.java.net/browse/JDK-8066492 is the most up to date reference + * for the structure of the jrt:// filesystem. + * + * The implementation assumes that no classes exist in the empty package. + */ +final class JrtClassPath(fs: java.nio.file.FileSystem) extends ClassPath with NoSourcePaths { + import java.nio.file.Path, java.nio.file._ + type F = Path + private val dir: Path = fs.getPath("/packages") + + // e.g. "java.lang" -> Seq("/modules/java.base") + private val packageToModuleBases: Map[String, Seq[Path]] = { + val ps = Files.newDirectoryStream(dir).iterator().asScala + def lookup(pack: Path): Seq[Path] = { + Files.list(pack).iterator().asScala.map(l => if (Files.isSymbolicLink(l)) Files.readSymbolicLink(l) else l).toList + } + ps.map(p => (p.toString.stripPrefix("/packages/"), lookup(p))).toMap + } + + override private[dotty] def packages(inPackage: String): Seq[PackageEntry] = { + def matches(packageDottedName: String) = + if (packageDottedName.contains(".")) + packageOf(packageDottedName) == inPackage + else inPackage == "" + packageToModuleBases.keysIterator.filter(matches).map(PackageEntryImpl(_)).toVector + } + private[dotty] def classes(inPackage: String): Seq[ClassFileEntry] = { + if (inPackage == "") Nil + else { + packageToModuleBases.getOrElse(inPackage, Nil).flatMap(x => + Files.list(x.resolve(inPackage.replace('.', '/'))).iterator().asScala.filter(_.getFileName.toString.endsWith(".class"))).map(x => + ClassFileEntryImpl(new PlainNioFile(x))).toVector + } + } + + override private[dotty] def list(inPackage: String): ClassPathEntries = + if (inPackage == "") ClassPathEntries(packages(inPackage), Nil) + else ClassPathEntries(packages(inPackage), classes(inPackage)) + + def asURLs: Seq[URL] = Seq(dir.toUri.toURL) + // We don't yet have a scheme to represent the JDK modules in our `-classpath`. + // java models them as entries in the new "module path", we'll probably need to follow this. + def asClassPathStrings: Seq[String] = Nil + + def findClassFile(className: String): Option[AbstractFile] = { + if (!className.contains(".")) None + else { + val inPackage = packageOf(className) + packageToModuleBases.getOrElse(inPackage, Nil).iterator.flatMap{x => + val file = x.resolve(className.replace('.', '/') + ".class") + if (Files.exists(file)) new PlainNioFile(file) :: Nil else Nil + }.take(1).toList.headOption + } + } + private def packageOf(dottedClassName: String): String = + dottedClassName.substring(0, dottedClassName.lastIndexOf(".")) +} + +case class DirectoryClassPath(dir: File) extends JFileDirectoryLookup[ClassFileEntryImpl] with NoSourcePaths { + override def findClass(className: String): Option[ClassRepresentation] = findClassFile(className) map ClassFileEntryImpl + + def findClassFile(className: String): Option[AbstractFile] = { + val relativePath = FileUtils.dirPath(className) + val classFile = new File(s"$dir/$relativePath.class") + if (classFile.exists) { + val wrappedClassFile = new scala.reflect.io.File(classFile) + val abstractClassFile = new PlainFile(wrappedClassFile) + Some(abstractClassFile) + } else None + } + + protected def createFileEntry(file: AbstractFile): ClassFileEntryImpl = ClassFileEntryImpl(file) + protected def isMatchingFile(f: File): Boolean = f.isClass + + private[dotty] def classes(inPackage: String): Seq[ClassFileEntry] = files(inPackage) +} + +case class DirectorySourcePath(dir: File) extends JFileDirectoryLookup[SourceFileEntryImpl] with NoClassPaths { + def asSourcePathString: String = asClassPathString + + protected def createFileEntry(file: AbstractFile): SourceFileEntryImpl = SourceFileEntryImpl(file) + protected def isMatchingFile(f: File): Boolean = endsScalaOrJava(f.getName) + + override def findClass(className: String): Option[ClassRepresentation] = findSourceFile(className) map SourceFileEntryImpl + + private def findSourceFile(className: String): Option[AbstractFile] = { + val relativePath = FileUtils.dirPath(className) + val sourceFile = Stream("scala", "java") + .map(ext => new File(s"$dir/$relativePath.$ext")) + .collectFirst { case file if file.exists() => file } + + sourceFile.map { file => + val wrappedSourceFile = new scala.reflect.io.File(file) + val abstractSourceFile = new PlainFile(wrappedSourceFile) + abstractSourceFile + } + } + + private[dotty] def sources(inPackage: String): Seq[SourceFileEntry] = files(inPackage) +} diff --git a/compiler/src/dotty/tools/dotc/classpath/FileUtils.scala b/compiler/src/dotty/tools/dotc/classpath/FileUtils.scala new file mode 100644 index 000000000..823efbb9d --- /dev/null +++ b/compiler/src/dotty/tools/dotc/classpath/FileUtils.scala @@ -0,0 +1,74 @@ +/* + * Copyright (c) 2014 Contributor. All rights reserved. + */ +package dotty.tools.dotc.classpath + +import java.io.{File => JFile, FileFilter} +import java.net.URL +import scala.reflect.internal.FatalError +import scala.reflect.io.AbstractFile + +/** + * Common methods related to Java files and abstract files used in the context of classpath + */ +object FileUtils { + implicit class AbstractFileOps(val file: AbstractFile) extends AnyVal { + def isPackage: Boolean = file.isDirectory && mayBeValidPackage(file.name) + + 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 isScalaOrJavaSource: Boolean = !file.isDirectory && (file.hasExtension("scala") || file.hasExtension("java")) + + // TODO do we need to check also other files using ZipMagicNumber like in scala.tools.nsc.io.Jar.isJarOrZip? + def isJarOrZip: Boolean = file.hasExtension("jar") || file.hasExtension("zip") + + /** + * Safe method returning a sequence containing one URL representing this file, when underlying file exists, + * and returning given default value in other case + */ + def toURLs(default: => Seq[URL] = Seq.empty): Seq[URL] = if (file.file == null) default else Seq(file.toURL) + } + + implicit class FileOps(val file: JFile) extends AnyVal { + def isPackage: Boolean = file.isDirectory && mayBeValidPackage(file.getName) + + def isClass: Boolean = file.isFile && file.getName.endsWith(".class") && !file.getName.endsWith("$class.class") + // FIXME: drop last condition when we stop being compatible with Scala 2.11 + } + + def stripSourceExtension(fileName: String): String = { + if (endsScala(fileName)) stripClassExtension(fileName) + else if (endsJava(fileName)) stripJavaExtension(fileName) + else throw new FatalError("Unexpected source file ending: " + fileName) + } + + def dirPath(forPackage: String) = forPackage.replace('.', '/') + + def endsClass(fileName: String): Boolean = + fileName.length > 6 && fileName.substring(fileName.length - 6) == ".class" + + def endsScalaOrJava(fileName: String): Boolean = + endsScala(fileName) || endsJava(fileName) + + def endsJava(fileName: String): Boolean = + fileName.length > 5 && fileName.substring(fileName.length - 5) == ".java" + + def endsScala(fileName: String): Boolean = + fileName.length > 6 && fileName.substring(fileName.length - 6) == ".scala" + + def stripClassExtension(fileName: String): String = + fileName.substring(0, fileName.length - 6) // equivalent of fileName.length - ".class".length + + def stripJavaExtension(fileName: String): String = + fileName.substring(0, fileName.length - 5) + + // probably it should match a pattern like [a-z_]{1}[a-z0-9_]* but it cannot be changed + // because then some tests in partest don't pass + def mayBeValidPackage(dirName: String): Boolean = + (dirName != "META-INF") && (dirName != "") && (dirName.charAt(0) != '.') + + def mkFileFilter(f: JFile => Boolean) = new FileFilter { + def accept(pathname: JFile): Boolean = f(pathname) + } +} diff --git a/compiler/src/dotty/tools/dotc/classpath/PackageNameUtils.scala b/compiler/src/dotty/tools/dotc/classpath/PackageNameUtils.scala new file mode 100644 index 000000000..303f142b9 --- /dev/null +++ b/compiler/src/dotty/tools/dotc/classpath/PackageNameUtils.scala @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2014 Contributor. All rights reserved. + */ +package dotty.tools.dotc.classpath + +import dotty.tools.io.ClassPath.RootPackage + +/** + * Common methods related to package names represented as String + */ +object PackageNameUtils { + + /** + * @param fullClassName full class name with package + * @return (package, simple class name) + */ + def separatePkgAndClassNames(fullClassName: String): (String, String) = { + val lastDotIndex = fullClassName.lastIndexOf('.') + if (lastDotIndex == -1) + (RootPackage, fullClassName) + else + (fullClassName.substring(0, lastDotIndex), fullClassName.substring(lastDotIndex + 1)) + } + + def packagePrefix(inPackage: String): String = if (inPackage == RootPackage) "" else inPackage + "." +} diff --git a/compiler/src/dotty/tools/dotc/classpath/VirtualDirectoryClassPath.scala b/compiler/src/dotty/tools/dotc/classpath/VirtualDirectoryClassPath.scala new file mode 100644 index 000000000..5b0855554 --- /dev/null +++ b/compiler/src/dotty/tools/dotc/classpath/VirtualDirectoryClassPath.scala @@ -0,0 +1,52 @@ +package dotty.tools.dotc.classpath + +import dotty.tools.io.ClassRepresentation +import scala.reflect.io.{AbstractFile, Path, PlainFile, VirtualDirectory} +import FileUtils._ +import java.net.URL + +import scala.reflect.internal.util.AbstractFileClassLoader +import dotty.tools.io.ClassPath + +case class VirtualDirectoryClassPath(dir: VirtualDirectory) extends ClassPath with DirectoryLookup[ClassFileEntryImpl] with NoSourcePaths { + type F = AbstractFile + + // From AbstractFileClassLoader + private final def lookupPath(base: AbstractFile)(pathParts: Seq[String], directory: Boolean): AbstractFile = { + var file: AbstractFile = base + for (dirPart <- pathParts.init) { + file = file.lookupName(dirPart, directory = true) + if (file == null) + return null + } + + file.lookupName(pathParts.last, directory = directory) + } + + protected def emptyFiles: Array[AbstractFile] = Array.empty + protected def getSubDir(packageDirName: String): Option[AbstractFile] = + Option(lookupPath(dir)(packageDirName.split('/'), 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.toArray + } + def getName(f: AbstractFile): String = f.name + def toAbstractFile(f: AbstractFile): AbstractFile = f + def isPackage(f: AbstractFile): Boolean = f.isPackage + + // mimic the behavior of the old nsc.util.DirectoryClassPath + def asURLs: Seq[URL] = Seq(new URL(dir.name)) + def asClassPathStrings: Seq[String] = Seq(dir.path) + + override def findClass(className: String): Option[ClassRepresentation] = findClassFile(className) map ClassFileEntryImpl + + def findClassFile(className: String): Option[AbstractFile] = { + val relativePath = FileUtils.dirPath(className) + ".class" + Option(lookupPath(dir)(relativePath split '/', directory = false)) + } + + private[dotty] def classes(inPackage: String): Seq[ClassFileEntry] = files(inPackage) + + protected def createFileEntry(file: AbstractFile): ClassFileEntryImpl = ClassFileEntryImpl(file) + protected def isMatchingFile(f: AbstractFile): Boolean = f.isClass +} diff --git a/compiler/src/dotty/tools/dotc/classpath/ZipAndJarFileLookupFactory.scala b/compiler/src/dotty/tools/dotc/classpath/ZipAndJarFileLookupFactory.scala new file mode 100644 index 000000000..5210c699e --- /dev/null +++ b/compiler/src/dotty/tools/dotc/classpath/ZipAndJarFileLookupFactory.scala @@ -0,0 +1,176 @@ +/* + * Copyright (c) 2014 Contributor. All rights reserved. + */ +package dotty.tools.dotc.classpath + +import java.io.File +import java.net.URL +import scala.annotation.tailrec +import scala.reflect.io.{AbstractFile, FileZipArchive, ManifestResources} +import dotty.tools.io.ClassPath +import dotty.tools.dotc.config.Settings +import dotty.tools.dotc.core.Contexts.Context +import FileUtils._ + +/** + * A trait providing an optional cache for classpath entries obtained from zip and jar files. + * It's possible to create such a cache assuming that entries in such files won't change (at + * least will be the same each time we'll load classpath during the lifetime of JVM process) + * - unlike class and source files in directories, which can be modified and recompiled. + * It allows us to e.g. reduce significantly memory used by PresentationCompilers in Scala IDE + * when there are a lot of projects having a lot of common dependencies. + */ +sealed trait ZipAndJarFileLookupFactory { + private val cache = collection.mutable.Map.empty[AbstractFile, ClassPath] + + def create(zipFile: AbstractFile)(implicit ctx: Context): ClassPath = { + if (ctx.settings.YdisableFlatCpCaching.value) createForZipFile(zipFile) + else createUsingCache(zipFile) + } + + protected def createForZipFile(zipFile: AbstractFile): ClassPath + + private def createUsingCache(zipFile: AbstractFile)(implicit ctx: Context): ClassPath = cache.synchronized { + def newClassPathInstance = { + if (ctx.settings.verbose.value || ctx.settings.Ylogcp.value) + println(s"$zipFile is not yet in the classpath cache") + createForZipFile(zipFile) + } + cache.getOrElseUpdate(zipFile, newClassPathInstance) + } +} + +/** + * Manages creation of classpath for class files placed in zip and jar files. + * It should be the only way of creating them as it provides caching. + */ +object ZipAndJarClassPathFactory extends ZipAndJarFileLookupFactory { + private case class ZipArchiveClassPath(zipFile: File) + extends ZipArchiveFileLookup[ClassFileEntryImpl] + with NoSourcePaths { + + override def findClassFile(className: String): Option[AbstractFile] = { + val (pkg, simpleClassName) = PackageNameUtils.separatePkgAndClassNames(className) + classes(pkg).find(_.name == simpleClassName).map(_.file) + } + + override private[dotty] def classes(inPackage: String): Seq[ClassFileEntry] = files(inPackage) + + override protected def createFileEntry(file: FileZipArchive#Entry): ClassFileEntryImpl = ClassFileEntryImpl(file) + override protected def isRequiredFileType(file: AbstractFile): Boolean = file.isClass + } + + /** + * This type of classpath is closely related to the support for JSR-223. + * Its usage can be observed e.g. when running: + * jrunscript -classpath scala-compiler.jar;scala-reflect.jar;scala-library.jar -l scala + * with a particularly prepared scala-library.jar. It should have all classes listed in the manifest like e.g. this entry: + * Name: scala/Function2$mcFJD$sp.class + */ + private case class ManifestResourcesClassPath(file: ManifestResources) extends ClassPath with NoSourcePaths { + override def findClassFile(className: String): Option[AbstractFile] = { + val (pkg, simpleClassName) = PackageNameUtils.separatePkgAndClassNames(className) + classes(pkg).find(_.name == simpleClassName).map(_.file) + } + + override def asClassPathStrings: Seq[String] = Seq(file.path) + + override def asURLs: Seq[URL] = file.toURLs() + + import ManifestResourcesClassPath.PackageFileInfo + import ManifestResourcesClassPath.PackageInfo + + /** + * A cache mapping package name to abstract file for package directory and subpackages of given package. + * + * ManifestResources can iterate through the collections of entries from e.g. remote jar file. + * We can't just specify the path to the concrete directory etc. so we can't just 'jump' into + * given package, when it's needed. On the other hand we can iterate over entries to get + * AbstractFiles, iterate over entries of these files etc. + * + * Instead of traversing a tree of AbstractFiles once and caching all entries or traversing each time, + * when we need subpackages of a given package or its classes, we traverse once and cache only packages. + * Classes for given package can be then easily loaded when they are needed. + */ + private lazy val cachedPackages: collection.mutable.HashMap[String, PackageFileInfo] = { + val packages = collection.mutable.HashMap[String, PackageFileInfo]() + + def getSubpackages(dir: AbstractFile): List[AbstractFile] = + (for (file <- dir if file.isPackage) yield file)(collection.breakOut) + + @tailrec + def traverse(packagePrefix: String, + filesForPrefix: List[AbstractFile], + subpackagesQueue: collection.mutable.Queue[PackageInfo]): Unit = filesForPrefix match { + case pkgFile :: remainingFiles => + val subpackages = getSubpackages(pkgFile) + val fullPkgName = packagePrefix + pkgFile.name + packages.put(fullPkgName, PackageFileInfo(pkgFile, subpackages)) + val newPackagePrefix = fullPkgName + "." + subpackagesQueue.enqueue(PackageInfo(newPackagePrefix, subpackages)) + traverse(packagePrefix, remainingFiles, subpackagesQueue) + case Nil if subpackagesQueue.nonEmpty => + val PackageInfo(packagePrefix, filesForPrefix) = subpackagesQueue.dequeue() + traverse(packagePrefix, filesForPrefix, subpackagesQueue) + case _ => + } + + val subpackages = getSubpackages(file) + packages.put(ClassPath.RootPackage, PackageFileInfo(file, subpackages)) + traverse(ClassPath.RootPackage, subpackages, collection.mutable.Queue()) + packages + } + + override private[dotty] def packages(inPackage: String): Seq[PackageEntry] = cachedPackages.get(inPackage) match { + case None => Seq.empty + case Some(PackageFileInfo(_, subpackages)) => + val prefix = PackageNameUtils.packagePrefix(inPackage) + subpackages.map(packageFile => PackageEntryImpl(prefix + packageFile.name)) + } + + override private[dotty] def classes(inPackage: String): Seq[ClassFileEntry] = cachedPackages.get(inPackage) match { + case None => Seq.empty + case Some(PackageFileInfo(pkg, _)) => + (for (file <- pkg if file.isClass) yield ClassFileEntryImpl(file))(collection.breakOut) + } + + override private[dotty] def list(inPackage: String): ClassPathEntries = ClassPathEntries(packages(inPackage), classes(inPackage)) + } + + private object ManifestResourcesClassPath { + case class PackageFileInfo(packageFile: AbstractFile, subpackages: Seq[AbstractFile]) + case class PackageInfo(packageName: String, subpackages: List[AbstractFile]) + } + + override protected def createForZipFile(zipFile: AbstractFile): ClassPath = + if (zipFile.file == null) createWithoutUnderlyingFile(zipFile) + else ZipArchiveClassPath(zipFile.file) + + private def createWithoutUnderlyingFile(zipFile: AbstractFile) = zipFile match { + case manifestRes: ManifestResources => + ManifestResourcesClassPath(manifestRes) + case _ => + val errorMsg = s"Abstract files which don't have an underlying file and are not ManifestResources are not supported. There was $zipFile" + throw new IllegalArgumentException(errorMsg) + } +} + +/** + * Manages creation of classpath for source files placed in zip and jar files. + * It should be the only way of creating them as it provides caching. + */ +object ZipAndJarSourcePathFactory extends ZipAndJarFileLookupFactory { + private case class ZipArchiveSourcePath(zipFile: File) + extends ZipArchiveFileLookup[SourceFileEntryImpl] + with NoClassPaths { + + override def asSourcePathString: String = asClassPathString + + override private[dotty] def sources(inPackage: String): Seq[SourceFileEntry] = files(inPackage) + + override protected def createFileEntry(file: FileZipArchive#Entry): SourceFileEntryImpl = SourceFileEntryImpl(file) + override protected def isRequiredFileType(file: AbstractFile): Boolean = file.isScalaOrJavaSource + } + + override protected def createForZipFile(zipFile: AbstractFile): ClassPath = ZipArchiveSourcePath(zipFile.file) +} diff --git a/compiler/src/dotty/tools/dotc/classpath/ZipArchiveFileLookup.scala b/compiler/src/dotty/tools/dotc/classpath/ZipArchiveFileLookup.scala new file mode 100644 index 000000000..8184708ad --- /dev/null +++ b/compiler/src/dotty/tools/dotc/classpath/ZipArchiveFileLookup.scala @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2014 Contributor. All rights reserved. + */ +package dotty.tools.dotc.classpath + +import java.io.File +import java.net.URL +import scala.collection.Seq +import scala.reflect.io.AbstractFile +import scala.reflect.io.FileZipArchive +import FileUtils.AbstractFileOps +import dotty.tools.io.{ClassPath, ClassRepresentation} + +/** + * A trait allowing to look for classpath entries of given type in zip and jar files. + * It provides common logic for classes handling class and source files. + * It's aware of things like e.g. META-INF directory which is correctly skipped. + */ +trait ZipArchiveFileLookup[FileEntryType <: ClassRepresentation] extends ClassPath { + val zipFile: File + + assert(zipFile != null, "Zip file in ZipArchiveFileLookup cannot be null") + + override def asURLs: Seq[URL] = Seq(zipFile.toURI.toURL) + override def asClassPathStrings: Seq[String] = Seq(zipFile.getPath) + + private val archive = new FileZipArchive(zipFile) + + override private[dotty] def packages(inPackage: String): Seq[PackageEntry] = { + val prefix = PackageNameUtils.packagePrefix(inPackage) + for { + dirEntry <- findDirEntry(inPackage).toSeq + entry <- dirEntry.iterator if entry.isPackage + } yield PackageEntryImpl(prefix + entry.name) + } + + protected def files(inPackage: String): Seq[FileEntryType] = + for { + dirEntry <- findDirEntry(inPackage).toSeq + entry <- dirEntry.iterator if isRequiredFileType(entry) + } yield createFileEntry(entry) + + override private[dotty] def list(inPackage: String): ClassPathEntries = { + val foundDirEntry = findDirEntry(inPackage) + + foundDirEntry map { dirEntry => + val pkgBuf = collection.mutable.ArrayBuffer.empty[PackageEntry] + val fileBuf = collection.mutable.ArrayBuffer.empty[FileEntryType] + val prefix = PackageNameUtils.packagePrefix(inPackage) + + for (entry <- dirEntry.iterator) { + if (entry.isPackage) + pkgBuf += PackageEntryImpl(prefix + entry.name) + else if (isRequiredFileType(entry)) + fileBuf += createFileEntry(entry) + } + ClassPathEntries(pkgBuf, fileBuf) + } getOrElse ClassPathEntries(Seq.empty, Seq.empty) + } + + private def findDirEntry(pkg: String): Option[archive.DirEntry] = { + val dirName = s"${FileUtils.dirPath(pkg)}/" + archive.allDirs.get(dirName) + } + + protected def createFileEntry(file: FileZipArchive#Entry): FileEntryType + protected def isRequiredFileType(file: AbstractFile): Boolean +} diff --git a/compiler/src/dotty/tools/dotc/config/JavaPlatform.scala b/compiler/src/dotty/tools/dotc/config/JavaPlatform.scala index 8bc18c387..d2a8e18a2 100644 --- a/compiler/src/dotty/tools/dotc/config/JavaPlatform.scala +++ b/compiler/src/dotty/tools/dotc/config/JavaPlatform.scala @@ -2,8 +2,8 @@ package dotty.tools package dotc package config -import io.{AbstractFile,ClassPath,JavaClassPath,MergedClassPath,DeltaClassPath} -import ClassPath.{ JavaContext, DefaultJavaContext } +import io._ +import classpath.AggregateClassPath import core._ import Symbols._, Types._, Contexts._, Denotations._, SymDenotations._, StdNames._, Names._ import Flags._, Scopes._, Decorators._, NameOps._, util.Positions._ @@ -11,7 +11,7 @@ import transform.ExplicitOuter, transform.SymUtils._ class JavaPlatform extends Platform { - private var currentClassPath: Option[MergedClassPath] = None + private var currentClassPath: Option[ClassPath] = None def classPath(implicit ctx: Context): ClassPath = { if (currentClassPath.isEmpty) @@ -35,8 +35,12 @@ class JavaPlatform extends Platform { } /** Update classpath with a substituted subentry */ - def updateClassPath(subst: Map[ClassPath, ClassPath]) = - currentClassPath = Some(new DeltaClassPath(currentClassPath.get, subst)) + def updateClassPath(subst: Map[ClassPath, ClassPath]): Unit = currentClassPath.get match { + case AggregateClassPath(entries) => + currentClassPath = Some(AggregateClassPath(entries map (e => subst.getOrElse(e, e)))) + case cp: ClassPath => + currentClassPath = Some(subst.getOrElse(cp, cp)) + } def rootLoader(root: TermSymbol)(implicit ctx: Context): SymbolLoader = new ctx.base.loaders.PackageLoader(root, classPath) diff --git a/compiler/src/dotty/tools/dotc/config/PathResolver.scala b/compiler/src/dotty/tools/dotc/config/PathResolver.scala index 159989e6f..f0709f4d3 100644 --- a/compiler/src/dotty/tools/dotc/config/PathResolver.scala +++ b/compiler/src/dotty/tools/dotc/config/PathResolver.scala @@ -4,8 +4,9 @@ package config import java.net.{ URL, MalformedURLException } import WrappedProperties.AccessControl -import io.{ ClassPath, JavaClassPath, File, Directory, Path, AbstractFile } -import ClassPath.{ JavaContext, DefaultJavaContext, join, split } +import io.{ ClassPath, File, Directory, Path, AbstractFile } +import classpath.{AggregateClassPath, ClassPathFactory } +import ClassPath.{ JavaContext, join, split } import PartialFunction.condOpt import scala.language.postfixOps import core.Contexts._ @@ -128,7 +129,7 @@ object PathResolver { ) } - def fromPathString(path: String)(implicit ctx: Context): JavaClassPath = { + def fromPathString(path: String)(implicit ctx: Context): ClassPath = { val settings = ctx.settings.classpath.update(path) new PathResolver()(ctx.fresh.setSettings(settings)).result } @@ -150,7 +151,11 @@ object PathResolver { val pr = new PathResolver()(ctx.fresh.setSettings(sstate)) println(" COMMAND: 'scala %s'".format(args.mkString(" "))) println("RESIDUAL: 'scala %s'\n".format(rest.mkString(" "))) - pr.result.show + + pr.result match { + case cp: AggregateClassPath => + println(s"ClassPath has ${cp.aggregates.size} entries and results in:\n${cp.asClassPathStrings}") + } } } } @@ -159,7 +164,7 @@ import PathResolver.{ Defaults, Environment, firstNonEmpty, ppcp } class PathResolver(implicit ctx: Context) { import ctx.base.settings - val context = ClassPath.DefaultJavaContext + private val classPathFactory = new ClassPathFactory private def cmdLineOrElse(name: String, alt: String) = { (commandLineFor(name) match { @@ -214,7 +219,7 @@ class PathResolver(implicit ctx: Context) { else sys.env.getOrElse("CLASSPATH", ".") } - import context._ + import classPathFactory._ // Assemble the elements! // priority class path takes precedence @@ -254,8 +259,8 @@ class PathResolver(implicit ctx: Context) { def containers = Calculated.containers - lazy val result: JavaClassPath = { - val cp = new JavaClassPath(containers.toIndexedSeq, context) + lazy val result: ClassPath = { + val cp = AggregateClassPath(containers.toIndexedSeq) if (settings.Ylogcp.value) { Console.println("Classpath built from " + settings.toConciseString(ctx.sstate)) diff --git a/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala b/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala index 63c3d5f74..941434dd5 100644 --- a/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala +++ b/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala @@ -73,6 +73,8 @@ class ScalaSettings extends Settings.SettingGroup { val log = PhasesSetting("-Ylog", "Log operations during") val emitTasty = BooleanSetting("-YemitTasty", "Generate tasty in separate *.tasty file.") val Ylogcp = BooleanSetting("-Ylog-classpath", "Output information about what classpath is being applied.") + val YdisableFlatCpCaching = BooleanSetting("-YdisableFlatCpCaching", "Do not cache flat classpath representation of classpath elements from jars across compiler instances.") + val YnoImports = BooleanSetting("-Yno-imports", "Compile without importing scala.*, java.lang.*, or Predef.") val YnoPredef = BooleanSetting("-Yno-predef", "Compile without importing Predef.") val Yskip = PhasesSetting("-Yskip", "Skip") diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index a97589d73..eee6ba785 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -10,7 +10,6 @@ import scala.collection.{ mutable, immutable } import PartialFunction._ import collection.mutable import util.common.alwaysZero -import typer.Applications object Definitions { @@ -846,6 +845,9 @@ class Definitions { TupleType(elems.size).appliedTo(elems) } + def isProductSubType(tp: Type)(implicit ctx: Context) = + tp.derivesFrom(ProductType.symbol) + /** Is `tp` (an alias) of either a scala.FunctionN or a scala.ImplicitFunctionN? */ def isFunctionType(tp: Type)(implicit ctx: Context) = { val arity = functionArity(tp) diff --git a/compiler/src/dotty/tools/dotc/core/SymbolLoaders.scala b/compiler/src/dotty/tools/dotc/core/SymbolLoaders.scala index e4d2d446f..63c2817a6 100644 --- a/compiler/src/dotty/tools/dotc/core/SymbolLoaders.scala +++ b/compiler/src/dotty/tools/dotc/core/SymbolLoaders.scala @@ -9,7 +9,8 @@ package core import java.io.IOException import scala.compat.Platform.currentTime -import dotty.tools.io.{ ClassPath, AbstractFile } +import dotty.tools.io.{ ClassPath, ClassRepresentation, AbstractFile } +import classpath._ import Contexts._, Symbols._, Flags._, SymDenotations._, Types._, Scopes._, util.Positions._, Names._ import StdNames._, NameOps._ import Decorators.{PreNamedString, StringInterpolators} @@ -60,8 +61,7 @@ class SymbolLoaders { /** Enter package with given `name` into scope of `owner` * and give them `completer` as type. */ - def enterPackage(owner: Symbol, pkg: ClassPath)(implicit ctx: Context): Symbol = { - val pname = pkg.name.toTermName + def enterPackage(owner: Symbol, pname: TermName, completer: (TermSymbol, ClassSymbol) => PackageLoader)(implicit ctx: Context): Symbol = { val preExisting = owner.info.decls lookup pname if (preExisting != NoSymbol) { // Some jars (often, obfuscated ones) include a package and @@ -84,7 +84,7 @@ class SymbolLoaders { } } ctx.newModuleSymbol(owner, pname, PackageCreationFlags, PackageCreationFlags, - (module, modcls) => new PackageLoader(module, pkg)).entered + completer).entered } /** Enter class and module with given `name` into scope of `owner` @@ -126,7 +126,7 @@ class SymbolLoaders { /** Initialize toplevel class and module symbols in `owner` from class path representation `classRep` */ - def initializeFromClassPath(owner: Symbol, classRep: ClassPath#ClassRep)(implicit ctx: Context): Unit = { + def initializeFromClassPath(owner: Symbol, classRep: ClassRepresentation)(implicit ctx: Context): Unit = { ((classRep.binary, classRep.source): @unchecked) match { case (Some(bin), Some(src)) if needCompile(bin, src) && !binaryOnly(owner, classRep.name) => if (ctx.settings.verbose.value) ctx.inform("[symloader] picked up newer source file for " + src.path) @@ -144,10 +144,10 @@ class SymbolLoaders { /** Load contents of a package */ - class PackageLoader(_sourceModule: TermSymbol, classpath: ClassPath) + class PackageLoader(_sourceModule: TermSymbol, classPath: ClassPath) extends SymbolLoader { override def sourceModule(implicit ctx: Context) = _sourceModule - def description = "package loader " + classpath.name + def description(implicit ctx: Context) = "package loader " + sourceModule.fullName private var enterFlatClasses: Option[Context => Unit] = None @@ -188,23 +188,25 @@ class SymbolLoaders { def isFlatName(name: SimpleTermName) = name.lastIndexOf('$', name.length - 2) >= 0 - def isFlatName(classRep: ClassPath#ClassRep) = { + def isFlatName(classRep: ClassRepresentation) = { val idx = classRep.name.indexOf('$') idx >= 0 && idx < classRep.name.length - 1 } - def maybeModuleClass(classRep: ClassPath#ClassRep) = classRep.name.last == '$' + def maybeModuleClass(classRep: ClassRepresentation) = classRep.name.last == '$' - private def enterClasses(root: SymDenotation, flat: Boolean)(implicit ctx: Context) = { - def isAbsent(classRep: ClassPath#ClassRep) = + private def enterClasses(root: SymDenotation, packageName: String, flat: Boolean)(implicit ctx: Context) = { + def isAbsent(classRep: ClassRepresentation) = !root.unforcedDecls.lookup(classRep.name.toTypeName).exists if (!root.isRoot) { - for (classRep <- classpath.classes) + val classReps = classPath.classes(packageName) + + for (classRep <- classReps) if (!maybeModuleClass(classRep) && isFlatName(classRep) == flat && (!flat || isAbsent(classRep))) // on 2nd enter of flat names, check that the name has not been entered before initializeFromClassPath(root.symbol, classRep) - for (classRep <- classpath.classes) + for (classRep <- classReps) if (maybeModuleClass(classRep) && isFlatName(classRep) == flat && isAbsent(classRep)) initializeFromClassPath(root.symbol, classRep) @@ -217,14 +219,24 @@ class SymbolLoaders { root.info = ClassInfo(pre, root.symbol.asClass, Nil, currentDecls, pre select sourceModule) if (!sourceModule.isCompleted) sourceModule.completer.complete(sourceModule) + + val packageName = if (root.isEffectiveRoot) "" else root.fullName.toString + enterFlatClasses = Some { ctx => enterFlatClasses = None - enterClasses(root, flat = true)(ctx) + enterClasses(root, packageName, flat = true)(ctx) } - enterClasses(root, flat = false) + enterClasses(root, packageName, flat = false) if (!root.isEmptyPackage) - for (pkg <- classpath.packages) - enterPackage(root.symbol, pkg) + for (pkg <- classPath.packages(packageName)) { + val fullName = pkg.name + val name = + if (packageName.isEmpty) fullName + else fullName.substring(packageName.length + 1) + + enterPackage(root.symbol, name.toTermName, + (module, modcls) => new PackageLoader(module, classPath)) + } } } } @@ -242,7 +254,7 @@ abstract class SymbolLoader extends LazyType { /** Description of the resource (ClassPath, AbstractFile) * being processed by this loader */ - def description: String + def description(implicit ctx: Context): String override def complete(root: SymDenotation)(implicit ctx: Context): Unit = { def signalError(ex: Exception): Unit = { @@ -283,7 +295,7 @@ class ClassfileLoader(val classfile: AbstractFile) extends SymbolLoader { override def sourceFileOrNull: AbstractFile = classfile - def description = "class file " + classfile.toString + def description(implicit ctx: Context) = "class file " + classfile.toString def rootDenots(rootDenot: ClassDenotation)(implicit ctx: Context): (ClassDenotation, ClassDenotation) = { val linkedDenot = rootDenot.scalacLinkedClass.denot match { @@ -318,7 +330,7 @@ class ClassfileLoader(val classfile: AbstractFile) extends SymbolLoader { } class SourcefileLoader(val srcfile: AbstractFile) extends SymbolLoader { - def description = "source file " + srcfile.toString + def description(implicit ctx: Context) = "source file " + srcfile.toString override def sourceFileOrNull = srcfile def doComplete(root: SymDenotation)(implicit ctx: Context): Unit = unsupported("doComplete") } diff --git a/compiler/src/dotty/tools/dotc/core/classfile/ClassfileParser.scala b/compiler/src/dotty/tools/dotc/core/classfile/ClassfileParser.scala index 27afa4d09..9415c047f 100644 --- a/compiler/src/dotty/tools/dotc/core/classfile/ClassfileParser.scala +++ b/compiler/src/dotty/tools/dotc/core/classfile/ClassfileParser.scala @@ -7,7 +7,7 @@ import Contexts._, Symbols._, Types._, Names._, StdNames._, NameOps._, Scopes._, import SymDenotations._, unpickleScala2.Scala2Unpickler._, Constants._, Annotations._, util.Positions._ import NameKinds.{ModuleClassName, DefaultGetterName} import ast.tpd._ -import java.io.{ File, IOException } +import java.io.{ ByteArrayInputStream, DataInputStream, File, IOException } import java.lang.Integer.toHexString import scala.collection.{ mutable, immutable } import scala.collection.mutable.{ ListBuffer, ArrayBuffer } @@ -194,13 +194,21 @@ class ClassfileParser( val name = pool.getName(in.nextChar) val isConstructor = name eq nme.CONSTRUCTOR - /** Strip leading outer param from constructor. - * Todo: Also strip trailing access tag for private inner constructors? + /** Strip leading outer param from constructor and trailing access tag for + * private inner constructors. */ - def stripOuterParamFromConstructor() = innerClasses.get(currentClassName) match { + def normalizeConstructorParams() = innerClasses.get(currentClassName) match { case Some(entry) if !isStatic(entry.jflags) => val mt @ MethodTpe(paramNames, paramTypes, resultType) = denot.info - denot.info = mt.derivedLambdaType(paramNames.tail, paramTypes.tail, resultType) + var normalizedParamNames = paramNames.tail + var normalizedParamTypes = paramTypes.tail + if ((jflags & JAVA_ACC_SYNTHETIC) != 0) { + // SI-7455 strip trailing dummy argument ("access constructor tag") from synthetic constructors which + // are added when an inner class needs to access a private constructor. + normalizedParamNames = paramNames.dropRight(1) + normalizedParamTypes = paramTypes.dropRight(1) + } + denot.info = mt.derivedLambdaType(normalizedParamNames, normalizedParamTypes, resultType) case _ => } @@ -216,7 +224,7 @@ class ClassfileParser( denot.info = pool.getType(in.nextChar) if (isEnum) denot.info = ConstantType(Constant(sym)) - if (isConstructor) stripOuterParamFromConstructor() + if (isConstructor) normalizeConstructorParams() setPrivateWithin(denot, jflags) denot.info = translateTempPoly(parseAttributes(sym, denot.info)) if (isConstructor) normalizeConstructorInfo() @@ -227,8 +235,12 @@ class ClassfileParser( // seal java enums if (isEnum) { val enumClass = sym.owner.linkedClass - if (!(enumClass is Flags.Sealed)) enumClass.setFlag(Flags.AbstractSealed) - enumClass.addAnnotation(Annotation.makeChild(sym)) + if (!enumClass.exists) + ctx.warning(s"no linked class for java enum $sym in ${sym.owner}. A referencing class file might be missing an InnerClasses entry.") + else { + if (!(enumClass is Flags.Sealed)) enumClass.setFlag(Flags.AbstractSealed) + enumClass.addAnnotation(Annotation.makeChild(sym)) + } } } finally { in.bp = oldbp @@ -665,7 +677,7 @@ class ClassfileParser( for (entry <- innerClasses.values) { // create a new class member for immediate inner classes if (entry.outerName == currentClassName) { - val file = ctx.platform.classPath.findBinaryFile(entry.externalName.toString) getOrElse { + val file = ctx.platform.classPath.findClassFile(entry.externalName.toString) getOrElse { throw new AssertionError(entry.externalName) } enterClassAndModule(entry, file, entry.jflags) @@ -923,12 +935,16 @@ class ClassfileParser( case null => val start = starts(index) if (in.buf(start).toInt != CONSTANT_UTF8) errorBadTag(start) - val name = termName(in.buf, start + 3, in.getChar(start + 1)) + val len = in.getChar(start + 1).toInt + val name = termName(fromMUTF8(in.buf, start + 1, len + 2)) values(index) = name name } } + private def fromMUTF8(bytes: Array[Byte], offset: Int, len: Int): String = + new DataInputStream(new ByteArrayInputStream(bytes, offset, len)).readUTF + /** Return the name found at given index in the constant pool, with '/' replaced by '.'. */ def getExternalName(index: Int): SimpleTermName = { if (index <= 0 || len <= index) diff --git a/compiler/src/dotty/tools/dotc/repl/CompilingInterpreter.scala b/compiler/src/dotty/tools/dotc/repl/CompilingInterpreter.scala index 65c64f708..eed75fe88 100644 --- a/compiler/src/dotty/tools/dotc/repl/CompilingInterpreter.scala +++ b/compiler/src/dotty/tools/dotc/repl/CompilingInterpreter.scala @@ -138,7 +138,7 @@ class CompilingInterpreter( private val prevRequests = new ArrayBuffer[Request]() /** the compiler's classpath, as URL's */ - val compilerClasspath: List[URL] = ictx.platform.classPath(ictx).asURLs + val compilerClasspath: Seq[URL] = ictx.platform.classPath(ictx).asURLs /* A single class loader is used for all commands interpreted by this Interpreter. It would also be possible to create a new class loader for each command diff --git a/compiler/src/dotty/tools/dotc/transform/LambdaLift.scala b/compiler/src/dotty/tools/dotc/transform/LambdaLift.scala index 7578b57f1..a729368d4 100644 --- a/compiler/src/dotty/tools/dotc/transform/LambdaLift.scala +++ b/compiler/src/dotty/tools/dotc/transform/LambdaLift.scala @@ -143,13 +143,17 @@ class LambdaLift extends MiniPhase with IdentityDenotTransformer { thisTransform /** Set `liftedOwner(sym)` to `owner` if `owner` is more deeply nested * than the previous value of `liftedowner(sym)`. */ - def narrowLiftedOwner(sym: Symbol, owner: Symbol)(implicit ctx: Context) = + def narrowLiftedOwner(sym: Symbol, owner: Symbol)(implicit ctx: Context): Unit = if (sym.maybeOwner.isTerm && owner.isProperlyContainedIn(liftedOwner(sym)) && owner != sym) { - ctx.log(i"narrow lifted $sym to $owner") - changedLiftedOwner = true - liftedOwner(sym) = owner + if (sym.is(InSuperCall) && owner.isProperlyContainedIn(sym.enclosingClass)) + narrowLiftedOwner(sym, sym.enclosingClass) + else { + ctx.log(i"narrow lifted $sym to $owner") + changedLiftedOwner = true + liftedOwner(sym) = owner + } } /** Mark symbol `sym` as being free in `enclosure`, unless `sym` is defined diff --git a/compiler/src/dotty/tools/dotc/transform/PatternMatcher.scala b/compiler/src/dotty/tools/dotc/transform/PatternMatcher.scala index 41a1218eb..447a003e7 100644 --- a/compiler/src/dotty/tools/dotc/transform/PatternMatcher.scala +++ b/compiler/src/dotty/tools/dotc/transform/PatternMatcher.scala @@ -1408,7 +1408,7 @@ class PatternMatcher extends MiniPhaseTransform with DenotTransformer { protected def seqTree(binder: Symbol) = tupleSel(binder)(firstIndexingBinder + 1) protected def tupleSel(binder: Symbol)(i: Int): Tree = { val accessors = - if (Applications.canProductMatch(binder.info)) + if (defn.isProductSubType(binder.info)) productSelectors(binder.info) else binder.caseAccessors val res = diff --git a/compiler/src/dotty/tools/dotc/typer/Applications.scala b/compiler/src/dotty/tools/dotc/typer/Applications.scala index c4d3e2292..7e17abbcd 100644 --- a/compiler/src/dotty/tools/dotc/typer/Applications.scala +++ b/compiler/src/dotty/tools/dotc/typer/Applications.scala @@ -48,9 +48,6 @@ object Applications { ref.info.widenExpr.dealias } - def canProductMatch(tp: Type)(implicit ctx: Context) = - extractorMemberType(tp, nme._1).exists - /** Does `tp` fit the "product match" conditions as an unapply result type * for a pattern with `numArgs` subpatterns? * This is the case of `tp` has members `_1` to `_N` where `N == numArgs`. @@ -72,7 +69,7 @@ object Applications { } def productArity(tp: Type)(implicit ctx: Context) = - if (canProductMatch(tp)) productSelectorTypes(tp).size else -1 + if (defn.isProductSubType(tp)) productSelectorTypes(tp).size else -1 def productSelectors(tp: Type)(implicit ctx: Context): List[Symbol] = { val sels = for (n <- Iterator.from(0)) yield tp.member(nme.selectorName(n)).symbol @@ -114,7 +111,7 @@ object Applications { getUnapplySelectors(getTp, args, pos) else if (unapplyResult isRef defn.BooleanClass) Nil - else if (canProductMatch(unapplyResult)) + else if (defn.isProductSubType(unapplyResult)) productSelectorTypes(unapplyResult) // this will cause a "wrong number of arguments in pattern" error later on, // which is better than the message in `fail`. diff --git a/compiler/src/dotty/tools/dotc/typer/Namer.scala b/compiler/src/dotty/tools/dotc/typer/Namer.scala index 19b6dfa71..da9f9f6ac 100644 --- a/compiler/src/dotty/tools/dotc/typer/Namer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Namer.scala @@ -843,7 +843,7 @@ class Namer { typer: Typer => val targs1 = targs map (typedAheadType(_)) val ptype = typedAheadType(tpt).tpe appliedTo targs1.tpes if (ptype.typeParams.isEmpty) ptype - else typedAheadExpr(parent).tpe + else fullyDefinedType(typedAheadExpr(parent).tpe, "class parent", parent.pos) } /* Check parent type tree `parent` for the following well-formedness conditions: diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index ba55dfe30..02538671e 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -179,6 +179,20 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit previous } + def selection(imp: ImportInfo, name: Name) = + if (imp.sym.isCompleting) { + ctx.warning(i"cyclic ${imp.sym}, ignored", tree.pos) + NoType + } else if (unimported.nonEmpty && unimported.contains(imp.site.termSymbol)) + NoType + else { + val pre = imp.site + val denot = pre.member(name).accessibleFrom(pre)(refctx) + // Pass refctx so that any errors are reported in the context of the + // reference instead of the + if (reallyExists(denot)) pre.select(name, denot) else NoType + } + /** The type representing a named import with enclosing name when imported * from given `site` and `selectors`. */ @@ -194,25 +208,15 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit found } - def selection(name: Name) = - if (imp.sym.isCompleting) { - ctx.warning(i"cyclic ${imp.sym}, ignored", tree.pos) - NoType - } - else if (unimported.nonEmpty && unimported.contains(imp.site.termSymbol)) - NoType - else { - // Pass refctx so that any errors are reported in the context of the - // reference instead of the - checkUnambiguous(selectionType(imp.site, name, tree.pos)(refctx)) - } + def unambiguousSelection(name: Name) = + checkUnambiguous(selection(imp, name)) selector match { case Thicket(fromId :: Ident(Name) :: _) => val Ident(from) = fromId - selection(if (name.isTypeName) from.toTypeName else from) + unambiguousSelection(if (name.isTypeName) from.toTypeName else from) case Ident(Name) => - selection(name) + unambiguousSelection(name) case _ => recur(rest) } @@ -225,18 +229,10 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit /** The type representing a wildcard import with enclosing name when imported * from given import info */ - def wildImportRef(imp: ImportInfo)(implicit ctx: Context): Type = { - if (imp.isWildcardImport) { - val pre = imp.site - if (!unimported.contains(pre.termSymbol) && - !imp.excluded.contains(name.toTermName) && - name != nme.CONSTRUCTOR) { - val denot = pre.member(name).accessibleFrom(pre)(refctx) - if (reallyExists(denot)) return pre.select(name, denot) - } - } - NoType - } + def wildImportRef(imp: ImportInfo)(implicit ctx: Context): Type = + if (imp.isWildcardImport && !imp.excluded.contains(name.toTermName) && name != nme.CONSTRUCTOR) + selection(imp, name) + else NoType /** Is (some alternative of) the given predenotation `denot` * defined in current compilation unit? @@ -763,7 +759,7 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit /** Is `formal` a product type which is elementwise compatible with `params`? */ def ptIsCorrectProduct(formal: Type) = { isFullyDefined(formal, ForceDegree.noBottom) && - Applications.canProductMatch(formal) && + defn.isProductSubType(formal) && Applications.productSelectorTypes(formal).corresponds(params) { (argType, param) => param.tpt.isEmpty || argType <:< typedAheadType(param.tpt).tpe |