diff --git a/src/compiler/scala/tools/nsc/Global.scala b/src/compiler/scala/tools/nsc/Global.scala index c917ec998471..4461ae1cc16d 100644 --- a/src/compiler/scala/tools/nsc/Global.scala +++ b/src/compiler/scala/tools/nsc/Global.scala @@ -72,7 +72,10 @@ class Global(var currentSettings: Settings, var reporter: Reporter) extends Symb if (forMSIL) new { val global: Global.this.type = Global.this } with MSILPlatform else new { val global: Global.this.type = Global.this } with JavaPlatform - def classPath: ClassPath[platform.BinaryRepr] = platform.classPath + type PlatformClassPath = ClassPath[platform.BinaryRepr] + + def classPath: PlatformClassPath = platform.classPath + def rootLoader: LazyType = platform.rootLoader // sub-components -------------------------------------------------- @@ -840,6 +843,97 @@ class Global(var currentSettings: Settings, var reporter: Reporter) extends Symb def printAfterEachPhase[T](op: => T): Unit = describeAfterEachPhase(op) foreach (m => println(" " + m)) + // ------------ Invalidations --------------------------------- + + /** Is given package class a system package class that cannot be invalidated? + */ + private def isSystemPackageClass(pkg: Symbol) = + pkg == definitions.RootClass || + pkg == definitions.ScalaPackageClass || { + val pkgname = pkg.fullName + (pkgname startsWith "scala.") && !(pkgname startsWith "scala.tools") + } + + /** Invalidates packages that contain classes defined in a classpath entry, and + * rescans that entry. + * @param path A fully qualified name that refers to a directory or jar file that's + * an entry on the classpath. + * First, causes the classpath entry referred to by `path` to be rescanned, so that + * any new files or deleted files or changes in subpackages are picked up. + * Second, invalidates any packages for which one of the following considitions is met: + + * - the classpath entry contained during the last compilation run classfiles + * that represent a member in the package + * - the classpath entry now contains classfiles + * that represent a member in the package + * - the set of subpackages has changed. + * + * The invalidated packages are reset in their entirety; all member classes and member packages + * are re-accessed using the new classpath. + * Not invalidated are system packages that the compiler needs to access as parts + * of standard definitions. The criterion what is a system package is currently: + * any package rooted in "scala", with the exception of packages rooted in "scala.tools". + * This can be refined later. + * @return A pair consisting of + * - a list of invalidated packages + * - a list of of packages that should have been invalidated but were not because + * they are system packages. + */ + def invalidateClassPathEntry(path: String): (List[Symbol], List[Symbol]) = { + val invalidated, failed = new mutable.ListBuffer[Symbol] + classPath match { + case cp: util.MergedClassPath[_] => + val dir = AbstractFile getDirectory path + val canonical = Some(dir.canonicalPath) + cp.entries find (_.origin == canonical) match { + case Some(oldEntry) => + val newEntry = cp.context.newClassPath(dir) + platform.updateClassPath(oldEntry, newEntry) + informProgress(s"classpath updated to $classPath") + reSync(definitions.RootClass, classPath, oldEntry, newEntry, invalidated, failed) + case None => + error(s"cannot invalidate: no entry named $path in classpath $classPath") + } + } + def show(msg: String, syms: collection.Traversable[Symbol]) = + if (syms.nonEmpty) + informProgress(s"$msg: ${syms map (_.fullName) mkString ","}") + show("invalidated packages", invalidated) + show("could not invalidate system packages", failed) + (invalidated.toList, failed.toList) + } + + private def reSync(root: Symbol, all: PlatformClassPath, + oldEntry: PlatformClassPath, newEntry: PlatformClassPath, + invalidated: mutable.ListBuffer[Symbol], failed: mutable.ListBuffer[Symbol]) { + ifDebug(informProgress(s"syncing $root, $oldEntry -> $newEntry")) + val getName: ClassPath[platform.BinaryRepr] => String = (_.name) + val oldPackages = oldEntry.packages sortBy getName + val newPackages = newEntry.packages sortBy getName + val hasChanged = + oldEntry.classes.nonEmpty || + newEntry.classes.nonEmpty || + (oldPackages map getName) != (newPackages map getName) + if (hasChanged && !isSystemPackageClass(root)) { + root setInfo new loaders.PackageLoader(all) + invalidated += root + } else { + if (hasChanged) failed += root + for ((oldNested, newNested) <- oldPackages zip newPackages) { + val pkgname = newNested.name + val pkg = root.info decl newTermName(pkgname) + val allNested = (all.packages find (_.name == pkgname)).get + reSync(pkg.moduleClass, allNested, oldNested, newNested, invalidated, failed) + } + } + } + + /** Invalidate contents of setting -Yinvalidate */ + def doInvalidation() = settings.Yinvalidate.value match { + case "" => + case entry => invalidateClassPathEntry(entry) + } + // ----------- Runs --------------------------------------- private var curRun: Run = null @@ -859,6 +953,7 @@ class Global(var currentSettings: Settings, var reporter: Reporter) extends Symb * * @param sym A class symbol, object symbol, package, or package class. */ + @deprecated("use invalidateClassPathEntry instead") def clearOnNextRun(sym: Symbol) = false /* To try out clearOnNext run on the scala.tools.nsc project itself * replace `false` above with the following code @@ -870,7 +965,7 @@ class Global(var currentSettings: Settings, var reporter: Reporter) extends Symb } }} - * Then, fsc -Xexperimental clears the nsc porject between successive runs of `fsc`. + * Then, fsc -Xexperimental clears the nsc project between successive runs of `fsc`. */ /** Remove the current run when not needed anymore. Used by the build @@ -1115,6 +1210,7 @@ class Global(var currentSettings: Settings, var reporter: Reporter) extends Symb /** Reset all classes contained in current project, as determined by * the clearOnNextRun hook */ + @deprecated("use invalidateClassPathEntry instead") def resetProjectClasses(root: Symbol): Unit = try { def unlink(sym: Symbol) = if (sym != NoSymbol) root.info.decls.unlink(sym) @@ -1353,6 +1449,8 @@ class Global(var currentSettings: Settings, var reporter: Reporter) extends Symb } private def compileUnitsInternal(units: List[CompilationUnit], fromPhase: Phase) { + doInvalidation() + units foreach addUnit val startTime = currentTime diff --git a/src/compiler/scala/tools/nsc/backend/JavaPlatform.scala b/src/compiler/scala/tools/nsc/backend/JavaPlatform.scala index 7da7611ce2c1..e5e67e88115e 100644 --- a/src/compiler/scala/tools/nsc/backend/JavaPlatform.scala +++ b/src/compiler/scala/tools/nsc/backend/JavaPlatform.scala @@ -7,7 +7,7 @@ package scala.tools.nsc package backend import io.AbstractFile -import util.{ClassPath,JavaClassPath} +import util.{ClassPath,JavaClassPath,MergedClassPath,DeltaClassPath} import util.ClassPath.{ JavaContext, DefaultJavaContext } import scala.tools.util.PathResolver @@ -17,7 +17,17 @@ trait JavaPlatform extends Platform { type BinaryRepr = AbstractFile - lazy val classPath = new PathResolver(settings).result + private var currentClassPath: Option[MergedClassPath[BinaryRepr]] = None + + def classPath: ClassPath[BinaryRepr] = { + if (currentClassPath.isEmpty) currentClassPath = Some(new PathResolver(settings).result) + currentClassPath.get + } + + /** Update classpath with a substituted subentry */ + def updateClassPath(oldEntry: ClassPath[BinaryRepr], newEntry: ClassPath[BinaryRepr]) = + currentClassPath = Some(new DeltaClassPath(currentClassPath.get, oldEntry, newEntry)) + def rootLoader = new loaders.PackageLoader(classPath.asInstanceOf[ClassPath[platform.BinaryRepr]]) // [Martin] Why do we need a cast here? // The problem is that we cannot specify at this point that global.platform should be of type JavaPlatform. diff --git a/src/compiler/scala/tools/nsc/backend/MSILPlatform.scala b/src/compiler/scala/tools/nsc/backend/MSILPlatform.scala index 65b1fbc22959..30447e1274af 100644 --- a/src/compiler/scala/tools/nsc/backend/MSILPlatform.scala +++ b/src/compiler/scala/tools/nsc/backend/MSILPlatform.scala @@ -30,7 +30,11 @@ trait MSILPlatform extends Platform { lazy val classPath = MsilClassPath.fromSettings(settings) def rootLoader = new loaders.PackageLoader(classPath.asInstanceOf[ClassPath[platform.BinaryRepr]]) // See discussion in JavaPlatForm for why we need a cast here. - + + /** Update classpath with a substituted subentry */ + def updateClassPath(oldEntry: ClassPath[BinaryRepr], newEntry: ClassPath[BinaryRepr]) = + throw new UnsupportedOperationException("classpath invalidations not supported on MSIL") + def platformPhases = List( genMSIL // generate .msil files ) diff --git a/src/compiler/scala/tools/nsc/backend/Platform.scala b/src/compiler/scala/tools/nsc/backend/Platform.scala index 23592eeb6156..f97d3e3d5640 100644 --- a/src/compiler/scala/tools/nsc/backend/Platform.scala +++ b/src/compiler/scala/tools/nsc/backend/Platform.scala @@ -23,6 +23,9 @@ trait Platform { /** The root symbol loader. */ def rootLoader: LazyType + + /** Update classpath with a substituted subentry */ + def updateClassPath(oldEntry: ClassPath[BinaryRepr], newEntry: ClassPath[BinaryRepr]) /** Any platform-specific phases. */ def platformPhases: List[SubComponent] diff --git a/src/compiler/scala/tools/nsc/backend/icode/Members.scala b/src/compiler/scala/tools/nsc/backend/icode/Members.scala index 019e887c4ed9..efb4e7a1997f 100644 --- a/src/compiler/scala/tools/nsc/backend/icode/Members.scala +++ b/src/compiler/scala/tools/nsc/backend/icode/Members.scala @@ -156,7 +156,7 @@ trait Members { def newBlock() = code.newBlock def startBlock = code.startBlock - def lastBlock = blocks.last + def lastBlock = { assert(blocks.nonEmpty, symbol); blocks.last } def blocks = code.blocksList def linearizedBlocks(lin: Linearizer = self.linearizer): List[BasicBlock] = lin linearize this diff --git a/src/compiler/scala/tools/nsc/settings/ScalaSettings.scala b/src/compiler/scala/tools/nsc/settings/ScalaSettings.scala index 4aa30038f613..efde2cee256f 100644 --- a/src/compiler/scala/tools/nsc/settings/ScalaSettings.scala +++ b/src/compiler/scala/tools/nsc/settings/ScalaSettings.scala @@ -171,6 +171,7 @@ trait ScalaSettings extends AbsScalaSettings val Ynotnull = BooleanSetting ("-Ynotnull", "Enable (experimental and incomplete) scala.NotNull.") val YmethodInfer = BooleanSetting ("-Yinfer-argument-types", "Infer types for arguments of overriden methods.") val etaExpandKeepsStar = BooleanSetting ("-Yeta-expand-keeps-star", "Eta-expand varargs methods to T* rather than Seq[T]. This is a temporary option to ease transition.") + val Yinvalidate = StringSetting ("-Yinvalidate", "classpath-entry", "Invalidate classpath entry before run", "") val noSelfCheck = BooleanSetting ("-Yno-self-type-checks", "Suppress check for self-type conformance among inherited members.") val YvirtClasses = false // too embryonic to even expose as a -Y //BooleanSetting ("-Yvirtual-classes", "Support virtual classes") diff --git a/src/compiler/scala/tools/nsc/util/ClassPath.scala b/src/compiler/scala/tools/nsc/util/ClassPath.scala index 404490bd49ba..18b9c7dd01c8 100644 --- a/src/compiler/scala/tools/nsc/util/ClassPath.scala +++ b/src/compiler/scala/tools/nsc/util/ClassPath.scala @@ -312,6 +312,11 @@ class DirectoryClassPath(val dir: AbstractFile, val context: ClassPathContext[Ab override def toString() = "directory classpath: "+ origin.getOrElse("?") } +class DeltaClassPath[T](original: MergedClassPath[T], oldEntry: ClassPath[T], newEntry: ClassPath[T]) +extends MergedClassPath[T](original.entries map (e => if (e == oldEntry) newEntry else e), original.context) { + require(original.entries contains oldEntry) +} + /** * A classpath unifying multiple class- and sourcepath entries. */