diff options
author | Martin Odersky <odersky@gmail.com> | 2011-01-23 15:15:30 +0000 |
---|---|---|
committer | Martin Odersky <odersky@gmail.com> | 2011-01-23 15:15:30 +0000 |
commit | 168a3ffdd91dc9bfd6cab93ad771e79ba226794e (patch) | |
tree | 82a8a59f2b3e66f0de7df2c6d4de96022a99b329 | |
parent | a99604e60b23589558fd1b51fdd099f8febb6a36 (diff) | |
download | scala-168a3ffdd91dc9bfd6cab93ad771e79ba226794e.tar.gz scala-168a3ffdd91dc9bfd6cab93ad771e79ba226794e.tar.bz2 scala-168a3ffdd91dc9bfd6cab93ad771e79ba226794e.zip |
hardeing of the presentation compiler.
4 files changed, 197 insertions, 119 deletions
diff --git a/src/compiler/scala/tools/nsc/interactive/CompilerControl.scala b/src/compiler/scala/tools/nsc/interactive/CompilerControl.scala index 4a14bef96a..3171df2fdc 100644 --- a/src/compiler/scala/tools/nsc/interactive/CompilerControl.scala +++ b/src/compiler/scala/tools/nsc/interactive/CompilerControl.scala @@ -8,72 +8,35 @@ import scala.tools.nsc.symtab._ import scala.tools.nsc.ast._ /** Interface of interactive compiler to a client such as an IDE + * The model the presentation compiler consists of the following parts: + * + * unitOfFile: The map from sourcefiles to loaded units. A sourcefile/unit is loaded if it occurs in that map. + * + * manipulated by: removeUnitOf, reloadSources. + * + * A call to reloadSources will add the given sources to the loaded units, and + * start a new background compiler pass to compile all loaded units (with the indicated sources first). + * Each background compiler pass has its own typer run. + * The background compiler thread can be interrupted each time an AST node is + * completely typechecked in the following ways: + + * 1. by a new call to reloadSources. This starts a new background compiler pass with a new typer run. + * 2. by a call to askTypeTree. This starts a new typer run if the forceReload parameter = true + * 3. by a call to askTypeAt, askTypeCompletion, askScopeCompletion, askToDoFirst, askLinkPos, askLastType. + * 4. by raising an exception in the scheduler. + * 5. by passing a high-priority action wrapped in ask { ... }. + * + * Actions under 1-3 can themselves be interrupted if they involve typechecking + * AST nodes. High-priority actions under 5 cannot; they always run to completion. + * So these high-priority actions should to be short. + * + * Normally, an interrupted action continues after the interrupting action is finished. + * However, if the interrupting action created a new typer run, the interrupted + * action is aborted. If there's an outstanding response, it will be set to + * a Right value with a FreshRunReq exception. */ trait CompilerControl { self: Global => - abstract class WorkItem extends (() => Unit) - - case class ReloadItem(sources: List[SourceFile], response: Response[Unit]) extends WorkItem { - def apply() = reload(sources, response) - override def toString = "reload "+sources - } - - class AskTypeAtItem(val pos: Position, response: Response[Tree]) extends WorkItem { - def apply() = self.getTypedTreeAt(pos, response) - override def toString = "typeat "+pos.source+" "+pos.show - } - - class AskTypeItem(val source: SourceFile, val forceReload: Boolean, response: Response[Tree]) extends WorkItem { - def apply() = self.getTypedTree(source, forceReload, response) - override def toString = "typecheck" - } - - class AskLastTypeItem(val source: SourceFile, response: Response[Tree]) extends WorkItem { - def apply() = self.getLastTypedTree(source, response) - override def toString = "reconcile" - } - - class AskTypeCompletionItem(val pos: Position, response: Response[List[Member]]) extends WorkItem { - def apply() = self.getTypeCompletion(pos, response) - override def toString = "type completion "+pos.source+" "+pos.show - } - - class AskScopeCompletionItem(val pos: Position, response: Response[List[Member]]) extends WorkItem { - def apply() = self.getScopeCompletion(pos, response) - override def toString = "scope completion "+pos.source+" "+pos.show - } - - class AskToDoFirstItem(val source: SourceFile) extends WorkItem { - def apply() = moveToFront(List(source)) - override def toString = "dofirst "+source - } - - class AskLinkPosItem(val sym: Symbol, val source: SourceFile, response: Response[Position]) extends WorkItem { - def apply() = self.getLinkPos(sym, source, response) - override def toString = "linkpos "+sym+" in "+source - } - - /** Info given for every member found by completion - */ - abstract class Member { - val sym: Symbol - val tpe: Type - val accessible: Boolean - } - - case class TypeMember( - sym: Symbol, - tpe: Type, - accessible: Boolean, - inherited: Boolean, - viaView: Symbol) extends Member - - case class ScopeMember( - sym: Symbol, - tpe: Type, - accessible: Boolean, - viaImport: Tree) extends Member - type Response[T] = scala.tools.nsc.interactive.Response[T] /** The scheduler by which client and compiler communicate @@ -81,29 +44,28 @@ trait CompilerControl { self: Global => */ protected[interactive] val scheduler = new WorkScheduler - /** The compilation unit corresponding to a source file + /** Return the compilation unit attached to a source file, or None + * if source is not loaded. + */ + def getUnitOf(s: SourceFile): Option[RichCompilationUnit] = getUnit(s) + + /** The compilation unit corresponding to a source file * if it does not yet exist create a new one atomically + * Note: We want to get roid of this operation as it messes compiler invariants. */ - def unitOf(s: SourceFile): RichCompilationUnit = unitOfFile.synchronized { - unitOfFile get s.file match { - case Some(unit) => - unit - case None => - val unit = new RichCompilationUnit(s) - unitOfFile(s.file) = unit - unit - } - } + @deprecated("use getUnitOf(s) instead") + def unitOf(s: SourceFile): RichCompilationUnit = getOrCreateUnitOf(s) /** The compilation unit corresponding to a position */ - def unitOf(pos: Position): RichCompilationUnit = unitOf(pos.source) + @deprecated("use getUnitOf(pos.source) instead") + def unitOf(pos: Position): RichCompilationUnit = getOrCreateUnitOf(pos.source) - /** Remove the CompilationUnit corresponding to the given SourceFile + /** Removes the CompilationUnit corresponding to the given SourceFile * from consideration for recompilation. */ - def removeUnitOf(s: SourceFile) = unitOfFile remove s.file + def removeUnitOf(s: SourceFile): Option[RichCompilationUnit] = { toBeRemoved += s.file; unitOfFile get s.file } - /* returns the top level classes and objects that were deleted + /** Returns the top level classes and objects that were deleted * in the editor since last time recentlyDeleted() was called. */ def recentlyDeleted(): List[Symbol] = deletedTopLevelSyms.synchronized { @@ -128,8 +90,10 @@ trait CompilerControl { self: Global => throw new FatalError("no context found for "+pos) } - /** Make sure a set of compilation units is loaded and parsed. - * Return () to syncvar `response` on completion. + /** Makes sure a set of compilation units is loaded and parsed. + * Returns () to syncvar `response` on completions. + * Afterwards a new background compiler run is started with + * the given sources at the head of the list of to-be-compiled sources. */ def askReload(sources: List[SourceFile], response: Response[Unit]) = { val superseeded = scheduler.dequeueAll { @@ -140,17 +104,19 @@ trait CompilerControl { self: Global => scheduler postWorkItem new ReloadItem(sources, response) } - /** Set sync var `response` to the smallest fully attributed tree that encloses position `pos`. + /** Sets sync var `response` to the smallest fully attributed tree that encloses position `pos`. + * @pre The source file belonging to `pos` needs to be loaded. */ def askTypeAt(pos: Position, response: Response[Tree]) = scheduler postWorkItem new AskTypeAtItem(pos, response) - /** Set sync var `response` to the fully attributed & typechecked tree contained in `source`. + /** Sets sync var `response` to the fully attributed & typechecked tree contained in `source`. + * @pre `source` needs to be loaded. */ def askType(source: SourceFile, forceReload: Boolean, response: Response[Tree]) = scheduler postWorkItem new AskTypeItem(source, forceReload, response) - /** Set sync var `response` to the position of the definition of the given link in + /** Sets sync var `response` to the position of the definition of the given link in * the given sourcefile. * * @param sym The symbol referenced by the link (might come from a classfile) @@ -158,42 +124,109 @@ trait CompilerControl { self: Global => * @param response A response that will be set to the following: * If `source` contains a definition that is referenced by the given link * the position of that definition, otherwise NoPosition. + * Note: This operation does not automatically load `source`. If `source` + * is unloaded, it stays that way. */ def askLinkPos(sym: Symbol, source: SourceFile, response: Response[Position]) = scheduler postWorkItem new AskLinkPosItem(sym, source, response) - /** Set sync var `response` to the last fully attributed & typechecked tree produced from `source`. + /** Sets sync var `response` to the last fully attributed & typechecked tree produced from `source`. * If no such tree exists yet, do a normal askType(source, false, response) */ def askLastType(source: SourceFile, response: Response[Tree]) = scheduler postWorkItem new AskLastTypeItem(source, response) - /** Set sync var `response' to list of members that are visible + /** Sets sync var `response' to list of members that are visible * as members of the tree enclosing `pos`, possibly reachable by an implicit. */ def askTypeCompletion(pos: Position, response: Response[List[Member]]) = scheduler postWorkItem new AskTypeCompletionItem(pos, response) - /** Set sync var `response' to list of members that are visible + /** Sets sync var `response' to list of members that are visible * as members of the scope enclosing `pos`. */ def askScopeCompletion(pos: Position, response: Response[List[Member]]) = scheduler postWorkItem new AskScopeCompletionItem(pos, response) - /** Ask to do unit first on present and subsequent type checking passes */ - def askToDoFirst(f: SourceFile) = - scheduler postWorkItem new AskToDoFirstItem(f) + /** Asks to do unit corresponding to given source file on present and subsequent type checking passes */ + def askToDoFirst(source: SourceFile) = + scheduler postWorkItem new AskToDoFirstItem(source) - /** Cancel current compiler run and start a fresh one where everything will be re-typechecked + /** Cancels current compiler run and start a fresh one where everything will be re-typechecked * (but not re-loaded). */ def askReset() = scheduler raise FreshRunReq - /** Tell the compile server to shutdown, and do not restart again */ + /** Tells the compile server to shutdown, and not to restart again */ def askShutdown() = scheduler raise ShutdownReq - /** Ask for a computation to be done quickly on the presentation compiler thread */ + /** Asks for a computation to be done quickly on the presentation compiler thread */ def ask[A](op: () => A): A = scheduler doQuickly op + + /** Info given for every member found by completion + */ + abstract class Member { + val sym: Symbol + val tpe: Type + val accessible: Boolean + } + + case class TypeMember( + sym: Symbol, + tpe: Type, + accessible: Boolean, + inherited: Boolean, + viaView: Symbol) extends Member + + case class ScopeMember( + sym: Symbol, + tpe: Type, + accessible: Boolean, + viaImport: Tree) extends Member + + // items that get sent to scheduler + + abstract class WorkItem extends (() => Unit) + + case class ReloadItem(sources: List[SourceFile], response: Response[Unit]) extends WorkItem { + def apply() = reload(sources, response) + override def toString = "reload "+sources + } + + class AskTypeAtItem(val pos: Position, response: Response[Tree]) extends WorkItem { + def apply() = self.getTypedTreeAt(pos, response) + override def toString = "typeat "+pos.source+" "+pos.show + } + + class AskTypeItem(val source: SourceFile, val forceReload: Boolean, response: Response[Tree]) extends WorkItem { + def apply() = self.getTypedTree(source, forceReload, response) + override def toString = "typecheck" + } + + class AskLastTypeItem(val source: SourceFile, response: Response[Tree]) extends WorkItem { + def apply() = self.getLastTypedTree(source, response) + override def toString = "reconcile" + } + + class AskTypeCompletionItem(val pos: Position, response: Response[List[Member]]) extends WorkItem { + def apply() = self.getTypeCompletion(pos, response) + override def toString = "type completion "+pos.source+" "+pos.show + } + + class AskScopeCompletionItem(val pos: Position, response: Response[List[Member]]) extends WorkItem { + def apply() = self.getScopeCompletion(pos, response) + override def toString = "scope completion "+pos.source+" "+pos.show + } + + class AskToDoFirstItem(val source: SourceFile) extends WorkItem { + def apply() = moveToFront(List(source)) + override def toString = "dofirst "+source + } + + class AskLinkPosItem(val sym: Symbol, val source: SourceFile, response: Response[Position]) extends WorkItem { + def apply() = self.getLinkPos(sym, source, response) + override def toString = "linkpos "+sym+" in "+source + } } // ---------------- Interpreted exceptions ------------------- diff --git a/src/compiler/scala/tools/nsc/interactive/Global.scala b/src/compiler/scala/tools/nsc/interactive/Global.scala index 3e5e2ee5bf..30a6374e01 100644 --- a/src/compiler/scala/tools/nsc/interactive/Global.scala +++ b/src/compiler/scala/tools/nsc/interactive/Global.scala @@ -2,13 +2,14 @@ package scala.tools.nsc package interactive import java.io.{ PrintWriter, StringWriter, FileReader, FileWriter } +import collection.mutable.{ArrayBuffer, SynchronizedBuffer} import scala.collection.mutable import mutable.{LinkedHashMap, SynchronizedMap,LinkedHashSet, SynchronizedSet} import scala.concurrent.SyncVar import scala.util.control.ControlThrowable -import scala.tools.nsc.io.{AbstractFile, LogReplay, Logger, NullLogger, Replayer} -import scala.tools.nsc.util.{SourceFile, BatchSourceFile, Position, RangePosition, NoPosition, WorkScheduler} +import scala.tools.nsc.io.{ AbstractFile, LogReplay, Logger, NullLogger, Replayer } +import scala.tools.nsc.util.{ SourceFile, BatchSourceFile, Position, RangePosition, NoPosition, WorkScheduler } import scala.tools.nsc.reporters._ import scala.tools.nsc.symtab._ import scala.tools.nsc.ast._ @@ -45,9 +46,9 @@ self => @inline final def debugLog(msg: => String) = if (debugIDE) println(msg) - /** Inform with msg only when debugIDE is true. */ + /** Inform with msg only when verboseIDE is true. */ @inline final def informIDE(msg: => String) = - if (verboseIDE) reporter.info(NoPosition, msg, true) + if (verboseIDE) println("["+msg+"]") override def forInteractive = true @@ -56,6 +57,40 @@ self => val unitOfFile = new LinkedHashMap[AbstractFile, RichCompilationUnit] with SynchronizedMap[AbstractFile, RichCompilationUnit] + protected val toBeRemoved = new ArrayBuffer[AbstractFile] with SynchronizedBuffer[AbstractFile] + + /** The compilation unit corresponding to a source file + * if it does not yet exist create a new one atomically + * Note: We want to rmeove this. + */ + protected[interactive] def getOrCreateUnitOf(s: SourceFile): RichCompilationUnit = + unitOfFile.synchronized { + unitOfFile get s.file match { + case Some(unit) => + unit + case None => + println("*** precondition violated: executing operation on non-loaded file " + s) + val unit = new RichCompilationUnit(s) + unitOfFile(s.file) = unit + unit + } + } + + /** Work through toBeRemoved list to remove any units. + * Then return optionlly unit associated with given source. + */ + protected[interactive] def getUnit(s: SourceFile): Option[RichCompilationUnit] = { + toBeRemoved.synchronized { + for (f <- toBeRemoved) { + unitOfFile -= f + allSources = allSources filter (_.file != f) + } + toBeRemoved.clear() + } + unitOfFile get s.file + } + + /** A list giving all files to be typechecked in the order they should be checked. */ var allSources: List[SourceFile] = List() @@ -90,8 +125,8 @@ self => */ override def signalDone(context: Context, old: Tree, result: Tree) { def integrateNew() { - // still needed? - context.unit.body = new TreeReplacer(old, result) transform context.unit.body + if (context.unit == null) + context.unit.body = new TreeReplacer(old, result) transform context.unit.body } if (activeLocks == 0) { // can we try to avoid that condition (?) if (context.unit != null && @@ -107,7 +142,7 @@ self => } val typerRun = currentTyperRun - while(true) + while (true) try { try { pollForWork(old.pos) @@ -123,7 +158,8 @@ self => integrateNew() throw FreshRunReq } catch { - case ex : ValidateException => // Ignore, this will have been reported elsewhere + case ex: ValidateException => // Ignore, this will have been reported elsewhere + debugLog("validate exception caught: "+ex) } } } @@ -162,7 +198,6 @@ self => var moreWorkAtNode: Int = -1 var nodesSeen = 0 - var noWorkFoundAtNode: Int = -1 /** Called from runner thread and signalDone: * Poll for interrupts and execute them immediately. @@ -220,10 +255,6 @@ self => debugLog("quitting work item: "+action) } case None => - if (nodesSeen > noWorkFoundAtNode) { - debugLog("no work found") - noWorkFoundAtNode = nodesSeen - } } } } @@ -287,14 +318,12 @@ self => // remove any files in first that are no longer maintained by presentation compiler (i.e. closed) allSources = allSources filter (s => unitOfFile contains (s.file)) - for (s <- allSources) { - val unit = unitOf(s) + for (s <- allSources; unit <- getUnit(s)) { if (!unit.isUpToDate && unit.status != JustParsed) reset(unit) // reparse previously typechecked units. if (unit.status == NotLoaded) parse(unit) } - for (s <- allSources) { - val unit = unitOf(s) + for (s <- allSources; unit <- getUnit(s)) { if (!unit.isUpToDate) typeCheck(unit) } @@ -376,7 +405,7 @@ self => } } catch { case CancelException => - ; + debugLog("cancelled") /* Commented out. Typing should always cancel requests case ex @ FreshRunReq => scheduler.postWorkItem(() => respondGradually(response)(op)) @@ -424,11 +453,11 @@ self => debugLog("already attributed") tree } else { - val unit = unitOf(pos) + val unit = getOrCreateUnitOf(pos.source) unit.targetPos = pos try { debugLog("starting targeted type check") - //newTyperRun() // not deeded for idempotent type checker phase + //newTyperRun() // not needed for idempotent type checker phase typeCheck(unit) println("tree not found at "+pos) EmptyTree @@ -443,7 +472,7 @@ self => /** A fully attributed tree corresponding to the entire compilation unit */ def typedTree(source: SourceFile, forceReload: Boolean): Tree = { informIDE("typedTree" + source + " forceReload: " + forceReload) - val unit = unitOf(source) + val unit = getOrCreateUnitOf(source) if (forceReload) reset(unit) if (unit.status <= PartiallyChecked) { //newTyperRun() // not deeded for idempotent type checker phase @@ -460,13 +489,13 @@ self => /** Set sync var `response` to a fully attributed tree corresponding to the * entire compilation unit */ - def getTypedTree(source : SourceFile, forceReload: Boolean, response: Response[Tree]) { + def getTypedTree(source: SourceFile, forceReload: Boolean, response: Response[Tree]) { respond(response)(typedTree(source, forceReload)) } /** Set sync var `response` to the last fully attributed tree produced from the * entire compilation unit */ - def getLastTypedTree(source : SourceFile, response: Response[Tree]) { + def getLastTypedTree(source: SourceFile, response: Response[Tree]) { informIDE("getLastTyped" + source) respond(response) { val unit = unitOf(source) @@ -485,7 +514,18 @@ self => if (owner.isClass) { val pre = adaptToNewRunMap(ThisType(owner)) val newsym = pre.decl(sym.name) filter { alt => - sym.isType || matchesType(pre.memberType(alt), adaptToNewRunMap(sym.tpe), false) + sym.isType || { + try { + val tp1 = pre.memberType(alt) + val tp2 = adaptToNewRunMap(sym.tpe) + matchesType(tp1, tp2, false) + } catch { + case ex: Throwable => + println("error in hyperlinking: "+ex) + ex.printStackTrace() + false + } + } } if (!preExisting) removeUnitOf(source) if (newsym == NoSymbol) { @@ -619,7 +659,9 @@ self => analyzer.newTyper(context.makeImplicit(reportAmbiguousErrors = false)) .typed(Apply(view.tree, List(tree)) setPos tree.pos) } catch { - case ex: TypeError => EmptyTree + case ex: TypeError => + debugLog("type error caught") + EmptyTree } } diff --git a/src/compiler/scala/tools/nsc/interactive/PresentationCompilerThread.scala b/src/compiler/scala/tools/nsc/interactive/PresentationCompilerThread.scala index cf013be7b8..f504427076 100644 --- a/src/compiler/scala/tools/nsc/interactive/PresentationCompilerThread.scala +++ b/src/compiler/scala/tools/nsc/interactive/PresentationCompilerThread.scala @@ -23,6 +23,7 @@ class PresentationCompilerThread(var compiler: Global, threadId: Int) extends Th compiler.outOfDate = false } catch { case FreshRunReq => + compiler.debugLog("fresh run req caught, starting new pass") } compiler.log.flush() } @@ -40,8 +41,10 @@ class PresentationCompilerThread(var compiler: Global, threadId: Int) extends Th compiler.newRunnerThread() ex match { - case FreshRunReq => // This shouldn't be reported + case FreshRunReq => + compiler.debugLog("fresh run req caught outside presentation compiler loop; ignored") // This shouldn't be reported case _ : Global#ValidateException => // This will have been reported elsewhere + compiler.debugLog("validate exception caught outside presentation compiler loop; ignored") case _ => ex.printStackTrace(); compiler.informIDE("Fatal Error: "+ex) } diff --git a/src/compiler/scala/tools/nsc/symtab/BrowsingLoaders.scala b/src/compiler/scala/tools/nsc/symtab/BrowsingLoaders.scala index 62c10a56c3..998b855111 100644 --- a/src/compiler/scala/tools/nsc/symtab/BrowsingLoaders.scala +++ b/src/compiler/scala/tools/nsc/symtab/BrowsingLoaders.scala @@ -87,7 +87,7 @@ abstract class BrowsingLoaders extends SymbolLoaders { } } - System.out.println("Browsing "+src) +// System.out.println("Browsing "+src) val source = new BatchSourceFile(src) val body = new OutlineParser(source).parse() System.out.println(body) |