From 8964f6f1bcc8500f1bc6a2808ef70d8852d208ec Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 30 Aug 2010 16:02:34 +0000 Subject: New wider interface of presentation compiler. --- src/compiler/scala/tools/nsc/Main.scala | 4 +- .../tools/nsc/interactive/CompilerControl.scala | 32 +++++----- .../scala/tools/nsc/interactive/ContextTrees.scala | 2 +- .../scala/tools/nsc/interactive/Global.scala | 65 +++++++++++++++------ .../scala/tools/nsc/interactive/REPL.scala | 12 ++-- .../scala/tools/nsc/interactive/Response.scala | 68 ++++++++++++++++++++++ .../scala/tools/nsc/typechecker/Typers.scala | 2 + 7 files changed, 141 insertions(+), 44 deletions(-) create mode 100644 src/compiler/scala/tools/nsc/interactive/Response.scala (limited to 'src') diff --git a/src/compiler/scala/tools/nsc/Main.scala b/src/compiler/scala/tools/nsc/Main.scala index 948c5f6fb8..1e6699079d 100644 --- a/src/compiler/scala/tools/nsc/Main.scala +++ b/src/compiler/scala/tools/nsc/Main.scala @@ -8,8 +8,6 @@ package scala.tools.nsc import java.io.File import File.pathSeparator -import scala.concurrent.SyncVar - import scala.tools.nsc.interactive.{ RefinedBuildManager, SimpleBuildManager } import scala.tools.nsc.io.AbstractFile import scala.tools.nsc.reporters.{Reporter, ConsoleReporter} @@ -59,7 +57,7 @@ object Main extends AnyRef with EvalLoop { import compiler.{ reporter => _, _ } val sfs = command.files.map(getSourceFile(_)) - val reloaded = new SyncVar[Either[Unit, Throwable]] + val reloaded = new interactive.Response[Unit] askReload(sfs, reloaded) reloaded.get.right.toOption match { case Some(ex) => reporter.cancelled = true // Causes exit code to be non-0 diff --git a/src/compiler/scala/tools/nsc/interactive/CompilerControl.scala b/src/compiler/scala/tools/nsc/interactive/CompilerControl.scala index c6106a9865..b00da77b46 100644 --- a/src/compiler/scala/tools/nsc/interactive/CompilerControl.scala +++ b/src/compiler/scala/tools/nsc/interactive/CompilerControl.scala @@ -1,7 +1,6 @@ package scala.tools.nsc package interactive -import scala.concurrent.SyncVar import scala.util.control.ControlThrowable import scala.tools.nsc.io.AbstractFile import scala.tools.nsc.util.{SourceFile, Position, WorkScheduler} @@ -12,14 +11,6 @@ import scala.tools.nsc.ast._ */ trait CompilerControl { self: Global => - /** Response { - override def toString = "TypeMember("+sym+","+tpe+","+accessible+","+inherited+","+viaView+")" - }{ - override def toString = "TypeMember("+sym+","+tpe+","+accessible+","+inherited+","+viaView+")" - }wrapper to client - */ - type Response[T] = SyncVar[Either[T, Throwable]] - abstract class WorkItem extends (() => Unit) /** Info given for every member found by completion @@ -30,8 +21,18 @@ trait CompilerControl { self: Global => 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 + 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 /** The scheduler by which client and compiler communicate * Must be initialized before starting compilerRunner @@ -82,7 +83,7 @@ trait CompilerControl { self: Global => override def toString = "reload "+sources } - /** Set sync var `result` to a fully attributed tree located at position `pos` + /** Set sync var `result` to the smallest fully attributed tree that encloses position `pos`. */ def askTypeAt(pos: Position, result: Response[Tree]) = scheduler postWorkItem new WorkItem { @@ -90,6 +91,8 @@ trait CompilerControl { self: Global => override def toString = "typeat "+pos.source+" "+pos.show } + /** Set sync var `result` to the fully attributed & typechecked tree contained in `source`. + */ def askType(source: SourceFile, forceReload: Boolean, result: Response[Tree]) = scheduler postWorkItem new WorkItem { def apply() = self.getTypedTree(source, forceReload, result) @@ -98,7 +101,6 @@ trait CompilerControl { self: Global => /** Set sync var `result' to list of members that are visible * as members of the tree enclosing `pos`, possibly reachable by an implicit. - * - if `selection` is false, as identifiers in the scope enclosing `pos` */ def askTypeCompletion(pos: Position, result: Response[List[Member]]) = scheduler postWorkItem new WorkItem { @@ -123,9 +125,6 @@ trait CompilerControl { self: Global => } } - /** Cancel currently pending high-priority jobs */ - def askCancel() = scheduler raise CancelActionReq - /** Cancel current compiler run and start a fresh one where everything will be re-typechecked * (but not re-loaded). */ @@ -139,7 +138,6 @@ trait CompilerControl { self: Global => // ---------------- Interpreted exceptions ------------------- - object CancelActionReq extends ControlThrowable object FreshRunReq extends ControlThrowable object ShutdownReq extends ControlThrowable } diff --git a/src/compiler/scala/tools/nsc/interactive/ContextTrees.scala b/src/compiler/scala/tools/nsc/interactive/ContextTrees.scala index 1fad3180ab..8fa4b86219 100644 --- a/src/compiler/scala/tools/nsc/interactive/ContextTrees.scala +++ b/src/compiler/scala/tools/nsc/interactive/ContextTrees.scala @@ -17,7 +17,7 @@ trait ContextTrees { self: Global => * 3. The `pos` field of a context is the same as `context.tree.pos`, unless that * position is transparent. In that case, `pos` equals the position of * one of the solid descendants of `context.tree`. - * 4. Children of a context have non-overlapping increasining positions. + * 4. Children of a context have non-overlapping increasing positions. * 5. No context in the tree has a transparent position. */ class ContextTree(val pos: Position, val context: Context, val children: ArrayBuffer[ContextTree]) { diff --git a/src/compiler/scala/tools/nsc/interactive/Global.scala b/src/compiler/scala/tools/nsc/interactive/Global.scala index de0d6ade13..bf901b321a 100644 --- a/src/compiler/scala/tools/nsc/interactive/Global.scala +++ b/src/compiler/scala/tools/nsc/interactive/Global.scala @@ -42,12 +42,17 @@ self => /** The currently active typer run */ private var currentTyperRun: TyperRun = _ - /** Is a background compiler run needed? */ + /** Is a background compiler run needed? + * Note: outOfDate is true as long as there is a backgroud compile scheduled or going on. + */ private var outOfDate = false /** Units compiled by a run with id >= minRunId are considered up-to-date */ private[interactive] var minRunId = 1 + private val NoResponse: Response[_] = new Response[Any] + private var pendingResponse: Response[_] = NoResponse + /** Is a reload/background compiler currently running? */ private var acting = false @@ -120,8 +125,9 @@ self => ir.execute(); pollForWork() case _ => } + if (pendingResponse.isCancelled) + throw CancelException scheduler.pollThrowable() match { - case Some(ex @ CancelActionReq) => if (acting) throw ex case Some(ex @ FreshRunReq) => currentTyperRun = newTyperRun minRunId = currentRunId @@ -137,9 +143,6 @@ self => if (debugIDE) println("picked up work item: "+action) action() if (debugIDE) println("done with work item: "+action) - } catch { - case CancelActionReq => - if (debugIDE) println("cancelled work item: "+action) } finally { if (debugIDE) println("quitting work item: "+action) acting = false @@ -226,10 +229,16 @@ self => private def backgroundCompile() { if (debugIDE) inform("Starting new presentation compiler type checking pass") reporter.reset + + // remove any files in first that are no longer maintained by presentation compiler (i.e. closed) firsts = firsts filter (s => unitOfFile contains (s.file)) + val prefix = firsts map unitOf + val units = prefix ::: (unitOfFile.values.toList diff prefix) filter (!_.isUpToDate) + recompile(units) + if (debugIDE) inform("Everything is now up to date") } @@ -267,6 +276,8 @@ self => activeLocks = 0 currentTyperRun.typeCheck(unit) unit.status = currentRunId + // todo: garbage collect any tyop-level symbols whose types are no longer valid for + // currentRunId } } @@ -277,18 +288,24 @@ self => // ----------------- Implementations of client commands ----------------------- - def respond[T](result: Response[T])(op: => T): Unit = + def respond[T](result: Response[T])(op: => T): Unit = { + val prevResponse = pendingResponse try { - result set Left(op) - return + pendingResponse = result + if (!result.isCancelled) result set op } catch { + case CancelException => + ; case ex @ FreshRunReq => scheduler.postWorkItem(() => respond(result)(op)) throw ex case ex => - result set Right(ex) + result raise ex throw ex + } finally { + pendingResponse = prevResponse } + } /** Make sure a set of compilation units is loaded and parsed */ def reloadSources(sources: List[SourceFile]) { @@ -304,8 +321,8 @@ self => /** Make sure a set of compilation units is loaded and parsed */ def reload(sources: List[SourceFile], result: Response[Unit]) { respond(result)(reloadSources(sources)) - if (outOfDate) throw FreshRunReq - else outOfDate = true + if (outOfDate) throw FreshRunReq // cancel background compile + else outOfDate = true // proceed normally and enable new background compile } /** A fully attributed tree located at position `pos` */ @@ -339,7 +356,7 @@ self => def stabilizedType(tree: Tree): Type = tree match { case Ident(_) if tree.symbol.isStable => singleType(NoPrefix, tree.symbol) - case Select(qual, _) if qual.tpe != null && tree.symbol.isStable => singleType(qual.tpe, tree.symbol) + case Select(qual, _) if qual.tpe != null && tree.symbol.isStable => singleType(qual.tpe, tree.symbol) case Import(expr, selectors) => tree.symbol.info match { case analyzer.ImportType(expr) => expr match { @@ -405,11 +422,16 @@ self => def typeMembers(pos: Position): List[TypeMember] = { var tree = typedTreeAt(pos) + + // Let's say you have something like val x: List[Int] and ypu want to get completion after List + // Then the tree found at first is a TypeTree, ???? tree match { - case tt : TypeTree => tree = tt.original + case tt : TypeTree if tt.original != null => tree = tt.original // ??? case _ => } + // if tree consists of just x. or x.fo where fo is not yet a full member name + // ignore the selection and look in just x. tree match { case Select(qual, name) if tree.tpe == ErrorType => tree = qual case _ => @@ -425,6 +447,7 @@ self => val superAccess = tree.isInstanceOf[Super] val scope = new Scope val members = new LinkedHashMap[Symbol, TypeMember] + def addTypeMember(sym: Symbol, pre: Type, inherited: Boolean, viaView: Symbol) { val symtpe = pre.memberType(sym) if (scope.lookupAll(sym.name) forall (sym => !(members(sym).tpe matches symtpe))) { @@ -437,22 +460,28 @@ self => viaView) } } + + /** Create a fucntion application of a given view function to `tree` and typechecked it. + */ def viewApply(view: SearchResult): Tree = { assert(view.tree != EmptyTree) try { - analyzer.newTyper(context.makeImplicit(false)).typed(Apply(view.tree, List(tree)) setPos tree.pos) + analyzer.newTyper(context.makeImplicit(reportAmbiguousErrors = false)) + .typed(Apply(view.tree, List(tree)) setPos tree.pos) } catch { case ex: TypeError => EmptyTree } } + val pre = stabilizedType(tree) val ownerTpe = if (tree.tpe != null) tree.tpe else pre + for (sym <- ownerTpe.decls) addTypeMember(sym, pre, false, NoSymbol) for (sym <- ownerTpe.members) addTypeMember(sym, pre, true, NoSymbol) val applicableViews: List[SearchResult] = - new ImplicitSearch(tree, functionType(List(ownerTpe), AnyClass.tpe), true, context.makeImplicit(false)) + new ImplicitSearch(tree, functionType(List(ownerTpe), AnyClass.tpe), isView = true, context.makeImplicit(reportAmbiguousErrors = false)) .allImplicits for (view <- applicableViews) { val vtree = viewApply(view) @@ -505,7 +534,7 @@ self => tree } else { val unit = unitOf(pos) - assert(unit.status >= JustParsed) + assert(unit.isParsed) unit.targetPos = pos try { println("starting targeted type check") @@ -521,7 +550,7 @@ self => } def typedTree(unit: RichCompilationUnit): Tree = { - assert(unit.status >= JustParsed) + assert(unit.isParsed) unit.targetPos = NoPosition typeCheck(unit) unit.body @@ -545,3 +574,5 @@ self => assert(globalPhase.id == 0) } +object CancelException extends Exception + diff --git a/src/compiler/scala/tools/nsc/interactive/REPL.scala b/src/compiler/scala/tools/nsc/interactive/REPL.scala index 5589ddb9b1..775b979851 100644 --- a/src/compiler/scala/tools/nsc/interactive/REPL.scala +++ b/src/compiler/scala/tools/nsc/interactive/REPL.scala @@ -80,9 +80,9 @@ object REPL { * complete file off1 off2? */ def run(comp: Global) { - val reloadResult = new comp.Response[Unit] - val typeatResult = new comp.Response[comp.Tree] - val completeResult = new comp.Response[List[comp.Member]] + val reloadResult = new Response[Unit] + val typeatResult = new Response[comp.Tree] + val completeResult = new Response[List[comp.Member]] def makePos(file: String, off1: String, off2: String) = { val source = toSourceFile(file) comp.rangePos(source, off1.toInt, off1.toInt, off2.toInt) @@ -118,11 +118,11 @@ object REPL { def toSourceFile(name: String) = new BatchSourceFile(new PlainFile(new java.io.File(name))) - def show[T](svar: SyncVar[Either[T, Throwable]]) { + def show[T](svar: Response[T]) { svar.get match { case Left(result) => println("==> "+result) - case Right(exc/*: Throwable ??*/) => exc.printStackTrace; println("ERROR: "+exc) + case Right(exc) => exc.printStackTrace; println("ERROR: "+exc) } - svar.unset() + svar.clear() } } diff --git a/src/compiler/scala/tools/nsc/interactive/Response.scala b/src/compiler/scala/tools/nsc/interactive/Response.scala new file mode 100644 index 0000000000..96e474a34a --- /dev/null +++ b/src/compiler/scala/tools/nsc/interactive/Response.scala @@ -0,0 +1,68 @@ +package scala.tools.nsc +package interactive + +import scala.concurrent.SyncVar + +/** Typical interaction, given a predicate , a function , + * and an exception handler : + * + * val TIMEOUT = 100 // (milliseconds) or something like that + * val r = new Response() + * while (!r.isComplete && !r.isCancelled) { + * if () r.cancel() + * else r.get(TIMEOUT) match { + * case Some(Left(data)) => (data) + * case Some(Right(exc)) => (exc) + * case None => + * } + * } + */ +class Response[T] { + + private val data = new SyncVar[Either[T, Throwable]] + private var complete = false + private var cancelled = false + + /** Set provisional data, more to come + */ + def setProvisionally(x: T) = + data.set(Left(x)) + + /** Set final data, and mark resposne as complete. + */ + def set(x: T) = data.synchronized { + data.set(Left(x)) + complete = true + } + + def raise(exc: Throwable) = data.synchronized { + data.set(Right(exc)) + complete = true + } + + /** Get data, wait as long as necessary + */ + def get: Either[T, Throwable] = data.get + + /** Optionally get data within `timeout` milliseconds. + */ + def get(timeout: Long): Option[Either[T, Throwable]] = data.get(timeout) + + /** Final data set was stored + */ + def isComplete = data.synchronized { complete } + + /** Cancel action computing this response + */ + def cancel() = data.synchronized { cancelled = true } + + /** A cancel request for this response has been issued + */ + def isCancelled = data.synchronized { cancelled } + + def clear() = data.synchronized { + data.unset() + complete = false + cancelled = false + } +} diff --git a/src/compiler/scala/tools/nsc/typechecker/Typers.scala b/src/compiler/scala/tools/nsc/typechecker/Typers.scala index b389a4c033..a003a4fd7e 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Typers.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Typers.scala @@ -2097,6 +2097,8 @@ trait Typers { self: Analyzer => EmptyTree case _ => if (localTarget && !includesTargetPos(stat)) { + // skip typechecking of statements in a sequence where some other statement includes + // the targetposition stat } else { val localTyper = if (inBlock || (stat.isDef && !stat.isInstanceOf[LabelDef])) this -- cgit v1.2.3