summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorMartin Odersky <odersky@gmail.com>2011-01-09 14:33:41 +0000
committerMartin Odersky <odersky@gmail.com>2011-01-09 14:33:41 +0000
commit785621901acb1888f168c2b2075e85a64af70fb8 (patch)
tree29ba5e499c4b07bde75dc33b86afb6c70b5ed6b0 /src
parentd94210996beabaf1256f0b4fcdaf210e8431c2e5 (diff)
downloadscala-785621901acb1888f168c2b2075e85a64af70fb8.tar.gz
scala-785621901acb1888f168c2b2075e85a64af70fb8.tar.bz2
scala-785621901acb1888f168c2b2075e85a64af70fb8.zip
Implemented toplevel browsing in IDE, so that s...
Implemented toplevel browsing in IDE, so that source files newer than their class files are scanned for contained classes and modules. That way, top-level symbols with names different than their enclosing class can still be identified. I believe the same strategy should be used by IDE/sbt builders. Reviw by plocinik
Diffstat (limited to 'src')
-rw-r--r--src/compiler/scala/tools/nsc/ast/parser/Parsers.scala19
-rw-r--r--src/compiler/scala/tools/nsc/ast/parser/Scanners.scala6
-rw-r--r--src/compiler/scala/tools/nsc/interactive/Global.scala94
-rw-r--r--src/compiler/scala/tools/nsc/symtab/SymbolLoaders.scala44
4 files changed, 136 insertions, 27 deletions
diff --git a/src/compiler/scala/tools/nsc/ast/parser/Parsers.scala b/src/compiler/scala/tools/nsc/ast/parser/Parsers.scala
index ba913bf693..e71cb98fc2 100644
--- a/src/compiler/scala/tools/nsc/ast/parser/Parsers.scala
+++ b/src/compiler/scala/tools/nsc/ast/parser/Parsers.scala
@@ -151,8 +151,8 @@ self =>
def warning(offset: Int, msg: String) {}
def deprecationWarning(offset: Int, msg: String) {}
- def syntaxError(offset: Int, msg: String): Unit = throw new MalformedInput
- def incompleteInputError(msg: String): Unit = throw new MalformedInput
+ def syntaxError(offset: Int, msg: String): Unit = throw new MalformedInput(offset, msg)
+ def incompleteInputError(msg: String): Unit = throw new MalformedInput(source.content.length - 1, msg)
/** the markup parser */
lazy val xmlp = new MarkupParser(this, true)
@@ -170,14 +170,21 @@ self =>
def skipBraces[T](body: T): T = {
accept(LBRACE)
- while (in.token != EOF && in.token != RBRACE)
- if (in.token == XMLSTART) xmlLiteral() else in.nextToken()
- body
+ var openBraces = 1
+ while (in.token != EOF && openBraces > 0) {
+ if (in.token == XMLSTART) xmlLiteral()
+ else {
+ if (in.token == LBRACE) openBraces += 1
+ else if (in.token == RBRACE) openBraces -= 1
+ in.nextToken()
+ }
+ }
+ body
}
override def blockExpr(): Tree = skipBraces(EmptyTree)
- override def templateStatSeq(isPre: Boolean) = skipBraces(emptyValDef, List())
+ override def templateBody(isPre: Boolean) = skipBraces(emptyValDef, List(EmptyTree))
}
class UnitParser(val unit: global.CompilationUnit, patches: List[BracePatch]) extends SourceFileParser(unit.source) {
diff --git a/src/compiler/scala/tools/nsc/ast/parser/Scanners.scala b/src/compiler/scala/tools/nsc/ast/parser/Scanners.scala
index 504fa75f6d..69063c3f69 100644
--- a/src/compiler/scala/tools/nsc/ast/parser/Scanners.scala
+++ b/src/compiler/scala/tools/nsc/ast/parser/Scanners.scala
@@ -1027,7 +1027,7 @@ trait Scanners extends ScannersCommon {
else "'<" + token + ">'"
}
- class MalformedInput extends Exception
+ class MalformedInput(val offset: Int, val msg: String) extends Exception
/** A scanner for a given source file not necessarily attached to a compilation unit.
* Useful for looking inside source files that aren not currently compiled to see what's there
@@ -1038,8 +1038,8 @@ trait Scanners extends ScannersCommon {
// suppress warnings, throw exception on errors
def warning(off: Offset, msg: String): Unit = {}
- def error (off: Offset, msg: String): Unit = throw new MalformedInput
- def incompleteInputError(off: Offset, msg: String): Unit = throw new MalformedInput
+ def error (off: Offset, msg: String): Unit = throw new MalformedInput(off, msg)
+ def incompleteInputError(off: Offset, msg: String): Unit = throw new MalformedInput(off, msg)
}
/** A scanner over a given compilation unit
diff --git a/src/compiler/scala/tools/nsc/interactive/Global.scala b/src/compiler/scala/tools/nsc/interactive/Global.scala
index 1de1f9592f..cb8c4a8a60 100644
--- a/src/compiler/scala/tools/nsc/interactive/Global.scala
+++ b/src/compiler/scala/tools/nsc/interactive/Global.scala
@@ -8,7 +8,7 @@ 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, Position, RangePosition, NoPosition, WorkScheduler}
+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._
@@ -163,14 +163,92 @@ self =>
*/
override lazy val loaders = new SymbolLoaders {
val global: Global.this.type = Global.this
- override def enterToplevelsFromSource(root: Symbol, name: String, src: AbstractFile) {
- // todo: change
- if (root.isEmptyPackageClass) {
- // currentRun is null for the empty package, since its type is taken during currentRun
- // initialization. todo: remove when refactored.
- super.enterToplevelsFromSource(root, name, src)
+
+ import syntaxAnalyzer.{OutlineParser, MalformedInput}
+
+ /** In browse mode, it can happen that an encountered symbol is already
+ * present. For instance, if the source file has a name different from
+ * the classes and objects it contains, the symbol loader will always
+ * reparse the source file. The symbols it encounters might already be loaded
+ * as class files. In this case we return the one which has a sourcefile
+ * (and the other has not), and issue an error if both have sourcefiles.
+ */
+ override def enterIfNew(owner: Symbol, member: Symbol, completer: SymbolLoader): Symbol = {
+ completer.sourcefile match {
+ case Some(src) =>
+ (if (member.isModule) member.moduleClass else member).sourceFile = src
+ case _ =>
+ }
+ val decls = owner.info.decls
+ val existing = decls.lookup(member.name)
+ if (existing == NoSymbol) {
+ decls enter member
+ member
+ } else if (existing.sourceFile == null) {
+ decls unlink existing
+ decls enter member
+ member
} else {
- currentRun.compileLate(src)
+ if (member.sourceFile != null) {
+ if (existing.sourceFile != member.sourceFile)
+ error(member+"is defined twice,"+
+ "\n in "+existing.sourceFile+
+ "\n and also in "+member.sourceFile)
+ }
+ existing
+ }
+ }
+
+ def browseTopLevel(root: Symbol, src: AbstractFile) {
+
+ class BrowserTraverser extends Traverser {
+ var packagePrefix = ""
+ var entered = 0
+ def addPackagePrefix(pkg: Tree): Unit = pkg match {
+ case Select(pre, name) =>
+ addPackagePrefix(pre)
+ packagePrefix += ("." + name)
+ case Ident(name) =>
+ if (packagePrefix.length != 0) packagePrefix += "."
+ packagePrefix += name
+ case _ =>
+ throw new MalformedInput(pkg.pos.point, "illegal tree node in package prefix: "+pkg)
+ }
+ override def traverse(tree: Tree): Unit = tree match {
+ case PackageDef(pkg, body) =>
+ addPackagePrefix(pkg)
+ body foreach traverse
+ case ClassDef(_, name, _, _) =>
+ if (packagePrefix == root.fullName) {
+ enterClass(root, name.toString, new SourcefileLoader(src))
+ entered += 1
+ } else println("prefixes differ: "+packagePrefix+","+root.fullName)
+ case ModuleDef(_, name, _) =>
+ if (packagePrefix == root.fullName) {
+ enterModule(root, name.toString, new SourcefileLoader(src))
+ entered += 1
+ } else println("prefixes differ: "+packagePrefix+","+root.fullName)
+ case _ =>
+ }
+ }
+
+ System.out.println("Browsing "+src)
+ val source = new BatchSourceFile(src)
+ val body = new OutlineParser(source).parse()
+ System.out.println(body)
+ val browser = new BrowserTraverser
+ browser.traverse(body)
+ if (browser.entered == 0)
+ warning("No classes or objects found in "+source+" that go in "+root)
+ }
+
+ override def enterToplevelsFromSource(root: Symbol, name: String, src: AbstractFile) {
+ try {
+ browseTopLevel(root, src)
+ } catch {
+ case ex: syntaxAnalyzer.MalformedInput =>
+ println("caught malformed input exception at offset "+ex.offset+": "+ex.msg)
+ super.enterToplevelsFromSource(root, name, src)
}
}
}
diff --git a/src/compiler/scala/tools/nsc/symtab/SymbolLoaders.scala b/src/compiler/scala/tools/nsc/symtab/SymbolLoaders.scala
index 1926d856be..d1b2655827 100644
--- a/src/compiler/scala/tools/nsc/symtab/SymbolLoaders.scala
+++ b/src/compiler/scala/tools/nsc/symtab/SymbolLoaders.scala
@@ -25,19 +25,43 @@ abstract class SymbolLoaders {
val global: Global
import global._
- /** Enter class and module with given `name` into scope of `root`
+ protected def enterIfNew(owner: Symbol, member: Symbol, completer: SymbolLoader): Symbol = {
+ assert(owner.info.decls.lookup(member.name) == NoSymbol, owner.fullName + "." + member.name)
+ owner.info.decls enter member
+ member
+ }
+
+ private def realOwner(root: Symbol): Symbol = {
+ if (root.isRoot) definitions.EmptyPackageClass else root
+ }
+
+ /** Enter class with given `name` into scope of `root`
* and give them `completer` as type.
*/
- def enterClassAndModule(root: Symbol, name: String, completer: SymbolLoader) {
- val owner = if (root.isRoot) definitions.EmptyPackageClass else root
- assert(owner.info.decls.lookup(name) == NoSymbol, owner.fullName + "." + name)
+ def enterClass(root: Symbol, name: String, completer: SymbolLoader): Symbol = {
+ val owner = realOwner(root)
val clazz = owner.newClass(NoPosition, newTypeName(name))
- val module = owner.newModule(NoPosition, name)
clazz setInfo completer
+ enterIfNew(owner, clazz, completer)
+ }
+
+ /** Enter module with given `name` into scope of `root`
+ * and give them `completer` as type.
+ */
+ def enterModule(root: Symbol, name: String, completer: SymbolLoader): Symbol = {
+ val owner = realOwner(root)
+ val module = owner.newModule(NoPosition, newTermName(name))
module setInfo completer
module.moduleClass setInfo moduleClassLoader
- owner.info.decls enter clazz
- owner.info.decls enter module
+ enterIfNew(owner, module, completer)
+ }
+
+ /** Enter class and module with given `name` into scope of `root`
+ * and give them `completer` as type.
+ */
+ def enterClassAndModule(root: Symbol, name: String, completer: SymbolLoader) {
+ val clazz = enterClass(root, name, completer)
+ val module = enterModule(root, name, completer)
assert(clazz.companionModule == module || clazz.isAnonymousClass, module)
assert(module.companionClass == clazz, clazz)
}
@@ -61,7 +85,7 @@ abstract class SymbolLoaders {
/** Load source or class file for `root', return */
protected def doComplete(root: Symbol): Unit
- protected def sourcefile: Option[AbstractFile] = None
+ def sourcefile: Option[AbstractFile] = None
/**
* Description of the resource (ClassPath, AbstractFile, MSILType)
@@ -257,7 +281,7 @@ abstract class SymbolLoaders {
classfileParser.parse(classfile, root)
stopTimer(classReadNanos, start)
}
- override protected def sourcefile = classfileParser.srcfile
+ override def sourcefile = classfileParser.srcfile
}
class MSILTypeLoader(typ: MSILType) extends SymbolLoader {
@@ -271,7 +295,7 @@ abstract class SymbolLoaders {
class SourcefileLoader(val srcfile: AbstractFile) extends SymbolLoader {
protected def description = "source file "+ srcfile.toString
- override protected def sourcefile = Some(srcfile)
+ override def sourcefile = Some(srcfile)
protected def doComplete(root: Symbol): Unit = global.currentRun.compileLate(srcfile)
}