From 488f986078c4f80381849f66d30d2e24818dbff9 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 29 May 2009 18:35:04 +0000 Subject: first attempt at command completion; more fiddl... first attempt at command completion; more fiddling with positions --- src/compiler/scala/tools/nsc/ast/Trees.scala | 5 +- .../tools/nsc/interactive/CompilerControl.scala | 17 ++++- .../scala/tools/nsc/interactive/Global.scala | 74 +++++++++++++++------- .../scala/tools/nsc/interactive/Positions.scala | 18 ++++-- .../scala/tools/nsc/interactive/REPL.scala | 38 +++++++---- 5 files changed, 106 insertions(+), 46 deletions(-) diff --git a/src/compiler/scala/tools/nsc/ast/Trees.scala b/src/compiler/scala/tools/nsc/ast/Trees.scala index 3de4df932f..97852afe81 100644 --- a/src/compiler/scala/tools/nsc/ast/Trees.scala +++ b/src/compiler/scala/tools/nsc/ast/Trees.scala @@ -428,7 +428,8 @@ trait Trees { object emptyValDef extends ValDef(Modifiers(PRIVATE), nme.WILDCARD, TypeTree(NoType), EmptyTree) { override def isEmpty = true - setPos(NoPosition) + super.setPos(NoPosition) + override def setPos(pos: Position) = { assert(false); this } } /** Method definition @@ -612,7 +613,7 @@ trait Trees { !vparamss1.head.isEmpty && (vparamss1.head.head.mods.flags & IMPLICIT) != 0) vparamss1 = List() :: vparamss1; val superRef: Tree = Select(Super(nme.EMPTY.toTypeName, nme.EMPTY.toTypeName), nme.CONSTRUCTOR) - val superCall = atPos(parents.head) { (superRef /: argss) (Apply) } + val superCall = (superRef /: argss) (Apply) List( DefDef(constrMods, nme.CONSTRUCTOR, List(), vparamss1, TypeTree(), Block(lvdefs ::: List(superCall), Literal(())))) } diff --git a/src/compiler/scala/tools/nsc/interactive/CompilerControl.scala b/src/compiler/scala/tools/nsc/interactive/CompilerControl.scala index 8d4c18fbf7..9db09c7f88 100644 --- a/src/compiler/scala/tools/nsc/interactive/CompilerControl.scala +++ b/src/compiler/scala/tools/nsc/interactive/CompilerControl.scala @@ -10,6 +10,14 @@ import scala.tools.nsc.ast._ */ trait CompilerControl { self: Global => + object MemberStatus extends Enumeration { + val Accessible, Inherited, Implicit = Value + } + + type Response[T] = SyncVar[Either[T, Throwable]] + + type Member = (Symbol, Type, MemberStatus.ValueSet) + /* Must be initialized before starting compilerRunner */ protected val scheduler = new WorkScheduler @@ -38,12 +46,15 @@ trait CompilerControl { self: Global => locateContext(unitOf(pos).contexts, pos) /** Make sure a set of compilation units is loaded and parsed */ - def askReload(sources: List[SourceFile], result: SyncVar[Either[Unit, Throwable]]) = + def askReload(sources: List[SourceFile], result: Response[Unit]) = scheduler.postWorkItem(() => reload(sources, result)) /** Set sync var `result` to a fully attributed tree located at position `pos` */ - def askTypeAt(pos: Position, result: SyncVar[Either[Tree, Throwable]]) = - scheduler.postWorkItem(() => self.typedTreeAt(pos, result)) + def askTypeAt(pos: Position, result: Response[Tree]) = + scheduler.postWorkItem(() => self.getTypedTreeAt(pos, result)) + + def askCompletion(pos: Position, result: Response[List[Member]]) = + scheduler.postWorkItem(() => self.completion(pos, result)) /** Ask to do unit first on present and subsequent type checking passes */ def askToDoFirst(f: SourceFile) = { diff --git a/src/compiler/scala/tools/nsc/interactive/Global.scala b/src/compiler/scala/tools/nsc/interactive/Global.scala index 7d1333fab1..325718f865 100755 --- a/src/compiler/scala/tools/nsc/interactive/Global.scala +++ b/src/compiler/scala/tools/nsc/interactive/Global.scala @@ -1,7 +1,6 @@ package scala.tools.nsc.interactive import scala.collection.mutable.{LinkedHashMap, SynchronizedMap} -import scala.concurrent.SyncVar import scala.tools.nsc.io.AbstractFile import scala.tools.nsc.util.{SourceFile, Position, RangePosition, OffsetPosition, NoPosition, WorkScheduler} import scala.tools.nsc.reporters._ @@ -191,44 +190,73 @@ self => // ----------------- Implementations of client commmands ----------------------- + def respond[T](result: Response[T])(op: => T): Unit = try { + result set Left(op) + } catch { + case ex => + result set Right(ex) + throw ex + } + /** Make sure a set of compilation units is loaded and parsed */ - def reload(sources: List[SourceFile], result: SyncVar[Either[Unit, Throwable]]) { - try { + def reload(sources: List[SourceFile], result: Response[Unit]) { + respond(result) { currentTyperRun = new TyperRun() for (source <- sources) { val unit = new RichCompilationUnit(source) unitOfFile(source.file) = unit parse(unit) + if (settings.Xprintpos.value) treePrinter.print(unit) } moveToFront(sources) - result set Left(()) - } catch { - case ex => - result set Right(ex) - throw ex + () } if (outOfDate) throw new FreshRunReq else outOfDate = true } + /** A fully attributed tree located at position `pos` */ + def typedTreeAt(pos: Position): Tree = { + val unit = unitOf(pos) + assert(unit.status != NotLoaded) + moveToFront(List(unit.source)) + val typedTree = currentTyperRun.typedTreeAt(pos) + new Locator(pos) locateIn typedTree + } + /** Set sync var `result` to a fully attributed tree located at position `pos` */ - def typedTreeAt(pos: Position, result: SyncVar[Either[Tree, Throwable]]) { - try { - println("typed tree at "+pos.show) - val unit = unitOf(pos) - assert(unit.status != NotLoaded) - moveToFront(List(unit.source)) - val typedTree = currentTyperRun.typedTreeAt(pos) - val located = new Locator(pos) locateIn typedTree - result set Left(located) - } catch { - case ex => - result set Right(ex) - throw ex - } + def getTypedTreeAt(pos: Position, result: Response[Tree]) { + respond(result)(typedTreeAt(pos)) } - def typeCompletion() {} + def stabilizedType(tree: Tree): Type = tree match { + case Ident(_) if tree.symbol.isStable => singleType(NoPrefix, tree.symbol) + case Select(qual, _) if tree.symbol.isStable => singleType(qual.tpe, tree.symbol) + case _ => tree.tpe + } + + def completion(pos: Position, result: Response[List[Member]]) { + import MemberStatus._ + respond(result) { + val tree = typedTreeAt(pos) + locateContext(pos) match { + case Some(context) => + val superAccess = tree.isInstanceOf[Super] + val pre = stabilizedType(tree) + def withStatus(sym: Symbol, vs: ValueSet) = ( + sym, + pre.memberType(sym), + if (context.isAccessible(sym, pre, superAccess)) vs + Accessible else vs + ) + val decls = tree.tpe.decls.toList map (sym => withStatus(sym, ValueSet())) + val inherited = tree.tpe.members.toList diff decls map (sym => withStatus(sym, ValueSet(Inherited))) + val implicits = List() // not yet done + decls ::: inherited ::: implicits + case None => + throw new FatalError("no context found for "+pos) + } + } + } // ---------------- Helper classes --------------------------- diff --git a/src/compiler/scala/tools/nsc/interactive/Positions.scala b/src/compiler/scala/tools/nsc/interactive/Positions.scala index 36c60e7a2c..141f710b88 100755 --- a/src/compiler/scala/tools/nsc/interactive/Positions.scala +++ b/src/compiler/scala/tools/nsc/interactive/Positions.scala @@ -29,11 +29,17 @@ self: Global => } class TransparentPosition(source0: SourceFile, start: Int, point: Int, end: Int) extends RangePosition(source0, start, point, end) { - override def toString = "<"+super.toString+">" + override def show = "<"+super.show+">" } def isTransparent(pos: Position) = pos.isInstanceOf[TransparentPosition] def isRange(pos: Position) = pos.isInstanceOf[RangePosition] + def isPositionable(tree: Tree) = tree match { + case EmptyTree => false + case `emptyValDef` => false + case TypeTree() => tree.tpe != NoType + case _ => true + } // -------------- ensuring no overlaps ------------------------------- @@ -136,7 +142,7 @@ self: Global => private def setChildrenPos(pos: Position, trees: List[Tree]) { var currentPos = pos for (tree <- trees) { - if (tree != EmptyTree && tree.pos == NoPosition) { + if (isPositionable(tree) && tree.pos == NoPosition) { val children = tree.children if (children.isEmpty) tree setPos OffsetPosition(pos.source.get, currentPos.start) @@ -157,7 +163,7 @@ self: Global => */ override def atPos[T <: Tree](pos: Position)(tree: T): T = if (isRange(pos)) { - if (tree != EmptyTree && tree.pos == NoPosition) { + if (isPositionable(tree) && tree.pos == NoPosition) { tree.setPos(pos) val children = tree.children if (children.nonEmpty) { @@ -180,11 +186,11 @@ self: Global => inform(tree.toString) } def validate(tree: Tree, encltree: Tree) { - if (tree != EmptyTree && !tree.pos.isDefined) + if (isPositionable(tree) && !tree.pos.isDefined) error("tree without position: "+tree) - if (encltree.pos.isSynthetic && !tree.pos.isSynthetic) + if (encltree.pos.isSynthetic && !tree.pos.isDefined && tree.pos.isSynthetic) error("synthetic "+encltree+" contains nonsynthetic" + tree) - if (!(encltree.pos includes tree.pos)) + if (tree.pos.isDefined && !(encltree.pos includes tree.pos)) error(encltree+" does not include "+tree) for ((t1, t2) <- findOverlapping(tree.children flatMap solidDescendants)) error("overlapping trees: "+t1+" === and === "+t2) diff --git a/src/compiler/scala/tools/nsc/interactive/REPL.scala b/src/compiler/scala/tools/nsc/interactive/REPL.scala index 331cdd597a..86e680a599 100644 --- a/src/compiler/scala/tools/nsc/interactive/REPL.scala +++ b/src/compiler/scala/tools/nsc/interactive/REPL.scala @@ -75,25 +75,39 @@ object REPL { /** Commands: * * reload file1 ... fileN - * typeat file line col - * - * + * typeat file line off1 off2? + * complete file line off1 off2? */ def run(comp: Global) { - val reloadResult = new SyncVar[Either[Unit, Throwable]] - val typeatResult = new SyncVar[Either[comp.Tree, Throwable]] + val reloadResult = new comp.Response[Unit] + val typeatResult = new comp.Response[comp.Tree] + val completeResult = new comp.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) + } + def doTypeAt(pos: Position) { + comp.askTypeAt(pos, typeatResult) + show(typeatResult) + } + def doComplete(pos: Position) { + comp.askCompletion(pos, completeResult) + show(completeResult) + } loop { line => (line split " ").toList match { case "reload" :: args => comp.askReload(args map toSourceFile, reloadResult) show(reloadResult) - case "typeat" :: file :: line :: col1 :: col2 :: Nil => - val source = toSourceFile(file) - val linestart = source.lineToOffset(line.toInt) - val pos = comp.rangePos(source, linestart + col1.toInt, linestart + col1.toInt, linestart + col2.toInt) - comp.askTypeAt(pos, typeatResult) - show(typeatResult) - case "quit" :: Nil => + case List("typeat", file, off1, off2) => + doTypeAt(makePos(file, off1, off2)) + case List("typeat", file, off1) => + doTypeAt(makePos(file, off1, off1)) + case List("complete", file, off1, off2) => + doComplete(makePos(file, off1, off2)) + case List("complete", file, off1) => + doComplete(makePos(file, off1, off1)) + case List("quit") => System.exit(1) case _ => println("unrecongized command") -- cgit v1.2.3