summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorMartin Odersky <odersky@gmail.com>2011-01-23 15:15:30 +0000
committerMartin Odersky <odersky@gmail.com>2011-01-23 15:15:30 +0000
commit168a3ffdd91dc9bfd6cab93ad771e79ba226794e (patch)
tree82a8a59f2b3e66f0de7df2c6d4de96022a99b329 /src
parenta99604e60b23589558fd1b51fdd099f8febb6a36 (diff)
downloadscala-168a3ffdd91dc9bfd6cab93ad771e79ba226794e.tar.gz
scala-168a3ffdd91dc9bfd6cab93ad771e79ba226794e.tar.bz2
scala-168a3ffdd91dc9bfd6cab93ad771e79ba226794e.zip
hardeing of the presentation compiler.
Diffstat (limited to 'src')
-rw-r--r--src/compiler/scala/tools/nsc/interactive/CompilerControl.scala217
-rw-r--r--src/compiler/scala/tools/nsc/interactive/Global.scala92
-rw-r--r--src/compiler/scala/tools/nsc/interactive/PresentationCompilerThread.scala5
-rw-r--r--src/compiler/scala/tools/nsc/symtab/BrowsingLoaders.scala2
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)