diff options
11 files changed, 409 insertions, 296 deletions
diff --git a/src/compiler/scala/tools/nsc/interpreter/CompletionOutput.scala b/src/compiler/scala/tools/nsc/interpreter/CompletionOutput.scala index 0dc35559a6..9c34565928 100644 --- a/src/compiler/scala/tools/nsc/interpreter/CompletionOutput.scala +++ b/src/compiler/scala/tools/nsc/interpreter/CompletionOutput.scala @@ -13,11 +13,10 @@ package interpreter */ trait CompletionOutput { val global: Global + import global._ import definitions.{ NothingClass, AnyClass, isTupleTypeOrSubtype, isFunctionType, isRepeatedParamType } - def DBG(msg: => Any): Unit - /** Reducing fully qualified noise for some common packages. */ val typeTransforms = List( @@ -81,9 +80,7 @@ trait CompletionOutput { case NullaryMethodType(resType) => ": " + typeToString(resType) case PolyType(tparams, resType) => tparamsString(tparams) + typeToString(resType) case mt @ MethodType(_, _) => methodTypeToString(mt) - case x => - DBG("methodString(): %s / %s".format(x.getClass, x)) - x.toString + case x => x.toString }) } } diff --git a/src/compiler/scala/tools/nsc/interpreter/Dossiers.scala b/src/compiler/scala/tools/nsc/interpreter/Dossiers.scala new file mode 100644 index 0000000000..bfa6576b25 --- /dev/null +++ b/src/compiler/scala/tools/nsc/interpreter/Dossiers.scala @@ -0,0 +1,53 @@ +/* NSC -- new Scala compiler + * Copyright 2005-2011 LAMP/EPFL + * @author Paul Phillips + */ + +package scala.tools.nsc +package interpreter + +// Coming soon +trait Dossiers { + val intp: IMain + + import intp._ + import intp.global._ + + trait Dossier { + def symbol: Symbol + def staticType: Type + + def id = name.toString + def name = symbol.name + def normalizedType = staticType.typeSymbolDirect.tpe.normalize + def simpleNameOfType = staticType.typeSymbol.simpleName + def staticTypeString = staticType.toString + + override def toString = "Dossier on %s:\n static type %s (normalized %s)".format( + symbol, staticType, normalizedType + ) + } + + class TypeDossier(val symbol: TypeSymbol, val staticType: Type) extends Dossier { + override def toString = super.toString + } + + class TermDossier(val symbol: TermSymbol, val staticType: Type, val value: AnyRef) extends Dossier { + def runtimeClass: Class[_] = value.getClass + def runtimeSymbol: Symbol = safeClass(runtimeClass.getName) getOrElse NoSymbol + def runtimeType: Type = runtimeSymbol.tpe + def runtimeTypeString = TypeStrings.fromClazz(runtimeClass) + + def runtimeTypedParam = NamedParamClass(id, runtimeTypeString, value) + def staticTypedParam = NamedParamClass(id, staticTypeString, value) + + def isRuntimeTypeTighter = runtimeSymbol.ancestors contains normalizedType.typeSymbol + + override def toString = super.toString + ( + "\n runtime type %s/%s\n value %s".format( + runtimeType, runtimeTypeString, value + ) + ) + } +} + diff --git a/src/compiler/scala/tools/nsc/interpreter/ILoop.scala b/src/compiler/scala/tools/nsc/interpreter/ILoop.scala index a075fe5bb0..c7739b8a8a 100644 --- a/src/compiler/scala/tools/nsc/interpreter/ILoop.scala +++ b/src/compiler/scala/tools/nsc/interpreter/ILoop.scala @@ -196,6 +196,7 @@ class ILoop(in0: Option[BufferedReader], protected val out: PrintWriter) NoArgs("help", "print this help message", printHelp), VarArgs("history", "show the history (optional arg: lines to show)", printHistory), LineArg("h?", "search the history", searchHistory), + LineArg("implicits", "show the implicits in scope (-v to include Predef)", implicitsCommand), LineArg("javap", "disassemble a file or class name", javapCommand), LineArg("keybindings", "show how ctrl-[A-Z] and other keys are bound", keybindingsCommand), OneArg("load", "load and interpret a Scala file", load), @@ -216,6 +217,79 @@ class ILoop(in0: Option[BufferedReader], protected val out: PrintWriter) LineArg("wrap", "code to wrap around all executions", wrapCommand) ) } + + private val typeTransforms = List( + "scala.collection.immutable." -> "immutable.", + "scala.collection.mutable." -> "mutable.", + "scala.collection.generic." -> "generic.", + "java.lang." -> "jl.", + "scala.runtime." -> "runtime." + ) + + private def implicitsCommand(line: String): Result = { + val intp = ILoop.this.intp + import intp._ + import global.Symbol + + def p(x: Any) = intp.reporter.printMessage("" + x) + def toDefString(sym: Symbol) = { + TypeStrings.quieter( + intp.afterTyper(sym.defString), + sym.owner.name + ".this.", + sym.owner.fullName + "." + ) + } + + // If an argument is given, only show a source with that + // in its name somewhere. + val args = line split "\\s+" + val filtered = intp.implicitSymbolsBySource filter { + case (source, syms) => + (args contains "-v") || { + if (line == "") (source.fullName.toString != "scala.Predef") + else (args exists (source.name.toString contains _)) + } + } + + if (filtered.isEmpty) + return "No implicits have been imported other than those in Predef." + + filtered foreach { + case (source, syms) => + p("/* " + syms.size + " implicit members imported from " + source.fullName + " */") + + // This groups the members by where the symbol is defined + val byOwner = syms groupBy (_.owner) + val sortedOwners = byOwner.toList sortBy { case (owner, _) => intp.afterTyper(source.info.baseClasses indexOf owner) } + + sortedOwners foreach { + case (owner, members) => + // Within each owner, we cluster results based on the final result type + // if there are more than a couple, and sort each cluster based on name. + // This is really just trying to make the 100 or so implicits imported + // by default into something readable. + val memberGroups: List[List[Symbol]] = { + val groups = members groupBy (_.tpe.finalResultType) toList + val (big, small) = groups partition (_._2.size > 3) + val xss = ( + (big sortBy (_._1.toString) map (_._2)) :+ + (small flatMap (_._2)) + ) + + xss map (xs => xs sortBy (_.name.toString)) + } + + val ownerMessage = if (owner == source) " defined in " else " inherited from " + p(" /* " + members.size + ownerMessage + owner.fullName + " */") + + memberGroups foreach { group => + group foreach (s => p(" " + toDefString(s))) + p("") + } + } + p("") + } + } private def javapCommand(line: String): Result = { if (line == "") return ":javap <filename or classname>" diff --git a/src/compiler/scala/tools/nsc/interpreter/IMain.scala b/src/compiler/scala/tools/nsc/interpreter/IMain.scala index 3c54c18fe1..21c9dd735e 100644 --- a/src/compiler/scala/tools/nsc/interpreter/IMain.scala +++ b/src/compiler/scala/tools/nsc/interpreter/IMain.scala @@ -58,7 +58,7 @@ import IMain._ * @author Lex Spoon */ class IMain(val settings: Settings, protected val out: PrintWriter) { - intp => + imain => /** construct an interpreter that reports to Console */ def this(settings: Settings) = this(settings, new NewLinePrintWriter(new ConsoleWriter, true)) @@ -150,10 +150,11 @@ class IMain(val settings: Settings, protected val out: PrintWriter) { lazy val compiler = global import global._ + import definitions.{ ScalaPackage, JavaLangPackage, PredefModule, RootClass } import nme.{ INTERPRETER_IMPORT_WRAPPER } object naming extends { - val global: intp.global.type = intp.global + val global: imain.global.type = imain.global } with Naming { // make sure we don't overwrite their unwisely named res3 etc. override def freshUserVarName(): String = { @@ -164,16 +165,26 @@ class IMain(val settings: Settings, protected val out: PrintWriter) { } import naming._ + // object dossiers extends { + // val intp: imain.type = imain + // } with Dossiers { } + // import dossiers._ + lazy val memberHandlers = new { - val intp: IMain.this.type = IMain.this + val intp: imain.type = imain } with MemberHandlers import memberHandlers._ + def atPickler[T](op: => T): T = atPhase(currentRun.picklerPhase)(op) + def afterTyper[T](op: => T): T = atPhase(currentRun.typerPhase.next)(op) + /** Temporarily be quiet */ def beQuietDuring[T](operation: => T): T = { val wasPrinting = printResults ultimately(printResults = wasPrinting) { - printResults = false + if (isReplDebug) echo(">> beQuietDuring") + else printResults = false + operation } } @@ -259,14 +270,6 @@ class IMain(val settings: Settings, protected val out: PrintWriter) { // Set the current Java "context" class loader to this interpreter's class loader def setContextClassLoader() = classLoader.setAsContext() - /** the previous requests this interpreter has processed */ - private lazy val prevRequests = mutable.ArrayBuffer[Request]() - private lazy val referencedNameMap = mutable.Map[Name, Request]() - private lazy val definedNameMap = mutable.Map[Name, Request]() - private def allHandlers = prevRequests.toList flatMap (_.handlers) - private def allReqAndHandlers = prevRequests.toList flatMap (req => req.handlers map (req -> _)) - private def importHandlers = allHandlers collect { case x: ImportHandler => x } - /** Given a simple repl-defined name, returns the real name of * the class representing it, e.g. for "Bippy" it may return * @@ -278,6 +281,7 @@ class IMain(val settings: Settings, protected val out: PrintWriter) { case _ => id } } + def allDefinedNames = definedNameMap.keys.toList sortBy (_.toString) def pathToType(id: String): String = pathToName(newTypeName(id)) def pathToTerm(id: String): String = pathToName(newTermName(id)) @@ -330,10 +334,6 @@ class IMain(val settings: Settings, protected val out: PrintWriter) { } } - def allSeenTypes = prevRequests.toList flatMap (_.typeOf.values.toList) distinct - def allDefinedTypes = prevRequests.toList flatMap (_.definedTypes.values.toList) distinct - def allImplicits = allHandlers filter (_.definesImplicit) flatMap (_.definedNames) - /** Compute imports that allow definitions from previous * requests to be visible in a new request. Returns * three pieces of related code: @@ -592,6 +592,15 @@ class IMain(val settings: Settings, protected val out: PrintWriter) { case _ => DBG("Set failed in bind(%s, %s, %s)".format(name, boundType, value)) ; IR.Error } } + def rebind(p: NamedParam): IR.Result = { + val name = p.name + val oldType = typeOfTerm(name) getOrElse { return IR.Error } + val newType = p.tpe + val tempName = freshInternalVarName() + + quietRun("val %s = %s".format(tempName, name)) + quietRun("val %s = %s.asInstanceOf[%s]".format(name, tempName, newType)) + } def quietBind(p: NamedParam): IR.Result = beQuietDuring(bind(p)) def bind(p: NamedParam): IR.Result = bind(p.name, p.tpe, p.value) @@ -651,16 +660,26 @@ class IMain(val settings: Settings, protected val out: PrintWriter) { None } - lazy val readRoot = definitions.getModule(readPath) // the outermost wrapper lazy val evalClass = loadByName(evalPath) lazy val evalValue = callOpt(valueMethod) def compile(source: String): Boolean = compileAndSaveRun("<console>", source) - def afterTyper[T](op: => T): T = { + def lineAfterTyper[T](op: => T): T = { assert(lastRun != null, "Internal error: trying to use atPhase, but Run is null." + this) atPhase(lastRun.typerPhase.next)(op) } + /** The innermost object inside the wrapper, found by + * following accessPath into the outer one. + */ + def resolvePathToSymbol(accessPath: String): Symbol = { + val readRoot = definitions.getModule(readPath) // the outermost wrapper + (accessPath split '.').foldLeft(readRoot) { (sym, name) => + if (name == "") sym else + lineAfterTyper(sym.info member newTermName(name)) + } + } + // def compileAndTypeExpr(expr: String): Option[Typer] = { // class TyperRun extends Run { // override def stopPhase(name: String) = name == "superaccessors" @@ -685,7 +704,7 @@ class IMain(val settings: Settings, protected val out: PrintWriter) { // private class Request(val line: String, val trees: List[Tree]) { val lineRep = new ReadEvalPrint() - import lineRep.{ afterTyper } + import lineRep.lineAfterTyper private var _originalLine: String = null def withOriginalLine(s: String): this.type = { _originalLine = s ; this } @@ -778,7 +797,7 @@ class IMain(val settings: Settings, protected val out: PrintWriter) { // ensure it has been compiled compile // try to load it and call the value method - lineRep.evalValue + lineRep.evalValue filterNot (_ == null) } /** Compile the object file. Returns whether the compilation succeeded. @@ -791,20 +810,15 @@ class IMain(val settings: Settings, protected val out: PrintWriter) { lineRep.compile(ObjectSourceCode(handlers)) && { // extract and remember types typeOf - definedTypes + typesOfDefinedTerms // compile the result-extraction object lineRep compile ResultObjectSourceCode(handlers) } } - /** The innermost object inside the wrapper, found by - * following accessPath into the outer one. */ - lazy val resObjSym = - accessPath.split("\\.").foldLeft(lineRep.readRoot) { (sym, name) => - if (name == "") sym else - afterTyper(sym.info member newTermName(name)) - } + lazy val resultSymbol = lineRep.resolvePathToSymbol(accessPath) + def applyToResultMember[T](name: Name, f: Symbol => T) = lineAfterTyper(f(resultSymbol.info.nonPrivateDecl(name))) /* typeOf lookup with encoding */ def lookupTypeOf(name: Name) = typeOf.getOrElse(name, typeOf(global.encode(name.toString))) @@ -813,12 +827,12 @@ class IMain(val settings: Settings, protected val out: PrintWriter) { private def typeMap[T](f: Type => T): Map[Name, T] = { def toType(name: Name): T = { // the types are all =>T; remove the => - val tp1 = afterTyper(resObjSym.info.nonPrivateDecl(name).tpe match { + val tp1 = lineAfterTyper(resultSymbol.info.nonPrivateDecl(name).tpe match { case NullaryMethodType(tp) => tp case tp => tp }) // normalize non-public types so we don't see protected aliases like Self - afterTyper(tp1 match { + lineAfterTyper(tp1 match { case TypeRef(_, sym, _) if !sym.isPublic => f(tp1.normalize) case tp => f(tp) }) @@ -830,9 +844,14 @@ class IMain(val settings: Settings, protected val out: PrintWriter) { /** String representations of same. */ lazy val typeOf = typeMap[String](_.toString) - lazy val definedTypes: Map[Name, Type] = { - typeNames map (x => x -> afterTyper(resObjSym.info.nonPrivateDecl(x).tpe)) toMap - } + // lazy val definedTypes: Map[Name, Type] = { + // typeNames map (x => x -> afterTyper(resultSymbol.info.nonPrivateDecl(x).tpe)) toMap + // } + lazy val definedSymbols: Map[Name, Symbol] = + termNames map (x => x -> applyToResultMember(x, x => x)) toMap + + lazy val typesOfDefinedTerms: Map[Name, Type] = + termNames map (x => x -> applyToResultMember(x, _.tpe)) toMap private def bindExceptionally(t: Throwable) = { val ex: Exceptional = @@ -866,9 +885,7 @@ class IMain(val settings: Settings, protected val out: PrintWriter) { } try { - val execution = lineManager.set(originalLine) { - lineRep call "$export" - } + val execution = lineManager.set(originalLine)(lineRep call "$export") execution.await() execution.state match { @@ -903,26 +920,62 @@ class IMain(val settings: Settings, protected val out: PrintWriter) { private def requestForIdent(line: String): Option[Request] = requestForName(newTermName(line)) orElse requestForName(newTypeName(line)) + def safeClass(name: String): Option[Symbol] = { + try Some(definitions.getClass(newTypeName(name))) + catch { case _: MissingRequirementError => None } + } + def safeModule(name: String): Option[Symbol] = { + try Some(definitions.getModule(newTermName(name))) + catch { case _: MissingRequirementError => None } + } + def definitionForName(name: Name): Option[MemberHandler] = requestForName(name) flatMap { req => req.handlers find (_.definedNames contains name) } + // + + def valueOfTerm(id: String): Option[AnyRef] = + requestForIdent(id) flatMap (_.getEval) + + def classOfTerm(id: String): Option[Class[_]] = + valueOfTerm(id) map (_.getClass) - def typeOfDefinedName(name: Name): Option[Type] = - if (name == nme.ROOTPKG) Some(definitions.RootClass.tpe) - else requestForName(name) flatMap (_.compilerTypeOf get name) + def typeOfTerm(id: String): Option[Type] = newTermName(id) match { + case nme.ROOTPKG => Some(RootClass.tpe) + case name => requestForName(name) flatMap (_.compilerTypeOf get name) + } + def symbolOfTerm(id: String): Symbol = + requestForIdent(id) flatMap (_.definedSymbols get newTermName(id)) getOrElse NoSymbol + + def runtimeClassAndTypeOfTerm(id: String): Option[(Class[_], Type)] = + for (clazz <- classOfTerm(id) ; tpe <- runtimeTypeOfTerm(id)) yield ((clazz, tpe)) + + def runtimeTypeOfTerm(id: String): Option[Type] = { + for { + tpe <- typeOfTerm(id) + clazz <- classOfTerm(id) + val staticSym = tpe.typeSymbol + runtimeSym <- safeClass(clazz.getName) + if runtimeSym != staticSym + if runtimeSym isSubClass staticSym + } yield { + runtimeSym.info + } + } // XXX literals. // 1) Identifiers defined in the repl. // 2) A path loadable via getModule. // 3) Try interpreting it as an expression. + private var typeOfExpressionDepth = 0 def typeOfExpression(expr: String): Option[Type] = { - val name = newTermName(expr) - - def asModule = { - try Some(definitions.getModule(name).tpe) - catch { case _: MissingRequirementError => None } + if (typeOfExpressionDepth > 2) { + DBG("Terminating typeOfExpression recursion for expression: " + expr) + return None } + + def asModule = safeModule(expr) map (_.tpe) def asExpr = beSilentDuring { val lhs = freshInternalVarName() interpret("val " + lhs + " = { " + expr + " } ") match { @@ -931,91 +984,40 @@ class IMain(val settings: Settings, protected val out: PrintWriter) { } } - typeOfDefinedName(name) orElse asModule orElse asExpr - } - - def clazzForIdent(id: String): Option[Class[_]] = - for (res <- getEvalForIdent(id); v <- Option(res)) yield v.getClass - - def methodsOf(name: String) = - evalExpr[List[String]](methodsCode(name)) map (x => NameTransformer.decode(getOriginalName(x))) - - def completionAware(name: String) = { - // XXX working around "object is not a value" crash, i.e. - // import java.util.ArrayList ; ArrayList.<tab> - clazzForIdent(name) flatMap (_ => evalExpr[Option[CompletionAware]](asCompletionAwareCode(name))) - } - - def getEvalForIdent(id: String): Option[AnyRef] = - requestForIdent(id) flatMap (_.getEval) - - /** Executes code looking for a manifest of type T. - */ - def manifestFor[T: Manifest] = - evalExpr[Manifest[T]]("""manifest[%s]""".format(manifest[T])) - - /** Executes code looking for an implicit value of type T. - */ - def implicitFor[T: Manifest] = { - val s = manifest[T].toString - evalExpr[Option[T]]("{ def f(implicit x: %s = null): %s = x ; Option(f) }".format(s, s)) - // We don't use implicitly so as to fail without failing. - // evalExpr[T]("""implicitly[%s]""".format(manifest[T])) + typeOfExpressionDepth += 1 + try typeOfTerm(expr) orElse asModule orElse asExpr + finally typeOfExpressionDepth -= 1 } + // def compileAndTypeExpr(expr: String): Option[Typer] = { + // class TyperRun extends Run { + // override def stopPhase(name: String) = name == "superaccessors" + // } + // } - private def methodsCode(name: String) = - "%s.%s(%s)".format(classOf[ReflectionCompletion].getName, "methodsOf", name) - - private def asCompletionAwareCode(name: String) = - "%s.%s(%s)".format(classOf[CompletionAware].getName, "unapply", name) - - private def getOriginalName(name: String): String = - nme.originalName(newTermName(name)).toString + private def onlyTerms(xs: List[Name]) = xs collect { case x: TermName => x } + private def onlyTypes(xs: List[Name]) = xs collect { case x: TypeName => x } - case class InterpreterEvalException(msg: String) extends Exception(msg) - def evalError(msg: String) = throw InterpreterEvalException(msg) + def importHandlers = allHandlers collect { case x: ImportHandler => x } + def definedTerms = onlyTerms(allDefinedNames) filterNot isInternalVarName + def definedTypes = onlyTypes(allDefinedNames) + def importedTerms = onlyTerms(importHandlers flatMap (_.importedNames)) + def importedTypes = onlyTypes(importHandlers flatMap (_.importedNames)) - /** The user-facing eval in :power mode wraps an Option. - */ - def eval[T: Manifest](line: String): Option[T] = - try Some(evalExpr[T](line)) - catch { case InterpreterEvalException(msg) => out println indentCode(msg) ; None } - - def evalExpr[T: Manifest](line: String): T = { - // Nothing means the type could not be inferred. - if (manifest[T] eq Manifest.Nothing) - evalError("Could not infer type: try 'eval[SomeType](%s)' instead".format(line)) - - val lhs = freshInternalVarName() - beQuietDuring { interpret("val " + lhs + " = { " + line + " } ") } - - // TODO - can we meaningfully compare the inferred type T with - // the internal compiler Type assigned to lhs? - // def assignedType = prevRequests.last.typeOf(newTermName(lhs)) - - val req = requestFromLine(lhs, true) match { - case Left(result) => evalError(result.toString) - case Right(req) => req - } - if (req == null || !req.compile || req.handlers.size != 1) - evalError("Eval error.") - - try req.getEvalTyped[T] getOrElse evalError("No result.") - catch { case e: Exception => evalError(e.getMessage) } - } + /** the previous requests this interpreter has processed */ + private lazy val prevRequests = mutable.ArrayBuffer[Request]() + private lazy val referencedNameMap = mutable.Map[Name, Request]() + private lazy val definedNameMap = mutable.Map[Name, Request]() + private def allHandlers = prevRequests.toList flatMap (_.handlers) + private def allReqAndHandlers = prevRequests.toList flatMap (req => req.handlers map (req -> _)) + def allSeenTypes = prevRequests.toList flatMap (_.typeOf.values.toList) distinct + def allImplicits = allHandlers filter (_.definesImplicit) flatMap (_.definedNames) - def interpretExpr[T: Manifest](code: String): Option[T] = beQuietDuring { - interpret(code) match { - case IR.Success => - try prevRequests.last.getEvalTyped[T] - catch { case e: Exception => out println e ; None } - case _ => None - } - } + private def membersAtPickler(sym: Symbol): List[Symbol] = + atPickler(sym.info.nonPrivateMembers) - /** Another entry point for tab-completion, ids in scope */ - private def simpleTermNames = - allHandlers flatMap (_.definedOrImported) filter (x => x.isTermName && !isInternalVarName(x)) + /** Symbols whose contents are language-defined to be imported. */ + def languageWildcardSyms: List[Symbol] = List(JavaLangPackage, ScalaPackage, PredefModule) + def languageWildcards: List[Type] = languageWildcardSyms map (_.tpe) /** Types which have been wildcard imported, such as: * val x = "abc" ; import x._ // type java.lang.String @@ -1028,36 +1030,68 @@ class IMain(val settings: Settings, protected val out: PrintWriter) { * scope twiddling which should be swept away in favor of digging * into the compiler scopes. */ - def wildcardImportedTypes(): List[Type] = { + def sessionWildcards: List[Type] = { importHandlers flatMap { case x if x.importsWildcard => x.targetType case _ => None } distinct } + def wildcardTypes = languageWildcards ++ sessionWildcards - /** Another entry point for tab-completion, ids in scope */ - def unqualifiedIds() = (simpleTermNames map (_.toString)).distinct.sorted + def languageSymbols = languageWildcardSyms flatMap membersAtPickler + def sessionSymbols = importHandlers flatMap (_.importedSymbols) + def importedSymbols = languageSymbols ++ sessionSymbols + def implicitSymbols = importedSymbols filter (_.isImplicit) + + /** Tuples of (source, imported symbols) in the order they were imported. + */ + def importedSymbolsBySource: List[(Symbol, List[Symbol])] = { + val lang = languageWildcardSyms map (sym => (sym, membersAtPickler(sym))) + val session = importHandlers filter (_.targetType.isDefined) map { mh => + (mh.targetType.get.typeSymbol, mh.importedSymbols) + } + + lang ++ session + } + def implicitSymbolsBySource: List[(Symbol, List[Symbol])] = { + importedSymbolsBySource map { + case (k, vs) => (k, vs filter (_.isImplicit)) + } filterNot (_._2.isEmpty) + } + + def visibleTermNames: List[Name] = definedTerms ++ importedTerms distinct - /** For static/object method completion */ - def getClassObject(path: String): Option[Class[_]] = classLoader tryToLoadClass path + /** Another entry point for tab-completion, ids in scope */ + def unqualifiedIds = visibleTermNames map (_.toString) filterNot (_ contains "$") sorted /** Parse the ScalaSig to find type aliases */ def aliasForType(path: String) = ByteCode.aliasForType(path) + def withoutUnwrapping(op: => Unit): Unit = { + val saved = isettings.unwrapStrings + isettings.unwrapStrings = false + try op + finally isettings.unwrapStrings = saved + } + def showCodeIfDebugging(code: String) { /** Secret bookcase entrance for repl debuggers: end the line * with "// show" and see what's going on. */ if (code.lines exists (_.trim endsWith "// show")) { echo(code) - parse(code) foreach (ts => ts foreach (t => DBG(asCompactString(t)))) + parse(code) foreach (ts => ts foreach (t => withoutUnwrapping(DBG(asCompactString(t))))) } } // debugging - def isCompletionDebug = settings.Ycompletion.value - def DBG(s: => String) = - try if (isReplDebug) repldbg(s) + def debugging[T](msg: String)(res: T) = { + DBG(msg + " " + res) + res + } + def DBG(s: => String) = if (isReplDebug) { + try repldbg(s) catch { case x: AssertionError => repldbg("Assertion error printing debug string:\n " + x) } + } } /** Utility methods for the Interpreter. */ @@ -1121,7 +1155,7 @@ object IMain { // $line3.$read.$iw.$iw.Bippy = // $line3.$read$$iw$$iw$Bippy@4a6a00ca private def removeLineWrapper(s: String) = s.replaceAll("""\$line\d+[./]\$(read|eval|print)[$.]""", "") - private def removeIWPackages(s: String) = s.replaceAll("""\$iw[$.]""", "") + private def removeIWPackages(s: String) = s.replaceAll("""\$(iw|read|eval|print)[$.]""", "") } class ReplReporter(intp: IMain) extends ConsoleReporter(intp.settings, null, new ReplStrippingWriter(intp)) { diff --git a/src/compiler/scala/tools/nsc/interpreter/JLineCompletion.scala b/src/compiler/scala/tools/nsc/interpreter/JLineCompletion.scala index 8093ae1d3c..2a19c6fb95 100644 --- a/src/compiler/scala/tools/nsc/interpreter/JLineCompletion.scala +++ b/src/compiler/scala/tools/nsc/interpreter/JLineCompletion.scala @@ -20,24 +20,12 @@ class JLineCompletion(val intp: IMain) extends Completion with CompletionOutput import global._ import definitions.{ PredefModule, RootClass, AnyClass, AnyRefClass, ScalaPackage, JavaLangPackage } type ExecResult = Any + import intp.{ DBG, debugging, afterTyper } // verbosity goes up with consecutive tabs private var verbosity: Int = 0 def resetVerbosity() = verbosity = 0 - def isCompletionDebug = intp.isCompletionDebug - def DBG(msg: => Any) = if (isCompletionDebug) println(msg.toString) - def debugging[T](msg: String): T => T = (res: T) => returning[T](res)(x => DBG(msg + x)) - - // XXX not yet used. - lazy val dottedPaths = { - def walk(tp: Type): scala.List[Symbol] = { - val pkgs = tp.nonPrivateMembers filter (_.isPackage) - pkgs ++ (pkgs map (_.tpe) flatMap walk) - } - walk(RootClass.tpe) - } - def getType(name: String, isModule: Boolean) = { val f = if (isModule) definitions.getModule(_: Name) else definitions.getClass(_: Name) try Some(f(name).tpe) @@ -66,7 +54,7 @@ class JLineCompletion(val intp: IMain) extends Completion with CompletionOutput // XXX we'd like to say "filterNot (_.isDeprecated)" but this causes the // compiler to crash for reasons not yet known. - def members = (effectiveTp.nonPrivateMembers ++ anyMembers) filter (_.isPublic) + def members = afterTyper((effectiveTp.nonPrivateMembers ++ anyMembers) filter (_.isPublic)) def methods = members filter (_.isMethod) def packages = members filter (_.isPackage) def aliases = members filter (_.isAliasType) @@ -78,6 +66,31 @@ class JLineCompletion(val intp: IMain) extends Completion with CompletionOutput } object TypeMemberCompletion { + def apply(tp: Type, runtimeType: Type, param: NamedParam): TypeMemberCompletion = { + new TypeMemberCompletion(tp) { + var upgraded = false + lazy val upgrade = { + intp rebind param + intp.reporter.printMessage("\nRebinding stable value %s from %s to %s".format(param.name, tp, param.tpe)) + upgraded = true + new TypeMemberCompletion(runtimeType) + } + override def completions(verbosity: Int) = { + super.completions(verbosity) ++ ( + if (verbosity == 0) Nil + else upgrade.completions(verbosity) + ) + } + override def follow(s: String) = super.follow(s) orElse { + if (upgraded) upgrade.follow(s) + else None + } + override def alternativesFor(id: String) = super.alternativesFor(id) ++ ( + if (upgraded) upgrade.alternativesFor(id) + else Nil + ) distinct + } + } def apply(tp: Type): TypeMemberCompletion = { if (tp.typeSymbol.isPackageClass) new PackageCompletion(tp) else new TypeMemberCompletion(tp) @@ -91,10 +104,7 @@ class JLineCompletion(val intp: IMain) extends Completion with CompletionOutput def excludeStartsWith: List[String] = List("<") // <byname>, <repeated>, etc. def excludeNames: List[String] = (anyref.methodNames filterNot anyRefMethodsToShow) :+ "_root_" - def methodSignatureString(sym: Symbol) = { - def asString = new MethodSymbolOutput(sym).methodString() - atPhase(currentRun.typerPhase)(asString) - } + def methodSignatureString(sym: Symbol) = afterTyper(new MethodSymbolOutput(sym).methodString()) def exclude(name: String): Boolean = ( (name contains "$") || @@ -146,16 +156,29 @@ class JLineCompletion(val intp: IMain) extends Completion with CompletionOutput override def completions(verbosity: Int) = intp.unqualifiedIds ++ List("classOf") //, "_root_") // now we use the compiler for everything. override def follow(id: String) = { - if (completions(0) contains id) - intp typeOfExpression id map (tpe => TypeMemberCompletion(tpe)) + if (completions(0) contains id) { + intp typeOfExpression id map { tpe => + intp runtimeClassAndTypeOfTerm id match { + case Some((clazz, runtimeType)) => + val sym = intp.symbolOfTerm(id) + if (sym.isStable) { + val param = new NamedParam.Untyped(id, intp valueOfTerm id getOrElse null) + TypeMemberCompletion(tpe, runtimeType, param) + } + else TypeMemberCompletion(tpe) + case _ => + TypeMemberCompletion(tpe) + } + } + } else None } override def toString = "<repl ids> (%s)".format(completions(0).size) } - // wildcard imports in the repl like "import global._" or "import String._" - private def imported = intp.wildcardImportedTypes map TypeMemberCompletion.imported + // user-issued wildcard imports like "import global._" or "import String._" + private def imported = intp.sessionWildcards map TypeMemberCompletion.imported // literal Ints, Strings, etc. object literals extends CompletionAware { diff --git a/src/compiler/scala/tools/nsc/interpreter/MemberHandlers.scala b/src/compiler/scala/tools/nsc/interpreter/MemberHandlers.scala index 3992a0bdfa..29c31f50d6 100644 --- a/src/compiler/scala/tools/nsc/interpreter/MemberHandlers.scala +++ b/src/compiler/scala/tools/nsc/interpreter/MemberHandlers.scala @@ -14,7 +14,7 @@ import util.Chars trait MemberHandlers { val intp: IMain - import intp.{ Request, global, naming } + import intp.{ Request, global, naming, atPickler } import global._ import naming._ @@ -81,10 +81,10 @@ trait MemberHandlers { } sealed abstract class MemberDefHandler(override val member: MemberDef) extends MemberHandler(member) { - def name: Name = member.name + def name: Name = member.name def mods: Modifiers = member.mods - def keyword = member.keyword - def prettyName = NameTransformer.decode(name) + def keyword = member.keyword + def prettyName = NameTransformer.decode(name) override def definesImplicit = member.mods.isImplicit override def definesTerm: Option[TermName] = Some(name.toTermName) filter (_ => name.isTermName) @@ -95,7 +95,6 @@ trait MemberHandlers { * in a single interpreter request. */ sealed abstract class MemberHandler(val member: Tree) { - def tpe = member.tpe def definesImplicit = false def definesValue = false def isLegalTopLevel = member match { @@ -192,24 +191,30 @@ trait MemberHandlers { val Import(expr, selectors) = imp def targetType = intp.typeOfExpression("" + expr) - private def selectorWild = selectors filter (_.name == nme.USCOREkw) // wildcard imports, e.g. import foo._ + // wildcard imports, e.g. import foo._ + private def selectorWild = selectors filter (_.name == nme.USCOREkw) + // renamed imports, e.g. import foo.{ bar => baz } private def selectorRenames = selectors map (_.rename) filterNot (_ == null) /** Whether this import includes a wildcard import */ val importsWildcard = selectorWild.nonEmpty + def implicitSymbols = importedSymbols filter (_.isImplicit) + def importedSymbols = individualSymbols ++ wildcardSymbols + + lazy val individualSymbols: List[Symbol] = + atPickler(targetType.toList flatMap (tp => individualNames map (tp nonPrivateMember _))) + + lazy val wildcardSymbols: List[Symbol] = + if (importsWildcard) atPickler(targetType.toList flatMap (_.nonPrivateMembers)) + else Nil + /** Complete list of names imported by a wildcard */ - def wildcardImportedNames: List[Name] = ( - for (tpe <- targetType ; if importsWildcard) yield - tpe.nonPrivateMembers filter (x => x.isMethod && x.isPublic) map (_.name) distinct - ).toList.flatten - - /** The individual names imported by this statement */ - /** XXX come back to this and see what can be done with wildcards now that - * we know how to enumerate the identifiers. - */ - override lazy val importedNames: List[Name] = - selectorRenames filterNot (_ == nme.USCOREkw) flatMap (_.bothNames) + lazy val wildcardNames: List[Name] = wildcardSymbols map (_.name) + lazy val individualNames: List[Name] = selectorRenames filterNot (_ == nme.USCOREkw) flatMap (_.bothNames) + + /** The names imported by this statement */ + override lazy val importedNames: List[Name] = wildcardNames ++ individualNames override def resultExtractionCode(req: Request) = codegenln(imp.toString) + "\n" } diff --git a/src/compiler/scala/tools/nsc/interpreter/NamedParam.scala b/src/compiler/scala/tools/nsc/interpreter/NamedParam.scala index 878c5b20b1..e92888d89b 100644 --- a/src/compiler/scala/tools/nsc/interpreter/NamedParam.scala +++ b/src/compiler/scala/tools/nsc/interpreter/NamedParam.scala @@ -11,6 +11,7 @@ import NamedParam._ trait NamedParamCreator { protected def freshName: () => String + def apply(name: String, tpe: String, value: Any): NamedParam = NamedParamClass(name, tpe, value) def apply[T: Manifest](name: String, x: T): NamedParam = new Typed[T](name, x) def apply[T: Manifest](x: T): NamedParam = apply(freshName(), x) @@ -35,6 +36,8 @@ object NamedParam extends NamedParamCreator { } } +case class NamedParamClass(name: String, tpe: String, value: Any) extends NamedParam { } + trait NamedParam { def name: String def tpe: String diff --git a/src/compiler/scala/tools/nsc/interpreter/ReflectionCompletion.scala b/src/compiler/scala/tools/nsc/interpreter/ReflectionCompletion.scala deleted file mode 100644 index 0cbac2d0d6..0000000000 --- a/src/compiler/scala/tools/nsc/interpreter/ReflectionCompletion.scala +++ /dev/null @@ -1,109 +0,0 @@ -/* NSC -- new Scala compiler - * Copyright 2005-2011 LAMP/EPFL - * @author Paul Phillips - */ - -package scala.tools.nsc -package interpreter - -import java.lang.reflect -import reflect.{ Modifier, AccessibleObject } -import Modifier.{ isPrivate, isProtected, isStatic } -import ReflectionCompletion._ - -trait ReflectionCompletion extends CompletionAware { - def clazz: JClass - protected def visibleMembers: List[AccessibleObject] - protected def memberCompletions = visibleMembers filter isPublic map reflectName - - def reflectName(m: AccessibleObject) = m match { - case x: reflect.Method => x.getName - case x: reflect.Field => x.getName - case x => sys.error(x.toString) - } - def isPublic(m: AccessibleObject) = m match { - case x: reflect.Method => Modifier isPublic x.getModifiers - case x: reflect.Field => Modifier isPublic x.getModifiers - case x => sys.error(x.toString) - } - - lazy val (staticMethods, instanceMethods) = clazz.getMethods.toList partition (x => isStatic(x.getModifiers)) - lazy val (staticFields, instanceFields) = clazz.getFields.toList partition (x => isStatic(x.getModifiers)) - - /** Oops, mirror classes don't descend from scalaobject. - */ - def isScalaClazz(cl: JClass) = { - (allInterfacesFor(cl) exists (_.getName == "scala.ScalaObject")) || - (classForName(cl.getName + "$").isDefined) - } - def allInterfacesFor(cl: JClass): List[JClass] = allInterfacesFor(cl, Nil) - - private def allInterfacesFor(cl: JClass, acc: List[JClass]): List[JClass] = { - if (cl == null) acc.distinct - else allInterfacesFor(cl.getSuperclass, acc ::: cl.getInterfaces.toList) - } -} - -/** A completion aware object representing a single instance of some class. - * It completes to instance fields and methods, and delegates to another - * InstanceCompletion object if it can determine the result type of the element. - */ -class InstanceCompletion(val clazz: JClass) extends ReflectionCompletion { - protected def visibleMembers = instanceMethods ::: instanceFields - def extras = List("isInstanceOf", "asInstanceOf", "toString") - lazy val completions = memberCompletions ::: extras - def completions(verbosity: Int) = completions - - val (zeroArg, otherArg) = instanceMethods partition (_.getParameterTypes.size == 0) - override def follow(id: String) = { - val nextClazz = zeroArg find (m => m.getName == id) map (_.getReturnType) - if (nextClazz.isDefined) nextClazz map (x => new InstanceCompletion(x)) - else instanceFields find (_.getName == id) map (x => new InstanceCompletion(x.getType)) - } -} - -/** The complementary class to InstanceCompletion. It has logic to deal with - * java static members and scala companion object members. - */ -class StaticCompletion(val clazz: JClass) extends ReflectionCompletion { - protected def visibleMembers = whichMethods ::: whichFields - lazy val completions = memberCompletions - def completions(verbosity: Int) = completions - - def className = clazz.getName - def isJava = !isScalaClazz(clazz) - - private def whichMethods = if (isJava) staticMethods else instanceMethods - private def whichFields = if (isJava) staticFields else instanceFields - val (zeroArg, otherArg) = whichMethods partition (_.getParameterTypes.size == 0) - - override def follow(id: String) = { - val nextClazz = zeroArg find (m => m.getName == id) map (_.getReturnType) - if (nextClazz.isDefined) nextClazz map (x => new InstanceCompletion(x)) - else staticFields find (_.getName == id) map (x => new InstanceCompletion(x.getType)) - } - - override def toString = "StaticCompletion(%s) => %s".format(clazz.getName, completions) -} - -object ReflectionCompletion { - import java.io.File - import java.util.jar.{ JarEntry, JarFile } - import scala.tools.nsc.io.Streamable - - // XXX at the moment this is imperfect because scala's protected semantics - // differ from java's, so protected methods appear public via reflection; - // yet scala enforces the protection. The result is that protected members - // appear in completion yet cannot actually be called. Fixing this - // properly requires a scala.reflect.* API. Fixing it uglily is possible - // too (cast to structural type!) but I deem poor use of energy. - private def skipModifiers(m: reflect.Method) = { - import java.lang.reflect.Modifier._ - val flags = STATIC | PRIVATE | PROTECTED - (m.getModifiers & flags) == 0 - } - private def getAnyClass(x: Any): JClass = x.asInstanceOf[AnyRef].getClass - - def methodsOf(target: Any): List[String] = - getAnyClass(target).getMethods filter skipModifiers map (_.getName) toList -} diff --git a/src/compiler/scala/tools/nsc/interpreter/TypeStrings.scala b/src/compiler/scala/tools/nsc/interpreter/TypeStrings.scala index f72409c8e7..4bc2246468 100644 --- a/src/compiler/scala/tools/nsc/interpreter/TypeStrings.scala +++ b/src/compiler/scala/tools/nsc/interpreter/TypeStrings.scala @@ -41,7 +41,18 @@ trait TypeStrings { else if (primitives(s)) "scala." + s.capitalize else primitiveMap.getOrElse(s, NameTransformer decode s) } - def scalaName(clazz: JClass): String = scalaName(clazz.getName) + def scalaName(clazz: JClass): String = { + val name = clazz.getName + scalaName(clazz.getEnclosingClass match { + case null => name + case encl => + val enclName = encl.getName + if (name startsWith (enclName + "$")) + enclName + "." + (name stripPrefix (enclName + "$")) + else + name + }) + } def scalaName(m: ClassManifest[_]): String = scalaName(m.erasure) def anyClass(x: Any): JClass = if (x == null) null else x.asInstanceOf[AnyRef].getClass @@ -55,8 +66,9 @@ trait TypeStrings { if (xs.isEmpty) "_" else scalaName(xs.head) } - private def tparamString(clazz: JClass): String = + private def tparamString(clazz: JClass): String = { brackets(clazz.getTypeParameters map tvarString: _*) + } private def tparamString[T: Manifest] : String = brackets(manifest[T].typeArguments map (m => tvarString(List(m.erasure))): _*) @@ -76,6 +88,20 @@ trait TypeStrings { /** Reducing fully qualified noise for some common packages. */ + def quieter(tpe: String, alsoStrip: String*): String = { + val transforms = List( + "scala.collection.immutable." -> "immutable.", + "scala.collection.mutable." -> "mutable.", + "scala.collection.generic." -> "generic.", + "java.lang." -> "jl.", + "scala.runtime." -> "runtime." + ) ++ (alsoStrip map (_ -> "")) + + transforms.foldLeft(tpe) { + case (res, (k, v)) => res.replaceAll(k, v) + } + } + val typeTransforms = List( "java.lang." -> "", "scala.collection.immutable." -> "immutable.", diff --git a/src/compiler/scala/tools/nsc/symtab/Names.scala b/src/compiler/scala/tools/nsc/symtab/Names.scala index 6772b8d5e2..4ca4880d35 100644 --- a/src/compiler/scala/tools/nsc/symtab/Names.scala +++ b/src/compiler/scala/tools/nsc/symtab/Names.scala @@ -401,6 +401,8 @@ trait Names extends reflect.generic.Names { (if (nameDebug && isTypeName) "!" else ""))//debug def isOperatorName: Boolean = decode != toString + def nameKind: String = if (isTypeName) "type" else "term" + def longString: String = nameKind + " " + NameTransformer.decode(toString) } final class TermName(_index: Int, _len: Int, hash: Int) extends Name(_index, _len) { diff --git a/src/compiler/scala/tools/nsc/symtab/Symbols.scala b/src/compiler/scala/tools/nsc/symtab/Symbols.scala index f64af26b1c..6a15c929b9 100644 --- a/src/compiler/scala/tools/nsc/symtab/Symbols.scala +++ b/src/compiler/scala/tools/nsc/symtab/Symbols.scala @@ -597,6 +597,14 @@ trait Symbols extends reflect.generic.Symbols { self: SymbolTable => rawowner = owner } + private[Symbols] def flattenName(): Name = { + // TODO: this assertion causes me a lot of trouble in the interpeter in situations + // where everything proceeds smoothly if there's no assert. I don't think calling "name" + // on a symbol is the right place to throw fatal exceptions if things don't look right. + // It really hampers exploration. + assert(rawowner.isClass, "fatal: %s has non-class owner %s after flatten.".format(rawname + idString, rawowner)) + nme.flattenedName(rawowner.name, rawname) + } def ownerChain: List[Symbol] = this :: owner.ownerChain @@ -1757,10 +1765,9 @@ trait Symbols extends reflect.generic.Symbols { self: SymbolTable => override def name: TermName = if (isFlatAdjusted) { - if (flatname == null) { - assert(rawowner.isClass, "fatal: %s has non-class owner %s after flatten.".format(rawname, rawowner)) - flatname = nme.flattenedName(rawowner.name, rawname) - } + if (flatname == null) + flatname = flattenName().toTermName + flatname } else rawname @@ -1961,10 +1968,8 @@ trait Symbols extends reflect.generic.Symbols { self: SymbolTable => override def name: TypeName = if (needsFlatClasses) { - if (flatname == null) { - assert(rawowner.isClass, "fatal: %s has owner %s, but a class owner is required".format(rawname+idString, rawowner)) - flatname = tpnme.flattenedName(rawowner.name, rawname) - } + if (flatname == null) + flatname = flattenName().toTypeName flatname } else rawname.asInstanceOf[TypeName] |