From 70da5a627fe2ce15df64741b5784ed97c361a95e Mon Sep 17 00:00:00 2001 From: Paul Phillips Date: Tue, 12 Jul 2011 19:43:14 +0000 Subject: A bunch of repl stuff. type mismatches, for real this time. :power mode goes to phase typer automatically. You can get the symbols for repl-defined names more directly: scala> case class Bippy(x: Int) defined class Bippy scala> intp.terms("Bippy") res1: intp.global.Symbol = object Bippy scala> intp.types("Bippy") res2: intp.global.Symbol = class Bippy scala> intp("Bippy") // tries type first res3: intp.global.Symbol = class Bippy scala> intp("scala.collection.Map") // falls back to fully qualified res4: intp.global.Symbol = trait Map I changed the implicit which used to install "tpe" and "symbol" to install "tpe_" and "symbol_" because it was too easy to do something you didn't mean to, like calling x.tpe where x is a Manifest. Said implicit now handles manifest type arguments, so you can get the full translation from a manifest representation to a compiler type, at least for simple types and only as much as manifests work, which is not that much. Fortunately that situation is all changing soon. scala> List(List(1, 2, 3)).tpe_ res5: power.Type = List[List[Int]] scala> res5.typeArgs res6: List[power.global.Type] = List(List[Int]) Review by moors. --- .../scala/tools/nsc/interpreter/ILoop.scala | 9 +- .../scala/tools/nsc/interpreter/IMain.scala | 93 ++++++++++++--- .../scala/tools/nsc/interpreter/LoopCommands.scala | 5 +- .../scala/tools/nsc/interpreter/Power.scala | 127 ++++++++++++--------- .../scala/tools/nsc/interpreter/ReplVals.scala | 32 +++++- test/files/run/repl-power.check | 7 +- 6 files changed, 187 insertions(+), 86 deletions(-) diff --git a/src/compiler/scala/tools/nsc/interpreter/ILoop.scala b/src/compiler/scala/tools/nsc/interpreter/ILoop.scala index b249d37006..8e7f73296f 100644 --- a/src/compiler/scala/tools/nsc/interpreter/ILoop.scala +++ b/src/compiler/scala/tools/nsc/interpreter/ILoop.scala @@ -43,11 +43,11 @@ class ILoop(in0: Option[BufferedReader], protected val out: JPrintWriter) var settings: Settings = _ var intp: IMain = _ + override def echoCommandMessage(msg: String): Unit = + intp.reporter.printMessage(msg) + def isAsync = !settings.Yreplsync.value - lazy val power = { - val g = intp.global - Power[g.type](this, g) - } + lazy val power = Power(this) // TODO // object opt extends AestheticSettings @@ -622,6 +622,7 @@ class ILoop(in0: Option[BufferedReader], protected val out: JPrintWriter) def enablePowerMode(isDuringInit: Boolean) = { replProps.power setValue true power.unleash() + intp.beSilentDuring(phaseCommand("typer")) if (isDuringInit) asyncMessage(power.banner) else echo(power.banner) } diff --git a/src/compiler/scala/tools/nsc/interpreter/IMain.scala b/src/compiler/scala/tools/nsc/interpreter/IMain.scala index dd147d7fff..6076f5add5 100644 --- a/src/compiler/scala/tools/nsc/interpreter/IMain.scala +++ b/src/compiler/scala/tools/nsc/interpreter/IMain.scala @@ -267,7 +267,10 @@ class IMain(val settings: Settings, protected val out: JPrintWriter) extends Imp definitions. */ private var _classLoader: AbstractFileClassLoader = null - def resetClassLoader() = _classLoader = makeClassLoader() + def resetClassLoader() = { + repldbg("Setting new classloader: was " + _classLoader) + _classLoader = makeClassLoader() + } def classLoader: AbstractFileClassLoader = { if (_classLoader == null) resetClassLoader() @@ -287,8 +290,10 @@ class IMain(val settings: Settings, protected val out: JPrintWriter) extends Imp override protected def findAbstractFile(name: String): AbstractFile = { super.findAbstractFile(name) match { // deadlocks on startup if we try to translate names too early - case null if isInitializeComplete => generatedName(name) map (x => super.findAbstractFile(x)) orNull - case file => file + case null if isInitializeComplete => + generatedName(name) map (x => super.findAbstractFile(x)) orNull + case file => + file } } } @@ -533,18 +538,14 @@ class IMain(val settings: Settings, protected val out: JPrintWriter) extends Imp def interpret(line: String, synthetic: Boolean): IR.Result = { def loadAndRunReq(req: Request) = { val (result, succeeded) = req.loadAndRun + /** To our displeasure, ConsoleReporter offers only printMessage, * which tacks a newline on the end. Since that breaks all the * output checking, we have to take one off to balance. */ - def show() = { - if (result == "") () - else printMessage(result stripSuffix "\n") - } - if (succeeded) { - if (printResults) - show() + if (printResults && result != "") + printMessage(result stripSuffix "\n") else if (isReplDebug) // show quiet-mode activity printMessage(result.trim.lines map ("[quiet] " + _) mkString "\n") @@ -555,7 +556,7 @@ class IMain(val settings: Settings, protected val out: JPrintWriter) extends Imp } else { // don't truncate stack traces - withoutTruncating(show()) + withoutTruncating(printMessage(result)) IR.Error } } @@ -588,11 +589,19 @@ class IMain(val settings: Settings, protected val out: JPrintWriter) extends Imp |} """.stripMargin.format(bindRep.evalName, boundType, boundType) ) - bindRep.callOpt("set", value) match { - case Some(_) => interpret("val %s = %s.value".format(name, bindRep.evalPath)) - case _ => repldbg("Set failed in bind(%s, %s, %s)".format(name, boundType, value)) ; IR.Error + bindRep.callEither("set", value) match { + case Left(ex) => + repldbg("Set failed in bind(%s, %s, %s)".format(name, boundType, value)) + repldbg(util.stackTraceString(ex)) + IR.Error + + case Right(_) => + val line = "val %s = %s.value".format(name, bindRep.evalPath) + repldbg("Interpreting: " + line) + interpret(line) } } + def rebind(p: NamedParam): IR.Result = { val name = p.name val oldType = typeOfTerm(name) getOrElse { return IR.Error } @@ -688,15 +697,30 @@ class IMain(val settings: Settings, protected val out: JPrintWriter) extends Imp def call(name: String, args: Any*): AnyRef = evalMethod(name).invoke(evalClass, args.map(_.asInstanceOf[AnyRef]): _*) + def callEither(name: String, args: Any*): Either[Throwable, AnyRef] = + try Right(call(name, args: _*)) + catch { case ex: Throwable => Left(ex) } + def callOpt(name: String, args: Any*): Option[AnyRef] = try Some(call(name, args: _*)) - catch { case ex: Exception => bindError(ex) ; None } + catch { case ex: Throwable => bindError(ex) ; None } + + class EvalException(msg: String, cause: Throwable) extends RuntimeException(msg, cause) { } - private def load(s: String): Class[_] = - (classLoader tryToInitializeClass s) getOrElse sys.error("Failed to load expected class: '" + s + "'") + private def evalError(path: String, ex: Throwable) = + throw new EvalException("Failed to load '" + path + "': " + ex.getMessage, ex) + private def load(path: String): Class[_] = { + try Class.forName(path, true, classLoader) + catch { case ex => evalError(path, unwrap(ex)) } + } + + var evalCaught: Option[Throwable] = None lazy val evalClass = load(evalPath) - lazy val evalValue = callOpt(evalName) + lazy val evalValue = callEither(evalName) match { + case Left(ex) => evalCaught = Some(ex) ; None + case Right(result) => Some(result) + } def compile(source: String): Boolean = compileAndSaveRun("", source) def lineAfterTyper[T](op: => T): T = { @@ -986,6 +1010,39 @@ class IMain(val settings: Settings, protected val out: JPrintWriter) extends Imp def definedTypes = onlyTypes(allDefinedNames) def definedSymbols = prevRequests.toSet flatMap ((x: Request) => x.definedSymbols.values) + private def findName(name: Name) = definedSymbols find (_.name == name) + + private def missingOpt(op: => Symbol): Option[Symbol] = + try Some(op) + catch { case _: MissingRequirementError => None } + private def missingWrap(op: => Symbol): Symbol = + try op + catch { case _: MissingRequirementError => NoSymbol } + + def optCompilerClass(name: String) = missingOpt(definitions.getClass(name)) + def optCompilerModule(name: String) = missingOpt(definitions.getModule(name)) + def getCompilerClass(name: String) = missingWrap(definitions.getClass(name)) + def getCompilerModule(name: String) = missingWrap(definitions.getModule(name)) + + /** Translate a repl-defined identifier into a Symbol. + */ + def apply(name: String): Symbol = { + val tpname = newTypeName(name) + ( + findName(tpname) + orElse findName(tpname.companionName) + orElse optCompilerClass(name) + orElse optCompilerModule(name) + getOrElse NoSymbol + ) + } + def types(name: String): Symbol = { + findName(newTypeName(name)) getOrElse getCompilerClass(name) + } + def terms(name: String): Symbol = { + findName(newTermName(name)) getOrElse getCompilerModule(name) + } + /** the previous requests this interpreter has processed */ private lazy val prevRequests = mutable.ListBuffer[Request]() private lazy val referencedNameMap = mutable.Map[Name, Request]() diff --git a/src/compiler/scala/tools/nsc/interpreter/LoopCommands.scala b/src/compiler/scala/tools/nsc/interpreter/LoopCommands.scala index 188f891054..9469baa4e2 100644 --- a/src/compiler/scala/tools/nsc/interpreter/LoopCommands.scala +++ b/src/compiler/scala/tools/nsc/interpreter/LoopCommands.scala @@ -29,6 +29,9 @@ object ProcessResult { trait LoopCommands { protected def out: JPrintWriter + // So outputs can be suppressed. + def echoCommandMessage(msg: String): Unit = out println msg + // a single interpreter command abstract class LoopCommand(val name: String, val help: String) extends (String => Result) { private var _longHelp: String = null @@ -95,7 +98,7 @@ trait LoopCommands { // to print something to the console, so we accomodate Unit and String returns. implicit def resultFromUnit(x: Unit): Result = default implicit def resultFromString(msg: String): Result = { - out println msg + echoCommandMessage(msg) default } } diff --git a/src/compiler/scala/tools/nsc/interpreter/Power.scala b/src/compiler/scala/tools/nsc/interpreter/Power.scala index d907d5024f..95e2e99dd1 100644 --- a/src/compiler/scala/tools/nsc/interpreter/Power.scala +++ b/src/compiler/scala/tools/nsc/interpreter/Power.scala @@ -6,7 +6,7 @@ package scala.tools.nsc package interpreter -import scala.reflect.NameTransformer +import scala.reflect.{ NameTransformer, AnyValManifest } import scala.collection.{ mutable, immutable } import scala.util.matching.Regex import scala.tools.nsc.util.{ BatchSourceFile } @@ -15,8 +15,9 @@ import scala.io.Codec import java.net.{ URL, MalformedURLException } import io.{ Path } -trait SharesGlobal[G <: Global] { - val global: G +trait SharesGlobal { + type GlobalType <: Global + val global: GlobalType // This business gets really old: // @@ -42,26 +43,26 @@ trait SharesGlobal[G <: Global] { } object Power { - def apply[G <: Global](repl: ILoop, g: G) = - new { final val global: G = g } with Power[G](repl, repl.intp) - - def apply(intp: IMain) = - new { final val global = intp.global } with Power[Global](null, intp) + def apply(intp: IMain): Power = apply(null, intp) + def apply(repl: ILoop): Power = apply(repl, repl.intp) + def apply(repl: ILoop, intp: IMain): Power = + new Power(repl, intp) { + type GlobalType = intp.global.type + final val global: intp.global.type = intp.global + } } /** A class for methods to be injected into the intp in power mode. */ -abstract class Power[G <: Global]( +abstract class Power( val repl: ILoop, val intp: IMain -) extends SharesGlobal[G] { - import intp.{ beQuietDuring, interpret, parse } - import global.{ - opt, definitions, analyzer, - stringToTermName, typeRef, - CompilationUnit, - NoSymbol, NoPrefix, NoType +) extends SharesGlobal { + import intp.{ + beQuietDuring, typeOfExpression, getCompilerClass, getCompilerModule, + interpret, parse } + import global._ abstract class SymSlurper { def isKeep(sym: Symbol): Boolean @@ -129,47 +130,36 @@ abstract class Power[G <: Global]( private def customInit = replProps.powerInitCode.option flatMap (f => io.File(f).safeSlurp()) def banner = customBanner getOrElse """ - |** Power User mode enabled - BEEP BOOP WHIR ** + |** Power User mode enabled - BEEP BOOP SPIZ ** + |** :phase has been set to 'typer'. ** |** scala.tools.nsc._ has been imported ** |** global._ and definitions._ also imported ** - |** New vals! Try repl, intp, global, power ** - |** New cmds! :help to discover them ** - |** New defs! Type power. to reveal ** + |** Try :help, vals., power. ** """.stripMargin.trim private def initImports = List( "scala.tools.nsc._", "scala.collection.JavaConverters._", "global.{ error => _, _ }", + "definitions.{ getClass => _, _ }", "power.Implicits._", "power.rutil._" ) - def init = customInit getOrElse "import " + initImports.mkString(", ") + + def init = customInit match { + case Some(x) => List(x) + case _ => initImports map ("import " + _) + } /** Starts up power mode and runs whatever is in init. */ def unleash(): Unit = beQuietDuring { val r = new ReplVals(repl) - intp.bind[ILoop]("repl", repl) - intp.bind[ReplVals]("$r", r) - - intp.bind("intp", r.intp) - intp.bind("global", r.global) - intp.bind("power", r.power) - intp.bind("phased", r.phased) - intp.bind("isettings", r.isettings) - intp.bind("completion", r.completion) - intp.bind("history", r.history) - - init split '\n' foreach interpret - } - - private def missingWrap(op: => Symbol): Symbol = - try op - catch { case _: MissingRequirementError => NoSymbol } + intp.bind("$r", r) + r bindWithPrefix intp.pathToTerm("$r") // binds all the vals - private def getCompilerClass(name: String) = missingWrap(definitions.getClass(name)) - private def getCompilerModule(name: String) = missingWrap(definitions.getModule(name)) + init foreach interpret + } trait LowPriorityInternalInfo { implicit def apply[T: Manifest] : InternalInfo[T] = new InternalInfo[T](None) @@ -181,14 +171,41 @@ abstract class Power[G <: Global]( * customizable symbol filter (had to hardcode no-spec to reduce noise) */ class InternalInfo[T: Manifest](value: Option[T] = None) { - def companion = symbol.companionSymbol - def info = symbol.info - def module = symbol.moduleClass - def owner = symbol.owner - def owners = symbol.ownerChain drop 1 - def symDef = symbol.defString - def symName = symbol.name - def tpe = symbol.tpe + // Decided it was unwise to have implicit conversions via commonly + // used type/symbol methods, because it's too easy to e.g. call + // "x.tpe" where x is a Type, and rather than failing you get the + // Type representing Types#Type (or Manifest, or whatever.) + private def tpe = tpe_ + private def symbol = symbol_ + private def name = name_ + + // Would love to have stuff like existential types working, + // but very unfortunately those manifests just stuff the relevant + // information into the toString method. Boo. + private def manifestToType(m: Manifest[_]): Type = m match { + case x: AnyValManifest[_] => + getCompilerClass("scala." + x).tpe + case _ => + val name = m.erasure.getName + if (name endsWith "$") getCompilerModule(name dropRight 1).tpe + else { + val sym = getCompilerClass(name) + val args = m.typeArguments + + if (args.isEmpty) sym.tpe + else typeRef(NoPrefix, sym, args map manifestToType) + } + } + + def symbol_ : Symbol = getCompilerClass(erasure.getName) + def tpe_ : Type = manifestToType(man) + def name_ : Name = symbol.name + def companion = symbol.companionSymbol + def info = symbol.info + def module = symbol.moduleClass + def owner = symbol.owner + def owners = symbol.ownerChain drop 1 + def defn = symbol.defString def declares = members filter (_.owner == symbol) def inherits = members filterNot (_.owner == symbol) @@ -197,8 +214,8 @@ abstract class Power[G <: Global]( def overrides = declares filter (_.isOverride) def inPackage = owners find (x => x.isPackageClass || x.isPackage) getOrElse definitions.RootPackage - def erasure = manifest[T].erasure - def symbol = getCompilerClass(erasure.getName) + def man = manifest[T] + def erasure = man.erasure def members = tpe.members filterNot (_.name.toString contains "$mc") def allMembers = tpe.members def bts = info.baseTypeSeq.toList @@ -331,10 +348,10 @@ abstract class Power[G <: Global]( implicit lazy val powerSymbolOrdering: Ordering[Symbol] = Ordering[Name] on (_.name) implicit lazy val powerTypeOrdering: Ordering[Type] = Ordering[Symbol] on (_.typeSymbol) + implicit def replInternalInfo[T: Manifest](x: T): InternalInfo[T] = new InternalInfo[T](Some(x)) implicit def replEnhancedStrings(s: String): RichReplString = new RichReplString(s) implicit def replMultiPrinting[T: Prettifier](xs: TraversableOnce[T]): MultiPrettifierClass[T] = new MultiPrettifierClass[T](xs.toSeq) - implicit def replInternalInfo[T: Manifest](x: T): InternalInfo[T] = new InternalInfo[T](Some(x)) implicit def replPrettifier[T] : Prettifier[T] = Prettifier.default[T] implicit def replTypeApplication(sym: Symbol): RichSymbol = new RichSymbol(sym) implicit def replInputStream(in: InputStream)(implicit codec: Codec) = new RichInputStream(in) @@ -343,6 +360,8 @@ abstract class Power[G <: Global]( object Implicits extends Implicits2 { } trait ReplUtilities { + def module[T: Manifest] = getCompilerModule(manifest[T].erasure.getName stripSuffix "$") + def clazz[T: Manifest] = getCompilerClass(manifest[T].erasure.getName) def info[T: Manifest] = InternalInfo[T] def ?[T: Manifest] = InternalInfo[T] def url(s: String) = { @@ -367,8 +386,10 @@ abstract class Power[G <: Global]( } lazy val rutil: ReplUtilities = new ReplUtilities { } - lazy val phased: Phased = new Phased with SharesGlobal[G] { - val global: G = Power.this.global + + lazy val phased: Phased = new Phased with SharesGlobal { + type GlobalType = Power.this.global.type + final val global: Power.this.global.type = Power.this.global } def context(code: String) = analyzer.rootContext(unit(code)) diff --git a/src/compiler/scala/tools/nsc/interpreter/ReplVals.scala b/src/compiler/scala/tools/nsc/interpreter/ReplVals.scala index 2c5f5474af..80ccd06c70 100644 --- a/src/compiler/scala/tools/nsc/interpreter/ReplVals.scala +++ b/src/compiler/scala/tools/nsc/interpreter/ReplVals.scala @@ -6,15 +6,35 @@ package scala.tools.nsc package interpreter -import java.lang.reflect.{ Method => JMethod } +import java.lang.reflect.{ Method => JMethod, Modifier => JModifier } -class ReplVals(final val r: ILoop) { +final class ReplVals(final val r: ILoop) { + final val vals = this final val intp = r.intp - final val global = r.power.global + final val global = intp.global final val power = r.power - final val phased = r.power.phased - final val isettings = r.intp.isettings + final val phased = power.phased + final val isettings = intp.isettings final val completion = r.in.completion final val history = r.in.history - final val rutil = r.power.rutil + final val rutil = power.rutil + + /** Reflectively finds the vals defined in this class. */ + private def valMethods = this.getClass.getDeclaredMethods.toList filter { m => + ( + JModifier.isPublic(m.getModifiers()) + && m.getParameterTypes.isEmpty + && !m.getName.contains('$') + ) + } + + /** Binds each val declared here into the repl with explicit singleton types + * based on the given prefix. + */ + def bindWithPrefix(prefix: String) { + valMethods foreach { m => + repldbg("intp.bind " + (m.getName, prefix + "." + m.getName + ".type", m.invoke(this))) + intp.bind(m.getName, prefix + "." + m.getName + ".type", m.invoke(this)) + } + } } diff --git a/test/files/run/repl-power.check b/test/files/run/repl-power.check index 9561c04eca..1b3883a839 100644 --- a/test/files/run/repl-power.check +++ b/test/files/run/repl-power.check @@ -2,12 +2,11 @@ Type in expressions to have them evaluated. Type :help for more information. scala> :power -** Power User mode enabled - BEEP BOOP WHIR ** +** Power User mode enabled - BEEP BOOP SPIZ ** +** :phase has been set to 'typer'. ** ** scala.tools.nsc._ has been imported ** ** global._ and definitions._ also imported ** -** New vals! Try repl, intp, global, power ** -** New cmds! :help to discover them ** -** New defs! Type power. to reveal ** +** Try :help, vals., power. ** scala> // guarding against "error: reference to global is ambiguous" -- cgit v1.2.3