diff options
Diffstat (limited to 'src/compiler')
68 files changed, 1896 insertions, 1337 deletions
diff --git a/src/compiler/scala/tools/cmd/CommandLine.scala b/src/compiler/scala/tools/cmd/CommandLine.scala index 8cb4c00b14..9b8bef4a9a 100644 --- a/src/compiler/scala/tools/cmd/CommandLine.scala +++ b/src/compiler/scala/tools/cmd/CommandLine.scala @@ -8,18 +8,19 @@ package cmd import scala.collection.mutable.ListBuffer +trait CommandLineConfig { + def enforceArity: Boolean = true + def onlyKnownOptions: Boolean = true +} + /** An instance of a command line, parsed according to a Spec. */ -class CommandLine(val spec: Reference, val originalArgs: List[String]) { +class CommandLine(val spec: Reference, val originalArgs: List[String]) extends CommandLineConfig { def this(spec: Reference, line: String) = this(spec, Parser tokenize line) def this(spec: Reference, args: Array[String]) = this(spec, args.toList) import spec.{ isAnyOption, isUnaryOption, isBinaryOption, isExpandOption } - def assumeBinary = true - def enforceArity = true - def onlyKnownOptions = false - val Terminator = "--" val ValueForUnaryOption = "true" // so if --opt is given, x(--opt) = true @@ -32,13 +33,6 @@ class CommandLine(val spec: Reference, val originalArgs: List[String]) { lazy val (argMap, residualArgs) = { val residualBuffer = new ListBuffer[String] - def isOption(s: String) = isAnyOption(s) || ((s startsWith "-") && !onlyKnownOptions) - - def unknownOption(opt: String) = - errorFn("Option '%s' not recognized.".format(opt)) - def missingArg(opt: String, what: String) = - errorFn("Option '%s' requires argument, found %s instead.".format(opt, what)) - def loop(args: List[String]): Map[String, String] = { def residual(xs: List[String]) = { residualBuffer ++= xs ; Map[String, String]() } @@ -54,22 +48,32 @@ class CommandLine(val spec: Reference, val originalArgs: List[String]) { else None } + /** Assumes known options have all been ruled out already. */ + def isUnknown(opt: String) = + onlyKnownOptions && (opt startsWith "-") && { + errorFn("Option '%s' not recognized.".format(opt)) + true + } + args match { case Nil => Map() case Terminator :: xs => residual(xs) case x :: Nil => expand(x) foreach (exp => return loop(exp)) if (isBinaryOption(x) && enforceArity) - missingArg(x, "EOF") + errorFn("Option '%s' requires argument, found EOF instead.".format(x)) if (isUnaryOption(x)) mapForUnary(x) + else if (isUnknown(x)) Map() else residual(args) + case x1 :: x2 :: xs => expand(x1) foreach (exp => return loop(exp ++ args.tail)) if (x2 == Terminator) mapForUnary(x1) ++ residual(xs) else if (isUnaryOption(x1)) mapForUnary(x1) ++ loop(args.tail) else if (isBinaryOption(x1)) Map(x1 -> x2) ++ loop(xs) + else if (isUnknown(x1)) loop(args.tail) else residual(List(x1)) ++ loop(args.tail) } } @@ -85,23 +89,3 @@ class CommandLine(val spec: Reference, val originalArgs: List[String]) { override def toString() = argMap.toString + " " + residualArgs.toString } - -object CommandLine { - def apply(args: List[String], unary: List[String], binary: List[String]) = { - /** XXX Temporarily assembling a fake spec so we can continue to - * do ad-hoc parsing based on a list of unary and binary args. - * Should either clean this up or do it differently. - */ - object NoSpec extends Reference { - unary foreach (_ --? ) - binary foreach (_ --| ) - - protected def creator(args: List[String]) = error("No Spec") - def programInfo = Spec.Names("", "") - lazy val referenceSpec = this - } - - new CommandLine(NoSpec, args) - } -} - diff --git a/src/compiler/scala/tools/cmd/Demo.scala b/src/compiler/scala/tools/cmd/Demo.scala index 7014e6b4d7..22cf50bd58 100644 --- a/src/compiler/scala/tools/cmd/Demo.scala +++ b/src/compiler/scala/tools/cmd/Demo.scala @@ -10,7 +10,7 @@ package cmd * First take advantage of the meta-options: * * // this command creates an executable runner script "demo" - * % scala scala.tools.cmd.Demo --generate-runner demo + * % scala scala.tools.cmd.Demo --self-update demo * * // this one creates and sources a completion file - note backticks * % `./demo --bash` @@ -19,13 +19,13 @@ package cmd * % ./demo --<tab> * --action --defint --int * --bash --defstr --str - * --defenv --generate-runner --unary + * --defenv --self-update --unary * * The normal option configuration is plausibly self-explanatory. */ trait DemoSpec extends Spec with Meta.StdOpts with Interpolation { lazy val referenceSpec = DemoSpec - lazy val programInfo = Spec.Names("demo", "scala.tools.cmd.Demo") + lazy val programInfo = Spec.Info("demo", "Usage: demo [<options>]", "scala.tools.cmd.Demo") help("""Usage: demo [<options>]""") heading("Unary options:") @@ -48,7 +48,6 @@ object DemoSpec extends DemoSpec with Property { type ThisCommandLine = SpecCommandLine def creator(args: List[String]) = new SpecCommandLine(args) { - override def onlyKnownOptions = true override def errorFn(msg: String) = { println("Error: " + msg) ; System.exit(0) } } } diff --git a/src/compiler/scala/tools/cmd/Instance.scala b/src/compiler/scala/tools/cmd/Instance.scala index 4d319b98cc..3c0dbbaa1f 100644 --- a/src/compiler/scala/tools/cmd/Instance.scala +++ b/src/compiler/scala/tools/cmd/Instance.scala @@ -16,7 +16,8 @@ trait Instance extends Spec { protected def help(str: => String): Unit = () def isSet(s: String) = parsed isSet toOpt(s) - def originalArgs = parsed.originalArgs + def originalArgs = parsed.originalArgs // the full original list + def residualArgs = parsed.residualArgs // only args which were not options or args to options type OptionMagic = Opt.Instance protected implicit def optionMagicAdditions(name: String) = new Opt.Instance(programInfo, parsed, name) diff --git a/src/compiler/scala/tools/cmd/Interpolation.scala b/src/compiler/scala/tools/cmd/Interpolation.scala index 6b86a35bb9..a326d48f64 100644 --- a/src/compiler/scala/tools/cmd/Interpolation.scala +++ b/src/compiler/scala/tools/cmd/Interpolation.scala @@ -52,8 +52,6 @@ object Interpolation { |#!/bin/sh |# | - |scala @@MAINCLASS@@ $* - | - """.stripMargin + |scala @@MAINCLASS@@ "$@" + |""".stripMargin.trim + "\n" } - diff --git a/src/compiler/scala/tools/cmd/Meta.scala b/src/compiler/scala/tools/cmd/Meta.scala index 5a09766b13..8609db3d50 100644 --- a/src/compiler/scala/tools/cmd/Meta.scala +++ b/src/compiler/scala/tools/cmd/Meta.scala @@ -22,11 +22,11 @@ object Meta { trait StdOpts { self: Spec with Interpolation => - Bash.name --> runAndExit(Bash.action()) - val runnerFileName = Runner.name --| ; + Bash.name --> runAndExit(Bash.action()) + val selfUpdateName = SelfUpdate.name --| ; - if (runnerFileName.isDefined) - runAndExit(Runner.action()) + if (selfUpdateName.isDefined) + runAndExit(SelfUpdate.action()) /** I think we're as close as we can get to bundling completion with * the program given the constraints imposed by bash. This outputs @@ -47,12 +47,17 @@ object Meta { } } - /** A very basic runner script. + /** Generates a very basic runner script. It's called SelfUpdate + * because once it exists you can do something like + * + * tools/scmp --self-update tools/scmp + * + * and it will overwrite itself with the current version. */ - object Runner extends Opt { - val name = "generate-runner" + object SelfUpdate extends Opt { + val name = "self-update" val action = () => { - val file = File(runnerFileName.get) + val file = File(selfUpdateName.get) file writeAll interpolate(runnerTemplate) file setExecutable true () diff --git a/src/compiler/scala/tools/cmd/Opt.scala b/src/compiler/scala/tools/cmd/Opt.scala index 9e3c324deb..beea590492 100644 --- a/src/compiler/scala/tools/cmd/Opt.scala +++ b/src/compiler/scala/tools/cmd/Opt.scala @@ -7,7 +7,7 @@ package scala.tools package cmd import nsc.Properties.envOrElse -import Spec.Names +import Spec.Info /** Machinery for what amounts to a command line specification DSL. * It is designed so the same specification trait can be used for @@ -25,7 +25,7 @@ object Opt { trait Implicit { def name: String - def programInfo: Names + def programInfo: Info protected def opt = toOpt(name) def --? : Boolean // --opt is set @@ -47,7 +47,7 @@ object Opt { def /(descr: String): String // --opt has help description 'descr' } - class Reference(val programInfo: Names, val options: Reference.Accumulators, val name: String) extends Implicit { + class Reference(val programInfo: Info, val options: Reference.Accumulators, val name: String) extends Implicit { import options._ def --? = { addUnary(opt) ; false } @@ -63,7 +63,7 @@ object Opt { def /(descr: String) = returning(name)(_ => addHelp(() => helpFormatStr.format(opt, descr))) } - class Instance(val programInfo: Names, val parsed: CommandLine, val name: String) extends Implicit with Error { + class Instance(val programInfo: Info, val parsed: CommandLine, val name: String) extends Implicit with Error { def --? = parsed isSet opt def --> (body: => Unit) = if (parsed isSet opt) body def --| = parsed get opt diff --git a/src/compiler/scala/tools/cmd/Reference.scala b/src/compiler/scala/tools/cmd/Reference.scala index 695868191b..3f3712766b 100644 --- a/src/compiler/scala/tools/cmd/Reference.scala +++ b/src/compiler/scala/tools/cmd/Reference.scala @@ -32,7 +32,8 @@ trait Reference extends Spec { protected def help(str: => String) = addHelp(() => str) - type ThisCommandLine <: SpecCommandLine + type ThisCommandLine <: CommandLine + class SpecCommandLine(args: List[String]) extends CommandLine(Reference.this, args) { } protected def creator(args: List[String]): ThisCommandLine final def apply(args: String*): ThisCommandLine = creator(propertyArgs ++ args flatMap expandArg) diff --git a/src/compiler/scala/tools/cmd/Spec.scala b/src/compiler/scala/tools/cmd/Spec.scala index c8283165d9..794bb3303f 100644 --- a/src/compiler/scala/tools/cmd/Spec.scala +++ b/src/compiler/scala/tools/cmd/Spec.scala @@ -12,7 +12,7 @@ package cmd */ trait Spec { def referenceSpec: Reference - def programInfo: Spec.Names + def programInfo: Spec.Info protected def help(str: => String): Unit protected def heading(str: => String): Unit = help("\n " + str) @@ -22,7 +22,14 @@ trait Spec { } object Spec { - case class Names(runner: String, mainClass: String) { } + class Info( + val runner: String, + val usage: String, + val mainClass: String + ) + object Info { + def apply(runner: String, help: String, mainClass: String): Info = new Info(runner, help, mainClass) + } class Accumulator[T: FromString]() { private var _buf: List[T] = Nil diff --git a/src/compiler/scala/tools/cmd/program/Scmp.scala b/src/compiler/scala/tools/cmd/program/Scmp.scala new file mode 100644 index 0000000000..ff4fa11eaf --- /dev/null +++ b/src/compiler/scala/tools/cmd/program/Scmp.scala @@ -0,0 +1,59 @@ +/* NEST (New Scala Test) + * Copyright 2007-2010 LAMP/EPFL + * @author Paul Phillips + */ + +package scala.tools +package cmd +package program + +import nsc.io._ + +object Scmp { + private val scmpUsage = """ + |Usage: scmp [options] <cmd line> + |Example: scmp --p1 '-no-specialization -Ydebug' scalac src/library/scala/Function1.scala + | + |Note: the command line must start with a path to scalac. + |""".stripMargin + private val scmpOptions = List( + "p1" -> "options for the first run only", + "p2" -> "options for the second run only" + ) + private val scmpInfo = Simple.scalaProgramInfo("scmp", scmpUsage) + lazy val ScmpSpec = Simple(scmpInfo, Nil, scmpOptions, x => returning(x)(_.onlyKnownOptions = false)) + + def main(args0: Array[String]): Unit = { + if (args0.isEmpty) + return println(scmpUsage) + + val runner = ScmpSpec instance args0 + import runner._ + + val p1args = parsed.getOrElse("--p1", "") + val p2args = parsed.getOrElse("--p2", "") + + if (p1args.isEmpty && p2args.isEmpty) + return println("At least one of --p1 and --p2 must be given.") + if (residualArgs.isEmpty) + return println("There is no command to run.") + + def createCmd(extras: String) = + fromArgs(residualArgs.patch(1, toArgs(extras), 0)) + + def runCmd(cmd: String) = { + val output = Process(cmd, redirect = true).slurp() + + returning(File.makeTemp())(_ writeAll output) + } + + val cmds = List(p1args, p2args) map createCmd + println(cmds.mkString("Running command lines:\n ", "\n ", "")) + + val files = cmds map runCmd map (_.path) + val diff = Process("diff %s %s".format(files: _*)).slurp() + + if (diff.isEmpty) println("No differences.") + else println(diff) + } +} diff --git a/src/compiler/scala/tools/cmd/program/Simple.scala b/src/compiler/scala/tools/cmd/program/Simple.scala new file mode 100644 index 0000000000..641be31c9e --- /dev/null +++ b/src/compiler/scala/tools/cmd/program/Simple.scala @@ -0,0 +1,81 @@ +/* NEST (New Scala Test) + * Copyright 2007-2010 LAMP/EPFL + * @author Paul Phillips + */ + +package scala.tools +package cmd +package program + +import Spec.Info + +/** A boilerplate reducer for commands with simple requirements. For examples, + * see Scmp and Tokens in this package. + */ +object Simple { + type CommandLineTransform = SimpleCommandLine => SimpleCommandLine + + abstract class SimpleSpec(val programInfo: Info) extends Spec with Meta.StdOpts with Interpolation + + trait SimpleInstance extends SimpleSpec with Instance { + val parsed: CommandLine + } + + class SimpleReference( + programInfo: Info, + unary: List[(String, String)] = Nil, + binary: List[(String, String)] = Nil, + postCreation: CommandLineTransform = null + ) extends SimpleSpec(programInfo) with Reference { + + spec => + + if (programInfo.usage != "") help(programInfo.usage) + unary foreach { case (option, help) => option / help --? } + binary foreach { case (option, help) => option / help --| } + + type ThisCommandLine = SimpleCommandLine + + def creator(args: List[String]) = new SimpleCommandLine(spec, args) + def instance(args: Array[String]): SimpleInstance = instance(args.toList) + def instance(args: List[String]): SimpleInstance = + new { + val parsed = spec(args: _*) + } with SimpleSpec(programInfo) with SimpleInstance { + lazy val referenceSpec = spec + } + + lazy val referenceSpec = spec + } + + def apply(info: Info, unary: List[(String, String)], binary: List[(String, String)], postCreation: CommandLineTransform): SimpleReference = { + new SimpleReference(info, unary, binary, postCreation) { + override def creator(args: List[String]) = { + val obj = super.creator(args) + if (postCreation == null) obj + else postCreation(obj) + } + } + } + + def scalaProgramInfo(name: String, help: String) = + Spec.Info(name, help, "scala.tools.cmd.program." + name.capitalize) + + /** You can't override a def with a var unless a setter exists. We cleverly + * sidestep this by mixing in a trait with dummy setters which will be + * inaccessible due to the overriding var. + */ + trait Ticket2338WontFixWorkaround { + def enforceArity_=(x: Boolean): Unit = error("unreachable") + def onlyKnownOptions_=(x: Boolean): Unit = error("unreachable") + } + + /** Configurability simplicity achieved by turning defs into vars and letting + * the spec creator apply a transformation. This way there's no need to create + * custom subclasses of CommandLine. + */ + class SimpleCommandLine(spec: Reference, args: List[String]) extends CommandLine(spec, args) with Ticket2338WontFixWorkaround { + override var enforceArity: Boolean = true + override var onlyKnownOptions: Boolean = true + } +} diff --git a/src/compiler/scala/tools/cmd/program/Tokens.scala b/src/compiler/scala/tools/cmd/program/Tokens.scala new file mode 100644 index 0000000000..36786aa2b7 --- /dev/null +++ b/src/compiler/scala/tools/cmd/program/Tokens.scala @@ -0,0 +1,100 @@ +/* NSC -- new Scala compiler + * Copyright 2005-2010 LAMP/EPFL + * @author Paul Phillips + */ + +package scala.tools +package cmd +package program + +import nsc._ +import util.Chars.char2uescape +import io._ +import ast.parser.Tokens._ + +/** Given paths on the command line, tokenizes any scala files found + * and prints one token per line. + */ +object Tokens { + private val tokensUsage = "Usage: tokens [options] <path1 path2 ...>\n\nOptions:" + private val tokensUnary = List( + "verbose" -> "be more verbose", + "freq" -> "combine token lists and sort by frequency", + "stats" -> "output some stats" + ) + private val tokensBinary = List( + "sliding" -> "print tokens in groups of given size" + ) + private val tokensInfo = Simple.scalaProgramInfo("tokens", tokensUsage) + private lazy val TokensSpec = Simple(tokensInfo, tokensUnary, tokensBinary, null) + + def sanitize(x: Any): String = sanitize(x.toString) + def sanitize(str: String): String = str flatMap (x => if (x.isControl) char2uescape(x) else x.toString) + + def main(args0: Array[String]): Unit = { + if (args0.isEmpty) + return println(TokensSpec.helpMsg) + + val runner = TokensSpec instance args0 + import runner._ + + val files = (residualArgs flatMap walk).distinct + if (parsed isSet "--verbose") + println("Tokenizing: " + (files map (_.name) mkString " ")) + + if (parsed isSet "--stats") + println("Stats not yet implemented.") + + def raw = files flatMap fromScalaSource + def tokens: List[Any] = + if (parsed isSet "--sliding") raw sliding parsed("--sliding").toInt map (_ map sanitize mkString " ") toList + else raw + + def output = + if (parsed isSet "--freq") + (tokens groupBy (x => x) mapValues (_.length)).toList sortBy (-_._2) map (x => x._2 + " " + x._1) + else + tokens + + output foreach println + } + + def fromPaths(paths: String*): List[Any] = + (paths.toList flatMap walk).distinct flatMap fromScalaSource + + /** Given a path, returns all .scala files underneath it. + */ + private def walk(arg: String): List[File] = { + def traverse = Path(arg) ifDirectory (_.deepList()) getOrElse Iterator(File(arg)) + + Path onlyFiles traverse filter (_ hasExtension "scala") toList + } + + /** Tokenizes a single scala file. + */ + def fromScalaSource(file: Path): List[Any] = fromScalaSource(file.path) + def fromScalaSource(file: String): List[Any] = { + val global = new Global(new Settings()) + import global._ + import syntaxAnalyzer.{ UnitScanner, token2string } + + val in = new UnitScanner(new CompilationUnit(getSourceFile(file))) + in.init() + + Iterator continually { + val token = in.token match { + case IDENTIFIER | BACKQUOTED_IDENT => in.name + case CHARLIT | INTLIT | LONGLIT => in.intVal + case DOUBLELIT | FLOATLIT => in.floatVal + case STRINGLIT => "\"" + in.strVal + "\"" + case SEMI | NEWLINE => ";" + case NEWLINES => ";;" + case COMMA => "," + case EOF => null + case x => token2string(x) + } + in.nextToken() + token + } takeWhile (_ != null) toList + } +} diff --git a/src/compiler/scala/tools/nsc/Global.scala b/src/compiler/scala/tools/nsc/Global.scala index 16bff5d399..0ec36cf9ab 100644 --- a/src/compiler/scala/tools/nsc/Global.scala +++ b/src/compiler/scala/tools/nsc/Global.scala @@ -137,7 +137,7 @@ class Global(var settings: Settings, var reporter: Reporter) extends SymbolTable def error(msg: String) = reporter.error(NoPosition, msg) def warning(msg: String) = - if (settings.Ywarnfatal.value) reporter.error(NoPosition, msg) + if (settings.Xwarnfatal.value) reporter.error(NoPosition, msg) else reporter.warning(NoPosition, msg) def inform(msg: String) = reporter.info(NoPosition, msg, true) def inform[T](msg: String, value: T): T = { inform(msg+value); value } @@ -714,7 +714,7 @@ class Global(var settings: Settings, var reporter: Reporter) extends SymbolTable /** Compile list of source files */ def compileSources(_sources: List[SourceFile]) { val depSources = dependencyAnalysis.filter(_sources.distinct) // bug #1268, scalac confused by duplicated filenames - val sources = scalaObjectFirst(depSources) + val sources = coreClassesFirst(depSources) if (reporter.hasErrors) return // there is a problem already, e.g. a // plugin was passed a bad option @@ -871,14 +871,43 @@ class Global(var settings: Settings, var reporter: Reporter) extends SymbolTable if (!pclazz.isRoot) resetPackageClass(pclazz.owner) } - private def scalaObjectFirst(files: List[SourceFile]) = { + /** + * Re-orders the source files to + * 1. ScalaObject + * 2. LowPriorityImplicits / StandardEmbeddings (i.e. parents of Predef) + * 3. the rest + * + * 1 is to avoid cyclic reference errors. + * 2 is due to the following. When completing "Predef" (*), typedIdent is called + * for its parents (e.g. "LowPriorityImplicits"). typedIdent checks wethter + * the symbol reallyExists, which tests if the type of the symbol after running + * its completer is != NoType. + * If the "namer" phase has not yet run for "LowPriorityImplicits", the symbol + * has a SourcefileLoader as type. Calling "doComplete" on it does nothing at + * all, because the source file is part of the files to be compiled anyway. + * So the "reallyExists" test will return "false". + * Only after the namer, the symbol has a lazy type which actually computes + * the info, and "reallyExists" behaves as expected. + * So we need to make sure that the "namer" phase is run on predef's parents + * before running it on predef. + * + * (*) Predef is completed early when calling "mkAttributedRef" during the + * addition of "import Predef._" to sourcefiles. So this situation can't + * happen for user classes. + * + */ + private def coreClassesFirst(files: List[SourceFile]) = { def inScalaFolder(f: SourceFile) = f.file.container.name == "scala" + var scalaObject: Option[SourceFile] = None val res = new ListBuffer[SourceFile] for (file <- files) file.file.name match { - case "ScalaObject.scala" if inScalaFolder(file) => file +=: res + case "ScalaObject.scala" if inScalaFolder(file) => scalaObject = Some(file) + case "LowPriorityImplicits.scala" if inScalaFolder(file) => file +=: res + case "StandardEmbeddings.scala" if inScalaFolder(file) => file +=: res case _ => res += file } + for (so <- scalaObject) so +=: res res.toList } } // class Run diff --git a/src/compiler/scala/tools/nsc/Interpreter.scala b/src/compiler/scala/tools/nsc/Interpreter.scala index 0cc0f65640..556a3107cd 100644 --- a/src/compiler/scala/tools/nsc/Interpreter.scala +++ b/src/compiler/scala/tools/nsc/Interpreter.scala @@ -27,7 +27,7 @@ import scala.util.control.Exception.{ Catcher, catching, ultimately, unwrapping import io.{ PlainFile, VirtualDirectory } import reporters.{ ConsoleReporter, Reporter } import symtab.{ Flags, Names } -import util.{ SourceFile, BatchSourceFile, ClassPath } +import util.{ SourceFile, BatchSourceFile, ClassPath, Chars } import scala.reflect.NameTransformer import scala.tools.nsc.{ InterpreterResults => IR } import interpreter._ @@ -74,6 +74,8 @@ import Interpreter._ * @author Lex Spoon */ class Interpreter(val settings: Settings, out: PrintWriter) { + repl => + /** construct an interpreter that reports to Console */ def this(settings: Settings) = this(settings, new NewLinePrintWriter(new ConsoleWriter, true)) def this() = this(new Settings()) @@ -236,6 +238,12 @@ class Interpreter(val settings: Settings, out: PrintWriter) { private val boundNameMap = new HashMap[Name, Request]() private def allHandlers = prevRequests.toList flatMap (_.handlers) + def printAllTypeOf = { + prevRequests foreach { req => + req.typeOf foreach { case (k, v) => Console.println(k + " => " + v) } + } + } + /** Most recent tree handled which wasn't wholly synthetic. */ private def mostRecentlyHandledTree: Option[Tree] = { for { @@ -487,42 +495,6 @@ class Interpreter(val settings: Settings, out: PrintWriter) { } } - /** For :power - create trees and type aliases from code snippets. */ - def mkContext(code: String = "") = compiler.analyzer.rootContext(mkUnit(code)) - def mkAlias(name: String, what: String) = interpret("type %s = %s".format(name, what)) - def mkSourceFile(code: String) = new BatchSourceFile("<console>", code) - def mkUnit(code: String) = new CompilationUnit(mkSourceFile(code)) - - def mkTree(code: String): Tree = mkTrees(code).headOption getOrElse EmptyTree - def mkTrees(code: String): List[Tree] = parse(code) getOrElse Nil - def mkTypedTrees(code: String*): List[compiler.Tree] = { - class TyperRun extends compiler.Run { - override def stopPhase(name: String) = name == "superaccessors" - } - - reporter.reset - val run = new TyperRun - run compileSources (code.toList.zipWithIndex map { - case (s, i) => new BatchSourceFile("<console %d>".format(i), s) - }) - run.units.toList map (_.body) - } - def mkTypedTree(code: String) = mkTypedTrees(code).head - - def mkType(id: String): compiler.Type = { - // if it's a recognized identifier, the type of that; otherwise treat the - // String like it is itself a type (e.g. scala.collection.Map) . - val typeName = typeForIdent(id) getOrElse id - - try definitions.getClass(newTermName(typeName)).tpe - catch { case _: Throwable => NoType } - } - - private[nsc] val powerMkImports = List( - "mkContext", "mkTree", "mkTrees", "mkAlias", "mkSourceFile", "mkUnit", "mkType", "mkTypedTree", "mkTypedTrees" - // , "treeWrapper" - ) - /** Compile an nsc SourceFile. Returns true if there are * no compilation errors, or false otherwise. */ @@ -796,16 +768,32 @@ class Interpreter(val settings: Settings, out: PrintWriter) { } private class ImportHandler(imp: Import) extends MemberHandler(imp) { + lazy val Import(expr, selectors) = imp + def targetType = stringToCompilerType(expr.toString) match { + case NoType => None + case x => Some(x) + } + + private def selectorWild = selectors filter (_.name == USCOREkw) // wildcard imports, e.g. import foo._ + private def selectorMasked = selectors filter (_.rename == USCOREkw) // masking imports, e.g. import foo.{ bar => _ } + private def selectorNames = selectors map (_.name) + private def selectorRenames = selectors map (_.rename) filterNot (_ == null) + /** Whether this import includes a wildcard import */ - val importsWildcard = imp.selectors map (_.name) contains USCOREkw + val importsWildcard = selectorWild.nonEmpty + + /** 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 */ - val importedNames: List[Name] = ( - imp.selectors - . map (x => x.rename) - . filter (x => x != null && x != USCOREkw) - . flatMap (x => List(x.toTypeName, x.toTermName)) - ) + /** XXX come back to this and see what can be done with wildcards now that + * we know how to enumerate the identifiers. + */ + val importedNames: List[Name] = + selectorRenames filterNot (_ == USCOREkw) flatMap (x => List(x.toTypeName, x.toTermName)) override def resultExtractionCode(req: Request, code: PrintWriter) = code println codegenln(imp.toString) @@ -830,9 +818,10 @@ class Interpreter(val settings: Settings, out: PrintWriter) { /** def and val names */ def defNames = partialFlatMap(handlers) { case x: DefHandler => x.boundNames } - def valAndVarNames = partialFlatMap(handlers) { + def valueNames = partialFlatMap(handlers) { case x: AssignHandler => List(x.helperName) case x: ValHandler => boundNames + case x: ModuleHandler => List(x.name) } /** Code to import bound names from previous lines - accessPath is code to @@ -940,7 +929,6 @@ class Interpreter(val settings: Settings, out: PrintWriter) { !reporter.hasErrors } - def atNextPhase[T](op: => T): T = compiler.atPhase(objRun.typerPhase.next)(op) /** The outermost wrapper object */ @@ -972,7 +960,7 @@ class Interpreter(val settings: Settings, out: PrintWriter) { } } - getTypes(valAndVarNames, nme.getterToLocal(_)) ++ getTypes(defNames, identity) + getTypes(valueNames, nme.getterToLocal(_)) ++ getTypes(defNames, identity) } /** load and run the code using reflection */ @@ -999,43 +987,64 @@ class Interpreter(val settings: Settings, out: PrintWriter) { } } - /** These methods are exposed so REPL commands can access them. - * The command infrastructure is in InterpreterLoop. + /** A container class for methods to be injected into the repl + * in power mode. */ - def dumpState(xs: List[String]): String = """ - | Names used: %s - | - | Identifiers: %s - | - | synthvars: %d - """.stripMargin.format( - allUsedNames mkString " ", - unqualifiedIds mkString " ", - allBoundNames filter isSynthVarName size - ) - - // def dumpTrees(xs: List[String]): String = { - // val treestrs = (xs map requestForIdent).flatten flatMap (_.trees) - // - // if (treestrs.isEmpty) "No trees found." - // else treestrs.map(t => t.toString + " (" + t.getClass.getSimpleName + ")\n").mkString - // } + object power { + lazy val compiler: repl.compiler.type = repl.compiler + import compiler.{ phaseNames, atPhase, currentRun } + + def mkContext(code: String = "") = compiler.analyzer.rootContext(mkUnit(code)) + def mkAlias(name: String, what: String) = interpret("type %s = %s".format(name, what)) + def mkSourceFile(code: String) = new BatchSourceFile("<console>", code) + def mkUnit(code: String) = new CompilationUnit(mkSourceFile(code)) + + def mkTree(code: String): Tree = mkTrees(code).headOption getOrElse EmptyTree + def mkTrees(code: String): List[Tree] = parse(code) getOrElse Nil + def mkTypedTrees(code: String*): List[compiler.Tree] = { + class TyperRun extends compiler.Run { + override def stopPhase(name: String) = name == "superaccessors" + } - def powerUser(): String = { - beQuietDuring { - this.bind("repl", "scala.tools.nsc.Interpreter", this) - this.bind("global", "scala.tools.nsc.Global", compiler) - interpret("import repl.{ %s, eval }".format(powerMkImports mkString ", "), false) + reporter.reset + val run = new TyperRun + run compileSources (code.toList.zipWithIndex map { + case (s, i) => new BatchSourceFile("<console %d>".format(i), s) + }) + run.units.toList map (_.body) } + def mkTypedTree(code: String) = mkTypedTrees(code).head + def mkType(id: String): compiler.Type = stringToCompilerType(id) + + def dump(): String = ( + ("Names used: " :: allUsedNames) ++ + ("\nIdentifiers: " :: unqualifiedIds) + ) mkString " " + + lazy val allPhases: List[Phase] = phaseNames map (currentRun phaseNamed _) + def atAllPhases[T](op: => T): List[(String, T)] = allPhases map (ph => (ph.name, atPhase(ph)(op))) + def showAtAllPhases(op: => Any): Unit = + atAllPhases(op.toString) foreach { case (ph, op) => Console.println("%15s -> %s".format(ph, op take 240)) } + } - """** Power User mode enabled - BEEP BOOP ** - |** New vals! Try repl, global ** - |** New cmds! :help to discover them ** - |** New defs! Give these a whirl: ** - |** mkAlias("Fn", "(String, Int) => Int") ** - |** mkTree("def f(x: Int, y: Int) = x+y") **""".stripMargin + def unleash(): Unit = beQuietDuring { + interpret("import scala.tools.nsc._") + repl.bind("repl", "scala.tools.nsc.Interpreter", this) + interpret("val global: repl.compiler.type = repl.compiler") + interpret("val power: repl.power.type = repl.power") + // interpret("val replVars = repl.replVars") } + /** Artificial object demonstrating completion */ + // lazy val replVars = CompletionAware( + // Map[String, CompletionAware]( + // "ids" -> CompletionAware(() => unqualifiedIds, completionAware _), + // "synthVars" -> CompletionAware(() => allBoundNames filter isSynthVarName map (_.toString)), + // "types" -> CompletionAware(() => allSeenTypes map (_.toString)), + // "implicits" -> CompletionAware(() => allImplicits map (_.toString)) + // ) + // ) + /** Returns the name of the most recent interpreter result. * Mostly this exists so you can conveniently invoke methods on * the previous result. @@ -1052,11 +1061,21 @@ class Interpreter(val settings: Settings, out: PrintWriter) { private def requestForName(name: Name): Option[Request] = prevRequests.reverse find (_.boundNames contains name) - private def requestForIdent(line: String): Option[Request] = - requestForName(newTermName(line)) + private def requestForIdent(line: String): Option[Request] = requestForName(newTermName(line)) + + def stringToCompilerType(id: String): compiler.Type = { + // if it's a recognized identifier, the type of that; otherwise treat the + // String like a value (e.g. scala.collection.Map) . + def findType = typeForIdent(id) match { + case Some(x) => definitions.getClass(newTermName(x)).tpe + case _ => definitions.getModule(newTermName(id)).tpe + } + + try findType catch { case _: MissingRequirementError => NoType } + } def typeForIdent(id: String): Option[String] = - requestForIdent(id) map (_ typeOf newTermName(id)) + requestForIdent(id) flatMap (x => x.typeOf get newTermName(id)) def methodsOf(name: String) = evalExpr[List[String]](methodsCode(name)) map (x => NameTransformer.decode(getOriginalName(x))) @@ -1160,6 +1179,22 @@ class Interpreter(val settings: Settings, out: PrintWriter) { case x: ImportHandler => x.importedNames } filterNot isSynthVarName + /** Types which have been wildcard imported, such as: + * val x = "abc" ; import x._ // type java.lang.String + * import java.lang.String._ // object java.lang.String + * + * Used by tab completion. + * + * XXX right now this gets import x._ and import java.lang.String._, + * but doesn't figure out import String._. There's a lot of ad hoc + * scope twiddling which should be swept away in favor of digging + * into the compiler scopes. + */ + def wildcardImportedTypes(): List[Type] = { + val xs = allHandlers collect { case x: ImportHandler if x.importsWildcard => x.targetType } + xs.flatten.reverse.distinct + } + /** Another entry point for tab-completion, ids in scope */ def unqualifiedIds() = (unqualifiedIdNames() map (_.toString)).distinct.sorted @@ -1169,16 +1204,6 @@ class Interpreter(val settings: Settings, out: PrintWriter) { /** Parse the ScalaSig to find type aliases */ def aliasForType(path: String) = ByteCode.aliasForType(path) - /** Artificial object demonstrating completion */ - def replVarsObject() = CompletionAware( - Map[String, CompletionAware]( - "ids" -> CompletionAware(() => unqualifiedIds, completionAware _), - "synthVars" -> CompletionAware(() => allBoundNames filter isSynthVarName map (_.toString)), - "types" -> CompletionAware(() => allSeenTypes map (_.toString)), - "implicits" -> CompletionAware(() => allImplicits map (_.toString)) - ) - ) - // Coming soon // implicit def string2liftedcode(s: String): LiftedCode = new LiftedCode(s) // case class LiftedCode(code: String) { @@ -1191,6 +1216,7 @@ class Interpreter(val settings: Settings, out: PrintWriter) { // debugging def isReplDebug = settings.Yrepldebug.value + def isCompletionDebug = settings.Ycompletion.value def DBG(s: String) = if (isReplDebug) out println s else () } @@ -1275,21 +1301,10 @@ object Interpreter { * This requires replacing all special characters by escape * codes. It does not add the surrounding " marks. */ def string2code(str: String): String = { - /** Convert a character to a backslash-u escape */ - def char2uescape(c: Char): String = { - var rest = c.toInt - val buf = new StringBuilder - for (i <- 1 to 4) { - buf ++= (rest % 16).toHexString - rest = rest / 16 - } - "\\u" + buf.toString.reverse - } - val res = new StringBuilder for (c <- str) c match { case '"' | '\'' | '\\' => res += '\\' ; res += c - case _ if c.isControl => res ++= char2uescape(c) + case _ if c.isControl => res ++= Chars.char2uescape(c) case _ => res += c } res.toString diff --git a/src/compiler/scala/tools/nsc/InterpreterLoop.scala b/src/compiler/scala/tools/nsc/InterpreterLoop.scala index 9d568418f8..4e8a04de44 100644 --- a/src/compiler/scala/tools/nsc/InterpreterLoop.scala +++ b/src/compiler/scala/tools/nsc/InterpreterLoop.scala @@ -211,15 +211,12 @@ class InterpreterLoop(in0: Option[BufferedReader], protected val out: PrintWrite } /** Power user commands */ - // XXX - why does a third argument like "interpreter dumpState(_)" throw an NPE - // while the version below works? var powerUserOn = false val powerCommands: List[Command] = { import CommandImplicits._ List( - VarArgs("dump", "displays a view of the interpreter's internal state", - (xs: List[String]) => interpreter dumpState xs), - OneArg("search", "search the classpath for classes matching regex", search) + OneArg("completions", "generate list of completions for a given String", completions), + NoArgs("dump", "displays a view of the interpreter's internal state", () => interpreter.power.dump()) // VarArgs("tree", "displays ASTs for specified identifiers", // (xs: List[String]) => interpreter dumpTrees xs) @@ -331,45 +328,26 @@ class InterpreterLoop(in0: Option[BufferedReader], protected val out: PrintWrite else out.println("The path '" + f + "' doesn't seem to exist.") } - /** This isn't going to win any efficiency awards, but it's only - * available in power mode so I'm unconcerned for the moment. - */ - def search(arg: String) { - val MAX_RESULTS = 40 - if (in.completion.isEmpty) return println("No classpath data available") - val comp = in.completion.get - - import java.util.regex.PatternSyntaxException - import comp.pkgs.agent._ - import scala.collection.JavaConversions._ - - try { - val regex = arg.r - val matches = ( - for ((k, vs) <- dottedPaths) yield { - val pkgs = if (regex findFirstMatchIn k isDefined) List("package " + k) else Nil - val classes = vs filter (regex findFirstMatchIn _.visibleName isDefined) map (" class " + k + "." + _.visibleName) - - pkgs ::: classes - } - ).flatten + def completions(arg: String): Unit = { + val comp = in.completion getOrElse { return println("Completion unavailable.") } + val xs = comp completions arg - matches take MAX_RESULTS foreach println - } - catch { - case _: PatternSyntaxException => - return println("Invalid regular expression: you must use java.util.regex.Pattern syntax.") - } + injectAndName(xs) } def power() { - powerUserOn = true - out println interpreter.powerUser() - if (in.history.isDefined) - interpreter.quietBind("history", "scala.collection.immutable.List[String]", in.historyList) + val powerUserBanner = + """** Power User mode enabled - BEEP BOOP ** + |** scala.tools.nsc._ has been imported ** + |** New vals! Try repl, global, power ** + |** New cmds! :help to discover them ** + |** New defs! Type power.<tab> to reveal **""".stripMargin - if (in.completion.isDefined) - interpreter.quietBind("replHelper", "scala.tools.nsc.interpreter.CompletionAware", interpreter.replVarsObject()) + powerUserOn = true + interpreter.unleash() + injectOne("history", in.historyList) + in.completion foreach (x => injectOne("completion", x)) + out println powerUserBanner } def verbosity() = { @@ -496,6 +474,9 @@ class InterpreterLoop(in0: Option[BufferedReader], protected val out: PrintWrite * to be recorded for replay, if any. */ def interpretStartingWith(code: String): Option[String] = { + // signal completion non-completion input has been received + in.completion foreach (_.resetVerbosity()) + def reallyInterpret = { interpreter.interpret(code) match { case IR.Error => None @@ -527,7 +508,7 @@ class InterpreterLoop(in0: Option[BufferedReader], protected val out: PrintWrite interpretAsPastedTranscript(List(code)) None } - else if (Completion.looksLikeInvocation(code)) { + else if (Completion.looksLikeInvocation(code) && interpreter.mostRecentVar != "") { interpretStartingWith(interpreter.mostRecentVar + code) } else { diff --git a/src/compiler/scala/tools/nsc/ast/TreeInfo.scala b/src/compiler/scala/tools/nsc/ast/TreeInfo.scala index 2e543a0960..430967298c 100644 --- a/src/compiler/scala/tools/nsc/ast/TreeInfo.scala +++ b/src/compiler/scala/tools/nsc/ast/TreeInfo.scala @@ -102,17 +102,20 @@ abstract class TreeInfo { case _ => false } - def isVariableOrGetter(tree: Tree) = tree match { - case Ident(_) => - tree.symbol.isVariable - case Select(qual, _) => - tree.symbol.isVariable || - (mayBeVarGetter(tree.symbol) && - tree.symbol.owner.info.member(nme.getterToSetter(tree.symbol.name)) != NoSymbol) - case Apply(Select(qual, nme.apply), _) => - qual.tpe.member(nme.update) != NoSymbol - case _ => - false + def isVariableOrGetter(tree: Tree) = { + def sym = tree.symbol + def isVar = sym.isVariable + def isGetter = mayBeVarGetter(sym) && sym.owner.info.member(nme.getterToSetter(sym.name)) != NoSymbol + + tree match { + case Ident(_) => isVar + case Select(_, _) => isVar || isGetter + case _ => + methPart(tree) match { + case Select(qual, nme.apply) => qual.tpe.member(nme.update) != NoSymbol + case _ => false + } + } } /** Is tree a self constructor call? @@ -295,10 +298,10 @@ abstract class TreeInfo { /** The method part of an application node */ def methPart(tree: Tree): Tree = tree match { - case Apply(fn, _) => methPart(fn) - case TypeApply(fn, _) => methPart(fn) + case Apply(fn, _) => methPart(fn) + case TypeApply(fn, _) => methPart(fn) case AppliedTypeTree(fn, _) => methPart(fn) - case _ => tree + case _ => tree } def firstArgument(tree: Tree): Tree = tree match { @@ -325,18 +328,19 @@ abstract class TreeInfo { false } - /** Compilation unit is the predef object + /** Compilation unit is class or object 'name' in package 'scala' */ def isUnitInScala(tree: Tree, name: Name) = tree match { - case PackageDef(Ident(nme.scala_), defs) => isObject(defs, name) + case PackageDef(Ident(nme.scala_), defs) => isImplDef(defs, name) case _ => false } - private def isObject(trees: List[Tree], name: Name): Boolean = trees match { - case Import(_, _) :: xs => isObject(xs, name) - case DocDef(_, tree1) :: Nil => isObject(List(tree1), name) - case Annotated(_, tree1) :: Nil => isObject(List(tree1), name) + private def isImplDef(trees: List[Tree], name: Name): Boolean = trees match { + case Import(_, _) :: xs => isImplDef(xs, name) + case DocDef(_, tree1) :: Nil => isImplDef(List(tree1), name) + case Annotated(_, tree1) :: Nil => isImplDef(List(tree1), name) case ModuleDef(_, `name`, _) :: Nil => true + case ClassDef(_, `name`, _, _) :: Nil => true case _ => false } diff --git a/src/compiler/scala/tools/nsc/ast/Trees.scala b/src/compiler/scala/tools/nsc/ast/Trees.scala index 3640b6825b..b40f286680 100644 --- a/src/compiler/scala/tools/nsc/ast/Trees.scala +++ b/src/compiler/scala/tools/nsc/ast/Trees.scala @@ -221,9 +221,8 @@ trait Trees extends reflect.generic.Trees { self: SymbolTable => var vparamss1 = vparamss map (vps => vps.map { vd => atPos(vd.pos.focus) { - val pa = if (vd.hasFlag(PRIVATE | LOCAL)) 0L else PARAMACCESSOR ValDef( - Modifiers(vd.mods.flags & (IMPLICIT | DEFAULTPARAM | BYNAMEPARAM) | PARAM | pa) withAnnotations vd.mods.annotations, + Modifiers(vd.mods.flags & (IMPLICIT | DEFAULTPARAM | BYNAMEPARAM) | PARAM | PARAMACCESSOR) withAnnotations vd.mods.annotations, vd.name, vd.tpt.duplicate, vd.rhs.duplicate) }}) val (edefs, rest) = body span treeInfo.isEarlyDef @@ -260,7 +259,7 @@ trait Trees extends reflect.generic.Trees { self: SymbolTable => } // println("typed template, gvdefs = "+gvdefs+", parents = "+parents+", constrs = "+constrs) constrs foreach (ensureNonOverlapping(_, parents ::: gvdefs)) - // remove defaults + // vparamss2 are used as field definitions for the class. remove defaults val vparamss2 = vparamss map (vps => vps map { vd => treeCopy.ValDef(vd, vd.mods &~ DEFAULTPARAM, vd.name, vd.tpt, EmptyTree) }) diff --git a/src/compiler/scala/tools/nsc/ast/parser/Parsers.scala b/src/compiler/scala/tools/nsc/ast/parser/Parsers.scala index 1cfee481bc..497cfc398b 100644 --- a/src/compiler/scala/tools/nsc/ast/parser/Parsers.scala +++ b/src/compiler/scala/tools/nsc/ast/parser/Parsers.scala @@ -2630,7 +2630,8 @@ self => } else if (isStatSep) { in.nextToken() } else { - syntaxErrorOrIncomplete("illegal start of statement", true) + val addendum = if (isModifier) " (no modifiers allowed here)" else "" + syntaxErrorOrIncomplete("illegal start of statement" + addendum, true) } } stats.toList diff --git a/src/compiler/scala/tools/nsc/ast/parser/SyntaxAnalyzer.scala b/src/compiler/scala/tools/nsc/ast/parser/SyntaxAnalyzer.scala index 87f2641f4f..fb97587ec4 100644 --- a/src/compiler/scala/tools/nsc/ast/parser/SyntaxAnalyzer.scala +++ b/src/compiler/scala/tools/nsc/ast/parser/SyntaxAnalyzer.scala @@ -19,6 +19,8 @@ abstract class SyntaxAnalyzer extends SubComponent with Parsers with MarkupParse class ParserPhase(prev: scala.tools.nsc.Phase) extends StdPhase(prev) { override val checkable = false + override val keepsTypeParams = false + def apply(unit: global.CompilationUnit) { global.informProgress("parsing " + unit) unit.body = diff --git a/src/compiler/scala/tools/nsc/backend/jvm/GenJVM.scala b/src/compiler/scala/tools/nsc/backend/jvm/GenJVM.scala index 5318c76311..dc193b03db 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/GenJVM.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/GenJVM.scala @@ -851,7 +851,7 @@ abstract class GenJVM extends SubComponent { def addForwarders(jclass: JClass, module: Symbol) { addForwarders(jclass, module, _ => true) } def addForwarders(jclass: JClass, module: Symbol, cond: (Symbol) => Boolean) { def conflictsIn(cls: Symbol, name: Name) = - cls.info.nonPrivateMembers.exists(_.name == name) + cls.info.members exists (_.name == name) /** List of parents shared by both class and module, so we don't add forwarders * for methods defined there - bug #1804 */ @@ -874,7 +874,7 @@ abstract class GenJVM extends SubComponent { atPhase(currentRun.picklerPhase) ( m.owner != definitions.ObjectClass && m.isMethod - && !m.hasFlag(Flags.CASE | Flags.PROTECTED | Flags.DEFERRED | Flags.SPECIALIZED) + && !m.hasFlag(Flags.CASE | Flags.PRIVATE | Flags.PROTECTED | Flags.DEFERRED | Flags.SPECIALIZED) && !m.isConstructor && !m.isStaticMember && !(m.owner == definitions.AnyClass) diff --git a/src/compiler/scala/tools/nsc/backend/msil/GenMSIL.scala b/src/compiler/scala/tools/nsc/backend/msil/GenMSIL.scala index 285e09295d..554dcd4e6d 100644 --- a/src/compiler/scala/tools/nsc/backend/msil/GenMSIL.scala +++ b/src/compiler/scala/tools/nsc/backend/msil/GenMSIL.scala @@ -1053,7 +1053,7 @@ abstract class GenMSIL extends SubComponent { } var doEmit = true - types.get(msym.owner) match { + getTypeOpt(msym.owner) match { case Some(typ) if (typ.IsEnum) => { def negBool = { mcode.Emit(OpCodes.Ldc_I4_0) @@ -1577,9 +1577,9 @@ abstract class GenMSIL extends SubComponent { mf = mf | FieldAttributes.Static else { mf = mf | MethodAttributes.Virtual - if (sym.isFinal && !types(sym.owner).IsInterface) + if (sym.isFinal && !getType(sym.owner).IsInterface) mf = mf | MethodAttributes.Final - if (sym.hasFlag(Flags.DEFERRED) || types(sym.owner).IsInterface) + if (sym.hasFlag(Flags.DEFERRED) || getType(sym.owner).IsInterface) mf = mf | MethodAttributes.Abstract } } @@ -1679,8 +1679,14 @@ abstract class GenMSIL extends SubComponent { sym.tpe.paramTypes.map(msilType).toArray } - def getType(sym: Symbol): MsilType = types.get(sym) match { - case Some(typ) => typ + def getType(sym: Symbol) = getTypeOpt(sym).getOrElse(abort(showsym(sym))) + + /** + * Get an MSIL type form a symbol. First look in the clrTypes.types map, then + * lookup the name using clrTypes.getType + */ + def getTypeOpt(sym: Symbol): Option[MsilType] = types.get(sym) match { + case typ @ Some(_) => typ case None => def typeString(sym: Symbol): String = { val s = if (sym.isNestedClass) typeString(sym.owner) +"+"+ sym.simpleName @@ -1690,10 +1696,10 @@ abstract class GenMSIL extends SubComponent { val name = typeString(sym) val typ = clrTypes.getType(name) if (typ == null) - abort(showsym(sym) + " with name " + name) + None else { - clrTypes.types(sym) = typ - typ + types(sym) = typ + Some(typ) } } @@ -1703,10 +1709,20 @@ abstract class GenMSIL extends SubComponent { } def createTypeBuilder(iclass: IClass) { + /** + * First look in the clrTypes.types map, then see if it's a class we're + * currently compiling by looking at the icodes.classes map, then finally + * lookup the name using clrTypes.getType (by calling getType). + */ def msilTypeFromSym(sym: Symbol): MsilType = { - types.get(sym) match { - case Some(mtype) => mtype - case None => createTypeBuilder(classes(sym)); types(sym) + types.get(sym).getOrElse { + classes.get(sym) match { + case Some(iclass) => + createTypeBuilder(iclass) + types (sym) + case None => + getType(sym) + } } } diff --git a/src/compiler/scala/tools/nsc/dependencies/DependencyAnalysis.scala b/src/compiler/scala/tools/nsc/dependencies/DependencyAnalysis.scala index 4c7b1977bb..aa784e9c87 100644 --- a/src/compiler/scala/tools/nsc/dependencies/DependencyAnalysis.scala +++ b/src/compiler/scala/tools/nsc/dependencies/DependencyAnalysis.scala @@ -1,6 +1,6 @@ package scala.tools.nsc -package dependencies; -import util.SourceFile; +package dependencies +import util.SourceFile import io.AbstractFile import collection._ import symtab.Flags @@ -8,11 +8,11 @@ import symtab.Flags trait DependencyAnalysis extends SubComponent with Files { import global._ - val phaseName = "dependencyAnalysis"; + val phaseName = "dependencyAnalysis" def off = settings.make.value == "all" - def newPhase(prev : Phase) = new AnalysisPhase(prev) + def newPhase(prev: Phase) = new AnalysisPhase(prev) lazy val maxDepth = settings.make.value match { case "changed" => 0 @@ -25,7 +25,7 @@ trait DependencyAnalysis extends SubComponent with Files { // todo: order insensible checking and, also checking timestamp? def validateClasspath(cp1: String, cp2: String): Boolean = cp1 == cp2 - def nameToFile(src: AbstractFile, name : String) = + def nameToFile(src: AbstractFile, name: String) = settings.outputDirs.outputDirFor(src) .lookupPathUnchecked(name.toString.replace(".", java.io.File.separator) + ".class", false) @@ -39,7 +39,7 @@ trait DependencyAnalysis extends SubComponent with Files { def dependenciesFile: Option[AbstractFile] = depFile def classpath = settings.classpath.value - def newDeps = new FileDependencies(classpath); + def newDeps = new FileDependencies(classpath) var dependencies = newDeps @@ -48,19 +48,19 @@ trait DependencyAnalysis extends SubComponent with Files { /** Top level definitions per source file. */ val definitions: mutable.Map[AbstractFile, List[Symbol]] = new mutable.HashMap[AbstractFile, List[Symbol]] { - override def default(f : AbstractFile) = Nil + override def default(f: AbstractFile) = Nil } /** External references used by source file. */ val references: mutable.Map[AbstractFile, immutable.Set[String]] = new mutable.HashMap[AbstractFile, immutable.Set[String]] { - override def default(f : AbstractFile) = immutable.Set() + override def default(f: AbstractFile) = immutable.Set() } /** External references for inherited members used in the source file */ val inherited: mutable.Map[AbstractFile, immutable.Set[Inherited]] = new mutable.HashMap[AbstractFile, immutable.Set[Inherited]] { - override def default(f : AbstractFile) = immutable.Set() + override def default(f: AbstractFile) = immutable.Set() } /** Write dependencies to the current file. */ @@ -71,15 +71,14 @@ trait DependencyAnalysis extends SubComponent with Files { /** Load dependencies from the given file and save the file reference for * future saves. */ - def loadFrom(f: AbstractFile, toFile: String => AbstractFile) : Boolean = { + def loadFrom(f: AbstractFile, toFile: String => AbstractFile): Boolean = { dependenciesFile = f FileDependencies.readFrom(f, toFile) match { case Some(fd) => val success = if (shouldCheckClasspath) validateClasspath(fd.classpath, classpath) else true dependencies = if (success) fd else { - if (settings.debug.value) { - println("Classpath has changed. Nuking dependencies"); - } + if (settings.debug.value) + println("Classpath has changed. Nuking dependencies") newDeps } @@ -88,15 +87,13 @@ trait DependencyAnalysis extends SubComponent with Files { } } - def filter(files : List[SourceFile]) : List[SourceFile] = + def filter(files: List[SourceFile]): List[SourceFile] = if (off) files - else if (dependencies.isEmpty){ - if(settings.debug.value){ - println("No known dependencies. Compiling everything"); - } + else if (dependencies.isEmpty) { + println("No known dependencies. Compiling " + + (if (settings.debug.value) files.mkString(", ") else "everything")) files - } - else { + } else { val (direct, indirect) = dependencies.invalidatedFiles(maxDepth); val filtered = files.filter(x => { val f = x.file.absolute @@ -105,8 +102,7 @@ trait DependencyAnalysis extends SubComponent with Files { filtered match { case Nil => println("No changes to recompile"); case x => println("Recompiling " + ( - if(settings.debug.value) x.mkString(", ") - else x.length + " files") + if(settings.debug.value) x.mkString(", ") else x.length + " files") ) } filtered @@ -114,13 +110,13 @@ trait DependencyAnalysis extends SubComponent with Files { case class Inherited(qualifier: String, member: Name) - class AnalysisPhase(prev : Phase) extends StdPhase(prev){ + class AnalysisPhase(prev: Phase) extends StdPhase(prev) { override def cancelled(unit: CompilationUnit) = super.cancelled(unit) && !unit.isJava def apply(unit : global.CompilationUnit) { - val f = unit.source.file.file; + val f = unit.source.file.file // When we're passed strings by the interpreter // they have no source file. We simply ignore this case // as irrelevant to dependency analysis. @@ -145,7 +141,7 @@ trait DependencyAnalysis extends SubComponent with Files { dependencies.reset(source) for (d <- unit.depends; if (d.sourceFile != null)){ - dependencies.depends(source, d.sourceFile); + dependencies.depends(source, d.sourceFile) } } diff --git a/src/compiler/scala/tools/nsc/doc/DocFactory.scala b/src/compiler/scala/tools/nsc/doc/DocFactory.scala index ca72f6581b..7fd6538566 100644 --- a/src/compiler/scala/tools/nsc/doc/DocFactory.scala +++ b/src/compiler/scala/tools/nsc/doc/DocFactory.scala @@ -52,7 +52,7 @@ class DocFactory(val reporter: Reporter, val settings: doc.Settings) { processor compiler.addSourceless assert(settings.docformat.value == "html") if (!reporter.hasErrors) { - val modelFactory = (new model.ModelFactory(compiler, settings)) + val modelFactory = (new model.ModelFactory(compiler, settings) with model.comment.CommentFactory) val docModel = modelFactory.makeModel println("model contains " + modelFactory.templatesCount + " documentable templates") (new html.HtmlFactory(docModel)) generate docModel diff --git a/src/compiler/scala/tools/nsc/doc/Settings.scala b/src/compiler/scala/tools/nsc/doc/Settings.scala index 3d02689605..4897d78488 100644 --- a/src/compiler/scala/tools/nsc/doc/Settings.scala +++ b/src/compiler/scala/tools/nsc/doc/Settings.scala @@ -32,4 +32,6 @@ class Settings(error: String => Unit) extends scala.tools.nsc.Settings(error) { // working around issue described in r18708. suppressVTWarn.value = true + + // TODO: add a new setting for whether or not to document sourceless entities (e.g., Any, Unit, etc) } diff --git a/src/compiler/scala/tools/nsc/doc/html/HtmlPage.scala b/src/compiler/scala/tools/nsc/doc/html/HtmlPage.scala index ae98ecf1f6..31b932ac53 100644 --- a/src/compiler/scala/tools/nsc/doc/html/HtmlPage.scala +++ b/src/compiler/scala/tools/nsc/doc/html/HtmlPage.scala @@ -215,4 +215,10 @@ abstract class HtmlPage { thisPage => case tpl :: tpls => templateToHtml(tpl) ++ sep ++ templatesToHtml(tpls, sep) } + def docEntityKindToString(ety: DocTemplateEntity) = + if (ety.isTrait) "trait" + else if (ety.isClass) "class" + else if (ety.isObject) "object" + else if (ety.isPackage) "package" + else "class" // FIXME: an entity *should* fall into one of the above categories, but AnyRef is somehow not } diff --git a/src/compiler/scala/tools/nsc/doc/html/page/Index.scala b/src/compiler/scala/tools/nsc/doc/html/page/Index.scala index 8f1d537c43..784a92f1ff 100644 --- a/src/compiler/scala/tools/nsc/doc/html/page/Index.scala +++ b/src/compiler/scala/tools/nsc/doc/html/page/Index.scala @@ -63,18 +63,41 @@ class Index(universe: Universe) extends HtmlPage { <ol class="templates">{ val tpls: Map[String, Seq[DocTemplateEntity]] = (pack.templates filter (t => !t.isPackage && !isExcluded(t) )) groupBy (_.name) + + val placeholderSeq: NodeSeq = <div class="placeholder"></div> + + def createLink(entity: DocTemplateEntity, includePlaceholder: Boolean, includeText: Boolean) = { + val entityType = docEntityKindToString(entity) + val linkContent = ( + { if (includePlaceholder) placeholderSeq else NodeSeq.Empty } + ++ + { if (includeText) <span class="tplLink">{ Text(packageQualifiedName(entity)) }</span> else NodeSeq.Empty } + ) + <a class="tplshow" href={ relativeLinkTo(entity) }><span class={ entityType }>({ Text(entityType) })</span>{ linkContent }</a> + } + for (tn <- tpls.keySet.toSeq sortBy (_.toLowerCase)) yield { - val entries = tpls(tn) sortWith { (less, more) => less.isTrait || more.isObject } - def doEntry(ety: DocTemplateEntity, firstEty: Boolean): NodeSeq = { - val etyTpe = - if (ety.isTrait) "trait" else if (ety.isClass) "class" else if (ety.isObject) "object" else "package" - <a class="tplshow" href={ relativeLinkTo(ety) }> - { if (firstEty) Text(packageQualifiedName(ety)) else NodeSeq.Empty } - <span class={ etyTpe }>({ Text(etyTpe) })</span> - </a> - } - <li title={ entries.head.qualifiedName }>{ - doEntry(entries.head, true) ++ (entries.tail map (doEntry(_, false))) + val entities = tpls(tn) + val row = (entities find (e => e.isPackage || e.isObject), entities find (e => e.isTrait || e.isClass)) + + val itemContents = row match { + case (Some(obj), None) => createLink(obj, includePlaceholder = true, includeText = true) + + case (maybeObj, Some(template)) => + val firstLink = maybeObj match { + case Some(obj) => createLink(obj, includePlaceholder = false, includeText = false) + case None => placeholderSeq + } + + firstLink ++ createLink(template, includePlaceholder = false, includeText = true) + + case _ => // FIXME: this default case should not be necessary. For some reason AnyRef is not a package, object, trait, or class + val entry = entities.head + placeholderSeq ++ createLink(entry, includePlaceholder = false, includeText = true) + } + + <li title={ entities.head.qualifiedName }>{ + itemContents }</li> } }</ol> diff --git a/src/compiler/scala/tools/nsc/doc/html/page/Template.scala b/src/compiler/scala/tools/nsc/doc/html/page/Template.scala index 41a7de93bd..12d24a7953 100644 --- a/src/compiler/scala/tools/nsc/doc/html/page/Template.scala +++ b/src/compiler/scala/tools/nsc/doc/html/page/Template.scala @@ -60,13 +60,13 @@ class Template(tpl: DocTemplateEntity) extends HtmlPage { <div id="mbrsel"> { if (tpl.linearization.isEmpty) NodeSeq.Empty else <div id="ancestors"> - <h3>Inherited</h3> + <span class="filtertype">Inherited</span> <ol><li class="hideall">Hide All</li><li class="showall">Show all</li></ol> <ol id="linearization">{ tpl.linearization map { wte => <li class="in" name={ wte.qualifiedName }>{ wte.name }</li> } }</ol> </div> } <div id="visbl"> - <h3>Visibility</h3> + <span class="filtertype">Visibility</span> <ol><li class="public in">Public</li><li class="all out">All</li></ol> </div> </div> @@ -263,8 +263,7 @@ class Template(tpl: DocTemplateEntity) extends HtmlPage { } def kindToString(mbr: MemberEntity): String = mbr match { - case tpl: DocTemplateEntity => - if (tpl.isPackage) "package" else if (tpl.isClass) "class" else if (tpl.isTrait) "trait" else "object" + case tpl: DocTemplateEntity => docEntityKindToString(tpl) case ctor: Constructor => "new" case tme: MemberEntity => ( if (tme.isImplicit) "implicit " else "" ) + diff --git a/src/compiler/scala/tools/nsc/doc/html/resource/lib/index.css b/src/compiler/scala/tools/nsc/doc/html/resource/lib/index.css index 0bae7dbc3a..fc3f6d4c29 100644 --- a/src/compiler/scala/tools/nsc/doc/html/resource/lib/index.css +++ b/src/compiler/scala/tools/nsc/doc/html/resource/lib/index.css @@ -145,10 +145,21 @@ h1 { } #tpl ol > li .icon { + padding-right: 5px; bottom: -2px; position: relative; } +#tpl .templates div.placeholder { + padding-right: 5px; + width: 13px; + display: inline-block; +} + +#tpl .templates span.tplLink { + padding-left: 8px; +} + #content { border-left-width: 1px; border-left-color: black; diff --git a/src/compiler/scala/tools/nsc/doc/html/resource/lib/template.css b/src/compiler/scala/tools/nsc/doc/html/resource/lib/template.css index f315a283b6..92de97f619 100644 --- a/src/compiler/scala/tools/nsc/doc/html/resource/lib/template.css +++ b/src/compiler/scala/tools/nsc/doc/html/resource/lib/template.css @@ -198,13 +198,11 @@ div.members > ol > li { .cmt code { font-family: monospace; -/* font-size: small;*/ } .cmt pre { display: block; font-family: monospace; -/* font-size: small;*/ margin: 2px 0 2px 0; } @@ -329,10 +327,12 @@ div.fullcomment dl.paramcmts > dd + dt + dd { margin-bottom: 10px; } -#mbrsel > div > h3 { +#mbrsel > div > span.filtertype { padding: 4px; - display: inline; + float: left; + display: inline-block; color: white; + width: 4.5em; } #mbrsel > div > ol { @@ -346,7 +346,6 @@ div.fullcomment dl.paramcmts > dd + dt + dd { #mbrsel > div > ol > li { padding: 4px 8px 4px 8px; -/* font-weight: bold;*/ background-color: white; display: inline-block; cursor: crosshair; diff --git a/src/compiler/scala/tools/nsc/doc/model/ModelFactory.scala b/src/compiler/scala/tools/nsc/doc/model/ModelFactory.scala index d7e1e2fe1a..e2a25d7ea4 100644 --- a/src/compiler/scala/tools/nsc/doc/model/ModelFactory.scala +++ b/src/compiler/scala/tools/nsc/doc/model/ModelFactory.scala @@ -13,7 +13,7 @@ import symtab.Flags import model.{ RootPackage => RootPackageEntity } /** This trait extracts all required information for documentation from compilation units */ -class ModelFactory(val global: Global, val settings: doc.Settings) extends CommentFactory { thisFactory => +class ModelFactory(val global: Global, val settings: doc.Settings) { thisFactory: ModelFactory with CommentFactory => import global._ import definitions.{ ObjectClass, ScalaObjectClass, RootPackage, EmptyPackage, NothingClass, AnyClass, AnyRefClass } @@ -502,9 +502,11 @@ class ModelFactory(val global: Global, val settings: doc.Settings) extends Comme val name = optimize(nameBuffer.toString) } - def templateShouldDocument(aSym: Symbol): Boolean = - (aSym.isPackageClass || (aSym.sourceFile != null)) && localShouldDocument(aSym) && + def templateShouldDocument(aSym: Symbol): Boolean = { + // TODO: document sourceless entities (e.g., Any, etc), based on a new Setting to be added + (aSym.isPackageClass || (aSym.sourceFile != null)) && localShouldDocument(aSym) && ( aSym.owner == NoSymbol || templateShouldDocument(aSym.owner) ) + } def localShouldDocument(aSym: Symbol): Boolean = !aSym.isPrivate && (aSym.isProtected || aSym.privateWithin == NoSymbol) && !aSym.isSynthetic diff --git a/src/compiler/scala/tools/nsc/interpreter/ByteCode.scala b/src/compiler/scala/tools/nsc/interpreter/ByteCode.scala index a998f9dfc8..a42b8347a7 100644 --- a/src/compiler/scala/tools/nsc/interpreter/ByteCode.scala +++ b/src/compiler/scala/tools/nsc/interpreter/ByteCode.scala @@ -10,7 +10,8 @@ import java.io.File import java.lang.reflect import java.util.jar.{ JarEntry, JarFile } import java.util.concurrent.ConcurrentHashMap -import util.ScalaClassLoader.getSystemLoader +import util.ScalaClassLoader +import ScalaClassLoader.getSystemLoader object ByteCode { /** Until I figure out why I can't get scalap onto the classpath such @@ -34,8 +35,8 @@ object ByteCode { def scalaSigBytesForPath(path: String) = for { module <- DECODER - method <- decoderMethod("scalaSigBytes", classOf[String], classOf[ClassLoader]) - names <- method.invoke(module, path, this.getClass.getClassLoader).asInstanceOf[Option[Array[Byte]]] + method <- decoderMethod("scalaSigAnnotationBytes", classOf[String]) + names <- method.invoke(module, path).asInstanceOf[Option[Array[Byte]]] } yield names diff --git a/src/compiler/scala/tools/nsc/interpreter/Completion.scala b/src/compiler/scala/tools/nsc/interpreter/Completion.scala index b90da5bf98..fddb1ee928 100644 --- a/src/compiler/scala/tools/nsc/interpreter/Completion.scala +++ b/src/compiler/scala/tools/nsc/interpreter/Completion.scala @@ -3,35 +3,14 @@ * @author Paul Phillips */ -// -// TODO, if practical: -// -// 1) Types: val s: String = x.<tab> should only show members which result in a String. -// Possible approach: evaluate buffer as if current identifier is -// 2) Implicits: x.<tab> should show not only x's members but those of anything for which -// there is an implicit conversion from x. -// 4) Imports: after import scala.collection.mutable._, HashMap should be among -// my top level identifiers. -// 5) Caching: parsing the jars every startup seems wasteful, but experimentally -// there is little to no gain from caching. package scala.tools.nsc package interpreter import jline._ -import java.net.URL import java.util.{ List => JList } -import java.lang.reflect -import scala.tools.util.PathResolver -import io.{ Path, Directory } object Completion { - // methods to leave out of completion - val excludeMethods = List("hashCode", "equals", "wait", "notify", "notifyAll") - - // strings to look for an exclude by default - val excludeStrings = List("$$super", "MODULE$") - def looksLikeInvocation(code: String) = ( (code != null) && (code startsWith ".") @@ -40,80 +19,231 @@ object Completion { && !(code startsWith "..") ) - trait Forwarder extends CompletionAware { - def forwardTo: Option[CompletionAware] - - override def completions() = forwardTo map (_.completions()) getOrElse Nil - override def follow(s: String) = forwardTo flatMap (_ follow s) + object Forwarder { + def apply(forwardTo: () => Option[CompletionAware]): CompletionAware = new CompletionAware { + def completions(verbosity: Int) = forwardTo() map (_ completions verbosity) getOrElse Nil + override def follow(s: String) = forwardTo() flatMap (_ follow s) + } } } import Completion._ // REPL completor - queries supplied interpreter for valid // completions based on current contents of buffer. -class Completion(repl: Interpreter) { - self => +class Completion(val repl: Interpreter) extends CompletionOutput { + // verbosity goes up with consecutive tabs + private var verbosity: Int = 0 + def resetVerbosity() = verbosity = 0 - private lazy val classPath = repl.compilerClasspath + def isCompletionDebug = repl.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)) - // the unqualified vals/defs/etc visible in the repl - val ids = new IdentCompletion(repl) - // the top level packages we know about - val pkgs = new PackageCompletion(classPath) - // members of Predef - val predef = new StaticCompletion(classOf[scala.Predef$]) { - override def filterNotFunction(s: String) = ( - (s contains "2") || - (s startsWith "wrap") || - (s endsWith "Wrapper") || - (s endsWith "Ops") - ) + lazy val global: repl.compiler.type = repl.compiler + import global._ + import definitions.{ PredefModule, RootClass, AnyClass, AnyRefClass, ScalaPackage, JavaLangPackage } + + // 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) } - // members of scala.* - val scalalang = new pkgs.SubCompletor("scala") with Forwarder { - def forwardTo = pkgs follow "scala" - val arityClasses = { - val names = List("Tuple", "Product", "Function") - val expanded = for (name <- names ; index <- 0 to 22 ; dollar <- List("", "$")) yield name + index + dollar - Set(expanded: _*) + def getType(name: String, isModule: Boolean) = { + val f = if (isModule) definitions.getModule(_: Name) else definitions.getClass(_: Name) + try Some(f(name).tpe) + catch { case _: MissingRequirementError => None } + } + + def typeOf(name: String) = getType(name, false) + def moduleOf(name: String) = getType(name, true) + + trait CompilerCompletion { + def tp: Type + def effectiveTp = tp match { + case MethodType(Nil, resType) => resType + case PolyType(Nil, resType) => resType + case _ => tp } - override def filterNotFunction(s: String) = { - val simple = s.reverse takeWhile (_ != '.') reverse + // for some reason any's members don't show up in subclasses, which + // we need so 5.<tab> offers asInstanceOf etc. + private def anyMembers = AnyClass.tpe.nonPrivateMembers + def anyRefMethodsToShow = List("isInstanceOf", "asInstanceOf", "toString") + + def tos(sym: Symbol) = sym.name.decode.toString + def memberNamed(s: String) = members find (x => tos(x) == s) + def hasMethod(s: String) = methods exists (x => tos(x) == s) + + // 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 methods = members filter (_.isMethod) + def packages = members filter (_.isPackage) + def aliases = members filter (_.isAliasType) - (arityClasses contains simple) || - (s endsWith "Exception") || - (s endsWith "Error") + def memberNames = members map tos + def methodNames = methods map tos + def packageNames = packages map tos + def aliasNames = aliases map tos + } + + object TypeMemberCompletion { + def apply(tp: Type): TypeMemberCompletion = { + if (tp.typeSymbol.isPackageClass) new PackageCompletion(tp) + else new TypeMemberCompletion(tp) } + def imported(tp: Type) = new ImportCompletion(tp) } - // members of java.lang.* - val javalang = new pkgs.SubCompletor("java.lang") with Forwarder { - def forwardTo = pkgs follow "java.lang" - import reflect.Modifier.isPublic - private def existsAndPublic(s: String): Boolean = { - val name = if (s contains ".") s else "java.lang." + s - val clazz = classForName(name) getOrElse (return false) - - isPublic(clazz.getModifiers) + + class TypeMemberCompletion(val tp: Type) extends CompletionAware with CompilerCompletion { + def excludeEndsWith: List[String] = Nil + def excludeStartsWith: List[String] = List("<") // <byname>, <repeated>, etc. + def excludeNames: List[String] = anyref.methodNames -- anyRefMethodsToShow ++ List("_root_") + + def methodSignatureString(sym: Symbol) = { + def asString = new MethodSymbolOutput(sym).methodString() + + if (isCompletionDebug) + repl.power.showAtAllPhases(asString) + + atPhase(currentRun.typerPhase)(asString) + } + + def exclude(name: String): Boolean = ( + (name contains "$") || + (excludeNames contains name) || + (excludeEndsWith exists (name endsWith _)) || + (excludeStartsWith exists (name startsWith _)) + ) + def filtered(xs: List[String]) = xs filterNot exclude distinct + + def completions(verbosity: Int) = + debugging(tp + " completions ==> ")(filtered(memberNames)) + + override def follow(s: String): Option[CompletionAware] = + debugging(tp + " -> '" + s + "' ==> ")(memberNamed(s) map (x => TypeMemberCompletion(x.tpe))) + + override def alternativesFor(id: String): List[String] = + debugging(id + " alternatives ==> ") { + val alts = members filter (x => x.isMethod && tos(x) == id) map methodSignatureString + + if (alts.nonEmpty) "" :: alts else Nil + } + + override def toString = "TypeMemberCompletion(%s)".format(tp) + } + + class PackageCompletion(tp: Type) extends TypeMemberCompletion(tp) { + override def excludeNames = anyref.methodNames + } + + class LiteralCompletion(lit: Literal) extends TypeMemberCompletion(lit.value.tpe) { + override def completions(verbosity: Int) = verbosity match { + case 0 => filtered(memberNames) + case _ => memberNames + } + } + + class ImportCompletion(tp: Type) extends TypeMemberCompletion(tp) { + override def completions(verbosity: Int) = verbosity match { + case 0 => filtered(members filterNot (_.isSetter) map tos) + case _ => super.completions(verbosity) + } + } + + // not for completion but for excluding + object anyref extends TypeMemberCompletion(AnyRefClass.tpe) { } + + // the unqualified vals/defs/etc visible in the repl + object ids extends CompletionAware { + override def completions(verbosity: Int) = repl.unqualifiedIds ::: List("classOf") + // we try to use the compiler and fall back on reflection if necessary + // (which at present is for anything defined in the repl session.) + override def follow(id: String) = + if (completions(0) contains id) { + for (clazz <- repl clazzForIdent id) yield { + (typeOf(clazz.getName) map TypeMemberCompletion.apply) getOrElse new InstanceCompletion(clazz) + } + } + else None + } + + // wildcard imports in the repl like "import global._" or "import String._" + private def imported = repl.wildcardImportedTypes map TypeMemberCompletion.imported + + // literal Ints, Strings, etc. + object literals extends CompletionAware { + def simpleParse(code: String): Tree = { + val unit = new CompilationUnit(new util.BatchSourceFile("<console>", code)) + val scanner = new syntaxAnalyzer.UnitParser(unit) + val tss = scanner.templateStatSeq(false)._2 + + if (tss.size == 1) tss.head else EmptyTree } - override def filterNotFunction(s: String) = { - (s endsWith "Exception") || - (s endsWith "Error") || - (s endsWith "Impl") || - (s startsWith "CharacterData") + + def completions(verbosity: Int) = Nil + + override def follow(id: String) = simpleParse(id) match { + case x: Literal => Some(new LiteralCompletion(x)) + case _ => None } - override def completions() = super.completions() filter existsAndPublic } - val literals = new LiteralCompletion { - lazy val global = repl.compiler - val parent = self + + // top level packages + object rootClass extends TypeMemberCompletion(RootClass.tpe) { } + // members of Predef + object predef extends TypeMemberCompletion(PredefModule.tpe) { + override def excludeEndsWith = super.excludeEndsWith ++ List("Wrapper", "ArrayOps") + override def excludeStartsWith = super.excludeStartsWith ++ List("wrap") + override def excludeNames = anyref.methodNames + + override def exclude(name: String) = super.exclude(name) || ( + (name contains "2") + ) + + override def completions(verbosity: Int) = verbosity match { + case 0 => Nil + case _ => super.completions(verbosity) + } } + // members of scala.* + object scalalang extends PackageCompletion(ScalaPackage.tpe) { + def arityClasses = List("Product", "Tuple", "Function") + def skipArity(name: String) = arityClasses exists (x => name != x && (name startsWith x)) + override def exclude(name: String) = super.exclude(name) || ( + skipArity(name) + ) - def lastResult = new Forwarder { - def forwardTo = ids follow repl.mostRecentVar + override def completions(verbosity: Int) = verbosity match { + case 0 => filtered(packageNames ++ aliasNames) + case _ => super.completions(verbosity) + } + } + // members of java.lang.* + object javalang extends PackageCompletion(JavaLangPackage.tpe) { + override lazy val excludeEndsWith = super.excludeEndsWith ++ List("Exception", "Error") + override lazy val excludeStartsWith = super.excludeStartsWith ++ List("CharacterData") + + override def completions(verbosity: Int) = verbosity match { + case 0 => filtered(packageNames) + case _ => super.completions(verbosity) + } } + // the list of completion aware objects which should be consulted + lazy val topLevelBase: List[CompletionAware] = List(ids, rootClass, predef, scalalang, javalang, literals) + def topLevel = topLevelBase ++ imported + + // the first tier of top level objects (doesn't include file completion) + def topLevelFor(parsed: Parsed) = topLevel flatMap (_ completionsFor parsed) + + // the most recent result + def lastResult = Forwarder(() => ids follow repl.mostRecentVar) + def lastResultFor(parsed: Parsed) = { /** The logic is a little tortured right now because normally '.' is * ignored as a delimiter, but on .<tab> it needs to be propagated. @@ -122,12 +252,6 @@ class Completion(repl: Interpreter) { if (parsed.isEmpty) xs map ("." + _) else xs } - // the list of completion aware objects which should be consulted - val topLevel: List[CompletionAware] = List(ids, pkgs, predef, scalalang, javalang, literals) - - // the first tier of top level objects (doesn't include file completion) - def topLevelFor(parsed: Parsed) = topLevel flatMap (_ completionsFor parsed) - // chasing down results which won't parse def execute(line: String): Option[Any] = { val parsed = Parsed(line) @@ -136,34 +260,43 @@ class Completion(repl: Interpreter) { if (noDotOrSlash) None // we defer all unqualified ids to the repl. else { (ids executionFor parsed) orElse - (pkgs executionFor parsed) orElse + (rootClass executionFor parsed) orElse (FileCompletion executionFor line) } } - // override if history is available - def lastCommand: Option[String] = None + // generic interface for querying (e.g. interpreter loop, testing) + def completions(buf: String): List[String] = + topLevelFor(Parsed.dotted(buf + ".", buf.length + 1)) // jline's entry point lazy val jline: ArgumentCompletor = returning(new ArgumentCompletor(new JLineCompletion, new JLineDelimiter))(_ setStrict false) + /** This gets a little bit hairy. It's no small feat delegating everything + * and also keeping track of exactly where the cursor is and where it's supposed + * to end up. The alternatives mechanism is a little hacky: if there is an empty + * string in the list of completions, that means we are expanding a unique + * completion, so don't update the "last" buffer because it'll be wrong. + */ class JLineCompletion extends Completor { // For recording the buffer on the last tab hit - private var lastTab: (String, String) = (null, null) + private var lastBuf: String = "" + private var lastCursor: Int = -1 // Does this represent two consecutive tabs? - def isConsecutiveTabs(buf: String) = (buf, lastCommand orNull) == lastTab + def isConsecutiveTabs(buf: String, cursor: Int) = cursor == lastCursor && buf == lastBuf - // verbosity goes up with consecutive tabs - // TODO - actually implement. - private var verbosity = 0 + // Longest common prefix + def commonPrefix(xs: List[String]) = + if (xs.isEmpty) "" + else xs.reduceLeft(_ zip _ takeWhile (x => x._1 == x._2) map (_._1) mkString) // This is jline's entry point for completion. - override def complete(buf: String, cursor: Int, candidates: JList[String]): Int = { - // println("complete: buf = %s, cursor = %d".format(buf, cursor)) - verbosity = if (isConsecutiveTabs(buf)) verbosity + 1 else 0 - lastTab = (buf, lastCommand orNull) + override def complete(_buf: String, cursor: Int, candidates: JList[String]): Int = { + val buf = onull(_buf) + verbosity = if (isConsecutiveTabs(buf, cursor)) verbosity + 1 else 0 + DBG("complete(%s, %d) last = (%s, %d), verbosity: %s".format(buf, cursor, lastBuf, lastCursor, verbosity)) // we don't try lower priority completions unless higher ones return no results. def tryCompletion(p: Parsed, completionFunction: Parsed => List[String]): Option[Int] = { @@ -172,17 +305,30 @@ class Completion(repl: Interpreter) { case xs => // modify in place and return the position xs foreach (candidates add _) - Some(p.position) + + // update the last buffer unless this is an alternatives list + if (xs contains "") Some(p.cursor) + else { + val advance = commonPrefix(xs) + lastCursor = p.position + advance.length + lastBuf = (buf take p.position) + advance + + DBG("tryCompletion(%s, _) lastBuf = %s, lastCursor = %s, p.position = %s".format(p, lastBuf, lastCursor, p.position)) + Some(p.position) + } } } + def mkDotted = Parsed.dotted(buf, cursor) withVerbosity verbosity + def mkUndelimited = Parsed.undelimited(buf, cursor) withVerbosity verbosity + // a single dot is special cased to completion on the previous result def lastResultCompletion = if (!looksLikeInvocation(buf)) None else tryCompletion(Parsed.dotted(buf drop 1, cursor), lastResultFor) - def regularCompletion = tryCompletion(Parsed.dotted(buf, cursor), topLevelFor) - def fileCompletion = tryCompletion(Parsed.undelimited(buf, cursor), FileCompletion completionsFor _.buffer) + def regularCompletion = tryCompletion(mkDotted, topLevelFor) + def fileCompletion = tryCompletion(mkUndelimited, FileCompletion completionsFor _.buffer) (lastResultCompletion orElse regularCompletion orElse fileCompletion) getOrElse cursor } diff --git a/src/compiler/scala/tools/nsc/interpreter/CompletionAware.scala b/src/compiler/scala/tools/nsc/interpreter/CompletionAware.scala index 7e94b687bf..cfd3b5e05f 100644 --- a/src/compiler/scala/tools/nsc/interpreter/CompletionAware.scala +++ b/src/compiler/scala/tools/nsc/interpreter/CompletionAware.scala @@ -21,8 +21,7 @@ trait CompletionAware { /** The complete list of unqualified Strings to which this * object will complete. */ - def completions(): List[String] - def completions(start: String): List[String] = completions filter (_ startsWith start) + def completions(verbosity: Int): List[String] /** Default filter to apply to completions. */ @@ -47,6 +46,19 @@ trait CompletionAware { */ def execute(id: String): Option[Any] = None + /** A list of useful information regarding a specific uniquely + * identified completion. This is specifically written for the + * following situation, but should be useful elsewhere too: + * + * x.y.z.methodName<tab> + * + * If "methodName" is among z's completions, and verbosity > 0 + * indicating tab has been pressed twice consecutively, then we + * call alternativesFor and show a list of overloaded method + * signatures. + */ + def alternativesFor(id: String): List[String] = Nil + /** Given string 'buf', return a list of all the strings * to which it can complete. This may involve delegating * to other CompletionAware objects. @@ -54,12 +66,16 @@ trait CompletionAware { def completionsFor(parsed: Parsed): List[String] = { import parsed._ - val cs = - if (isEmpty) completions() - else if (isUnqualified && !isLastDelimiter) completions(buffer) + val comps = completions(verbosity) filter (_ startsWith buffer) + val results = + if (isEmpty) comps + else if (isUnqualified && !isLastDelimiter) { + if (verbosity > 0 && (comps contains buffer)) alternativesFor(buffer) + else comps + } else follow(bufferHead) map (_ completionsFor bufferTail) getOrElse Nil - cs filterNot filterNotFunction map mapFunction sortWith (sortFunction _) + results filterNot filterNotFunction map mapFunction sortWith (sortFunction _) } /** TODO - unify this and completionsFor under a common traverser. @@ -67,14 +83,14 @@ trait CompletionAware { def executionFor(parsed: Parsed): Option[Any] = { import parsed._ - if (isUnqualified && !isLastDelimiter && (completions contains buffer)) execute(buffer) + if (isUnqualified && !isLastDelimiter && (completions(verbosity) contains buffer)) execute(buffer) else if (!isQualified) None else follow(bufferHead) flatMap (_ executionFor bufferTail) } } object CompletionAware { - val Empty = new CompletionAware { val completions = Nil } + val Empty = new CompletionAware { def completions(verbosity: Int) = Nil } // class Forwarder(underlying: CompletionAware) extends CompletionAware { // override def completions() = underlying.completions() @@ -101,6 +117,7 @@ object CompletionAware { def apply(terms: () => List[String], followFunction: String => Option[CompletionAware]): CompletionAware = new CompletionAware { def completions = terms() + def completions(verbosity: Int) = completions override def follow(id: String) = followFunction(id) } diff --git a/src/compiler/scala/tools/nsc/interpreter/CompletionOutput.scala b/src/compiler/scala/tools/nsc/interpreter/CompletionOutput.scala new file mode 100644 index 0000000000..9b9d9a36f1 --- /dev/null +++ b/src/compiler/scala/tools/nsc/interpreter/CompletionOutput.scala @@ -0,0 +1,88 @@ +/* NSC -- new Scala compiler + * Copyright 2005-2010 LAMP/EPFL + * @author Paul Phillips + */ + +package scala.tools.nsc +package interpreter + +/** This has a lot of duplication with other methods in Symbols and Types, + * but repl completion utility is very sensitive to precise output. Best + * thing would be to abstract an interface for how such things are printed, + * as is also in progress with error messages. + */ +trait CompletionOutput { + self: Completion => + + import global._ + import definitions.{ NothingClass, AnyClass, isTupleType, isFunctionType, isRepeatedParamType } + + /** Reducing fully qualified noise for some common packages. + */ + val typeTransforms = List( + "java.lang." -> "", + "scala.collection.immutable." -> "immutable.", + "scala.collection.mutable." -> "mutable.", + "scala.collection.generic." -> "generic." + ) + + def quietString(tp: String): String = + typeTransforms.foldLeft(tp) { + case (str, (prefix, replacement)) => + if (str startsWith prefix) replacement + (str stripPrefix prefix) + else str + } + + class MethodSymbolOutput(method: Symbol) { + val pkg = method.ownerChain find (_.isPackageClass) map (_.fullName) getOrElse "" + + def relativize(str: String): String = quietString(str stripPrefix (pkg + ".")) + def relativize(tp: Type): String = relativize(tp.normalize.toString) + def relativize(sym: Symbol): String = relativize(sym.info) + + def braceList(tparams: List[String]) = if (tparams.isEmpty) "" else (tparams map relativize).mkString("[", ", ", "]") + def parenList(params: List[Any]) = params.mkString("(", ", ", ")") + + def methodTypeToString(mt: MethodType) = + (mt.paramss map paramsString mkString "") + ": " + relativize(mt.finalResultType) + + def typeToString(tp: Type): String = relativize( + tp match { + case x if isFunctionType(x) => functionString(x) + case x if isTupleType(x) => tupleString(x) + case x if isRepeatedParamType(x) => typeToString(x.typeArgs.head) + "*" + case mt @ MethodType(_, _) => methodTypeToString(mt) + case x => x.toString + } + ) + + def tupleString(tp: Type) = parenList(tp.normalize.typeArgs map relativize) + def functionString(tp: Type) = tp.normalize.typeArgs match { + case List(t, r) => t + " => " + r + case xs => parenList(xs.init) + " => " + xs.last + } + + def tparamsString(tparams: List[Symbol]) = braceList(tparams map (_.defString)) + def paramsString(params: List[Symbol]) = { + def paramNameString(sym: Symbol) = if (sym.isSynthetic) "" else sym.nameString + ": " + def paramString(sym: Symbol) = paramNameString(sym) + typeToString(sym.info.normalize) + + val isImplicit = params.nonEmpty && params.head.isImplicit + val strs = (params map paramString) match { + case x :: xs if isImplicit => ("implicit " + x) :: xs + case xs => xs + } + parenList(strs) + } + + def methodString() = + method.keyString + " " + method.nameString + (method.info.normalize match { + case PolyType(Nil, resType) => ": " + typeToString(resType) // nullary method + 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 + }) + } +} diff --git a/src/compiler/scala/tools/nsc/interpreter/IdentCompletion.scala b/src/compiler/scala/tools/nsc/interpreter/IdentCompletion.scala deleted file mode 100644 index b0152dbbc6..0000000000 --- a/src/compiler/scala/tools/nsc/interpreter/IdentCompletion.scala +++ /dev/null @@ -1,25 +0,0 @@ -/* NSC -- new Scala compiler - * Copyright 2005-2010 LAMP/EPFL - * @author Paul Phillips - */ - -package scala.tools.nsc -package interpreter - -/** Top level identifiers visible in the repl. It immediately - * delegates to an InstanceCompletion. - */ -class IdentCompletion(repl: Interpreter) extends CompletionAware { - val INTERPRETER_VAR_PREFIX = "res" - - def completions() = repl.unqualifiedIds ::: List("classOf") - override def follow(id: String) = - // XXX this will be nice but needs solidifying. - // (repl completionAwareImplicit id) orElse - if (completions contains id) { - (repl completionAware id) orElse { - repl clazzForIdent id map (x => new InstanceCompletion(x)) - } - } - else None -} diff --git a/src/compiler/scala/tools/nsc/interpreter/LiteralCompletion.scala b/src/compiler/scala/tools/nsc/interpreter/LiteralCompletion.scala deleted file mode 100644 index 3b74549d27..0000000000 --- a/src/compiler/scala/tools/nsc/interpreter/LiteralCompletion.scala +++ /dev/null @@ -1,50 +0,0 @@ -/* NSC -- new Scala compiler - * Copyright 2005-2010 LAMP/EPFL - * @author Paul Phillips - */ - -package scala.tools.nsc -package interpreter - -import util.BatchSourceFile - -/** Literals, so we can pretend they are objects with methods. - */ -abstract class LiteralCompletion extends CompletionAware { - val parent: Completion - val global: Global - - import global._ - - // TODO - figure out how to enumerate available implicit conversions. - // def richInt = new InstanceCompletion(classOf[scala.runtime.RichInt]) - - class PrimitiveCompletion(x: Type) extends CompletionAware { - lazy val completions = x.nonPrivateMembers map (_.name.toString) - override def follow(s: String) = { - val member = x.nonPrivateMembers find (_.name.toString == s) - member flatMap (m => Option(m.tpe)) map (_.resultType) map (x => new PrimitiveCompletion(x)) - } - } - - def simpleParse(code: String): Tree = { - val unit = new CompilationUnit(new BatchSourceFile("<console>", code)) - val scanner = new syntaxAnalyzer.UnitParser(unit) - - // only single statements - scanner.templateStatSeq(false) match { - case (_, List(t)) => t - case (_, x) => EmptyTree - } - } - - def completions() = Nil - override def follow(id: String) = simpleParse(id) match { - case Literal(c @ Constant(_)) => Some(new PrimitiveCompletion(c.tpe)) - // TODO - more AST trees. - // case Apply(fn @ Ident(name), args) => - // classForName(name.toString) map (x => new StaticCompletion(x)) - // None - case x => None - } -} diff --git a/src/compiler/scala/tools/nsc/interpreter/PackageCompletion.scala b/src/compiler/scala/tools/nsc/interpreter/PackageCompletion.scala deleted file mode 100644 index 26ae4106c6..0000000000 --- a/src/compiler/scala/tools/nsc/interpreter/PackageCompletion.scala +++ /dev/null @@ -1,187 +0,0 @@ -/* NSC -- new Scala compiler - * Copyright 2005-2010 LAMP/EPFL - * @author Paul Phillips - */ - -package scala.tools.nsc -package interpreter - -import java.net.URL -import java.lang.reflect -import java.util.concurrent.ConcurrentHashMap -import io.{ Path, Directory, File, Streamable } -import scala.tools.util.PathResolver.Defaults.scalaHomeDir -import scala.concurrent.DelayedLazyVal -import scala.reflect.NameTransformer.{ decode, encode } -import PackageCompletion._ - -/** Completion among all known packages. It examines the jars in a - * separate thread so as not to slow down startup. If it arrives at - * an object, it delegates to StaticCompletion for that object. - */ -class PackageCompletion(classpath: List[URL]) extends CompletionAware { - // it takes a little while to look through the jars so we use a future and a concurrent map - class CompletionAgent { - val dottedPaths: ConcurrentHashMap[String, List[CompletionInfo]] = new ConcurrentHashMap[String, List[CompletionInfo]] - val topLevelPackages = new DelayedLazyVal( - () => enumToList(dottedPaths.keys) filterNot (_ contains '.'), - getDottedPaths(dottedPaths, classpath) - ) - } - val agent = new CompletionAgent - import agent._ - - def completions() = topLevelPackages() - override def follow(id: String) = - if (dottedPaths containsKey id) Some(new SubCompletor(id)) - else None - - class SubCompletor(root: String) extends CompletionAware { - // Look for a type alias - private def aliasCompletor(path: String): Option[CompletionAware] = - for (name <- ByteCode aliasForType path ; clazz <- classForName(name + "$")) yield - new StaticCompletion(clazz) - - lazy val pkgObject = classForName(root + ".package$") map (x => new PackageObjectCompletion(x)) - def pkgObjectMembers = pkgObject map (_ completionsFor Parsed("")) getOrElse Nil - - private def infos = Option(dottedPaths get root) getOrElse Nil - def completions() = { - val xs = infos map (_.visibleName) filterNot (_ == "package") - xs ::: pkgObjectMembers - } - - override def follow(segment: String): Option[CompletionAware] = { - PackageCompletion.this.follow(root + "." + segment) orElse { - for (CompletionInfo(`segment`, className) <- infos ; clazz <- classForName(className)) { - return Some(new StaticCompletion(clazz)) - } - - aliasCompletor(root + "." + segment) - } - } - override def toString = "SubCompletor(%s)" format root - } -} - -object PackageCompletion { - import java.util.jar.{ JarEntry, JarFile } - - val EXPAND_SEPARATOR_STRING = "$$" - val ANON_CLASS_NAME = "$anon" - val TRAIT_SETTER_SEPARATOR_STRING = "$_setter_$" - val IMPL_CLASS_SUFFIX ="$class" - - def ignoreClassName(x: String) = - (x contains EXPAND_SEPARATOR_STRING) || - (x contains ANON_CLASS_NAME) || - (x contains TRAIT_SETTER_SEPARATOR_STRING) || - (x endsWith IMPL_CLASS_SUFFIX) || - (x matches """.*\$\d+$""") - - def enumToList[T](e: java.util.Enumeration[T]): List[T] = enumToListInternal(e, Nil) - private def enumToListInternal[T](e: java.util.Enumeration[T], xs: List[T]): List[T] = - if (e == null || !e.hasMoreElements) xs else enumToListInternal(e, e.nextElement :: xs) - - private def isClass(s: String) = s endsWith ".class" - private def processNames(xs: List[String]) = xs map (_ dropRight 6) filterNot ignoreClassName distinct - - def getDirClassFiles(dir: Directory): List[String] = - processNames(dir.deepList() map (dir relativize _ path) filter isClass toList) - - def getJarClassFiles(jar: File): List[String] = - if (!jar.exists) Nil - else processNames(enumToList(new JarFile(jar.path).entries) map (_.getName) filter isClass) - - object CompletionInfo { - def unapply(that: Any) = that match { - case x: CompletionInfo => Some((x.visibleName, x.className)) - case _ => None - } - } - - abstract class CompletionInfo { - def visibleName: String - def className: String - def getBytes(): Array[Byte] - - override def hashCode = visibleName.hashCode - override def equals(other: Any) = other match { - case x: CompletionInfo => visibleName == x.visibleName - case _ => false - } - } - - case class DirCompletionInfo(visibleName: String, className: String, dir: Directory) extends CompletionInfo { - lazy val file = dir / File(className) - - def getBytes(): Array[Byte] = try file.toByteArray() catch { case _: Exception => Array() } - } - - case class JarCompletionInfo(visibleName: String, className: String, jar: File) extends CompletionInfo { - lazy val jarfile = new JarFile(jar.path) - lazy val entry = jarfile getEntry className - - def getBytes(): Array[Byte] = { - if (entry == null) Array() else { - val x = new Streamable.Bytes { def inputStream() = jarfile getInputStream entry } - x.toByteArray() - } - } - } - - // all the dotted path to classfiles we can find by poking through the jars - def getDottedPaths(map: ConcurrentHashMap[String, List[CompletionInfo]], classpath: List[URL]): Unit = { - val cp = classpath.distinct map (x => Path(x.getPath)) - val jars = cp filter (_ hasExtension "jar") map (_.toFile) - - /** If we process all dirs uncritically, someone who has '.' in their classpath and - * runs scala from the filesystem root directory will induce a traversal of their - * entire filesystem. We could apply some heuristics to avoid this, but for now we - * will look only in the scalaHome directories, which is most of what we want. - */ - def isUnderScalaHome(d: Directory) = d.parents exists (_ == scalaHomeDir) - val dirs = cp collect { case x: Directory => x } filter isUnderScalaHome - - // for e.g. foo.bar.baz.C, returns (foo -> bar), (foo.bar -> baz), (foo.bar.baz -> C) - // and scala.Range$BigInt needs to go scala -> Range -> BigInt - def subpaths(s: String): List[(String, String)] = { - val segs = decode(s).split("""[/.]""") - val components = segs dropRight 1 - - (1 to components.length).toList flatMap { i => - val k = components take i mkString "." - if (segs(i) contains "$") { - val dollarsegs = segs(i).split("$").toList - for (j <- 1 to (dollarsegs.length - 1) toList) yield { - val newk = k + "." + (dollarsegs take j mkString ".") - (k -> dollarsegs(j)) - } - } - else List(k -> segs(i)) - } - } - - def addToMap(key: String, info: CompletionInfo) = { - if (map containsKey key) { - val vs = map.get(key) - if (vs contains info) () - else map.put(key, info :: vs) - } - else map.put(key, List(info)) - } - - def oneDir(dir: Directory) { - for (cl <- getDirClassFiles(dir) ; (k, v) <- subpaths(cl)) - addToMap(k, DirCompletionInfo(v, cl, dir)) - } - - def oneJar(jar: File) { - for (cl <- getJarClassFiles(jar) ; (k, v) <- subpaths(cl)) - addToMap(k, JarCompletionInfo(v, cl, jar)) - } - - jars foreach oneJar - dirs foreach oneDir - } -}
\ No newline at end of file diff --git a/src/compiler/scala/tools/nsc/interpreter/Parsed.scala b/src/compiler/scala/tools/nsc/interpreter/Parsed.scala index b130396cc6..0b92608d88 100644 --- a/src/compiler/scala/tools/nsc/interpreter/Parsed.scala +++ b/src/compiler/scala/tools/nsc/interpreter/Parsed.scala @@ -15,18 +15,22 @@ class Parsed private ( val cursor: Int, val delimited: Char => Boolean ) extends Delimited { - def isEmpty = buffer == "" + def isEmpty = args.isEmpty def isUnqualified = args.size == 1 def isQualified = args.size > 1 def isAtStart = cursor <= 0 + private var _verbosity = 0 + def verbosity = _verbosity + def withVerbosity(v: Int): this.type = returning[this.type](this)(_ => _verbosity = v) + def args = toArgs(buffer take cursor).toList def bufferHead = args.head def headLength = bufferHead.length + 1 - def bufferTail = new Parsed(buffer drop headLength, cursor - headLength, delimited) + def bufferTail = new Parsed(buffer drop headLength, cursor - headLength, delimited) withVerbosity verbosity - def prev = new Parsed(buffer, cursor - 1, delimited) - def next = new Parsed(buffer, cursor + 1, delimited) + def prev = new Parsed(buffer, cursor - 1, delimited) withVerbosity verbosity + def next = new Parsed(buffer, cursor + 1, delimited) withVerbosity verbosity def currentChar = buffer(cursor) def currentArg = args.last def position = @@ -52,7 +56,7 @@ class Parsed private ( object Parsed { def apply(s: String): Parsed = apply(onull(s), onull(s).length) - def apply(s: String, cursor: Int): Parsed = apply(onull(s), cursor, "(){},`; \t" contains _) + def apply(s: String, cursor: Int): Parsed = apply(onull(s), cursor, "{},`; \t" contains _) def apply(s: String, cursor: Int, delimited: Char => Boolean): Parsed = new Parsed(onull(s), cursor, delimited) diff --git a/src/compiler/scala/tools/nsc/interpreter/ProductCompletion.scala b/src/compiler/scala/tools/nsc/interpreter/ProductCompletion.scala index 2aaa6114c2..6c066580ae 100644 --- a/src/compiler/scala/tools/nsc/interpreter/ProductCompletion.scala +++ b/src/compiler/scala/tools/nsc/interpreter/ProductCompletion.scala @@ -8,6 +8,7 @@ package interpreter class SeqCompletion[T](elems: Seq[T]) extends CompletionAware { lazy val completions = elems.indices.toList map ("(%d)" format _) + def completions(verbosity: Int) = completions private def elemAt(name: String) = if (completions contains name) Some(elems(name drop 1 dropRight 1 toInt)) else None @@ -27,6 +28,7 @@ class ProductCompletion(root: Product) extends CompletionAware { } lazy val completions = caseNames + def completions(verbosity: Int) = completions override def execute(name: String) = fieldForName(name) override def follow(name: String) = fieldForName(name) map (x => ProductCompletion(x)) } diff --git a/src/compiler/scala/tools/nsc/interpreter/ReflectionCompletion.scala b/src/compiler/scala/tools/nsc/interpreter/ReflectionCompletion.scala index 89490119ff..f9ff894d59 100644 --- a/src/compiler/scala/tools/nsc/interpreter/ReflectionCompletion.scala +++ b/src/compiler/scala/tools/nsc/interpreter/ReflectionCompletion.scala @@ -12,7 +12,6 @@ import Modifier.{ isPrivate, isProtected, isStatic } import scala.reflect.NameTransformer import scala.collection.mutable.HashMap import ReflectionCompletion._ -import Completion.{ excludeMethods } trait ReflectionCompletion extends CompletionAware { def clazz: Class[_] @@ -30,12 +29,6 @@ trait ReflectionCompletion extends CompletionAware { case x => error(x.toString) } - override def filterNotFunction(s: String): Boolean = { - (excludeMethods contains s) || - (s contains "$$super") || - (s == "MODULE$") - } - lazy val (staticMethods, instanceMethods) = clazz.getMethods.toList partition (x => isStatic(x.getModifiers)) lazy val (staticFields, instanceFields) = clazz.getFields.toList partition (x => isStatic(x.getModifiers)) @@ -53,15 +46,6 @@ trait ReflectionCompletion extends CompletionAware { } } -/** An instance completion which hides a few useless members. - */ -class PackageObjectCompletion(clazz: Class[_]) extends InstanceCompletion(clazz) { - override lazy val completions = memberCompletions - override def filterNotFunction(s: String) = { - super.filterNotFunction(s) || (s == "getClass") || (s == "toString") - } -} - /** 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. @@ -70,6 +54,7 @@ class InstanceCompletion(val clazz: Class[_]) 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) = { @@ -85,6 +70,7 @@ class InstanceCompletion(val clazz: Class[_]) extends ReflectionCompletion { class StaticCompletion(val clazz: Class[_]) extends ReflectionCompletion { protected def visibleMembers = whichMethods ::: whichFields lazy val completions = memberCompletions + def completions(verbosity: Int) = completions private def aliasForPath(path: String) = ByteCode aliasForType path flatMap (x => classForName(x + "$")) def className = clazz.getName diff --git a/src/compiler/scala/tools/nsc/interpreter/XMLCompletion.scala b/src/compiler/scala/tools/nsc/interpreter/XMLCompletion.scala index 67063192bd..f2af57cc36 100644 --- a/src/compiler/scala/tools/nsc/interpreter/XMLCompletion.scala +++ b/src/compiler/scala/tools/nsc/interpreter/XMLCompletion.scala @@ -33,6 +33,7 @@ class XMLCompletion(root: Node) extends CompletionAware { s :: res }).sorted } + def completions(verbosity: Int) = completions override def execute(id: String) = getNode(id) override def follow(id: String) = getNode(id) map (x => new XMLCompletion(x)) diff --git a/src/compiler/scala/tools/nsc/io/Process.scala b/src/compiler/scala/tools/nsc/io/Process.scala index 698082d19e..ebd7937f33 100644 --- a/src/compiler/scala/tools/nsc/io/Process.scala +++ b/src/compiler/scala/tools/nsc/io/Process.scala @@ -153,9 +153,8 @@ class Process(processCreator: () => JProcess) extends Iterable[String] { private val reader = new BufferedReader(new InputStreamReader(in)) private def finish() { - // make sure this thread is complete, and close the process's stdin + // make sure this thread is complete join() - _in.close() } def slurp(): String = { @@ -171,14 +170,19 @@ class Process(processCreator: () => JProcess) extends Iterable[String] { def next = it.next } } - @tailrec override final def run() { - reader.readLine match { - case null => - reader.close() - case x => - queue put x - run() + override final def run() { + @tailrec def loop() { + reader.readLine match { + case null => + reader.close() + case x => + queue put x + loop() + } } + + try loop() + catch { case _: IOException => () } } } diff --git a/src/compiler/scala/tools/nsc/reporters/AbstractReporter.scala b/src/compiler/scala/tools/nsc/reporters/AbstractReporter.scala index ce10f560e9..e35843fc9c 100644 --- a/src/compiler/scala/tools/nsc/reporters/AbstractReporter.scala +++ b/src/compiler/scala/tools/nsc/reporters/AbstractReporter.scala @@ -29,7 +29,7 @@ abstract class AbstractReporter extends Reporter { protected def info0(pos: Position, msg: String, _severity: Severity, force: Boolean) { val severity = - if (settings.Ywarnfatal.value && _severity == WARNING) ERROR + if (settings.Xwarnfatal.value && _severity == WARNING) ERROR else _severity severity match { diff --git a/src/compiler/scala/tools/nsc/settings/MutableSettings.scala b/src/compiler/scala/tools/nsc/settings/MutableSettings.scala index b32796e829..e8443d11c1 100644 --- a/src/compiler/scala/tools/nsc/settings/MutableSettings.scala +++ b/src/compiler/scala/tools/nsc/settings/MutableSettings.scala @@ -8,7 +8,7 @@ package scala.tools package nsc package settings -import io.AbstractFile +import io.{AbstractFile, VirtualDirectory} import scala.tools.util.StringOps import scala.collection.mutable.ListBuffer @@ -291,7 +291,11 @@ class MutableSettings(val errorFn: String => Unit) extends AbsSettings with Scal classFile.path.startsWith(outDir.path) singleOutDir match { - case Some(d) => Nil + case Some(d) => + d match { + case _: VirtualDirectory => Nil + case _ => List(d.lookupPathUnchecked(srcPath, false)) + } case None => (outputs filter (isBelow _).tupled) match { case Nil => Nil diff --git a/src/compiler/scala/tools/nsc/settings/ScalaSettings.scala b/src/compiler/scala/tools/nsc/settings/ScalaSettings.scala index 1b6b8297ef..51b47f87d6 100644 --- a/src/compiler/scala/tools/nsc/settings/ScalaSettings.scala +++ b/src/compiler/scala/tools/nsc/settings/ScalaSettings.scala @@ -27,7 +27,6 @@ trait ScalaSettings extends AbsScalaSettings with StandardScalaSettings { * Temporary Settings */ val suppressVTWarn = BooleanSetting ("-Ysuppress-vt-typer-warnings", "Suppress warnings from the typer when testing the virtual class encoding, NOT FOR FINAL!") - val javaignorecp = BooleanSetting ("-javaignorecp", "Does nothing - is being removed.") // !!! marked for death, but need new starr. /** * Standard settings @@ -80,6 +79,9 @@ trait ScalaSettings extends AbsScalaSettings with StandardScalaSettings { val Xshowobj = StringSetting ("-Xshow-object", "object", "Show object info", "") val showPhases = BooleanSetting ("-Xshow-phases", "Print a synopsis of compiler phases") val sourceReader = StringSetting ("-Xsource-reader", "classname", "Specify a custom method for reading source files", "scala.tools.nsc.io.SourceReader") + val Xwarnfatal = BooleanSetting ("-Xfatal-warnings", "Fail the compilation if there are any warnings.") + val Xwarninit = BooleanSetting ("-Xwarninit", "Warn about possible changes in initialization semantics") + val Xchecknull = BooleanSetting ("-Xcheck-null", "Emit warning on selection of nullable reference") /** Compatibility stubs for options whose value name did * not previously match the option name. @@ -133,21 +135,13 @@ trait ScalaSettings extends AbsScalaSettings with StandardScalaSettings { val Ytyperdebug = BooleanSetting ("-Ytyper-debug", "Trace all type assignements") val Ypmatdebug = BooleanSetting ("-Ypmat-debug", "Trace all pattern matcher activity.") val Yrepldebug = BooleanSetting ("-Yrepl-debug", "Trace all repl activity.") + val Ycompletion = BooleanSetting ("-Ycompletion-debug", "Trace all tab completion activity.") val Ypmatnaive = BooleanSetting ("-Ypmat-naive", "Desugar matches as naively as possible..") - val Ytailrec = BooleanSetting ("-Ytailrecommend", "Alert methods which would be tail-recursive if private or final.") val Yjenkins = BooleanSetting ("-Yjenkins-hashCodes", "Use jenkins hash algorithm for case class generated hashCodes.") // Warnings - val Ywarnfatal = BooleanSetting ("-Yfatal-warnings", "Fail the compilation if there are any warnings.") - val Xwarninit = BooleanSetting ("-Xwarninit", "Warn about possible changes in initialization semantics") - val Xchecknull = BooleanSetting ("-Xcheck-null", "Emit warning on selection of nullable reference") - val Xwarndeadcode = BooleanSetting ("-Ywarn-dead-code", "Emit warnings for dead code") - val YwarnShadow = BooleanSetting ("-Ywarn-shadowing", "Emit warnings about possible variable shadowing.") - val YwarnCatches = BooleanSetting ("-Ywarn-catches", "Emit warnings about catch blocks which catch everything.") - val Xwarnings = BooleanSetting ("-Xstrict-warnings", "Emit warnings about lots of things.") . - withPostSetHook(_ => - List(YwarnShadow, YwarnCatches, Xwarndeadcode, Xwarninit) foreach (_.value = true) - ) + val Ywarndeadcode = BooleanSetting ("-Ywarn-dead-code", "Emit warnings for dead code") + /** * "fsc-specific" settings. */ diff --git a/src/compiler/scala/tools/nsc/symtab/Definitions.scala b/src/compiler/scala/tools/nsc/symtab/Definitions.scala index a2382063c3..db48189b6b 100644 --- a/src/compiler/scala/tools/nsc/symtab/Definitions.scala +++ b/src/compiler/scala/tools/nsc/symtab/Definitions.scala @@ -449,7 +449,8 @@ trait Definitions extends reflect.generic.StandardDefinitions { lazy val BooleanBeanPropertyAttr: Symbol = getClass(sn.BooleanBeanProperty) lazy val AnnotationDefaultAttr: Symbol = { - val attr = newClass(RootClass, nme.AnnotationDefaultATTR, List(AnnotationClass.typeConstructor)) + val RuntimePackageClass = getModule("scala.runtime").tpe.typeSymbol + val attr = newClass(RuntimePackageClass, nme.AnnotationDefaultATTR, List(AnnotationClass.typeConstructor)) // This attribute needs a constructor so that modifiers in parsed Java code make sense attr.info.decls enter (attr newConstructor NoPosition setInfo MethodType(Nil, attr.tpe)) attr @@ -815,7 +816,7 @@ trait Definitions extends reflect.generic.StandardDefinitions { StringClass, "+", anyparam, stringtype) setFlag FINAL val forced = List( // force initialization of every symbol that is entered as a side effect - AnnotationDefaultAttr, + AnnotationDefaultAttr, // #2264 RepeatedParamClass, JavaRepeatedParamClass, ByNameParamClass, @@ -839,9 +840,6 @@ trait Definitions extends reflect.generic.StandardDefinitions { Object_asInstanceOf ) - // #2264 - var tmp = AnnotationDefaultAttr - tmp = RepeatedParamClass // force initialization if (forMSIL) { val intType = IntClass.typeConstructor val intParam = List(intType) diff --git a/src/compiler/scala/tools/nsc/symtab/StdNames.scala b/src/compiler/scala/tools/nsc/symtab/StdNames.scala index 5c7e7925ea..9133228768 100644 --- a/src/compiler/scala/tools/nsc/symtab/StdNames.scala +++ b/src/compiler/scala/tools/nsc/symtab/StdNames.scala @@ -95,6 +95,7 @@ trait StdNames extends reflect.generic.StdNames { self: SymbolTable => val SELECTOR_DUMMY = newTermName("<unapply-selector>") val MODULE_INSTANCE_FIELD = newTermName("MODULE$") + val SPECIALIZED_INSTANCE = newTermName("specInstance$") def isLocalName(name: Name) = name.endsWith(LOCAL_SUFFIX) def isSetterName(name: Name) = name.endsWith(SETTER_SUFFIX) @@ -122,6 +123,26 @@ trait StdNames extends reflect.generic.StdNames { self: SymbolTable => } else name } + /** Return the original name and the types on which this name + * is specialized. For example, + * {{{ + * splitSpecializedName("foo$mIcD$sp") == ('foo', "I", "D") + * }}} + * `foo$mIcD$sp` is the name of a method specialized on two type + * parameters, the first one belonging to the method itself, on Int, + * and another one belonging to the enclosing class, on Double. + */ + def splitSpecializedName(name: Name): (Name, String, String) = + if (name.endsWith("$sp")) { + val name1 = name.subName(0, name.length - 3) + val idxC = name1.lastPos('c') + val idxM = name1.lastPos('m', idxC) + (name1.subName(0, idxM - 1).toString, + name1.subName(idxC + 1, name1.length).toString, + name1.subName(idxM + 1, idxC).toString) + } else + (name, "", "") + def localToGetter(name: Name): Name = { assert(isLocalName(name))//debug name.subName(0, name.length - LOCAL_SUFFIX.length) diff --git a/src/compiler/scala/tools/nsc/symtab/Symbols.scala b/src/compiler/scala/tools/nsc/symtab/Symbols.scala index 9c6d6662e3..ebab5d9be7 100644 --- a/src/compiler/scala/tools/nsc/symtab/Symbols.scala +++ b/src/compiler/scala/tools/nsc/symtab/Symbols.scala @@ -606,6 +606,8 @@ trait Symbols extends reflect.generic.Symbols { self: SymbolTable => supersym == NoSymbol || supersym.isIncompleteIn(base) } + // Does not always work if the rawInfo is a SourcefileLoader, see comment + // in "def coreClassesFirst" in Global. final def exists: Boolean = this != NoSymbol && (!owner.isPackageClass || { rawInfo.load(this); rawInfo != NoType }) @@ -910,8 +912,11 @@ trait Symbols extends reflect.generic.Symbols { self: SymbolTable => else { val current = phase try { - while (phase.keepsTypeParams && (phase.prev ne NoPhase)) phase = phase.prev + while ((phase.prev ne NoPhase) && phase.prev.keepsTypeParams) phase = phase.prev +// while (phase.keepsTypeParams && (phase.prev ne NoPhase)) phase = phase.prev if (phase ne current) phase = phase.next + if (settings.debug.value && (phase ne current)) + log("checking unsafeTypeParams(" + this + ") at: " + current + " reading at: " + phase) rawInfo.typeParams } finally { phase = current @@ -1538,7 +1543,7 @@ trait Symbols extends reflect.generic.Symbols { self: SymbolTable => "package object "+owner.nameString else compose(List(kindString, - if (isClassConstructor) owner.simpleName+idString else nameString)) + if (isClassConstructor) owner.simpleName.decode+idString else nameString)) /** If owner is a package object, its owner, else the normal owner. */ @@ -1742,20 +1747,22 @@ trait Symbols extends reflect.generic.Symbols { self: SymbolTable => private var mtpePeriod = NoPeriod private var mtpePre: Type = _ private var mtpeResult: Type = _ + private var mtpeInfo: Type = _ override def cloneSymbolImpl(owner: Symbol): Symbol = new MethodSymbol(owner, pos, name).copyAttrsFrom(this) def typeAsMemberOf(pre: Type): Type = { if (mtpePeriod == currentPeriod) { - if (mtpePre eq pre) return mtpeResult + if ((mtpePre eq pre) && (mtpeInfo eq info)) return mtpeResult } else if (isValid(mtpePeriod)) { mtpePeriod = currentPeriod - if (mtpePre eq pre) return mtpeResult + if ((mtpePre eq pre) && (mtpeInfo eq info)) return mtpeResult } val res = pre.computeMemberType(this) mtpePeriod = currentPeriod mtpePre = pre + mtpeInfo = info mtpeResult = res res } diff --git a/src/compiler/scala/tools/nsc/symtab/Types.scala b/src/compiler/scala/tools/nsc/symtab/Types.scala index 863ad7bec4..8f1ece5b9e 100644 --- a/src/compiler/scala/tools/nsc/symtab/Types.scala +++ b/src/compiler/scala/tools/nsc/symtab/Types.scala @@ -1205,7 +1205,18 @@ trait Types extends reflect.generic.Types { self: SymbolTable => } else { incCounter(compoundBaseTypeSeqCount) baseTypeSeqCache = undetBaseTypeSeq - baseTypeSeqCache = memo(compoundBaseTypeSeq(this))(_.baseTypeSeq updateHead typeSymbol.tpe) + baseTypeSeqCache = if (typeSymbol.isRefinementClass) + memo(compoundBaseTypeSeq(this))(_.baseTypeSeq updateHead typeSymbol.tpe) + else + compoundBaseTypeSeq(this) + // [Martin] suppressing memo-ization solves the problem with "same type after erasure" errors + // when compiling with + // scalac scala.collection.IterableViewLike.scala scala.collection.IterableLike.scala + // I have not yet figured out precisely why this is the case. + // My current assumption is that taking memos forces baseTypeSeqs to be computed + // at stale types (i.e. the underlying typeSymbol has already another type). + // I do not yet see precisely why this would cause a problem, but it looks + // fishy in any case. } } //Console.println("baseTypeSeq(" + typeSymbol + ") = " + baseTypeSeqCache.toList);//DEBUG diff --git a/src/compiler/scala/tools/nsc/symtab/classfile/ClassfileParser.scala b/src/compiler/scala/tools/nsc/symtab/classfile/ClassfileParser.scala index c78664bc19..fc635874a6 100644 --- a/src/compiler/scala/tools/nsc/symtab/classfile/ClassfileParser.scala +++ b/src/compiler/scala/tools/nsc/symtab/classfile/ClassfileParser.scala @@ -876,6 +876,7 @@ abstract class ClassfileParser { val srcfileLeaf = pool.getName(in.nextChar).toString.trim val srcpath = sym.enclosingPackage match { case NoSymbol => srcfileLeaf + case definitions.EmptyPackage => srcfileLeaf case pkg => pkg.fullName(File.separatorChar)+File.separator+srcfileLeaf } srcfile0 = settings.outputDirs.srcFilesFor(in.file, srcpath).find(_.exists) diff --git a/src/compiler/scala/tools/nsc/transform/Constructors.scala b/src/compiler/scala/tools/nsc/transform/Constructors.scala index 7169516560..ad88b783b4 100644 --- a/src/compiler/scala/tools/nsc/transform/Constructors.scala +++ b/src/compiler/scala/tools/nsc/transform/Constructors.scala @@ -1,4 +1,4 @@ -/* NSC -- new Scala compiler +/* NSC -- new Scala compiler * Copyright 2005-2010 LAMP/EPFL * @author */ @@ -25,12 +25,18 @@ abstract class Constructors extends Transform with ast.TreeDSL { new ConstructorTransformer(unit) class ConstructorTransformer(unit: CompilationUnit) extends Transformer { + import collection.mutable + + private val guardedCtorStats: mutable.Map[Symbol, List[Tree]] = new mutable.HashMap[Symbol, List[Tree]] def transformClassTemplate(impl: Template): Template = { val clazz = impl.symbol.owner // the transformed class val stats = impl.body // the transformed template body val localTyper = typer.atOwner(impl, clazz) + val specializedFlag: Symbol = clazz.info.decl(nme.SPECIALIZED_INSTANCE) + val shouldGuard = (specializedFlag != NoSymbol) && !clazz.hasFlag(SPECIALIZED) + var constr: DefDef = null // The primary constructor var constrParams: List[Symbol] = null // ... and its parameters var constrBody: Block = null // ... and its body @@ -68,6 +74,7 @@ abstract class Constructors extends Transform with ast.TreeDSL { } var thisRefSeen: Boolean = false + var usesSpecializedField: Boolean = false // A transformer for expressions that go into the constructor val intoConstructorTransformer = new Transformer { @@ -87,6 +94,8 @@ abstract class Constructors extends Transform with ast.TreeDSL { gen.mkAttributedIdent(parameter(tree.symbol)) setPos tree.pos case Select(_, _) => thisRefSeen = true + if (specializeTypes.specializedTypeVars(tree.symbol).nonEmpty) + usesSpecializedField = true super.transform(tree) case This(_) => thisRefSeen = true @@ -275,12 +284,106 @@ abstract class Constructors extends Transform with ast.TreeDSL { copyParam(acc, parameter(acc)) } + /** Return a single list of statements, merging the generic class constructor with the + * specialized stats. The original statements are retyped in the current class, and + * assignments to generic fields that have a corresponding specialized assignment in + * `specializedStats` are replaced by the specialized assignment. + */ + def mergeConstructors(genericClazz: Symbol, originalStats: List[Tree], specializedStats: List[Tree]): List[Tree] = { + val specBuf = new ListBuffer[Tree] + specBuf ++= specializedStats + + def specializedAssignFor(sym: Symbol): Option[Tree] = + specializedStats.find { + case Assign(sel @ Select(This(_), _), rhs) if sel.symbol.hasFlag(SPECIALIZED) => + val (generic, _, _) = nme.splitSpecializedName(nme.localToGetter(sel.symbol.name)) + generic == nme.localToGetter(sym.name) + case _ => false + } + + log("merging: " + originalStats.mkString("\n") + " : " + specializedStats.mkString("\n")) + val res = for (s <- originalStats; val stat = s.duplicate) yield { + log("merge: looking at " + stat) + val stat1 = stat match { + case Assign(sel @ Select(This(_), field), _) => + specializedAssignFor(sel.symbol).getOrElse(stat) + case _ => stat + } + if (stat1 ne stat) { + log("replaced " + stat + " with " + stat1) + specBuf -= stat1 + } + + if (stat1 eq stat) { + // statements coming from the original class need retyping in the current context + if (settings.debug.value) log("retyping " + stat1) + val d = new specializeTypes.Duplicator + d.retyped(localTyper.context1.asInstanceOf[d.Context], + stat1, + genericClazz, + clazz, + Map.empty) + } else + stat1 + } + if (specBuf.nonEmpty) + println("residual specialized constructor statements: " + specBuf) + res + } + + /** Add an 'if' around the statements coming after the super constructor. This + * guard is necessary if the code uses specialized fields. A specialized field is + * initialized in the subclass constructor, but the accessors are (already) overridden + * and pointing to the (empty) fields. To fix this, a class with specialized fields + * will not run its constructor statements if the instance is specialized. The specialized + * subclass includes a copy of those constructor statements, and runs them. To flag that a class + * has specialized fields, and their initialization should be deferred to the subclass, method + * 'specInstance$' is added in phase specialize. + */ + def guardSpecializedInitializer(stats0: List[Tree]): List[Tree] = if (settings.nospecialization.value) stats0 else { + // split the statements in presuper and postsuper + var (prefix, postfix) = stats0.span(tree => !((tree.symbol ne null) && tree.symbol.isConstructor)) + if (postfix.nonEmpty) { + prefix = prefix :+ postfix.head + postfix = postfix.tail + } + + if (usesSpecializedField && shouldGuard && postfix.nonEmpty) { + // save them for duplication in the specialized subclass + guardedCtorStats(clazz) = postfix + + val tree = + If( + Apply( + Select( + Apply(gen.mkAttributedRef(specializedFlag), List()), + definitions.getMember(definitions.BooleanClass, nme.UNARY_!)), + List()), + Block(postfix, Literal(())), + EmptyTree) + + prefix ::: List(localTyper.typed(tree)) + } else if (clazz.hasFlag(SPECIALIZED)) { + // add initialization from its generic class constructor + val (genericName, _, _) = nme.splitSpecializedName(clazz.name) + val genericClazz = clazz.owner.info.decl(genericName.toTypeName) + assert(genericClazz != NoSymbol) + + guardedCtorStats.get(genericClazz) match { + case Some(stats1) => + val merged = mergeConstructors(genericClazz, stats1, postfix) + prefix ::: merged + case None => stats0 + } + } else stats0 + } + // Assemble final constructor defBuf += treeCopy.DefDef( constr, constr.mods, constr.name, constr.tparams, constr.vparamss, constr.tpt, treeCopy.Block( constrBody, - paramInits ::: constrPrefixBuf.toList ::: constrStatBuf.toList, + paramInits ::: constrPrefixBuf.toList ::: guardSpecializedInitializer(constrStatBuf.toList), constrBody.expr)); // Unlink all fields that can be dropped from class scope diff --git a/src/compiler/scala/tools/nsc/transform/Erasure.scala b/src/compiler/scala/tools/nsc/transform/Erasure.scala index 94d52bc1cb..88a7e13d80 100644 --- a/src/compiler/scala/tools/nsc/transform/Erasure.scala +++ b/src/compiler/scala/tools/nsc/transform/Erasure.scala @@ -152,7 +152,7 @@ abstract class Erasure extends AddInterfaces with typechecker.Analyzer with ast. if (unboundedGenericArrayLevel(tp) == 1) ObjectClass.tpe else if (args.head.typeSymbol == NothingClass || args.head.typeSymbol == NullClass) arrayType(ObjectClass.tpe) else typeRef(apply(pre), sym, args map this) - else if (sym == AnyClass || sym == AnyValClass || sym == SingletonClass) erasedTypeRef(ObjectClass) + else if (sym == AnyClass || sym == AnyValClass || sym == SingletonClass || sym == NotNullClass) erasedTypeRef(ObjectClass) else if (sym == UnitClass) erasedTypeRef(BoxedUnitClass) else if (sym.isRefinementClass) apply(intersectionDominator(tp.parents)) else if (sym.isClass) typeRef(apply(rebindInnerClass(pre, sym)), sym, List()) // #2585 diff --git a/src/compiler/scala/tools/nsc/transform/SpecializeTypes.scala b/src/compiler/scala/tools/nsc/transform/SpecializeTypes.scala index d5d7ca254e..1ab310282d 100644 --- a/src/compiler/scala/tools/nsc/transform/SpecializeTypes.scala +++ b/src/compiler/scala/tools/nsc/transform/SpecializeTypes.scala @@ -119,6 +119,8 @@ abstract class SpecializeTypes extends InfoTransform with TypingTransformers { * type bounds of other @specialized type parameters (and not in its result type). */ def degenerate = false + + def isAccessor = false } /** Symbol is a special overloaded method of 'original', in the environment env. */ @@ -132,11 +134,16 @@ abstract class SpecializeTypes extends InfoTransform with TypingTransformers { } /** Symbol is a specialized accessor for the `target' field. */ - case class SpecializedAccessor(target: Symbol) extends SpecializedInfo + case class SpecializedAccessor(target: Symbol) extends SpecializedInfo { + override def isAccessor = true + } /** Symbol is a specialized method whose body should be the target's method body. */ case class Implementation(target: Symbol) extends SpecializedInfo + /** Symbol is a specialized override paired with `target'. */ + case class SpecialOverride(target: Symbol) extends SpecializedInfo + /** An Inner class that specializes on a type parameter of the enclosing class. */ case class SpecializedInnerClass(target: Symbol, env: TypeEnv) extends SpecializedInfo @@ -217,18 +224,6 @@ abstract class SpecializeTypes extends InfoTransform with TypingTransformers { * specialization on method type parameters, the second on outer environment. */ private def specializedName(name: Name, types1: List[Type], types2: List[Type]): Name = { - def split: (String, String, String) = { - if (name.endsWith("$sp")) { - val name1 = name.subName(0, name.length - 3) - val idxC = name1.lastPos('c') - val idxM = name1.lastPos('m', idxC) - (name1.subName(0, idxM - 1).toString, - name1.subName(idxC + 1, name1.length).toString, - name1.subName(idxM + 1, idxC).toString) - } else - (name.toString, "", "") - } - if (nme.INITIALIZER == name || (types1.isEmpty && types2.isEmpty)) name else if (nme.isSetterName(name)) @@ -236,8 +231,8 @@ abstract class SpecializeTypes extends InfoTransform with TypingTransformers { else if (nme.isLocalName(name)) nme.getterToLocal(specializedName(nme.localToGetter(name), types1, types2)) else { - val (base, cs, ms) = split - newTermName(base + "$" + val (base, cs, ms) = nme.splitSpecializedName(name) + newTermName(base.toString + "$" + "m" + ms + types1.map(t => definitions.abbrvTag(t.typeSymbol)).mkString("", "", "") + "c" + cs + types2.map(t => definitions.abbrvTag(t.typeSymbol)).mkString("", "", "$sp")) } @@ -319,21 +314,20 @@ abstract class SpecializeTypes extends InfoTransform with TypingTransformers { })) - private def specializedTypeVars(tpe: List[Type]): immutable.Set[Symbol] = + def specializedTypeVars(tpe: List[Type]): immutable.Set[Symbol] = tpe.foldLeft(immutable.ListSet.empty[Symbol]: immutable.Set[Symbol]) { (s, tp) => s ++ specializedTypeVars(tp) } - private def specializedTypeVars(sym: Symbol): immutable.Set[Symbol] = + def specializedTypeVars(sym: Symbol): immutable.Set[Symbol] = specializedTypeVars(atPhase(currentRun.typerPhase)(sym.info)) /** Return the set of @specialized type variables mentioned by the given type. */ - private def specializedTypeVars(tpe: Type): immutable.Set[Symbol] = tpe match { + def specializedTypeVars(tpe: Type): immutable.Set[Symbol] = tpe match { case TypeRef(pre, sym, args) => if (sym.isTypeParameter && sym.hasAnnotation(SpecializedClass)) specializedTypeVars(args) + sym else if (sym.isTypeSkolem && sym.deSkolemize.hasAnnotation(SpecializedClass)) { - println("cought skolem without @specialized") specializedTypeVars(args) + sym } else specializedTypeVars(args) @@ -680,48 +674,51 @@ abstract class SpecializeTypes extends InfoTransform with TypingTransformers { */ private def specialOverrides(clazz: Symbol): List[Symbol] = { log("specialOverrides(" + clazz + ")") - val opc = new overridingPairs.Cursor(clazz) val oms = new mutable.ListBuffer[Symbol] - while (opc.hasNext) { + for (overriding <- clazz.info.decls; + val allOverridden = overriding.allOverriddenSymbols + if !allOverridden.isEmpty; + val overridden = allOverridden.head) { if (settings.debug.value) - log("\toverriding pairs: " + opc.overridden.fullName + ": " + opc.overridden.info - + " overriden by " + opc.overriding.fullName + ": " + opc.overriding.info) - if (opc.overriding.owner == clazz && !specializedTypeVars(opc.overridden.info).isEmpty) { - if (settings.debug.value) log("\t\tspecializedTVars: " + specializedTypeVars(opc.overridden.info)) - val env = unify(opc.overridden.info, opc.overriding.info, emptyEnv) + log("\toverriding pairs: " + overridden.fullName + ": " + overridden.info + + " overriden by " + overriding.fullName + ": " + overriding.info) + if (overriding.owner == clazz && !specializedTypeVars(overridden.info).isEmpty) { + if (settings.debug.value) log("\t\tspecializedTVars: " + specializedTypeVars(overridden.info)) + val env = unify(overridden.info, overriding.info, emptyEnv) if (settings.debug.value) log("\t\tenv: " + env + "isValid: " - + TypeEnv.isValid(env, opc.overridden) - + " exists: " + opc.overridden.owner.info.decl(specializedName(opc.overridden, env))) + + TypeEnv.isValid(env, overridden) + + " looking for: " + specializedName(overridden, env) + " in:\n" + + atPhase(phase.next)(overridden.owner.info.decls) + + "found: " + atPhase(phase.next)(overridden.owner.info.decl(specializedName(overridden, env)))) if (!env.isEmpty - && TypeEnv.isValid(env, opc.overridden) - && opc.overridden.owner.info.decl(specializedName(opc.overridden, env)) != NoSymbol) { - log("Added specialized overload for " + opc.overriding.fullName + " in env: " + env) - val om = specializedOverload(clazz, opc.overridden, env) + && TypeEnv.isValid(env, overridden) + && atPhase(phase.next)(overridden.owner.info.decl(specializedName(overridden, env))) != NoSymbol) { + log("Added specialized overload for " + overriding.fullName + " in env: " + env) + val om = specializedOverload(clazz, overridden, env) typeEnv(om) = env - if (!opc.overriding.isDeferred) { - concreteSpecMethods += opc.overriding + if (!overriding.isDeferred) { + concreteSpecMethods += overriding // if the override is a normalized member, 'om' gets the implementation from // its original target, and adds the environment of the normalized member (that is, // any specialized /method/ type parameter bindings) - info(om) = info.get(opc.overriding) match { + info(om) = info.get(overriding) match { case Some(NormalizedMember(target)) => - typeEnv(om) = env ++ typeEnv(opc.overriding) - Implementation(target) - case _ => Implementation(opc.overriding) + typeEnv(om) = env ++ typeEnv(overriding) + SpecialOverride(target) + case _ => SpecialOverride(overriding) } - info(opc.overriding) = Forward(om) + info(overriding) = Forward(om) log("typeEnv(om) = " + typeEnv(om)) - om setPos opc.overriding.pos // set the position of the concrete, overriding member + om setPos overriding.pos // set the position of the concrete, overriding member } - overloads(opc.overriding) = Overload(om, env) :: overloads(opc.overriding) + overloads(overriding) = Overload(om, env) :: overloads(overriding) oms += om atPhase(phase.next)( - assert(opc.overridden.owner.info.decl(om.name) != NoSymbol, - "Could not find " + om.name + " in " + opc.overridden.owner.info.decls)) + assert(overridden.owner.info.decl(om.name) != NoSymbol, + "Could not find " + om.name + " in " + overridden.owner.info.decls)) } } - opc.next } oms.toList } @@ -827,13 +824,19 @@ abstract class SpecializeTypes extends InfoTransform with TypingTransformers { case PolyType(targs, ClassInfoType(base, decls, clazz)) => val parents = base map specializedType log("transformInfo (poly) " + clazz + " with parents1: " + parents + " ph: " + phase) - PolyType(targs, ClassInfoType(parents, new Scope(specializeClass(clazz, typeEnv(clazz))), clazz)) +// if (clazz.name.toString == "$colon$colon") +// (new Throwable).printStackTrace + PolyType(targs, ClassInfoType(parents, + new Scope(specializeClass(clazz, typeEnv(clazz)) ::: specialOverrides(clazz)), + clazz)) case ClassInfoType(base, decls, clazz) if !clazz.isPackageClass => atPhase(phase.next)(base.map(_.typeSymbol.info)) val parents = base map specializedType log("transformInfo " + clazz + " with parents1: " + parents + " ph: " + phase) - val res = ClassInfoType(base map specializedType, new Scope(specializeClass(clazz, typeEnv(clazz))), clazz) + val res = ClassInfoType(base map specializedType, + new Scope(specializeClass(clazz, typeEnv(clazz)) ::: specialOverrides(clazz)), + clazz) res case _ => @@ -893,16 +896,12 @@ abstract class SpecializeTypes extends InfoTransform with TypingTransformers { satisfiable(env, silent) } - import java.io.PrintWriter - /*************************** Term transformation ************************************/ class Duplicator extends { val global: SpecializeTypes.this.global.type = SpecializeTypes.this.global } with typechecker.Duplicators - import global.typer.typed - def specializeCalls(unit: CompilationUnit) = new TypingTransformer(unit) { /** Map a specializable method to it's rhs, when not deferred. */ val body: mutable.Map[Symbol, Tree] = new mutable.HashMap @@ -928,8 +927,6 @@ abstract class SpecializeTypes extends InfoTransform with TypingTransformers { } } - import posAssigner._ - override def transform(tree: Tree): Tree = { val symbol = tree.symbol @@ -1036,7 +1033,7 @@ abstract class SpecializeTypes extends InfoTransform with TypingTransformers { case (tpe, idx) => TypeTree(tpe) setPos parents(idx).pos } treeCopy.Template(tree, - parents1 /*currentOwner.info.parents.map(tpe => TypeTree(tpe) setPos parents.head.pos)*/, + parents1 /*currentOwner.info.parents.map(tpe => TypeTree(tpe) setPos parents.head.pos)*/ , self, atOwner(currentOwner)(transformTrees(body ::: specMembers))) @@ -1091,6 +1088,18 @@ abstract class SpecializeTypes extends InfoTransform with TypingTransformers { treeCopy.DefDef(tree1, mods, name, tparams, vparamss, tpt, transform(rhs)) } + case SpecialOverride(target) => + assert(body.isDefinedAt(target), "sym: " + symbol.fullName + " target: " + target.fullName) + if (settings.debug.value) log("moving implementation: " + body(target)) + // we have an rhs, specialize it + val tree1 = addBody(ddef, target) + (new ChangeOwnerTraverser(target, tree1.symbol))(tree1.rhs) + if (settings.debug.value) + println("changed owners, now: " + tree1) + val DefDef(mods, name, tparams, vparamss, tpt, rhs) = tree1 + treeCopy.DefDef(tree1, mods, name, tparams, vparamss, tpt, transform(rhs)) + + case SpecialOverload(original, env) => log("completing specialized " + symbol.fullName + " calling " + original) val t = DefDef(symbol, { vparamss => @@ -1118,7 +1127,7 @@ abstract class SpecializeTypes extends InfoTransform with TypingTransformers { localTyper.typed(treeCopy.DefDef(tree, mods, name, tparams, vparamss, tpt, rhs1)) } - case ValDef(mods, name, tpt, rhs) if symbol.hasFlag(SPECIALIZED) => + case ValDef(mods, name, tpt, rhs) if symbol.hasFlag(SPECIALIZED) && !symbol.hasFlag(PARAMACCESSOR) => assert(body.isDefinedAt(symbol.alias)) val tree1 = treeCopy.ValDef(tree, mods, name, tpt, body(symbol.alias).duplicate) if (settings.debug.value) log("now typing: " + tree1 + " in " + tree.symbol.owner.fullName) @@ -1128,8 +1137,20 @@ abstract class SpecializeTypes extends InfoTransform with TypingTransformers { symbol.alias.enclClass, symbol.enclClass, typeEnv(symbol.alias) ++ typeEnv(tree.symbol)) +// val tree1 = +// treeCopy.ValDef(tree, mods, name, tpt, +// localTyper.typed( +// Apply(Select(Super(currentClass, nme.EMPTY), symbol.alias.getter(symbol.alias.owner)), +// List()))) +// if (settings.debug.value) log("replaced ValDef: " + tree1 + " in " + tree.symbol.owner.fullName) +// tree1 + + case Apply(sel @ Select(sup @ Super(qual, name), name1), args) + if (sup.symbol.info.parents != atPhase(phase.prev)(sup.symbol.info.parents)) => + + def parents = sup.symbol.info.parents + log(tree + " parents changed from: " + atPhase(phase.prev)(parents) + " to: " + parents) - case Apply(sel @ Select(sup @ Super(qual, name), name1), args) => val res = localTyper.typed( Apply(Select(Super(qual, name) setPos sup.pos, name1) setPos sel.pos, transformTrees(args)) setPos tree.pos) log("retyping call to super, from: " + symbol + " to " + res.symbol) @@ -1146,11 +1167,31 @@ abstract class SpecializeTypes extends InfoTransform with TypingTransformers { (tparams1, tparams map (_.symbol)) } - private def duplicateBody(tree: DefDef, target: Symbol): Tree = { + + private def duplicateBody(tree: DefDef, source: Symbol) = { + val symbol = tree.symbol + val meth = addBody(tree, source) + log("now typing: " + meth + " in " + symbol.owner.fullName) + val d = new Duplicator + d.retyped(localTyper.context1.asInstanceOf[d.Context], + meth, + source.enclClass, + symbol.enclClass, + typeEnv(source) ++ typeEnv(symbol)) + } + + + /** Put the body of 'source' as the right hand side of the method 'tree'. + * The destination method gets fresh symbols for type and value parameters, + * and the body is updated to the new symbols, and owners adjusted accordingly. + * However, if the same source tree is used in more than one place, full re-typing + * is necessary. @see method duplicateBody + */ + private def addBody(tree: DefDef, source: Symbol): DefDef = { val symbol = tree.symbol if (settings.debug.value) log("specializing body of" + symbol.fullName + ": " + symbol.info) val DefDef(mods, name, tparams, vparamss, tpt, _) = tree - val (_, origtparams) = splitParams(target.typeParams) + val (_, origtparams) = splitParams(source.typeParams) if (settings.debug.value) log("substituting " + origtparams + " for " + symbol.typeParams) // skolemize type parameters @@ -1163,21 +1204,12 @@ abstract class SpecializeTypes extends InfoTransform with TypingTransformers { // replace value and type parameters of the old method with the new ones val symSubstituter = new ImplementationAdapter( - parameters(target).flatten ::: origtparams, + parameters(source).flatten ::: origtparams, vparamss1.flatten.map(_.symbol) ::: newtparams) - val adapter = new AdaptSpecializedValues - val tmp = symSubstituter(adapter(body(target).duplicate)) + val tmp = symSubstituter(body(source).duplicate) tpt.tpe = tpt.tpe.substSym(oldtparams, newtparams) - val meth = treeCopy.DefDef(tree, mods, name, tparams, vparamss1, tpt, tmp) - - log("now typing: " + meth + " in " + symbol.owner.fullName) - val d = new Duplicator - d.retyped(localTyper.context1.asInstanceOf[d.Context], - meth, - target.enclClass, - symbol.enclClass, - typeEnv(target) ++ typeEnv(symbol)) + treeCopy.DefDef(tree, mods, name, tparams, vparamss1, tpt, tmp) } /** A tree symbol substituter that substitutes on type skolems. @@ -1201,10 +1233,12 @@ abstract class SpecializeTypes extends InfoTransform with TypingTransformers { */ override def transform(tree: Tree): Tree = tree match { case Select(qual, name) => - if (tree.symbol.hasFlag(PRIVATE | PROTECTED)) { - log("changing private flag of " + tree.symbol + " privateWithin: " + tree.symbol.privateWithin) + val sym = tree.symbol + if (sym.hasFlag(PRIVATE | PROTECTED) && !nme.isLocalName(sym.name) + && (currentClass != sym.owner.enclClass)) { + log("changing private flag of " + sym) // tree.symbol.resetFlag(PRIVATE).setFlag(PROTECTED) - tree.symbol.makeNotPrivate(tree.symbol.owner) + sym.makeNotPrivate(sym.owner) // tree.symbol.resetFlag(PRIVATE | PROTECTED) // tree.symbol.privateWithin = NoSymbol } @@ -1215,36 +1249,6 @@ abstract class SpecializeTypes extends InfoTransform with TypingTransformers { } } - /** Does the given tree need a cast to a type parameter's upper bound? - * A cast is needed for values of type A, where A is a specialized type - * variable with a non-trivial upper bound. When A is specialized, its - * specialization may not satisfy the upper bound. We generate casts to - * be able to type check code. Such methods will never be called, as they - * are not visible to the user. The compiler will insert such calls only when - * the bounds are satisfied. - */ - private class AdaptSpecializedValues extends Transformer { - private def needsCast(tree: Tree): Boolean = { - val sym = tree.tpe.typeSymbol - (sym.isTypeParameterOrSkolem - && sym.hasAnnotation(SpecializedClass) - && sym.info.bounds.hi != definitions.AnyClass.tpe - /*&& !(tree.tpe <:< sym.info.bounds.hi)*/) - } - - override def transform(tree: Tree): Tree = { - val tree1 = super.transform(tree) - if (needsCast(tree1)) { -// log("inserting cast for " + tree1 + " tpe: " + tree1.tpe) -// val tree2 = gen.mkAsInstanceOf(tree1, tree1.tpe.typeSymbol.info.bounds.hi) -// log(" casted to: " + tree2) - tree1 - } else - tree1 - } - def apply(t: Tree): Tree = transform(t) - } - def warn(clazz: Symbol)(pos: Position, err: String) = if (!clazz.hasFlag(SPECIALIZED)) unit.warning(pos, err) @@ -1254,9 +1258,10 @@ abstract class SpecializeTypes extends InfoTransform with TypingTransformers { */ private def makeSpecializedMembers(cls: Symbol): List[Tree] = { // add special overrides first - if (!cls.hasFlag(SPECIALIZED)) - for (m <- specialOverrides(cls)) cls.info.decls.enter(m) +// if (!cls.hasFlag(SPECIALIZED)) +// for (m <- specialOverrides(cls)) cls.info.decls.enter(m) val mbrs = new mutable.ListBuffer[Tree] + var hasSpecializedFields = false for (m <- cls.info.decls.toList if m.hasFlag(SPECIALIZED) @@ -1264,6 +1269,7 @@ abstract class SpecializeTypes extends InfoTransform with TypingTransformers { && satisfiable(typeEnv(m), warn(cls))) { log("creating tree for " + m.fullName) if (m.isMethod) { + if (info(m).target.isGetterOrSetter) hasSpecializedFields = true if (m.isClassConstructor) { val origParamss = parameters(info(m).target) assert(origParamss.length == 1) // we are after uncurry @@ -1294,6 +1300,14 @@ abstract class SpecializeTypes extends InfoTransform with TypingTransformers { // log("created synthetic class: " + m.fullName) } } + if (hasSpecializedFields) { + val sym = cls.newMethod(nme.SPECIALIZED_INSTANCE, cls.pos) + .setInfo(MethodType(Nil, definitions.BooleanClass.tpe)) + cls.info.decls.enter(sym) + mbrs += atPos(sym.pos) { + DefDef(sym, Literal(cls.hasFlag(SPECIALIZED)).setType(sym.tpe.finalResultType)).setType(NoType) + } + } mbrs.toList } @@ -1372,6 +1386,7 @@ abstract class SpecializeTypes extends InfoTransform with TypingTransformers { } class SpecializationTransformer(unit: CompilationUnit) extends Transformer { + log("specializing " + unit) override def transform(tree: Tree) = atPhase(phase.next) { val res = specializeCalls(unit).transform(tree) diff --git a/src/compiler/scala/tools/nsc/transform/TailCalls.scala b/src/compiler/scala/tools/nsc/transform/TailCalls.scala index adeab550ee..9fff8534d3 100644 --- a/src/compiler/scala/tools/nsc/transform/TailCalls.scala +++ b/src/compiler/scala/tools/nsc/transform/TailCalls.scala @@ -39,9 +39,6 @@ abstract class TailCalls extends Transform } } - /** The @tailrec annotation indicates TCO is mandatory */ - private def tailrecRequired(defdef: DefDef) = defdef.symbol hasAnnotation TailrecClass - /** * A Tail Call Transformer * @@ -105,6 +102,9 @@ abstract class TailCalls extends Transform /** Tells whether we are in a (possible) tail position */ var tailPos = false + /** The reason this method could not be optimized. */ + var tailrecFailReason = "it contains a recursive call not in tail position" + /** Is the label accessed? */ var accessed = false @@ -138,7 +138,8 @@ abstract class TailCalls extends Transform t } - private var ctx: Context = new Context() + private var ctx: Context = new Context() + private def enclosingType = ctx.currentMethod.enclClass.typeOfThis /** Rewrite this tree to contain no tail recursive calls */ def transform(tree: Tree, nctx: Context): Tree = { @@ -150,11 +151,50 @@ abstract class TailCalls extends Transform } override def transform(tree: Tree): Tree = { + /** A possibly polymorphic apply to be considered for tail call transformation. + */ + def rewriteApply(target: Tree, fun: Tree, targs: List[Tree], args: List[Tree]) = { + def isRecursiveCall = ctx.currentMethod eq fun.symbol + def isMandatory = ctx.currentMethod hasAnnotation TailrecClass + def isEligible = ctx.currentMethod.isEffectivelyFinal + def transformArgs = transformTrees(args, mkContext(ctx, false)) + def matchesTypeArgs = ctx.tparams sameElements (targs map (_.tpe.typeSymbol)) + def defaultTree = treeCopy.Apply(tree, target, transformArgs) + + def sameTypeOfThis(receiver: Tree) = + receiver.tpe.widen =:= enclosingType.widen + + /** Records failure reason in Context for reporting. + */ + def cannotRewrite(reason: String) = { + if (isMandatory) + ctx.tailrecFailReason = reason + + defaultTree + } + def rewriteTailCall(receiver: Tree, otherArgs: List[Tree]): Tree = { + log("Rewriting tail recursive method call at: " + fun.pos) + + ctx.accessed = true + typed { atPos(fun.pos)(Apply(Ident(ctx.label), receiver :: otherArgs)) } + } + + if (!isRecursiveCall) defaultTree + else if (!isEligible) cannotRewrite("it is neither private nor final so can be overridden") + else if (!ctx.tailPos) cannotRewrite("it contains a recursive call not in tail position") + else if (!matchesTypeArgs) cannotRewrite("it is called recursively with different type arguments") + else fun match { + case Select(_, _) if forMSIL => cannotRewrite("it cannot be optimized on MSIL") + case Select(qual, _) if !sameTypeOfThis(qual) => cannotRewrite("it changes type of 'this' on a polymorphic recursive call") + case Select(qual, _) => rewriteTailCall(qual, transformArgs) + case _ => rewriteTailCall(This(currentClass), transformArgs) + } + } + tree match { case dd @ DefDef(mods, name, tparams, vparams, tpt, rhs) => log("Entering DefDef: " + name) - var isTransformed = false val newCtx = mkContext(ctx) newCtx.currentMethod = tree.symbol newCtx.makeLabel() @@ -162,16 +202,10 @@ abstract class TailCalls extends Transform newCtx.label.setInfo(MethodType(currentClassParam :: tree.symbol.tpe.params, tree.symbol.tpe.finalResultType)) newCtx.tailPos = true - val isEligible = newCtx.currentMethod.isEffectivelyFinal || (newCtx.currentMethod.enclClass hasFlag Flags.MODULE) - // If -Ytailrecommend is given, we speculatively try transforming ineligible methods and - // report where we would have been successful. - val recommend = settings.Ytailrec.value - val savedFlags: Option[Long] = if (recommend) Some(newCtx.currentMethod.flags) else None - - if (isEligible || recommend) { - if (recommend) - newCtx.currentMethod.flags |= Flags.FINAL + val isEligible = newCtx.currentMethod.isEffectivelyFinal + val isMandatory = dd.symbol.hasAnnotation(TailrecClass) && !forMSIL // @tailrec annotation indicates mandatory transformation + if (isEligible) { newCtx.tparams = Nil log(" Considering " + name + " for tailcalls") tree.symbol.tpe match { @@ -185,12 +219,10 @@ abstract class TailCalls extends Transform val t1 = treeCopy.DefDef(tree, mods, name, tparams, vparams, tpt, { val transformed = transform(rhs, newCtx) - savedFlags foreach (newCtx.currentMethod.flags = _) transformed match { case newRHS if isEligible && newCtx.accessed => log("Rewrote def " + newCtx.currentMethod) - isTransformed = true val newThis = newCtx.currentMethod . newValue (tree.pos, nme.THIS) . setInfo (currentClass.typeOfThis) @@ -200,19 +232,14 @@ abstract class TailCalls extends Transform List(ValDef(newThis, This(currentClass))), LabelDef(newCtx.label, newThis :: (vparams.flatten map (_.symbol)), newRHS) ))) - case _ if recommend => - if (newCtx.accessed) - unit.warning(dd.pos, "method is tailrecommended") - // transform with the original flags restored - transform(rhs, newCtx) + case rhs => + if (isMandatory) + unit.error(dd.pos, "could not optimize @tailrec annotated method: " + newCtx.tailrecFailReason) - case rhs => rhs + rhs } }) - if (!forMSIL && !isTransformed && tailrecRequired(dd)) - unit.error(dd.pos, "could not optimize @tailrec annotated method") - log("Leaving DefDef: " + name) t1 @@ -269,50 +296,16 @@ abstract class TailCalls extends Transform case Typed(expr, tpt) => super.transform(tree) case Apply(tapply @ TypeApply(fun, targs), vargs) => - lazy val defaultTree = treeCopy.Apply(tree, tapply, transformTrees(vargs, mkContext(ctx, false))) - if ( ctx.currentMethod.isEffectivelyFinal && - ctx.tailPos && - isSameTypes(ctx.tparams, targs map (_.tpe.typeSymbol)) && - isRecursiveCall(fun)) { - fun match { - case Select(receiver, _) => - val recTpe = receiver.tpe.widen - val enclTpe = ctx.currentMethod.enclClass.typeOfThis - // make sure the type of 'this' doesn't change through this polymorphic recursive call - if (!forMSIL && - (receiver.tpe.typeParams.isEmpty || - (receiver.tpe.widen == ctx.currentMethod.enclClass.typeOfThis))) - rewriteTailCall(fun, receiver :: transformTrees(vargs, mkContext(ctx, false))) - else - defaultTree - case _ => rewriteTailCall(fun, This(currentClass) :: transformTrees(vargs, mkContext(ctx, false))) - } - } else - defaultTree + rewriteApply(tapply, fun, targs, vargs) case TypeApply(fun, args) => super.transform(tree) - case Apply(fun, args) if (fun.symbol == definitions.Boolean_or || - fun.symbol == definitions.Boolean_and) => - treeCopy.Apply(tree, fun, transformTrees(args)) - case Apply(fun, args) => - lazy val defaultTree = treeCopy.Apply(tree, fun, transformTrees(args, mkContext(ctx, false))) - if (ctx.currentMethod.isEffectivelyFinal && - ctx.tailPos && - isRecursiveCall(fun)) { - fun match { - case Select(receiver, _) => - if (!forMSIL) - rewriteTailCall(fun, receiver :: transformTrees(args, mkContext(ctx, false))) - else - defaultTree - case _ => rewriteTailCall(fun, This(currentClass) :: transformTrees(args, mkContext(ctx, false))) - } - } else - defaultTree - + if (fun.symbol == Boolean_or || fun.symbol == Boolean_and) + treeCopy.Apply(tree, fun, transformTrees(args)) + else + rewriteApply(fun, fun, Nil, args) case Super(qual, mix) => tree @@ -333,28 +326,5 @@ abstract class TailCalls extends Transform def transformTrees(trees: List[Tree], nctx: Context): List[Tree] = trees map ((tree) => transform(tree, nctx)) - - private def rewriteTailCall(fun: Tree, args: List[Tree]): Tree = { - log("Rewriting tail recursive method call at: " + - (fun.pos)) - ctx.accessed = true - //println("fun: " + fun + " args: " + args) - val t = atPos(fun.pos)(Apply(Ident(ctx.label), args)) - // println("TAIL: "+t) - typed(t) - } - - private def isSameTypes(ts1: List[Symbol], ts2: List[Symbol]) = ts1 sameElements ts2 - - /** Returns <code>true</code> if the fun tree refers to the same method as - * the one saved in <code>ctx</code>. - * - * @param fun the expression that is applied - * @return <code>true</code> if the tree symbol refers to the innermost - * enclosing method - */ - private def isRecursiveCall(fun: Tree): Boolean = - (fun.symbol eq ctx.currentMethod) } - } diff --git a/src/compiler/scala/tools/nsc/transform/UnCurry.scala b/src/compiler/scala/tools/nsc/transform/UnCurry.scala index e339560837..bad98193b0 100644 --- a/src/compiler/scala/tools/nsc/transform/UnCurry.scala +++ b/src/compiler/scala/tools/nsc/transform/UnCurry.scala @@ -659,11 +659,6 @@ abstract class UnCurry extends InfoTransform with TypingTransformers { } treeCopy.DefDef(tree, mods, name, tparams, List(vparamss.flatten), tpt, rhs1) case Try(body, catches, finalizer) => - // If warnings are enabled, alert about promiscuously catching cases. - if (settings.YwarnCatches.value) - for (cd <- catches find treeInfo.catchesThrowable) - unit.warning(cd.pos, "catch clause swallows everything: not advised.") - if (catches forall treeInfo.isCatchCase) tree else { val exname = unit.fresh.newName(tree.pos, "ex$") diff --git a/src/compiler/scala/tools/nsc/typechecker/Analyzer.scala b/src/compiler/scala/tools/nsc/typechecker/Analyzer.scala index 635608520d..70bf55e661 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Analyzer.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Analyzer.scala @@ -22,6 +22,7 @@ trait Analyzer extends AnyRef with SyntheticMethods with Unapplies with NamesDefaults + with TypeDiagnostics { val global : Global import global._ diff --git a/src/compiler/scala/tools/nsc/typechecker/Contexts.scala b/src/compiler/scala/tools/nsc/typechecker/Contexts.scala index becb4b069f..8758dd834c 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Contexts.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Contexts.scala @@ -256,23 +256,25 @@ trait Contexts { self: Analyzer => if (diagnostic.isEmpty) "" else diagnostic.mkString("\n","\n", "") - def error(pos: Position, err: Throwable) { - val msg = err.getMessage() + diagString - if (reportGeneralErrors) - unit.error(pos, if (checking) "**** ERROR DURING INTERNAL CHECKING ****\n" + msg else msg) - else - throw err + private def addDiagString(msg: String) = { + val ds = diagString + if (msg endsWith ds) msg else msg + ds } - def error(pos: Position, msg: String) { - val msg1 = msg + diagString - if (reportGeneralErrors) - unit.error(pos, if (checking) "**** ERROR DURING INTERNAL CHECKING ****\n" + msg1 else msg1) - else - throw new TypeError(pos, msg1) + private def unitError(pos: Position, msg: String) = + unit.error(pos, if (checking) "**** ERROR DURING INTERNAL CHECKING ****\n" + msg else msg) + + def error(pos: Position, err: Throwable) = + if (reportGeneralErrors) unitError(pos, addDiagString(err.getMessage())) + else throw err + + def error(pos: Position, msg: String) = { + val msg1 = addDiagString(msg) + if (reportGeneralErrors) unitError(pos, msg1) + else throw new TypeError(pos, msg1) } - def warning(pos: Position, msg: String) { + def warning(pos: Position, msg: String) = { if (reportGeneralErrors) unit.warning(pos, msg) } @@ -484,6 +486,19 @@ trait Contexts { self: Analyzer => } implicitsCache } + + def lookup(name: Name, expectedOwner: Symbol) = { + var res: Symbol = NoSymbol + var ctx = this + while(res == NoSymbol && ctx.outer != ctx) { + val s = ctx.scope.lookup(name) + if (s != NoSymbol && s.owner == expectedOwner) + res = s + else + ctx = ctx.outer + } + res + } } class ImportInfo(val tree: Import, val depth: Int) { /** The prefix expression */ diff --git a/src/compiler/scala/tools/nsc/typechecker/Duplicators.scala b/src/compiler/scala/tools/nsc/typechecker/Duplicators.scala index 1dbeb0afd9..de9318d6b6 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Duplicators.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Duplicators.scala @@ -94,6 +94,15 @@ abstract class Duplicators extends Analyzer { } else super.mapOver(tpe) + case ThisType(sym) => + val sym1 = updateSym(sym) + if (sym1 ne sym) { + log("fixing " + sym + " -> " + sym1) + ThisType(sym1) + } else + super.mapOver(tpe) + + case _ => super.mapOver(tpe) } @@ -136,7 +145,7 @@ abstract class Duplicators extends Analyzer { ldef.symbol = newsym log("newsym: " + newsym + " info: " + newsym.info) - case DefDef(_, _, tparams, vparamss, _, rhs) => + case DefDef(_, name, tparams, vparamss, _, rhs) => // invalidate parameters invalidate(tparams ::: vparamss.flatten) tree.symbol = NoSymbol @@ -244,6 +253,11 @@ abstract class Duplicators extends Analyzer { if (settings.debug.value) log("mapped " + tree + " to " + tree1) super.typed(atPos(tree.pos)(tree1), mode, pt) + case This(_) => + tree.symbol = updateSym(tree.symbol) + tree.tpe = null + super.typed(tree, mode, pt) + case Super(qual, mix) if (oldClassOwner ne null) && (tree.symbol == oldClassOwner) => val tree1 = Super(qual, mix) log("changed " + tree + " to " + tree1) diff --git a/src/compiler/scala/tools/nsc/typechecker/Implicits.scala b/src/compiler/scala/tools/nsc/typechecker/Implicits.scala index 4c07e50da9..9a6c4cc401 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Implicits.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Implicits.scala @@ -639,9 +639,9 @@ self: Analyzer => val applicable = applicableInfos(implicitInfoss, isLocal, invalidImplicits) if (applicable.isEmpty && !invalidImplicits.isEmpty) { - infer.setAddendum(tree.pos, () => + setAddendum(tree.pos, () => "\n Note: implicit "+invalidImplicits.head+" is not applicable here"+ - "\n because it comes after the application point and it lacks an explicit result type") + " because it comes after the application point and it lacks an explicit result type") } val start = startCounter(subtypeImprovCount) @@ -784,7 +784,6 @@ self: Analyzer => inferImplicit(tree, appliedType(manifestClass.typeConstructor, List(tp)), true, false, context).tree def findSubManifest(tp: Type) = findManifest(tp, if (full) FullManifestClass else OptManifestClass) - def findElemManifest(tp: Type) = findManifest(tp, if (full) FullManifestClass else PartialManifestClass) def mot(tp0: Type): Tree = { val tp1 = tp0.normalize @@ -801,39 +800,26 @@ self: Analyzer => } else if (sym == RepeatedParamClass || sym == ByNameParamClass) { EmptyTree } else if (sym == ArrayClass && args.length == 1) { - manifestFactoryCall("arrayType", args.head, findElemManifest(args.head)) + manifestFactoryCall("arrayType", args.head, findManifest(args.head)) } else if (sym.isClass) { - val suffix = gen.mkClassOf(tp1) :: (args map findSubManifest) + val classarg0 = gen.mkClassOf(tp1) + val classarg = tp match { + case ExistentialType(_, _) => + TypeApply(Select(classarg0, Any_asInstanceOf), + List(TypeTree(appliedType(ClassClass.typeConstructor, List(tp))))) + case _ => + classarg0 + } + val suffix = classarg :: (args map findSubManifest) manifestFactoryCall( "classType", tp, (if ((pre eq NoPrefix) || pre.typeSymbol.isStaticOwner) suffix else findSubManifest(pre) :: suffix): _*) - } else { - EmptyTree -/* the following is dropped because it is dangerous - * - if (sym.isAbstractType) { - if (sym.isExistentiallyBound) - EmptyTree // todo: change to existential parameter manifest - else if (sym.isTypeParameterOrSkolem) - EmptyTree // a manifest should have been found by normal searchImplicit - else { - // The following is tricky! We want to find the parameterized version of - // what will become the erasure of the upper bound. - // But there is a case where the erasure is not a superclass of the current type: - // Any erases to Object. So an abstract type having Any as upper bound will not see - // Object as a baseType. That's why we do the basetype trick only when we must, - // i.e. when the baseclass is parameterized. - var era = erasure.erasure(tp1) - if (era.typeSymbol.typeParams.nonEmpty) - era = tp1.baseType(era.typeSymbol) - manifestFactoryCall( - "abstractType", tp, - findSubManifest(pre) :: Literal(sym.name.toString) :: gen.mkClassOf(era) :: (args map findSubManifest): _*) - } + } else if (sym.isExistentiallyBound && full) { + manifestFactoryCall("wildcardType", tp, + findManifest(tp.bounds.lo), findManifest(tp.bounds.hi)) } else { EmptyTree // a manifest should have been found by normal searchImplicit -*/ } case RefinedType(parents, decls) => // refinement is not generated yet @@ -841,10 +827,7 @@ self: Analyzer => else if (full) manifestFactoryCall("intersectionType", tp, parents map (findSubManifest(_)): _*) else mot(erasure.erasure.intersectionDominator(parents)) case ExistentialType(tparams, result) => - existentialAbstraction(tparams, result) match { - case ExistentialType(_, _) => mot(result) - case t => mot(t) - } + mot(tp1.skolemizeExistential) case _ => EmptyTree } diff --git a/src/compiler/scala/tools/nsc/typechecker/Infer.scala b/src/compiler/scala/tools/nsc/typechecker/Infer.scala index c8d3308077..1ee1604319 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Infer.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Infer.scala @@ -120,7 +120,7 @@ trait Infer { case NoPrefix | ThisType(_) | ConstantType(_) => true case TypeRef(pre, sym, args) => - isFullyDefined(pre) && (args.isEmpty || (args forall isFullyDefined)) + isFullyDefined(pre) && (args forall isFullyDefined) case SingleType(pre, sym) => isFullyDefined(pre) case RefinedType(ts, decls) => @@ -197,72 +197,17 @@ trait Infer { /** The context-dependent inferencer part */ class Inferencer(context: Context) { - /* -- Error Messages --------------------------------------------------- */ - - private var addendumPos: Position = NoPosition - private var addendum: () => String = _ - - def setAddendum(pos: Position, msg: () => String) = { - addendumPos = pos - addendum = msg - } - def setError[T <: Tree](tree: T): T = { - if (tree.hasSymbol) - if (context.reportGeneralErrors) { - val name = newTermName("<error: " + tree.symbol + ">") - tree.setSymbol( - if (tree.isType) context.owner.newErrorClass(name.toTypeName) - else context.owner.newErrorValue(name)) - } else { - tree.setSymbol(if (tree.isType) stdErrorClass else stdErrorValue) - } - tree.setType(ErrorType) - } - - def decode(name: Name): String = - (if (name.isTypeName) "type " else "value ") + name.decode + def name = newTermName("<error: " + tree.symbol + ">") + def errorClass = if (context.reportGeneralErrors) context.owner.newErrorClass(name.toTypeName) else stdErrorClass + def errorValue = if (context.reportGeneralErrors) context.owner.newErrorValue(name) else stdErrorValue + def errorSym = if (tree.isType) errorClass else errorValue - def treeSymTypeMsg(tree: Tree): String = - if (tree.symbol eq null) - "expression of type " + tree.tpe - else if (tree.symbol.hasFlag(OVERLOADED)) - "overloaded method " + tree.symbol + " with alternatives " + tree.tpe - else - tree.symbol.toString() + - (if (tree.symbol.isModule) "" - else if (tree.tpe.paramSectionCount > 0) ": "+tree.tpe - else " of type "+tree.tpe) + - (if (tree.symbol.name == nme.apply) tree.symbol.locationString else "") - - def applyErrorMsg(tree: Tree, msg: String, argtpes: List[Type], pt: Type) = - treeSymTypeMsg(tree) + msg + argtpes.mkString("(", ",", ")") + - (if (isWildcard(pt)) "" else " with expected result type " + pt) - - // todo: use also for other error messages - private def existentialContext(tp: Type) = tp.existentialSkolems match { - case List() => "" - case skolems => - def disambiguate(ss: List[String]) = ss match { - case List() => ss - case s :: ss1 => s :: (ss1 map (s1 => if (s1 == s) "(some other)"+s1 else s1)) - } - " where "+(disambiguate(skolems map (_.existentialToString)) mkString ", ") - } - - def foundReqMsg(found: Type, req: Type): String = - withDisambiguation(found, req) { - ";\n found : " + found.toLongString + existentialContext(found) + - "\n required: " + req + existentialContext(req) - } + if (tree.hasSymbol) + tree setSymbol errorSym - def typeErrorMsg(found: Type, req: Type) = { - //println(found.baseTypeSeq) - "type mismatch" + foundReqMsg(found, req) + - (if ((found.resultApprox ne found) && isWeaklyCompatible(found.resultApprox, req)) - "\n possible cause: missing arguments for method or constructor" - else "") + tree setType ErrorType } def error(pos: Position, msg: String) { @@ -276,67 +221,27 @@ trait Infer { def typeError(pos: Position, found: Type, req: Type) { if (!found.isErroneous && !req.isErroneous) { - error(pos, - typeErrorMsg(found, req)+ - (if (pos != NoPosition && pos == addendumPos) addendum() - else "")) - if (settings.explaintypes.value) explainTypes(found, req) + error(pos, withAddendum(pos)(typeErrorMsg(found, req))) + + if (settings.explaintypes.value) + explainTypes(found, req) } } + def typeErrorMsg(found: Type, req: Type) = { + def isPossiblyMissingArgs = (found.resultApprox ne found) && isWeaklyCompatible(found.resultApprox, req) + def missingArgsMsg = if (isPossiblyMissingArgs) "\n possible cause: missing arguments for method or constructor" else "" + + "type mismatch" + foundReqMsg(found, req) + missingArgsMsg + } + def typeErrorTree(tree: Tree, found: Type, req: Type): Tree = { typeError(tree.pos, found, req) setError(tree) } def explainTypes(tp1: Type, tp2: Type) = - withDisambiguation(tp1, tp2) { global.explainTypes(tp1, tp2) } - - /** If types `tp1' `tp2' contain different type variables with same name - * differentiate the names by including owner information. Also, if the - * type error is because of a conflict between two identically named - * classes and one is in package scala, fully qualify the name so one - * need not deduce why "java.util.Iterator" and "Iterator" don't match. - */ - private def withDisambiguation[T](tp1: Type, tp2: Type)(op: => T): T = { - - def explainName(sym: Symbol) = { - if (!sym.name.toString.endsWith(")")) { - sym.name = newTypeName(sym.name.toString+"(in "+sym.owner+")") - } - } - - val patches = new ListBuffer[(Symbol, Symbol, Name)] - for { - t1 @ TypeRef(_, sym1, _) <- tp1 - t2 @ TypeRef(_, sym2, _) <- tp2 - if sym1 != sym2 - } { - if (t1.toString == t2.toString) { // type variable collisions - val name = sym1.name - explainName(sym1) - explainName(sym2) - if (sym1.owner == sym2.owner) sym2.name = newTypeName("(some other)"+sym2.name) - patches += ((sym1, sym2, name)) - } - else if (sym1.name == sym2.name) { // symbol name collisions where one is in scala._ - val name = sym1.name - def scalaQualify(s: Symbol) = - if (s.owner.isScalaPackageClass) s.name = newTypeName("scala." + s.name) - List(sym1, sym2) foreach scalaQualify - patches += ((sym1, sym2, name)) - } - } - - val result = op - - for ((sym1, sym2, name) <- patches) { - sym1.name = name - sym2.name = name - } - - result - } + withDisambiguation(tp1, tp2)(global.explainTypes(tp1, tp2)) /* -- Tests & Checks---------------------------------------------------- */ @@ -1591,7 +1496,7 @@ trait Infer { } def checkDead(tree: Tree): Tree = { - if (settings.Xwarndeadcode.value && tree.tpe != null && tree.tpe.typeSymbol == NothingClass) + if (settings.Ywarndeadcode.value && tree.tpe != null && tree.tpe.typeSymbol == NothingClass) context.warning (tree.pos, "dead code following this construct") tree } diff --git a/src/compiler/scala/tools/nsc/typechecker/Namers.scala b/src/compiler/scala/tools/nsc/typechecker/Namers.scala index a78ee61ee2..a7f573f98b 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Namers.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Namers.scala @@ -607,6 +607,17 @@ trait Namers { self: Analyzer => vparamss.map(_.map(enterValueParam)) } + /** + * Finds the companion module of a class symbol. Calling .companionModule + * does not work for classes defined inside methods. + */ + private def companionModuleOf(clazz: Symbol) = { + var res = clazz.companionModule + if (res == NoSymbol) + res = context.lookup(clazz.name.toTermName, clazz.owner) + res + } + private def templateSig(templ: Template): Type = { val clazz = context.owner def checkParent(tpt: Tree): Type = { @@ -712,29 +723,36 @@ trait Namers { self: Analyzer => // add apply and unapply methods to companion objects of case classes, // unless they exist already; here, "clazz" is the module class - Namers.this.caseClassOfModuleClass get clazz match { - case Some(cdef) => - addApplyUnapply(cdef, templateNamer) - caseClassOfModuleClass -= clazz - case None => + if (clazz.isModuleClass) { + Namers.this.caseClassOfModuleClass get clazz match { + case Some(cdef) => + addApplyUnapply(cdef, templateNamer) + caseClassOfModuleClass -= clazz + case None => + } } // add the copy method to case classes; this needs to be done here, not in SyntheticMethods, because // the namer phase must traverse this copy method to create default getters for its parameters. - Namers.this.caseClassOfModuleClass get clazz.companionModule.moduleClass match { - case Some(cdef) => - def hasCopy(decls: Scope) = { - decls.iterator exists (_.name == nme.copy) - } - if (!hasCopy(decls) && - !parents.exists(p => hasCopy(p.typeSymbol.info.decls)) && - !parents.flatMap(_.baseClasses).distinct.exists(bc => hasCopy(bc.info.decls))) - addCopyMethod(cdef, templateNamer) - case None => + // here, clazz is the ClassSymbol of the case class (not the module). + // @check: this seems to work only if the type completer of the class runs before the one of the + // module class: the one from the module class removes the entry form caseClassOfModuleClass (see above). + if (clazz.isClass && !clazz.hasFlag(MODULE)) { + Namers.this.caseClassOfModuleClass get companionModuleOf(clazz).moduleClass match { + case Some(cdef) => + def hasCopy(decls: Scope) = { + decls.iterator exists (_.name == nme.copy) + } + if (!hasCopy(decls) && + !parents.exists(p => hasCopy(p.typeSymbol.info.decls)) && + !parents.flatMap(_.baseClasses).distinct.exists(bc => hasCopy(bc.info.decls))) + addCopyMethod(cdef, templateNamer) + case None => + } } - // if default getters (for constructor defaults) need to be added to that module, - // here's the namer to use + // if default getters (for constructor defaults) need to be added to that module, here's the namer + // to use. clazz is the ModuleClass. sourceModule works also for classes defined in methods. val module = clazz.sourceModule if (classAndNamerOfModule contains module) { val (cdef, _) = classAndNamerOfModule(module) @@ -947,6 +965,7 @@ trait Namers { self: Analyzer => if (vparamss == List(Nil) && baseParamss.isEmpty) baseParamss = List(Nil) assert(!overrides || vparamss.length == baseParamss.length, ""+ meth.fullName + ", "+ overridden.fullName) + // cache the namer used for entering the default getter symbols var ownerNamer: Option[Namer] = None var moduleNamer: Option[(ClassDef, Namer)] = None @@ -978,7 +997,7 @@ trait Namers { self: Analyzer => val parentNamer = if (isConstr) { val (cdef, nmr) = moduleNamer.getOrElse { - val module = meth.owner.companionModule + val module = companionModuleOf(meth.owner) module.initialize // call type completer (typedTemplate), adds the // module's templateNamer to classAndNamerOfModule val (cdef, nmr) = classAndNamerOfModule(module) @@ -1029,7 +1048,8 @@ trait Namers { self: Analyzer => Modifiers(meth.flags & (PRIVATE | PROTECTED | FINAL)) | SYNTHETIC | DEFAULTPARAM | oflag, name, deftParams, defvParamss, defTpt, defRhs) } - meth.owner.resetFlag(INTERFACE) // there's a concrete member now + if (!isConstr) + meth.owner.resetFlag(INTERFACE) // there's a concrete member now val default = parentNamer.enterSyntheticSym(defaultTree) } else if (baseHasDefault) { // the parameter does not have a default itself, but the corresponding parameter diff --git a/src/compiler/scala/tools/nsc/typechecker/NamesDefaults.scala b/src/compiler/scala/tools/nsc/typechecker/NamesDefaults.scala index 89b4f910e5..18102a8bb4 100644 --- a/src/compiler/scala/tools/nsc/typechecker/NamesDefaults.scala +++ b/src/compiler/scala/tools/nsc/typechecker/NamesDefaults.scala @@ -187,10 +187,21 @@ trait NamesDefaults { self: Analyzer => baseFun1 match { // constructor calls - case Select(New(TypeTree()), _) if isConstr => - blockWithoutQualifier(None) - case Select(TypeApply(New(TypeTree()), _), _) if isConstr => - blockWithoutQualifier(None) + case Select(New(tp @ TypeTree()), _) if isConstr => + // fixes #3338. Same qualifier for selecting the companion object as for the class. + val dq = tp.tpe match { + case TypeRef(pre, _, _) if (!pre.typeSymbol.isEmptyPackageClass) => + moduleQual(tp.pos, mod => gen.mkAttributedSelect(gen.mkAttributedQualifier(pre), mod)) + case _ => None + } + blockWithoutQualifier(dq) + case Select(TypeApply(New(tp @ TypeTree()), _), _) if isConstr => + val dq = tp.tpe match { + case TypeRef(pre, _, _) if (!pre.typeSymbol.isEmptyPackageClass) => + moduleQual(tp.pos, mod => gen.mkAttributedSelect(gen.mkAttributedQualifier(pre), mod)) + case _ => None + } + blockWithoutQualifier(dq) case Select(New(Ident(_)), _) if isConstr => blockWithoutQualifier(None) @@ -350,8 +361,11 @@ trait NamesDefaults { self: Analyzer => if (missing forall (_.hasFlag(DEFAULTPARAM))) { val defaultArgs = missing map (p => { var default1 = qual match { - case Some(q) => gen.mkAttributedSelect(q.duplicate, defaultGetter(p, context)) - case None => gen.mkAttributedRef(defaultGetter(p, context)) + case Some(q) => gen.mkAttributedSelect(q.duplicate, defaultGetter(p, context)._1) + case None => + val (m, q) = defaultGetter(p, context) + if (q.isDefined) gen.mkAttributedSelect(q.get, m) + else gen.mkAttributedRef(m) } default1 = if (targs.isEmpty) default1 else TypeApply(default1, targs.map(_.duplicate)) @@ -369,35 +383,32 @@ trait NamesDefaults { self: Analyzer => /** * For a parameter with default argument, find the method symbol of - * the default getter. + * the default getter. Can return a qualifier tree for the selecting + * the method's symbol (part of #3334 fix). */ - def defaultGetter(param: Symbol, context: Context) = { + def defaultGetter(param: Symbol, context: Context): (Symbol, Option[Tree]) = { val i = param.owner.paramss.flatten.findIndexOf(p => p.name == param.name) + 1 if (i > 0) { if (param.owner.isConstructor) { val defGetterName = "init$default$"+ i - param.owner.owner.companionModule.info.member(defGetterName) + var mod = param.owner.owner.companionModule + // if the class's owner is a method, .companionModule does not work + if (mod == NoSymbol) + mod = context.lookup(param.owner.owner.name.toTermName, param.owner.owner.owner) + (mod.info.member(defGetterName), Some(gen.mkAttributedRef(mod))) } else { val defGetterName = param.owner.name +"$default$"+ i + // isClass also works for methods in objects, owner is the ModuleClassSymbol if (param.owner.owner.isClass) { // .toInterface: otherwise we get the method symbol of the impl class - param.owner.owner.toInterface.info.member(defGetterName) + (param.owner.owner.toInterface.info.member(defGetterName), None) } else { // the owner of the method is another method. find the default // getter in the context. - var res: Symbol = NoSymbol - var ctx = context - while(res == NoSymbol && ctx.outer != ctx) { - val s = ctx.scope.lookup(defGetterName) - if (s != NoSymbol && s.owner == param.owner.owner) - res = s - else - ctx = ctx.outer - } - res + (context.lookup(defGetterName, param.owner.owner), None) } } - } else NoSymbol + } else (NoSymbol, None) } /** diff --git a/src/compiler/scala/tools/nsc/typechecker/RefChecks.scala b/src/compiler/scala/tools/nsc/typechecker/RefChecks.scala index 003a173892..bd8482cd67 100644 --- a/src/compiler/scala/tools/nsc/typechecker/RefChecks.scala +++ b/src/compiler/scala/tools/nsc/typechecker/RefChecks.scala @@ -971,17 +971,20 @@ abstract class RefChecks extends InfoTransform { private def checkAnnotations(tpes: List[Type], pos: Position) = tpes foreach (tp => checkTypeRef(tp, pos)) private def doTypeTraversal(tree: Tree)(f: Type => Unit) = if (!inPattern) tree.tpe foreach f - private def applyRefchecksToAnnotations(tree: Tree) = tree match { - case m: MemberDef => - checkAnnotations(m.symbol.annotations map (_.atp), tree.pos) - transformTrees(m.symbol.annotations.flatMap(_.args)) - case TypeTree() => doTypeTraversal(tree) { - case AnnotatedType(annots, _, _) => - checkAnnotations(annots map (_.atp), tree.pos) - transformTrees(annots.flatMap(_.args)) + private def applyRefchecksToAnnotations(tree: Tree) = { + def applyChecks(annots: List[AnnotationInfo]) = { + checkAnnotations(annots map (_.atp), tree.pos) + transformTrees(annots flatMap (_.args)) + } + + tree match { + case m: MemberDef => applyChecks(m.symbol.annotations) + case TypeTree() => doTypeTraversal(tree) { + case AnnotatedType(annots, _, _) => applyChecks(annots) + case _ => + } case _ => } - case _ => } private def transformCaseApply(tree: Tree, ifNot: => Unit) = { diff --git a/src/compiler/scala/tools/nsc/typechecker/SuperAccessors.scala b/src/compiler/scala/tools/nsc/typechecker/SuperAccessors.scala index 6f5175c0ea..8a7f4b0958 100644 --- a/src/compiler/scala/tools/nsc/typechecker/SuperAccessors.scala +++ b/src/compiler/scala/tools/nsc/typechecker/SuperAccessors.scala @@ -54,7 +54,11 @@ abstract class SuperAccessors extends transform.Transform with transform.TypingT private def checkPackedConforms(tree: Tree, pt: Type): Tree = { if (tree.tpe exists (_.typeSymbol.isExistentialSkolem)) { val packed = localTyper.packedType(tree, NoSymbol) - if (!(packed <:< pt)) localTyper.infer.typeError(tree.pos, packed, pt) + if (!(packed <:< pt)) { + val errorContext = localTyper.context.make(localTyper.context.tree) + errorContext.reportGeneralErrors = true + analyzer.newTyper(errorContext).infer.typeError(tree.pos, packed, pt) + } } tree } diff --git a/src/compiler/scala/tools/nsc/typechecker/TypeDiagnostics.scala b/src/compiler/scala/tools/nsc/typechecker/TypeDiagnostics.scala new file mode 100644 index 0000000000..1166f62ddb --- /dev/null +++ b/src/compiler/scala/tools/nsc/typechecker/TypeDiagnostics.scala @@ -0,0 +1,268 @@ +/* NSC -- new Scala compiler + * Copyright 2005-2010 LAMP/EPFL + * @author Paul Phillips + */ + +package scala.tools.nsc +package typechecker + +import scala.collection.mutable +import scala.collection.mutable.ListBuffer +import scala.util.control.ControlThrowable +import scala.util.control.Exception.ultimately +import symtab.Flags._ +import PartialFunction._ + +/** An interface to enable higher configurability of diagnostic messages + * regarding type errors. This is barely a beginning as error messages are + * distributed far and wide across the codebase. The plan is to partition + * error messages into some broad groups and provide some mechanism for + * being more or less verbose on a selective basis. Possible groups include + * such examples as + * + * arity errors + * kind errors + * variance errors + * ambiguity errors + * volatility/stability errors + * implementation restrictions + * + * And more, and there is plenty of overlap, so it'll be a process. + * + * @author Paul Phillips + * @version 1.0 + */ +trait TypeDiagnostics { + self: Analyzer => + + import global._ + import definitions._ + import global.typer.infer + + /** It can be quite difficult to know which of the many functions called "error" + * is being called at any given point in the compiler. To alleviate this I am + * renaming such functions inside this trait based on where it originated. + */ + def inferError(pos: Position, msg: String) = infer.error(pos, msg) + + /** The common situation of making sure nothing is erroneous could be + * nicer if Symbols, Types, and Trees all implemented some common interface + * in which isErroneous and similar would be placed. + */ + def noErroneousTypes(tps: Type*) = tps forall (x => !x.isErroneous) + def noErroneousSyms(syms: Symbol*) = syms forall (x => !x.isErroneous) + def noErroneousTrees(trees: Tree*) = trees forall (x => !x.isErroneous) + + /** A map of Positions to addendums - if an error involves a position in + * the map, the addendum should also be printed. + */ + private var addendums = mutable.Map[Position, () => String]() + + def setAddendum(pos: Position, msg: () => String) = + if (pos != NoPosition) + addendums(pos) = msg + + def withAddendum(pos: Position) = (_: String) + addendums.getOrElse(pos, () => "")() + + def decodeWithNamespace(name: Name): String = { + val prefix = if (name.isTypeName) "type " else "value " + prefix + name.decode + } + + /** Does the positioned line assigned to t1 precede that of t2? + */ + def linePrecedes(t1: Tree, t2: Tree) = t1.pos.isDefined && t1.pos.isDefined && t1.pos.line < t2.pos.line + + def notAMember(sel: Tree, qual: Tree, name: Name) = { + def decoded = decodeWithNamespace(name) + + def msg: String = name match { + case nme.CONSTRUCTOR => qual.tpe.widen+" does not have a constructor" + case _ => + def memberOf = if (qual.tpe.typeSymbol.isTypeParameterOrSkolem) "type parameter " else "" + def possibleCause = + if (linePrecedes(qual, sel)) + "\npossible cause: maybe a semicolon is missing before `"+decoded+"'?" + else + "" + + decoded+" is not a member of "+ memberOf + qual.tpe.widen + possibleCause + } + inferError(sel.pos, withAddendum(qual.pos)(msg)) + } + + /** Only prints the parameter names if they're not synthetic, + * since "x$1: Int" does not offer any more information than "Int". + */ + private def methodTypeErrorString(tp: Type) = tp match { + case mt @ MethodType(params, resultType) => + def forString = + if (params exists (_.isSynthetic)) params map (_.tpe) + else params map (_.defString) + + forString.mkString("(", ",", ")") + resultType + case x => x.toString + } + + def alternatives(tree: Tree): List[Type] = tree.tpe match { + case OverloadedType(pre, alternatives) => alternatives map pre.memberType + case _ => Nil + } + def alternativesString(tree: Tree) = + alternatives(tree) map (x => " " + methodTypeErrorString(x)) mkString ("", " <and>\n", "\n") + + def missingParameterTypeError(fun: Tree, vparam: ValDef) = { + val suffix = if (vparam.mods.isSynthetic) " for expanded function "+fun else "" + + inferError(vparam.pos, "missing parameter type" + suffix) + ErrorType + } + + def treeSymTypeMsg(tree: Tree): String = { + val sym = tree.symbol + def hasParams = tree.tpe.paramSectionCount > 0 + def preResultString = if (hasParams) ": " else " of type " + + def nullMessage = "expression of type " + tree.tpe + def overloadedMessage = "overloaded method " + sym + " with alternatives:\n" + alternativesString(tree) + def moduleMessage = "" + sym + def defaultMessage = moduleMessage + preResultString + tree.tpe + def applyMessage = defaultMessage + tree.symbol.locationString + + if (sym == null) nullMessage + else if (sym.isOverloaded) overloadedMessage + else if (sym.isModule) moduleMessage + else if (sym.name == nme.apply) applyMessage + else defaultMessage + } + + def applyErrorMsg(tree: Tree, msg: String, argtpes: List[Type], pt: Type) = { + def asParams(xs: List[Any]) = xs.mkString("(", ", ", ")") + + def resType = if (isWildcard(pt)) "" else " with expected result type " + pt + def allTypes = (alternatives(tree) flatMap (_.paramTypes)) ++ argtpes :+ pt + + withDisambiguation(allTypes: _*) { + treeSymTypeMsg(tree) + msg + asParams(argtpes) + resType + } + } + + def disambiguate(ss: List[String]) = ss match { + case Nil => Nil + case s :: ss => s :: (ss map { case `s` => "(some other)"+s ; case x => x }) + } + + // todo: use also for other error messages + def existentialContext(tp: Type) = tp.existentialSkolems match { + case Nil => "" + case xs => " where " + (disambiguate(xs map (_.existentialToString)) mkString ", ") + } + + def foundReqMsg(found: Type, req: Type): String = + withDisambiguation(found, req) { + ";\n found : " + found.toLongString + existentialContext(found) + + "\n required: " + req + existentialContext(req) + } + + /** If two given types contain different type variables with the same name + * differentiate the names by including owner information. Also, if the + * type error is because of a conflict between two identically named + * classes and one is in package scala, fully qualify the name so one + * need not deduce why "java.util.Iterator" and "Iterator" don't match. + * Another disambiguation performed is to address the confusion present + * in the following snippet: + * def f[Int](x: Int) = x + 5. + */ + def withDisambiguation[T](types: Type*)(op: => T): T = { + object SymExtractor { + def unapply(x: Any) = x match { + case t @ TypeRef(_, sym, _) => Some(t -> sym) + case t @ ConstantType(value) => Some(t -> t.underlying.typeSymbol) + case _ => None + } + } + val typerefs = + for (tp <- types.toList ; SymExtractor(t, sym) <- tp) yield + t -> sym + + val savedNames = typerefs map { case (_, sym) => sym -> sym.name } toMap + def restoreNames = savedNames foreach { case (sym, name) => sym.name = name } + + def isAlreadyAltered(sym: Symbol) = sym.name != savedNames(sym) + + def modifyName(sym: Symbol)(f: String => String): Unit = + sym.name = newTypeName(f(sym.name.toString)) + + def scalaQualify(sym: Symbol) = + if (sym.owner.isScalaPackageClass) + modifyName(sym)("scala." + _) + + def explainName(sym: Symbol) = { + scalaQualify(sym) + + if (!isAlreadyAltered(sym)) + modifyName(sym)(_ + "(in " + sym.owner + ")") + } + + ultimately(restoreNames) { + for ((t1, sym1) <- typerefs ; (t2, sym2) <- typerefs ; if sym1 != sym2 && (sym1 isLess sym2)) { + + if (t1.toString == t2.toString) { // type variable collisions + List(sym1, sym2) foreach explainName + if (sym1.owner == sym2.owner) + sym2.name = newTypeName("(some other)"+sym2.name) + } + else if (sym1.name == sym2.name) { // symbol name collisions + List(sym1, sym2) foreach { x => + if (x.owner.isScalaPackageClass) + modifyName(x)("scala." + _) + else if (x.isTypeParameterOrSkolem) + explainName(x) + } + } + } + + // performing the actual operation + op + } + } + + trait TyperDiagnostics { + self: Typer => + + private def contextError(pos: Position, msg: String) = context.error(pos, msg) + private def contextError(pos: Position, err: Throwable) = context.error(pos, err) + + def symWasOverloaded(sym: Symbol) = sym.owner.isClass && sym.owner.info.member(sym.name).isOverloaded + def cyclicAdjective(sym: Symbol) = if (symWasOverloaded(sym)) "overloaded" else "recursive" + + /** Returns Some(msg) if the given tree is untyped apparently due + * to a cyclic reference, and None otherwise. + */ + def cyclicReferenceMessage(sym: Symbol, tree: Tree) = condOpt(tree) { + case ValDef(_, _, tpt, _) if tpt.tpe == null => "recursive "+sym+" needs type" + case DefDef(_, _, _, _, tpt, _) if tpt.tpe == null => List(cyclicAdjective(sym), sym, "needs result type") mkString " " + } + + /** Report a type error. + * + * @param pos0 The position where to report the error + * @param ex The exception that caused the error + */ + def reportTypeError(pos: Position, ex: TypeError) { + if (ex.pos == NoPosition) ex.pos = pos + if (!context.reportGeneralErrors) throw ex + if (settings.debug.value) ex.printStackTrace() + + ex match { + case CyclicReference(sym, info: TypeCompleter) => + contextError(ex.pos, cyclicReferenceMessage(sym, info.tree) getOrElse ex.getMessage()) + + if (sym == ObjectClass) + throw new FatalError("cannot redefine root "+sym) + case _ => + contextError(ex.pos, ex) + } + } + } +} diff --git a/src/compiler/scala/tools/nsc/typechecker/Typers.scala b/src/compiler/scala/tools/nsc/typechecker/Typers.scala index 3fe937f241..92dd368ac5 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Typers.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Typers.scala @@ -161,7 +161,7 @@ trait Typers { self: Analyzer => else mode } - abstract class Typer(context0: Context) { + abstract class Typer(context0: Context) extends TyperDiagnostics { import context0.unit val infer = new Inferencer(context0) { @@ -252,35 +252,6 @@ trait Typers { self: Analyzer => private[typechecker] var context = context0 def context1 = context - /** Report a type error. - * - * @param pos0 The position where to report the error - * @param ex The exception that caused the error - */ - def reportTypeError(pos: Position, ex: TypeError) { - if (ex.pos == NoPosition) ex.pos = pos - if (!context.reportGeneralErrors) throw ex - if (settings.debug.value) ex.printStackTrace() - ex match { - case CyclicReference(sym, info: TypeCompleter) => - val msg = - info.tree match { - case ValDef(_, _, tpt, _) if (tpt.tpe eq null) => - "recursive "+sym+" needs type" - case DefDef(_, _, _, _, tpt, _) if (tpt.tpe eq null) => - (if (sym.owner.isClass && sym.owner.info.member(sym.name).hasFlag(OVERLOADED)) "overloaded " - else "recursive ")+sym+" needs result type" - case _ => - ex.getMessage() - } - context.error(ex.pos, msg) - if (sym == ObjectClass) - throw new FatalError("cannot redefine root "+sym) - case _ => - context.error(ex.pos, ex) - } - } - /** Check that <code>tree</code> is a stable expression. * * @param tree ... @@ -397,7 +368,7 @@ trait Typers { self: Analyzer => error(pos, "methods with `*'-parameters cannot be converted to function values"); */ if (restpe.isDependent) - error(pos, "method with dependent type "+tpe+" cannot be converted to function value"); + error(pos, "method with dependent type "+tpe+" cannot be converted to function value") checkParamsConvertible(pos, restpe) case _ => } @@ -1230,7 +1201,7 @@ trait Typers { self: Analyzer => if (!ps.isEmpty && !superclazz.isSubClass(ps.head.typeSymbol)) error(parent.pos, "illegal inheritance; super"+superclazz+ "\n is not a subclass of the super"+ps.head.typeSymbol+ - "\n of the mixin " + psym); + "\n of the mixin " + psym) } else { error(parent.pos, psym+" needs to be a trait to be mixed in") } @@ -1352,6 +1323,7 @@ trait Typers { self: Analyzer => */ def addGetterSetter(stat: Tree): List[Tree] = stat match { case ValDef(mods, name, tpt, rhs) + // PRIVATE | LOCAL are fields generated for primary constructor arguments if (mods.flags & (PRIVATE | LOCAL)) != (PRIVATE | LOCAL).toLong && !stat.symbol.isModuleVar => val isDeferred = mods hasFlag DEFERRED @@ -1592,7 +1564,7 @@ trait Typers { self: Analyzer => * @param rhs ... */ def computeParamAliases(clazz: Symbol, vparamss: List[List[ValDef]], rhs: Tree) { - if (settings.debug.value) log("computing param aliases for "+clazz+":"+clazz.primaryConstructor.tpe+":"+rhs);//debug + if (settings.debug.value) log("computing param aliases for "+clazz+":"+clazz.primaryConstructor.tpe+":"+rhs)//debug def decompose(call: Tree): (Tree, List[Tree]) = call match { case Apply(fn, args) => val (superConstr, args1) = decompose(fn) @@ -1600,7 +1572,7 @@ trait Typers { self: Analyzer => val args2 = if (params.isEmpty || !isRepeatedParamType(params.last.tpe)) args else args.take(params.length - 1) ::: List(EmptyTree) if (args2.length != params.length) - assert(false, "mismatch " + clazz + " " + (params map (_.tpe)) + " " + args2);//debug + assert(false, "mismatch " + clazz + " " + (params map (_.tpe)) + " " + args2)//debug (superConstr, args1 ::: args2) case Block(stats, expr) if !stats.isEmpty => decompose(stats.last) @@ -1639,7 +1611,7 @@ trait Typers { self: Analyzer => ownAcc = ownAcc.accessed if (!ownAcc.isVariable && !alias.accessed.isVariable) { if (settings.debug.value) - log("" + ownAcc + " has alias "+alias + alias.locationString);//debug + log("" + ownAcc + " has alias "+alias + alias.locationString) //debug ownAcc.asInstanceOf[TermSymbol].setAlias(alias) } } @@ -1676,77 +1648,6 @@ trait Typers { self: Analyzer => } } - /** does given name name an identifier visible at this point? - * - * @param name the given name - * @return <code>true</code> if an identifier with the given name is visible. - */ - def namesSomeIdent(name: Name): Boolean = namesWhatIdent(name).isDefined - - /** If this name returns a visible identifier, return its symbol. - * - * @param name the given name - * @return <code>Some(sym)</code> if an ident is visible, None otherwise. - */ - def namesWhatIdent(name: Name): Option[Symbol] = { - var cx = context - while (cx != NoContext) { - val pre = cx.enclClass.prefix - val defEntry = cx.scope.lookupEntry(name) - if ((defEntry ne null) && reallyExists(defEntry.sym)) - return Some(defEntry.sym) - - cx = cx.enclClass - (pre member name filter (sym => reallyExists(sym) && context.isAccessible(sym, pre, false))) match { - case NoSymbol => cx = cx.outer - case other => return Some(other) - } - } - context.imports map (_ importedSymbol name) find (_ != NoSymbol) - } - - /** Does this tree declare a val or def with the same name as one in scope? - * This only catches identifiers in the same file, so more work is needed. - * - * @param tree the given tree - * @param filt filter for any conflicting symbols found -- false means ignore - */ - def checkShadowings(tree: Tree, filt: (Symbol) => Boolean = _ => true) { - def sameFile(other: Symbol) = - (tree.symbol != null) && tree.symbol.sourceFile == other.sourceFile - def inFile(other: Symbol) = - if (sameFile(other)) "" - else if (other.sourceFile != null) "in %s ".format(other.sourceFile) - else "" - - def positionStr(other: Symbol) = other.pos match { - case NoPosition => inFile(other) match { case "" => "(location unknown) " ; case x => x } - case pos => "%sat line %s\n%s".format(inFile(other), pos.line, pos.lineContent) + """ /* is shadowed by */""" - } - def include(v: ValOrDefDef, other: Symbol) = { - // shadowing on the same line is a good bet for noise - (v.pos == NoPosition || other.pos == NoPosition || v.pos.line != other.pos.line) && - // not likely we'll shadow a whole package without realizing it - !other.isPackage && - // (v.symbol == null || !v.symbol.hasTransOwner(other)) && - filt(other) - } - - tree match { - // while I try to figure out how to limit the noise far enough to make this - // genuinely useful, I'm setting minimum identifier length to 3 to omit all - // those x's and i's we so enjoy reusing. - case v: ValOrDefDef if v.name.toString.length > 2 => - namesWhatIdent(v.name) map { other => - if (include(v, other) && unit != null) { - val fstr = "%s (%s) shadows usage %s" - unit.warning(v.pos, fstr.format(v.name, v.tpt, positionStr(other))) - } - } - case _ => - } - } - def typedUseCase(useCase: UseCase) { def stringParser(str: String): syntaxAnalyzer.Parser = { val file = new BatchSourceFile(context.unit.source.file, str) { @@ -1793,15 +1694,6 @@ trait Typers { self: Analyzer => def typedDefDef(ddef: DefDef): DefDef = { val meth = ddef.symbol - // If warnings are enabled, attempt to alert about variable shadowing. This only - // catches method parameters shadowing identifiers declared in the same file, so more - // work is needed. Most of the code here is to filter out false positives. - def isAuxConstructor(sym: Symbol) = sym.isConstructor && !sym.isPrimaryConstructor - if (settings.YwarnShadow.value && !isAuxConstructor(ddef.symbol)) { - for (v <- ddef.vparamss.flatten ; if v.symbol != null && !(v.symbol hasFlag SYNTHETIC)) - checkShadowings(v, (sym => !sym.isDeferred && !sym.isMethod)) - } - reenterTypeParams(ddef.tparams) reenterValueParams(ddef.vparamss) @@ -1933,7 +1825,6 @@ trait Typers { self: Analyzer => while ((e ne null) && (e.sym ne stat.symbol)) e = e.tail if (e eq null) context.scope.enter(stat.symbol) } - if (settings.YwarnShadow.value) checkShadowings(stat) enterLabelDef(stat) } if (phaseId(currentPeriod) <= currentRun.typerPhase.id) { @@ -2155,18 +2046,18 @@ trait Typers { self: Analyzer => !(accessed hasFlag ACCESSOR) && accessed.isPrivateLocal def checkNoDoubleDefsAndAddSynthetics(stats: List[Tree]): List[Tree] = { - val scope = if (inBlock) context.scope else context.owner.info.decls; + val scope = if (inBlock) context.scope else context.owner.info.decls val newStats = new ListBuffer[Tree] var needsCheck = true var moreToAdd = true while (moreToAdd) { val initSize = scope.size - var e = scope.elems; + var e = scope.elems while ((e ne null) && e.owner == scope) { // check no double def if (needsCheck) { - var e1 = scope.lookupNextEntry(e); + var e1 = scope.lookupNextEntry(e) while ((e1 ne null) && e1.owner == scope) { if (!accesses(e.sym, e1.sym) && !accesses(e1.sym, e.sym) && (e.sym.isType || inBlock || (e.sym.tpe matches e1.sym.tpe))) @@ -2394,7 +2285,8 @@ trait Typers { self: Analyzer => val (allArgs, missing) = addDefaults(args, qual, targs, previousArgss, params, fun.pos.focus, context) if (allArgs.length == formals.length) { // useful when a default doesn't match parameter type, e.g. def f[T](x:T="a"); f[Int]() - context.diagnostic = "Error occured in an application involving default arguments." :: context.diagnostic + val note = "Error occurred in an application involving default arguments." + if (!(context.diagnostic contains note)) context.diagnostic = note :: context.diagnostic doTypedApply(tree, if (blockIsEmpty) fun else fun1, allArgs, mode, pt) } else { tryTupleApply.getOrElse { @@ -2466,7 +2358,7 @@ trait Typers { self: Analyzer => inferExprInstance(fun, tparams, WildcardType, true) doTypedApply(tree, fun, args, mode, pt) } else { - assert((mode & PATTERNmode) == 0); // this case cannot arise for patterns + assert((mode & PATTERNmode) == 0) // this case cannot arise for patterns val lenientTargs = protoTypeArgs(tparams, formals, mt.resultApprox, pt) val strictTargs = (lenientTargs, tparams).zipped map ((targ, tparam) => if (targ == WildcardType) tparam.tpe else targ) //@M TODO: should probably be .tpeHK @@ -2490,7 +2382,7 @@ trait Typers { self: Analyzer => val args1 = (args, formals).zipped map typedArgToPoly if (args1 exists (_.tpe.isError)) setError(tree) else { - if (settings.debug.value) log("infer method inst "+fun+", tparams = "+tparams+", args = "+args1.map(_.tpe)+", pt = "+pt+", lobounds = "+tparams.map(_.tpe.bounds.lo)+", parambounds = "+tparams.map(_.info));//debug + if (settings.debug.value) log("infer method inst "+fun+", tparams = "+tparams+", args = "+args1.map(_.tpe)+", pt = "+pt+", lobounds = "+tparams.map(_.tpe.bounds.lo)+", parambounds = "+tparams.map(_.info)) //debug // define the undetparams which have been fixed by this param list, replace the corresponding symbols in "fun" // returns those undetparams which have not been instantiated. val undetparams = inferMethodInstance(fun, tparams, args1, pt) @@ -3016,6 +2908,8 @@ trait Typers { self: Analyzer => * @return ... */ protected def typed1(tree: Tree, mode: Int, pt: Type): Tree = { + def isPatternMode = (mode & PATTERNmode) != 0 + //Console.println("typed1("+tree.getClass()+","+Integer.toHexString(mode)+","+pt+")") def ptOrLub(tps: List[Type]) = if (isFullyDefined(pt)) (pt, false) else weakLub(tps map (_.deconst)) @@ -3114,13 +3008,6 @@ trait Typers { self: Analyzer => if (vble == NoSymbol) vble = context.owner.newValue(tree.pos, name) if (vble.name.toTermName != nme.WILDCARD) { -/* - if (namesSomeIdent(vble.name)) - context.warning(tree.pos, - "pattern variable"+vble.name+" shadows a value visible in the environment;\n"+ - "use backquotes `"+vble.name+"` if you mean to match against that value;\n" + - "or rename the variable or use an explicit bind "+vble.name+"@_ to avoid this warning.") -*/ if ((mode & ALTmode) != 0) error(tree.pos, "illegal variable in pattern alternative") vble = namer.enterInScope(vble) @@ -3143,32 +3030,36 @@ trait Typers { self: Analyzer => } def typedAssign(lhs: Tree, rhs: Tree): Tree = { - val lhs1 = typed(lhs, EXPRmode | LHSmode, WildcardType) - val varsym = lhs1.symbol - if ((varsym ne null) && treeInfo.mayBeVarGetter(varsym)) + val lhs1 = typed(lhs, EXPRmode | LHSmode, WildcardType) + val varsym = lhs1.symbol + def failMsg = + if (varsym != null && varsym.isValue) "reassignment to val" + else "assignment to non variable" + + def fail = { + if (!lhs1.tpe.isError) + error(tree.pos, failMsg) + + setError(tree) + } + if (varsym == null) + return fail + + if (treeInfo.mayBeVarGetter(varsym)) { lhs1 match { case Select(qual, name) => - return typed( - Apply( - Select(qual, nme.getterToSetter(name)) setPos lhs.pos, - List(rhs)) setPos tree.pos, - mode, pt) + val sel = Select(qual, nme.getterToSetter(name)) setPos lhs.pos + val app = Apply(sel, List(rhs)) setPos tree.pos + return typed(app, mode, pt) case _ => - } - if ((varsym ne null) && (varsym.isVariable || varsym.isValue && phase.erasedTypes)) { + } + if (varsym.isVariable || varsym.isValue && phase.erasedTypes) { val rhs1 = typed(rhs, EXPRmode | BYVALmode, lhs1.tpe) treeCopy.Assign(tree, lhs1, checkDead(rhs1)) setType UnitClass.tpe - } else { - if (!lhs1.tpe.isError) { - //println(lhs1+" = "+rhs+" "+varsym+" "+mayBeVarGetter(varsym)+" "+varsym.ownerChain+" "+varsym.info)// DEBUG - error(tree.pos, - if ((varsym ne null) && varsym.isValue) "reassignment to val" - else "assignment to non variable") - } - setError(tree) } + else fail } def typedIf(cond: Tree, thenp: Tree, elsep: Tree) = { @@ -3269,7 +3160,7 @@ trait Typers { self: Analyzer => pt != WildcardType && pt != ErrorType && isSubType(pt, DelegateClass.tpe)) => - val scalaCaller = newScalaCaller(pt); + val scalaCaller = newScalaCaller(pt) addScalaCallerInfo(scalaCaller, expr1.symbol) val n: Name = scalaCaller.name val del = Ident(DelegateClass) setType DelegateClass.tpe @@ -3277,7 +3168,7 @@ trait Typers { self: Analyzer => //val f1 = TypeApply(f, List(Ident(pt.symbol) setType pt)) val args: List[Tree] = if(expr1.symbol.isStatic) List(Literal(Constant(null))) else List(qual) // where the scala-method is located - val rhs = Apply(f, args); + val rhs = Apply(f, args) typed(rhs) case _ => adapt(expr1, mode, functionType(formals map (t => WildcardType), WildcardType)) @@ -3314,19 +3205,19 @@ trait Typers { self: Analyzer => t case ex: TypeError => stopTimer(failedApplyNanos, start) - def errorInResult(tree: Tree): Boolean = tree.pos == ex.pos || { - tree match { - case Block(_, r) => errorInResult(r) - case Match(_, cases) => cases exists errorInResult - case CaseDef(_, _, r) => errorInResult(r) - case Annotated(_, r) => errorInResult(r) - case If(_, t, e) => errorInResult(t) || errorInResult(e) - case Try(b, catches, _) => errorInResult(b) || (catches exists errorInResult) - case Typed(r, Function(List(), EmptyTree)) => errorInResult(r) - case _ => false - } - } - if (errorInResult(fun) || (args exists errorInResult) || errorInResult(tree)) { + def treesInResult(tree: Tree): List[Tree] = tree :: (tree match { + case Block(_, r) => treesInResult(r) + case Match(_, cases) => cases + case CaseDef(_, _, r) => treesInResult(r) + case Annotated(_, r) => treesInResult(r) + case If(_, t, e) => treesInResult(t) ++ treesInResult(e) + case Try(b, catches, _) => treesInResult(b) ++ catches + case Typed(r, Function(Nil, EmptyTree)) => treesInResult(r) + case _ => Nil + }) + def errorInResult(tree: Tree) = treesInResult(tree) exists (_.pos == ex.pos) + + if (fun :: tree :: args exists errorInResult) { if (printTypings) println("second try for: "+fun+" and "+args) val Select(qual, name) = fun val args1 = tryTypedArgs(args, argMode(fun, mode), ex) @@ -3347,11 +3238,11 @@ trait Typers { self: Analyzer => def typedApply(fun: Tree, args: List[Tree]) = { val stableApplication = (fun.symbol ne null) && fun.symbol.isMethod && fun.symbol.isStable - if (stableApplication && (mode & PATTERNmode) != 0) { + if (stableApplication && isPatternMode) { // treat stable function applications f() as expressions. typed1(tree, mode & ~PATTERNmode | EXPRmode, pt) } else { - val funpt = if ((mode & PATTERNmode) != 0) pt else WildcardType + val funpt = if (isPatternMode) pt else WildcardType val appStart = startTimer(failedApplyNanos) val opeqStart = startTimer(failedOpEqNanos) silent(_.typed(fun, funMode(mode), funpt)) match { @@ -3392,7 +3283,7 @@ trait Typers { self: Analyzer => case ex: TypeError => fun match { case Select(qual, name) - if (mode & PATTERNmode) == 0 && nme.isOpAssignmentName(name.decode) => + if !isPatternMode && nme.isOpAssignmentName(name.decode) => val qual1 = typedQualifier(qual) if (treeInfo.isVariableOrGetter(qual1)) { stopTimer(failedOpEqNanos, opeqStart) @@ -3422,31 +3313,41 @@ trait Typers { self: Analyzer => Apply( Select(vble.duplicate, prefix) setPos fun.pos.focus, args) setPos tree.pos.makeTransparent ) setPos tree.pos + + def mkUpdate(table: Tree, indices: List[Tree]) = { + gen.evalOnceAll(table :: indices, context.owner, context.unit) { ts => + val tab = ts.head + val is = ts.tail + Apply( + Select(tab(), nme.update) setPos table.pos, + ((is map (i => i())) ::: List( + Apply( + Select( + Apply( + Select(tab(), nme.apply) setPos table.pos, + is map (i => i())) setPos qual.pos, + prefix) setPos fun.pos, + args) setPos tree.pos) + ) + ) setPos tree.pos + } + } + val tree1 = qual match { + case Ident(_) => + mkAssign(qual) + case Select(qualqual, vname) => gen.evalOnce(qualqual, context.owner, context.unit) { qq => val qq1 = qq() mkAssign(Select(qq1, vname) setPos qual.pos) } - case Apply(Select(table, nme.apply), indices) => - gen.evalOnceAll(table :: indices, context.owner, context.unit) { ts => - val tab = ts.head - val is = ts.tail - Apply( - Select(tab(), nme.update) setPos table.pos, - ((is map (i => i())) ::: List( - Apply( - Select( - Apply( - Select(tab(), nme.apply) setPos table.pos, - is map (i => i())) setPos qual.pos, - prefix) setPos fun.pos, - args) setPos tree.pos) - ) - ) setPos tree.pos - } - case Ident(_) => - mkAssign(qual) + + case Apply(fn, indices) => + treeInfo.methPart(fn) match { + case Select(table, nme.apply) => mkUpdate(table, indices) + case _ => errorTree(qual, "Unexpected tree during assignment conversion.") + } } typed1(tree1, mode, pt) /* @@ -3559,19 +3460,9 @@ trait Typers { self: Analyzer => if (name == nme.ERROR && onlyPresentation) return makeErrorTree - if (!qual.tpe.widen.isErroneous) { - error(tree.pos, - if (name == nme.CONSTRUCTOR) - qual.tpe.widen+" does not have a constructor" - else - decode(name)+" is not a member of "+ - (if (qual.tpe.typeSymbol.isTypeParameterOrSkolem) "type parameter " else "") + - qual.tpe.widen + - (if ((context.unit ne null) && // Martin: why is this condition needed? - qual.pos.isDefined && tree.pos.isDefined && qual.pos.line < tree.pos.line) - "\npossible cause: maybe a semicolon is missing before `"+decode(name)+"'?" - else "")) - } + if (!qual.tpe.widen.isErroneous) + notAMember(tree, qual, name) + if (onlyPresentation) makeErrorTree else setError(tree) } else { val tree1 = tree match { @@ -3647,8 +3538,8 @@ trait Typers { self: Analyzer => val symDepth = if (defEntry eq null) cx.depth else cx.depth - (cx.scope.nestingLevel - defEntry.owner.nestingLevel) - var impSym: Symbol = NoSymbol; // the imported symbol - var imports = context.imports; // impSym != NoSymbol => it is imported from imports.head + var impSym: Symbol = NoSymbol // the imported symbol + var imports = context.imports // impSym != NoSymbol => it is imported from imports.head while (!reallyExists(impSym) && !imports.isEmpty && imports.head.depth > symDepth) { impSym = imports.head.importedSymbol(name) if (!impSym.exists) imports = imports.tail @@ -3711,7 +3602,7 @@ trait Typers { self: Analyzer => if (settings.debug.value) { log(context.imports)//debug } - error(tree.pos, "not found: "+decode(name)) + error(tree.pos, "not found: "+decodeWithNamespace(name)) defSym = context.owner.newErrorSymbol(name) } } @@ -3779,7 +3670,7 @@ trait Typers { self: Analyzer => errorTree(tree, tpt1.tpe+" does not take type parameters") } else { //Console.println("\{tpt1}:\{tpt1.symbol}:\{tpt1.symbol.info}") - if (settings.debug.value) Console.println(tpt1+":"+tpt1.symbol+":"+tpt1.symbol.info);//debug + if (settings.debug.value) Console.println(tpt1+":"+tpt1.symbol+":"+tpt1.symbol.info)//debug errorTree(tree, "wrong number of type arguments for "+tpt1.tpe+", should be "+tparams.length) } } @@ -3948,7 +3839,7 @@ trait Typers { self: Analyzer => val tpt1 = typedType(tpt, mode) val expr1 = typed(expr, mode & stickyModes, tpt1.tpe.deconst) val owntype = - if ((mode & PATTERNmode) != 0) inferTypedPattern(tpt1.pos, tpt1.tpe, pt) + if (isPatternMode) inferTypedPattern(tpt1.pos, tpt1.tpe, pt) else tpt1.tpe //Console.println(typed pattern: "+tree+":"+", tp = "+tpt1.tpe+", pt = "+pt+" ==> "+owntype)//DEBUG treeCopy.Typed(tree, expr1, tpt1) setType owntype @@ -4146,15 +4037,15 @@ trait Typers { self: Analyzer => tree.tpe = null if (tree.hasSymbol) tree.symbol = NoSymbol } - if (printTypings) println("typing "+tree+", pt = "+pt+", undetparams = "+context.undetparams+", implicits-enabled = "+context.implicitsEnabled+", silent = "+context.reportGeneralErrors); //DEBUG + if (printTypings) println("typing "+tree+", pt = "+pt+", undetparams = "+context.undetparams+", implicits-enabled = "+context.implicitsEnabled+", silent = "+context.reportGeneralErrors) //DEBUG var tree1 = if (tree.tpe ne null) tree else typed1(tree, mode, dropExistential(pt)) - if (printTypings) println("typed "+tree1+":"+tree1.tpe+(if (isSingleType(tree1.tpe)) " with underlying "+tree1.tpe.widen else "")+", undetparams = "+context.undetparams+", pt = "+pt); //DEBUG + if (printTypings) println("typed "+tree1+":"+tree1.tpe+(if (isSingleType(tree1.tpe)) " with underlying "+tree1.tpe.widen else "")+", undetparams = "+context.undetparams+", pt = "+pt) //DEBUG tree1.tpe = addAnnotations(tree1, tree1.tpe) val result = if (tree1.isEmpty) tree1 else adapt(tree1, mode, pt, tree) - if (printTypings) println("adapted "+tree1+":"+tree1.tpe.widen+" to "+pt+", "+context.undetparams); //DEBUG + if (printTypings) println("adapted "+tree1+":"+tree1.tpe.widen+" to "+pt+", "+context.undetparams) //DEBUG // for (t <- tree1.tpe) assert(t != WildcardType) // if ((mode & TYPEmode) != 0) println("type: "+tree1+" has type "+tree1.tpe) if (phase.id <= currentRun.typerPhase.id) signalDone(context.asInstanceOf[analyzer.Context], tree, result) @@ -4162,7 +4053,7 @@ trait Typers { self: Analyzer => } catch { case ex: TypeError => tree.tpe = null - if (printTypings) println("caught "+ex+" in typed: "+tree);//DEBUG + if (printTypings) println("caught "+ex+" in typed: "+tree) //DEBUG reportTypeError(tree.pos, ex) setError(tree) case ex: Exception => @@ -4170,7 +4061,7 @@ trait Typers { self: Analyzer => Console.println("exception when typing "+tree+", pt = "+pt) if ((context ne null) && (context.unit ne null) && (context.unit.source ne null) && (tree ne null)) - logError("AT: " + (tree.pos).dbgString, ex); + logError("AT: " + (tree.pos).dbgString, ex) throw ex } finally { diff --git a/src/compiler/scala/tools/nsc/util/Chars.scala b/src/compiler/scala/tools/nsc/util/Chars.scala index 5a64f36eb4..562806b6eb 100755 --- a/src/compiler/scala/tools/nsc/util/Chars.scala +++ b/src/compiler/scala/tools/nsc/util/Chars.scala @@ -34,6 +34,17 @@ object Chars { -1 } + /** Convert a character to a backslash-u escape */ + def char2uescape(c: Char): String = { + var rest = c.toInt + val buf = new StringBuilder + for (i <- 1 to 4) { + buf ++= (rest % 16).toHexString + rest = rest / 16 + } + "\\u" + buf.toString.reverse + } + /** Is character a line break? */ @inline def isLineBreakChar(c: Char) = (c: @switch) match { case LF|FF|CR|SU => true diff --git a/src/compiler/scala/tools/nsc/util/ShowPickled.scala b/src/compiler/scala/tools/nsc/util/ShowPickled.scala index 740e42192b..b87d603570 100644 --- a/src/compiler/scala/tools/nsc/util/ShowPickled.scala +++ b/src/compiler/scala/tools/nsc/util/ShowPickled.scala @@ -13,6 +13,7 @@ import java.lang.Long.toHexString import java.lang.Float.intBitsToFloat import java.lang.Double.longBitsToDouble +import cmd.program.Simple import symtab.{ Flags, Names } import scala.reflect.generic.{ PickleBuffer, PickleFormat } import interpreter.ByteCode.scalaSigBytesForPath @@ -288,15 +289,23 @@ object ShowPickled extends Names { printFile(pickle, Console.out, bare) } + private lazy val ShowPickledSpec = + Simple( + Simple.scalaProgramInfo("showPickled", "Usage: showPickled [--bare] <classname>"), + List("--bare" -> "suppress numbers in output"), + Nil, + null + ) + /** Option --bare suppresses numbers so the output can be diffed. */ def main(args: Array[String]) { - val parsed = cmd.CommandLine(args.toList, List("--bare"), Nil) - def isBare = parsed isSet "--bare" + val runner = ShowPickledSpec instance args + import runner._ - parsed.residualArgs foreach { arg => + residualArgs foreach { arg => (fromFile(arg) orElse fromName(arg)) match { - case Some(pb) => show(arg, pb, isBare) + case Some(pb) => show(arg, pb, parsed isSet "--bare") case _ => Console.println("Cannot read " + arg) } } diff --git a/src/compiler/scala/tools/nsc/util/SourceFile.scala b/src/compiler/scala/tools/nsc/util/SourceFile.scala index 57d2cc782f..f9f3c5e5fe 100644 --- a/src/compiler/scala/tools/nsc/util/SourceFile.scala +++ b/src/compiler/scala/tools/nsc/util/SourceFile.scala @@ -163,7 +163,6 @@ extends BatchSourceFile(name, contents) override def positionInUltimateSource(position: Position) = { if (!position.isDefined) super.positionInUltimateSource(position) else { - println("!!!") var off = position.point var compsLeft = components // the search here has to be against the length of the files underlying the @@ -171,7 +170,6 @@ extends BatchSourceFile(name, contents) // less than the underlying length.) Otherwise we can and will overshoot the // correct component and return a garbage position. while (compsLeft.head.underlyingLength-1 <= off && !compsLeft.tail.isEmpty) { - println("discarding "+compsLeft.head) off = off - compsLeft.head.underlyingLength + 1 compsLeft = compsLeft.tail } |