diff options
Diffstat (limited to 'src/compiler')
59 files changed, 2768 insertions, 2477 deletions
diff --git a/src/compiler/scala/tools/nsc/CompilationUnits.scala b/src/compiler/scala/tools/nsc/CompilationUnits.scala index f23bca77cd..0a356ed7b6 100644 --- a/src/compiler/scala/tools/nsc/CompilationUnits.scala +++ b/src/compiler/scala/tools/nsc/CompilationUnits.scala @@ -135,11 +135,8 @@ trait CompilationUnits { global: Global => @deprecated("Call global.currentRun.reporting.uncheckedWarning directly instead.", "2.11.2") final def uncheckedWarning(pos: Position, msg: String): Unit = currentRun.reporting.uncheckedWarning(pos, msg) - // called by ScalaDocAnalyzer, overridden by the IDE (in Reporter) - // TODO: don't use reporter to communicate comments from parser to IDE! - @deprecated("This method will be removed.", "2.11.2") - final def comment(pos: Position, msg: String): Unit = reporter.comment(pos, msg) - + @deprecated("This method will be removed. It does nothing.", "2.11.2") + final def comment(pos: Position, msg: String): Unit = {} /** Is this about a .java source file? */ lazy val isJava = source.file.name.endsWith(".java") diff --git a/src/compiler/scala/tools/nsc/CompileServer.scala b/src/compiler/scala/tools/nsc/CompileServer.scala index 1f3a4237eb..029e1c4629 100644 --- a/src/compiler/scala/tools/nsc/CompileServer.scala +++ b/src/compiler/scala/tools/nsc/CompileServer.scala @@ -152,6 +152,7 @@ class StandardCompileServer extends SocketServer { clearCompiler() case ex: Throwable => warn("Compile server encountered fatal condition: " + ex) + reporter.error(null, "Compile server encountered fatal condition: " + ex.getMessage) shutdown = true throw ex } diff --git a/src/compiler/scala/tools/nsc/CompileSocket.scala b/src/compiler/scala/tools/nsc/CompileSocket.scala index c4f06b59ec..c693fbe8e2 100644 --- a/src/compiler/scala/tools/nsc/CompileSocket.scala +++ b/src/compiler/scala/tools/nsc/CompileSocket.scala @@ -32,7 +32,8 @@ trait HasCompileSocket { if (isErrorMessage(line)) noErrors = false - compileSocket.echo(line) + // be consistent with scalac: everything goes to stderr + compileSocket.warn(line) loop() } try loop() diff --git a/src/compiler/scala/tools/nsc/CompilerCommand.scala b/src/compiler/scala/tools/nsc/CompilerCommand.scala index a1d0d52dcf..3ded456378 100644 --- a/src/compiler/scala/tools/nsc/CompilerCommand.scala +++ b/src/compiler/scala/tools/nsc/CompilerCommand.scala @@ -103,7 +103,15 @@ class CompilerCommand(arguments: List[String], val settings: Settings) { val components = global.phaseNames // global.phaseDescriptors // one initializes s"Phase graph of ${components.size} components output to ${genPhaseGraph.value}*.dot." } - else "" + // would be nicer if we could ask all the options for their helpful messages + else { + val sb = new StringBuilder + allSettings foreach { + case s: MultiChoiceSetting if s.isHelping => sb append s.help + case _ => + } + sb.toString + } } /** diff --git a/src/compiler/scala/tools/nsc/Global.scala b/src/compiler/scala/tools/nsc/Global.scala index 572e579aca..82ffb35c3f 100644 --- a/src/compiler/scala/tools/nsc/Global.scala +++ b/src/compiler/scala/tools/nsc/Global.scala @@ -45,7 +45,8 @@ class Global(var currentSettings: Settings, var reporter: Reporter) with Printers with DocComments with Positions - with Reporting { self => + with Reporting + with Parsing { self => // the mirror -------------------------------------------------- @@ -218,6 +219,14 @@ class Global(var currentSettings: Settings, var reporter: Reporter) /** Called from parser, which signals hereby that a method definition has been parsed. */ def signalParseProgress(pos: Position) {} + /** Called by ScalaDocAnalyzer when a doc comment has been parsed. */ + def signalParsedDocComment(comment: String, pos: Position) = { + // TODO: this is all very borken (only works for scaladoc comments, not regular ones) + // --> add hooks to parser and refactor Interactive global to handle comments directly + // in any case don't use reporter for parser hooks + reporter.comment(pos, comment) + } + /** Register new context; called for every created context */ def registerContext(c: analyzer.Context) { @@ -395,7 +404,7 @@ class Global(var currentSettings: Settings, var reporter: Reporter) reporter.cancelled || unit.isJava && this.id > maxJavaPhase } - final def applyPhase(unit: CompilationUnit) { + final def withCurrentUnit(unit: CompilationUnit)(task: => Unit) { if ((unit ne null) && unit.exists) lastSeenSourceFile = unit.source @@ -407,7 +416,7 @@ class Global(var currentSettings: Settings, var reporter: Reporter) currentRun.currentUnit = unit if (!cancelled(unit)) { currentRun.informUnitStarting(this, unit) - apply(unit) + task } currentRun.advanceUnit() } finally { @@ -415,6 +424,8 @@ class Global(var currentSettings: Settings, var reporter: Reporter) currentRun.currentUnit = unit0 } } + + final def applyPhase(unit: CompilationUnit) = withCurrentUnit(unit)(apply(unit)) } // phaseName = "parser" @@ -831,198 +842,11 @@ class Global(var currentSettings: Settings, var reporter: Reporter) } reverse } - // ------------ Invalidations --------------------------------- - - /** Is given package class a system package class that cannot be invalidated? - */ - private def isSystemPackageClass(pkg: Symbol) = - pkg == RootClass || - pkg == definitions.ScalaPackageClass || { - val pkgname = pkg.fullName - (pkgname startsWith "scala.") && !(pkgname startsWith "scala.tools") - } - - /** Invalidates packages that contain classes defined in a classpath entry, and - * rescans that entry. - * @param paths Fully qualified names that refer to directories or jar files that are - * a entries on the classpath. - * First, causes the classpath entry referred to by `path` to be rescanned, so that - * any new files or deleted files or changes in subpackages are picked up. - * Second, invalidates any packages for which one of the following considitions is met: - - * - the classpath entry contained during the last compilation run classfiles - * that represent a member in the package - * - the classpath entry now contains classfiles - * that represent a member in the package - * - the set of subpackages has changed. - * - * The invalidated packages are reset in their entirety; all member classes and member packages - * are re-accessed using the new classpath. - * Not invalidated are system packages that the compiler needs to access as parts - * of standard definitions. The criterion what is a system package is currently: - * any package rooted in "scala", with the exception of packages rooted in "scala.tools". - * This can be refined later. - * @return A pair consisting of - * - a list of invalidated packages - * - a list of of packages that should have been invalidated but were not because - * they are system packages. - */ - def invalidateClassPathEntries(paths: String*): (List[ClassSymbol], List[ClassSymbol]) = { - val invalidated, failed = new mutable.ListBuffer[ClassSymbol] - classPath match { - case cp: MergedClassPath[_] => - def assoc(path: String): List[(PlatformClassPath, PlatformClassPath)] = { - val dir = AbstractFile getDirectory path - val canonical = dir.canonicalPath - def matchesCanonical(e: ClassPath[_]) = e.origin match { - case Some(opath) => - (AbstractFile getDirectory opath).canonicalPath == canonical - case None => - false - } - cp.entries find matchesCanonical match { - case Some(oldEntry) => - List(oldEntry -> cp.context.newClassPath(dir)) - case None => - println(s"canonical = $canonical, origins = ${cp.entries map (_.origin)}") - error(s"cannot invalidate: no entry named $path in classpath $classPath") - List() - } - } - val subst = Map(paths flatMap assoc: _*) - if (subst.nonEmpty) { - platform updateClassPath subst - informProgress(s"classpath updated on entries [${subst.keys mkString ","}]") - def mkClassPath(elems: Iterable[PlatformClassPath]): PlatformClassPath = - if (elems.size == 1) elems.head - else new MergedClassPath(elems, classPath.context) - val oldEntries = mkClassPath(subst.keys) - val newEntries = mkClassPath(subst.values) - reSync(RootClass, Some(classPath), Some(oldEntries), Some(newEntries), invalidated, failed) - } - } - def show(msg: String, syms: scala.collection.Traversable[Symbol]) = - if (syms.nonEmpty) - informProgress(s"$msg: ${syms map (_.fullName) mkString ","}") - show("invalidated packages", invalidated) - show("could not invalidate system packages", failed) - (invalidated.toList, failed.toList) - } - - /** Re-syncs symbol table with classpath - * @param root The root symbol to be resynced (a package class) - * @param allEntries Optionally, the corresponding package in the complete current classPath - * @param oldEntries Optionally, the corresponding package in the old classPath entries - * @param newEntries Optionally, the corresponding package in the new classPath entries - * @param invalidated A listbuffer collecting the invalidated package classes - * @param failed A listbuffer collecting system package classes which could not be invalidated - * The resyncing strategy is determined by the absence or presence of classes and packages. - * If either oldEntries or newEntries contains classes, root is invalidated, provided a corresponding package - * exists in allEntries, or otherwise is removed. - * Otherwise, the action is determined by the following matrix, with columns: - * - * old new all sym action - * + + + + recurse into all child packages of old ++ new - * + - + + invalidate root - * + - - + remove root from its scope - * - + + + invalidate root - * - + + - create and enter root - * - - * * no action - * - * Here, old, new, all mean classpaths and sym means symboltable. + is presence of an - * entry in its column, - is absence, * is don't care. - * - * Note that new <= all and old <= sym, so the matrix above covers all possibilities. - */ - private def reSync(root: ClassSymbol, - allEntries: OptClassPath, oldEntries: OptClassPath, newEntries: OptClassPath, - invalidated: mutable.ListBuffer[ClassSymbol], failed: mutable.ListBuffer[ClassSymbol]) { - ifDebug(informProgress(s"syncing $root, $oldEntries -> $newEntries")) - - val getName: ClassPath[AbstractFile] => String = (_.name) - def hasClasses(cp: OptClassPath) = cp.isDefined && cp.get.classes.nonEmpty - def invalidateOrRemove(root: ClassSymbol) = { - allEntries match { - case Some(cp) => root setInfo new loaders.PackageLoader(cp) - case None => root.owner.info.decls unlink root.sourceModule - } - invalidated += root - } - def packageNames(cp: PlatformClassPath): Set[String] = cp.packages.toSet map getName - def subPackage(cp: PlatformClassPath, name: String): OptClassPath = - cp.packages find (cp1 => getName(cp1) == name) - - val classesFound = hasClasses(oldEntries) || hasClasses(newEntries) - if (classesFound && !isSystemPackageClass(root)) { - invalidateOrRemove(root) - } else { - if (classesFound) { - if (root.isRoot) invalidateOrRemove(EmptyPackageClass) - else failed += root - } - (oldEntries, newEntries) match { - case (Some(oldcp) , Some(newcp)) => - for (pstr <- packageNames(oldcp) ++ packageNames(newcp)) { - val pname = newTermName(pstr) - val pkg = (root.info decl pname) orElse { - // package was created by external agent, create symbol to track it - assert(!subPackage(oldcp, pstr).isDefined) - loaders.enterPackage(root, pstr, new loaders.PackageLoader(allEntries.get)) - } - reSync( - pkg.moduleClass.asInstanceOf[ClassSymbol], - subPackage(allEntries.get, pstr), subPackage(oldcp, pstr), subPackage(newcp, pstr), - invalidated, failed) - } - case (Some(oldcp), None) => - invalidateOrRemove(root) - case (None, Some(newcp)) => - invalidateOrRemove(root) - case (None, None) => - } - } - } - - /** Invalidate contents of setting -Yinvalidate */ - def doInvalidation() = settings.Yinvalidate.value match { - case "" => - case entry => invalidateClassPathEntries(entry) - } - // ----------- Runs --------------------------------------- private var curRun: Run = null private var curRunId = 0 - /** A hook that lets subclasses of `Global` define whether a package or class should be kept loaded for the - * next compiler run. If the parameter `sym` is a class or object, and `clearOnNextRun(sym)` returns `true`, - * then the symbol is unloaded and reset to its state before the last compiler run. If the parameter `sym` is - * a package, and clearOnNextRun(sym)` returns `true`, the package is recursively searched for - * classes to drop. - * - * Example: Let's say I want a compiler that drops all classes corresponding to the current project - * between runs. Then `keepForNextRun` of a toplevel class or object should return `true` if the - * class or object does not form part of the current project, `false` otherwise. For a package, - * clearOnNextRun should return `true` if no class in that package forms part of the current project, - * `false` otherwise. - * - * @param sym A class symbol, object symbol, package, or package class. - */ - @deprecated("use invalidateClassPathEntries instead", "2.10.0") - def clearOnNextRun(sym: Symbol) = false - /* To try out clearOnNext run on the scala.tools.nsc project itself - * replace `false` above with the following code - - settings.Xexperimental.value && { sym.isRoot || { - sym.fullName match { - case "scala" | "scala.tools" | "scala.tools.nsc" => true - case _ => sym.owner.fullName.startsWith("scala.tools.nsc") - } - }} - - * Then, fsc -Xexperimental clears the nsc project between successive runs of `fsc`. - */ - object typeDeconstruct extends { val global: Global.this.type = Global.this } with typechecker.StructuredTypeStrings @@ -1155,7 +979,7 @@ class Global(var currentSettings: Settings, var reporter: Reporter) /** A Run is a single execution of the compiler on a set of units. */ - class Run extends RunContextApi with RunReporting { + class Run extends RunContextApi with RunReporting with RunParsing { /** Have been running into too many init order issues with Run * during erroneous conditions. Moved all these vals up to the * top of the file so at least they're not trivially null. @@ -1292,47 +1116,6 @@ class Global(var currentSettings: Settings, var reporter: Reporter) first } - /** Reset all classes contained in current project, as determined by - * the clearOnNextRun hook - */ - @deprecated("use invalidateClassPathEntries instead", "2.10.0") - def resetProjectClasses(root: Symbol): Unit = try { - def unlink(sym: Symbol) = - if (sym != NoSymbol) root.info.decls.unlink(sym) - if (settings.verbose) inform("[reset] recursing in "+root) - val toReload = mutable.Set[String]() - for (sym <- root.info.decls) { - if (sym.isInitialized && clearOnNextRun(sym)) - if (sym.hasPackageFlag) { - resetProjectClasses(sym.moduleClass) - openPackageModule(sym.moduleClass) - } else { - unlink(sym) - unlink(root.info.decls.lookup( - if (sym.isTerm) sym.name.toTypeName else sym.name.toTermName)) - toReload += sym.fullName - // note: toReload could be set twice with the same name - // but reinit must happen only once per name. That's why - // the following classPath.findClass { ... } code cannot be moved here. - } - } - for (fullname <- toReload) - classPath.findClass(fullname) match { - case Some(classRep) => - if (settings.verbose) inform("[reset] reinit "+fullname) - loaders.initializeFromClassPath(root, classRep) - case _ => - } - } catch { - case ex: Throwable => - // this handler should not be nessasary, but it seems that `fsc` - // eats exceptions if they appear here. Need to find out the cause for - // this and fix it. - inform("[reset] exception happened: "+ex) - ex.printStackTrace() - throw ex - } - // --------------- Miscellania ------------------------------- /** Progress tracking. Measured in "progress units" which are 1 per @@ -1397,7 +1180,7 @@ class Global(var currentSettings: Settings, var reporter: Reporter) val erasurePhase = phaseNamed("erasure") val posterasurePhase = phaseNamed("posterasure") // val lazyvalsPhase = phaseNamed("lazyvals") - // val lambdaliftPhase = phaseNamed("lambdalift") + val lambdaliftPhase = phaseNamed("lambdalift") // val constructorsPhase = phaseNamed("constructors") val flattenPhase = phaseNamed("flatten") val mixinPhase = phaseNamed("mixin") @@ -1542,8 +1325,6 @@ class Global(var currentSettings: Settings, var reporter: Reporter) compileUnitsInternal(units, fromPhase) private def compileUnitsInternal(units: List[CompilationUnit], fromPhase: Phase) { - doInvalidation() - units foreach addUnit val startTime = currentTime @@ -1619,13 +1400,6 @@ class Global(var currentSettings: Settings, var reporter: Reporter) // Clear any sets or maps created via perRunCaches. perRunCaches.clearAll() - - // Reset project - if (!stopPhase("namer")) { - enteringPhase(namerPhase) { - resetProjectClasses(RootClass) - } - } } /** Compile list of abstract files. */ diff --git a/src/compiler/scala/tools/nsc/Parsing.scala b/src/compiler/scala/tools/nsc/Parsing.scala new file mode 100644 index 0000000000..4dd3c3f378 --- /dev/null +++ b/src/compiler/scala/tools/nsc/Parsing.scala @@ -0,0 +1,36 @@ +/* NSC -- new Scala compiler + * Copyright 2005-2014 LAMP/EPFL, Typesafe Inc. + * @author Adriaan Moors + */ + +package scala +package tools.nsc + +import scala.reflect.internal.Positions +import scala.tools.nsc.reporters.Reporter + +/** Similar to Reporting: gather global functionality specific to parsing. + */ +trait Parsing { self : Positions with Reporting => + def currentRun: RunParsing + + trait RunParsing { + val parsing: PerRunParsing = new PerRunParsing + } + + class PerRunParsing { + // for repl + private[this] var incompleteHandler: (Position, String) => Unit = null + def withIncompleteHandler[T](handler: (Position, String) => Unit)(thunk: => T) = { + val saved = incompleteHandler + incompleteHandler = handler + try thunk + finally incompleteHandler = saved + } + + def incompleteHandled = incompleteHandler != null + def incompleteInputError(pos: Position, msg: String): Unit = + if (incompleteHandled) incompleteHandler(pos, msg) + else reporter.error(pos, msg) + } +}
\ No newline at end of file diff --git a/src/compiler/scala/tools/nsc/Properties.scala b/src/compiler/scala/tools/nsc/Properties.scala index ed5fda9c3f..59fefba954 100644 --- a/src/compiler/scala/tools/nsc/Properties.scala +++ b/src/compiler/scala/tools/nsc/Properties.scala @@ -14,6 +14,7 @@ object Properties extends scala.util.PropertiesTrait { // settings based on jar properties def residentPromptString = scalaPropOrElse("resident.prompt", "\nnsc> ") def shellPromptString = scalaPropOrElse("shell.prompt", "\nscala> ") + def shellInterruptedString = scalaPropOrElse("shell.interrupted", ":quit\n") // derived values def isEmacsShell = propOrEmpty("env.emacs") != "" diff --git a/src/compiler/scala/tools/nsc/Reporting.scala b/src/compiler/scala/tools/nsc/Reporting.scala index 0263586418..b164f395fe 100644 --- a/src/compiler/scala/tools/nsc/Reporting.scala +++ b/src/compiler/scala/tools/nsc/Reporting.scala @@ -104,20 +104,5 @@ trait Reporting extends scala.reflect.internal.Reporting { self: ast.Positions w if (settings.fatalWarnings && reporter.hasWarnings) reporter.error(NoPosition, "No warnings can be incurred under -Xfatal-warnings.") } - - // for repl - private[this] var incompleteHandler: (Position, String) => Unit = null - def withIncompleteHandler[T](handler: (Position, String) => Unit)(thunk: => T) = { - val saved = incompleteHandler - incompleteHandler = handler - try thunk - finally incompleteHandler = saved - } - - def incompleteHandled = incompleteHandler != null - def incompleteInputError(pos: Position, msg: String): Unit = - if (incompleteHandled) incompleteHandler(pos, msg) - else reporter.error(pos, msg) - } }
\ No newline at end of file diff --git a/src/compiler/scala/tools/nsc/ast/parser/Parsers.scala b/src/compiler/scala/tools/nsc/ast/parser/Parsers.scala index 883fd31dbc..8d810d456e 100644 --- a/src/compiler/scala/tools/nsc/ast/parser/Parsers.scala +++ b/src/compiler/scala/tools/nsc/ast/parser/Parsers.scala @@ -154,8 +154,8 @@ self => def unit = global.currentUnit // suppress warnings; silent abort on errors - def warning(offset: Offset, msg: String) {} - def deprecationWarning(offset: Offset, msg: String) {} + def warning(offset: Offset, msg: String): Unit = () + def deprecationWarning(offset: Offset, msg: String): Unit = () def syntaxError(offset: Offset, msg: String): Unit = throw new MalformedInput(offset, msg) def incompleteInputError(msg: String): Unit = throw new MalformedInput(source.content.length - 1, msg) @@ -204,13 +204,11 @@ self => override def newScanner() = new UnitScanner(unit, patches) - override def warning(offset: Offset, msg: String) { + override def warning(offset: Offset, msg: String): Unit = reporter.warning(o2p(offset), msg) - } - override def deprecationWarning(offset: Offset, msg: String) { + override def deprecationWarning(offset: Offset, msg: String): Unit = currentRun.reporting.deprecationWarning(o2p(offset), msg) - } private var smartParsing = false @inline private def withSmartParsing[T](body: => T): T = { @@ -226,15 +224,15 @@ self => for ((offset, msg) <- syntaxErrors) reporter.error(o2p(offset), msg) - override def syntaxError(offset: Offset, msg: String) { + override def syntaxError(offset: Offset, msg: String): Unit = { if (smartParsing) syntaxErrors += ((offset, msg)) else reporter.error(o2p(offset), msg) } - override def incompleteInputError(msg: String) { + override def incompleteInputError(msg: String): Unit = { val offset = source.content.length - 1 if (smartParsing) syntaxErrors += ((offset, msg)) - else currentRun.reporting.incompleteInputError(o2p(offset), msg) + else currentRun.parsing.incompleteInputError(o2p(offset), msg) } /** parse unit. If there are inbalanced braces, @@ -335,7 +333,7 @@ self => */ private var inScalaPackage = false private var currentPackage = "" - def resetPackage() { + def resetPackage(): Unit = { inScalaPackage = false currentPackage = "" } @@ -514,7 +512,7 @@ self => finally inFunReturnType = saved } - protected def skip(targetToken: Token) { + protected def skip(targetToken: Token): Unit = { var nparens = 0 var nbraces = 0 while (true) { @@ -544,27 +542,25 @@ self => } def warning(offset: Offset, msg: String): Unit def incompleteInputError(msg: String): Unit - private def syntaxError(pos: Position, msg: String, skipIt: Boolean) { - syntaxError(pos pointOrElse in.offset, msg, skipIt) - } def syntaxError(offset: Offset, msg: String): Unit - def syntaxError(msg: String, skipIt: Boolean) { + + private def syntaxError(pos: Position, msg: String, skipIt: Boolean): Unit = + syntaxError(pos pointOrElse in.offset, msg, skipIt) + def syntaxError(msg: String, skipIt: Boolean): Unit = syntaxError(in.offset, msg, skipIt) - } - def syntaxError(offset: Offset, msg: String, skipIt: Boolean) { + def syntaxError(offset: Offset, msg: String, skipIt: Boolean): Unit = { if (offset > lastErrorOffset) { syntaxError(offset, msg) - // no more errors on this token. - lastErrorOffset = in.offset + lastErrorOffset = in.offset // no more errors on this token. } if (skipIt) skip(UNDEF) } - def warning(msg: String) { warning(in.offset, msg) } + def warning(msg: String): Unit = warning(in.offset, msg) - def syntaxErrorOrIncomplete(msg: String, skipIt: Boolean) { + def syntaxErrorOrIncomplete(msg: String, skipIt: Boolean): Unit = { if (in.token == EOF) incompleteInputError(msg) else @@ -719,7 +715,7 @@ self => /** Convert tree to formal parameter. */ def convertToParam(tree: Tree): ValDef = atPos(tree.pos) { - def removeAsPlaceholder(name: Name) { + def removeAsPlaceholder(name: Name): Unit = { placeholderParams = placeholderParams filter (_.name != name) } def errorParam = makeParam(nme.ERROR, errorTypeTree setPos o2p(tree.pos.end)) @@ -1233,15 +1229,15 @@ self => skipIt = true)(EmptyTree) // Like Swiss cheese, with holes def stringCheese: Tree = atPos(in.offset) { - val start = in.offset + val start = in.offset val interpolator = in.name.encoded // ident() for INTERPOLATIONID val partsBuf = new ListBuffer[Tree] - val exprBuf = new ListBuffer[Tree] + val exprsBuf = new ListBuffer[Tree] in.nextToken() while (in.token == STRINGPART) { partsBuf += literal() - exprBuf += ( + exprsBuf += ( if (inPattern) dropAnyBraces(pattern()) else in.token match { case IDENTIFIER => atPos(in.offset)(Ident(ident())) @@ -1254,11 +1250,13 @@ self => } if (in.token == STRINGLIT) partsBuf += literal() + // Documenting that it is intentional that the ident is not rooted for purposes of virtualization + //val t1 = atPos(o2p(start)) { Select(Select (Ident(nme.ROOTPKG), nme.scala_), nme.StringContext) } val t1 = atPos(o2p(start)) { Ident(nme.StringContext) } val t2 = atPos(start) { Apply(t1, partsBuf.toList) } t2 setPos t2.pos.makeTransparent val t3 = Select(t2, interpolator) setPos t2.pos - atPos(start) { Apply(t3, exprBuf.toList) } + atPos(start) { Apply(t3, exprsBuf.toList) } } if (inPattern) stringCheese else withPlaceholders(stringCheese, isAny = true) // strinterpolator params are Any* by definition @@ -1266,21 +1264,21 @@ self => /* ------------- NEW LINES ------------------------------------------------- */ - def newLineOpt() { + def newLineOpt(): Unit = { if (in.token == NEWLINE) in.nextToken() } - def newLinesOpt() { + def newLinesOpt(): Unit = { if (in.token == NEWLINE || in.token == NEWLINES) in.nextToken() } - def newLineOptWhenFollowedBy(token: Offset) { + def newLineOptWhenFollowedBy(token: Offset): Unit = { // note: next is defined here because current == NEWLINE if (in.token == NEWLINE && in.next.token == token) newLineOpt() } - def newLineOptWhenFollowing(p: Token => Boolean) { + def newLineOptWhenFollowing(p: Token => Boolean): Unit = { // note: next is defined here because current == NEWLINE if (in.token == NEWLINE && p(in.next.token)) newLineOpt() } diff --git a/src/compiler/scala/tools/nsc/ast/parser/Scanners.scala b/src/compiler/scala/tools/nsc/ast/parser/Scanners.scala index 572497ac90..9ebc94b5fc 100644 --- a/src/compiler/scala/tools/nsc/ast/parser/Scanners.scala +++ b/src/compiler/scala/tools/nsc/ast/parser/Scanners.scala @@ -113,7 +113,7 @@ trait Scanners extends ScannersCommon { case SU | CR | LF => case _ => nextChar() ; skipLineComment() } - private def maybeOpen() { + private def maybeOpen(): Unit = { putCommentChar() if (ch == '*') { putCommentChar() @@ -137,7 +137,7 @@ trait Scanners extends ScannersCommon { def skipDocComment(): Unit = skipNestedComments() def skipBlockComment(): Unit = skipNestedComments() - private def skipToCommentEnd(isLineComment: Boolean) { + private def skipToCommentEnd(isLineComment: Boolean): Unit = { nextChar() if (isLineComment) skipLineComment() else { @@ -185,7 +185,7 @@ trait Scanners extends ScannersCommon { /** append Unicode character to "cbuf" buffer */ - protected def putChar(c: Char) { + protected def putChar(c: Char): Unit = { // assert(cbuf.size < 10000, cbuf) cbuf.append(c) } @@ -196,7 +196,7 @@ trait Scanners extends ScannersCommon { protected def emitIdentifierDeprecationWarnings = true /** Clear buffer and set name and token */ - private def finishNamed(idtoken: Token = IDENTIFIER) { + private def finishNamed(idtoken: Token = IDENTIFIER): Unit = { name = newTermName(cbuf.toString) cbuf.clear() token = idtoken @@ -215,7 +215,7 @@ trait Scanners extends ScannersCommon { } /** Clear buffer and set string */ - private def setStrVal() { + private def setStrVal(): Unit = { strVal = cbuf.toString cbuf.clear() } @@ -270,7 +270,7 @@ trait Scanners extends ScannersCommon { /** Produce next token, filling TokenData fields of Scanner. */ - def nextToken() { + def nextToken(): Unit = { val lastToken = token // Adapt sepRegions according to last token (lastToken: @switch) match { @@ -341,7 +341,7 @@ trait Scanners extends ScannersCommon { prev copyFrom this val nextLastOffset = charOffset - 1 fetchToken() - def resetOffset() { + def resetOffset(): Unit = { offset = prev.offset lastOffset = prev.lastOffset } @@ -399,7 +399,7 @@ trait Scanners extends ScannersCommon { /** read next token, filling TokenData fields of Scanner. */ - protected final def fetchToken() { + protected final def fetchToken(): Unit = { offset = charOffset - 1 (ch: @switch) match { @@ -604,7 +604,7 @@ trait Scanners extends ScannersCommon { // Identifiers --------------------------------------------------------------- - private def getBackquotedIdent() { + private def getBackquotedIdent(): Unit = { nextChar() getLitChars('`') if (ch == '`') { @@ -664,7 +664,7 @@ trait Scanners extends ScannersCommon { else finishNamed() } - private def getIdentOrOperatorRest() { + private def getIdentOrOperatorRest(): Unit = { if (isIdentifierPart(ch)) getIdentRest() else ch match { @@ -688,9 +688,11 @@ trait Scanners extends ScannersCommon { setStrVal() nextChar() token = STRINGLIT - } else syntaxError("unclosed string literal") + } else unclosedStringLit() } + private def unclosedStringLit(): Unit = syntaxError("unclosed string literal") + private def getRawStringLit(): Unit = { if (ch == '\"') { nextRawChar() @@ -764,7 +766,7 @@ trait Scanners extends ScannersCommon { if (multiLine) incompleteInputError("unclosed multi-line string literal") else - syntaxError("unclosed string literal") + unclosedStringLit() } else { putChar(ch) @@ -857,7 +859,7 @@ trait Scanners extends ScannersCommon { /** read fractional part and exponent of floating point number * if one is present. */ - protected def getFraction() { + protected def getFraction(): Unit = { token = DOUBLELIT while ('0' <= ch && ch <= '9') { putChar(ch) @@ -966,14 +968,13 @@ trait Scanners extends ScannersCommon { def floatVal: Double = floatVal(negated = false) - def checkNoLetter() { + def checkNoLetter(): Unit = { if (isIdentifierPart(ch) && ch >= ' ') syntaxError("Invalid literal number") } - /** Read a number into strVal and set base - */ - protected def getNumber() { + /** Read a number into strVal and set base */ + protected def getNumber(): Unit = { val base1 = if (base < 10) 10 else base // Read 8,9's even if format is octal, produce a malformed number error afterwards. // At this point, we have already read the first digit, so to tell an innocent 0 apart @@ -1052,7 +1053,7 @@ trait Scanners extends ScannersCommon { /** Parse character literal if current character is followed by \', * or follow with given op and return a symbol literal token */ - def charLitOr(op: () => Unit) { + def charLitOr(op: () => Unit): Unit = { putChar(ch) nextChar() if (ch == '\'') { @@ -1068,21 +1069,19 @@ trait Scanners extends ScannersCommon { // Errors ----------------------------------------------------------------- - /** generate an error at the given offset - */ - def syntaxError(off: Offset, msg: String) { + /** generate an error at the given offset */ + def syntaxError(off: Offset, msg: String): Unit = { error(off, msg) token = ERROR } - /** generate an error at the current token offset - */ + /** generate an error at the current token offset */ def syntaxError(msg: String): Unit = syntaxError(offset, msg) def deprecationWarning(msg: String): Unit = deprecationWarning(offset, msg) /** signal an error where the input ended in the middle of a token */ - def incompleteInputError(msg: String) { + def incompleteInputError(msg: String): Unit = { incompleteInputError(offset, msg) token = EOF } @@ -1134,7 +1133,7 @@ trait Scanners extends ScannersCommon { /** Initialization method: read first char, then first token */ - def init() { + def init(): Unit = { nextChar() nextToken() } @@ -1261,7 +1260,7 @@ trait Scanners extends ScannersCommon { override def deprecationWarning(off: Offset, msg: String) = currentRun.reporting.deprecationWarning(unit.position(off), msg) override def error (off: Offset, msg: String) = reporter.error(unit.position(off), msg) - override def incompleteInputError(off: Offset, msg: String) = currentRun.reporting.incompleteInputError(unit.position(off), msg) + override def incompleteInputError(off: Offset, msg: String) = currentRun.parsing.incompleteInputError(unit.position(off), msg) private var bracePatches: List[BracePatch] = patches @@ -1490,6 +1489,6 @@ trait Scanners extends ScannersCommon { // when skimming through the source file trying to heal braces override def emitIdentifierDeprecationWarnings = false - override def error(offset: Offset, msg: String) {} + override def error(offset: Offset, msg: String): Unit = () } } diff --git a/src/compiler/scala/tools/nsc/ast/parser/SyntaxAnalyzer.scala b/src/compiler/scala/tools/nsc/ast/parser/SyntaxAnalyzer.scala index 64b762696e..df2073785b 100644 --- a/src/compiler/scala/tools/nsc/ast/parser/SyntaxAnalyzer.scala +++ b/src/compiler/scala/tools/nsc/ast/parser/SyntaxAnalyzer.scala @@ -83,7 +83,7 @@ abstract class SyntaxAnalyzer extends SubComponent with Parsers with MarkupParse private def initialUnitBody(unit: CompilationUnit): Tree = { if (unit.isJava) new JavaUnitParser(unit).parse() - else if (currentRun.reporting.incompleteHandled) newUnitParser(unit).parse() + else if (currentRun.parsing.incompleteHandled) newUnitParser(unit).parse() else newUnitParser(unit).smartParse() } diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BCodeAsmCommon.scala b/src/compiler/scala/tools/nsc/backend/jvm/BCodeAsmCommon.scala new file mode 100644 index 0000000000..e5b4c4a6c2 --- /dev/null +++ b/src/compiler/scala/tools/nsc/backend/jvm/BCodeAsmCommon.scala @@ -0,0 +1,130 @@ +/* NSC -- new Scala compiler + * Copyright 2005-2014 LAMP/EPFL + * @author Martin Odersky + */ + +package scala.tools.nsc.backend.jvm + +import scala.tools.nsc.Global +import PartialFunction._ + +/** + * This trait contains code shared between GenBCode and GenASM that depends on types defined in + * the compiler cake (Global). + */ +final class BCodeAsmCommon[G <: Global](val global: G) { + import global._ + + /** + * True if `classSym` is an anonymous class or a local class. I.e., false if `classSym` is a + * member class. This method is used to decide if we should emit an EnclosingMethod attribute. + * It is also used to decide whether the "owner" field in the InnerClass attribute should be + * null. + */ + def isAnonymousOrLocalClass(classSym: Symbol): Boolean = { + def isDelambdafyLambdaClass(classSym: Symbol): Boolean = { + classSym.isAnonymousFunction && (settings.Ydelambdafy.value == "method") + } + + assert(classSym.isClass, s"not a class: $classSym") + + !isDelambdafyLambdaClass(classSym) && + (classSym.isAnonymousClass || !classSym.originalOwner.isClass) + } + + /** + * Returns the enclosing method for non-member classes. In the following example + * + * class A { + * def f = { + * class B { + * class C + * } + * } + * } + * + * the method returns Some(f) for B, but None for C, because C is a member class. For non-member + * classes that are not enclosed by a method, it returns None: + * + * class A { + * { class B } + * } + * + * In this case, for B, we return None. + * + * The EnclosingMethod attribute needs to be added to non-member classes (see doc in BTypes). + * This is a source-level property, so we need to use the originalOwner chain to reconstruct it. + */ + private def enclosingMethodForEnclosingMethodAttribute(classSym: Symbol): Option[Symbol] = { + assert(classSym.isClass, classSym) + def enclosingMethod(sym: Symbol): Option[Symbol] = { + if (sym.isClass || sym == NoSymbol) None + else if (sym.isMethod) Some(sym) + else enclosingMethod(sym.originalOwner) + } + enclosingMethod(classSym.originalOwner) + } + + /** + * The enclosing class for emitting the EnclosingMethod attribute. Since this is a source-level + * property, this method looks at the originalOwner chain. See doc in BTypes. + */ + private def enclosingClassForEnclosingMethodAttribute(classSym: Symbol): Symbol = { + assert(classSym.isClass, classSym) + def enclosingClass(sym: Symbol): Symbol = { + if (sym.isClass) sym + else enclosingClass(sym.originalOwner) + } + enclosingClass(classSym.originalOwner) + } + + final case class EnclosingMethodEntry(owner: String, name: String, methodDescriptor: String) + + /** + * If data for emitting an EnclosingMethod attribute. None if `classSym` is a member class (not + * an anonymous or local class). See doc in BTypes. + * + * The class is parametrized by two functions to obtain a bytecode class descriptor for a class + * symbol, and to obtain a method signature descriptor fro a method symbol. These function depend + * on the implementation of GenASM / GenBCode, so they need to be passed in. + */ + def enclosingMethodAttribute(classSym: Symbol, classDesc: Symbol => String, methodDesc: Symbol => String): Option[EnclosingMethodEntry] = { + if (isAnonymousOrLocalClass(classSym)) { + val methodOpt = enclosingMethodForEnclosingMethodAttribute(classSym) + debuglog(s"enclosing method for $classSym is $methodOpt (in ${methodOpt.map(_.enclClass)})") + Some(EnclosingMethodEntry( + classDesc(enclosingClassForEnclosingMethodAttribute(classSym)), + methodOpt.map(_.javaSimpleName.toString).orNull, + methodOpt.map(methodDesc).orNull)) + } else { + None + } + } + + /** + * This is basically a re-implementation of sym.isStaticOwner, but using the originalOwner chain. + * + * The problem is that we are interested in a source-level property. Various phases changed the + * symbol's properties in the meantime, mostly lambdalift modified (destructively) the owner. + * Therefore, `sym.isStatic` is not what we want. For example, in + * object T { def f { object U } } + * the owner of U is T, so UModuleClass.isStatic is true. Phase travel does not help here. + */ + def isOriginallyStaticOwner(sym: Symbol): Boolean = { + sym.isPackageClass || sym.isModuleClass && isOriginallyStaticOwner(sym.originalOwner) + } + + /** + * The member classes of a class symbol. Note that the result of this method depends on the + * current phase, for example, after lambdalift, all local classes become member of the enclosing + * class. + */ + def memberClassesOf(classSymbol: Symbol): List[Symbol] = classSymbol.info.decls.collect({ + case sym if sym.isClass => + sym + case sym if sym.isModule => + val r = exitingPickler(sym.moduleClass) + assert(r != NoSymbol, sym.fullLocationString) + r + })(collection.breakOut) +} diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BCodeBodyBuilder.scala b/src/compiler/scala/tools/nsc/backend/jvm/BCodeBodyBuilder.scala index 4583462b71..397171049f 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/BCodeBodyBuilder.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/BCodeBodyBuilder.scala @@ -23,8 +23,9 @@ import scala.tools.asm abstract class BCodeBodyBuilder extends BCodeSkelBuilder { import global._ import definitions._ - import bCodeICodeCommon._ import bTypes._ + import bCodeICodeCommon._ + import coreBTypes._ /* * Functionality to build the body of ASM MethodNode, except for `synchronized` and `try` expressions. @@ -92,7 +93,7 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder { val thrownKind = tpeTK(expr) // `throw null` is valid although scala.Null (as defined in src/libray-aux) isn't a subtype of Throwable. // Similarly for scala.Nothing (again, as defined in src/libray-aux). - assert(thrownKind.isNullType || thrownKind.isNothingType || exemplars.get(thrownKind).isSubtypeOf(ThrowableReference)) + assert(thrownKind.isNullType || thrownKind.isNothingType || thrownKind.asClassBType.isSubtypeOf(ThrowableReference)) genLoad(expr, thrownKind) lineNumber(expr) emit(asm.Opcodes.ATHROW) // ICode enters here into enterIgnoreMode, we'll rely instead on DCE at ClassNode level. @@ -123,7 +124,7 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder { // binary operation case rarg :: Nil => - resKind = maxType(tpeTK(larg), tpeTK(rarg)) + resKind = tpeTK(larg).maxType(tpeTK(rarg)) if (scalaPrimitives.isShiftOp(code) || scalaPrimitives.isBitwiseOp(code)) { assert(resKind.isIntegralType || (resKind == BOOL), s"$resKind incompatible with arithmetic modulo operation.") @@ -321,7 +322,7 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder { mnode.visitVarInsn(asm.Opcodes.ALOAD, 0) generatedType = if (tree.symbol == ArrayClass) ObjectReference - else ClassBType(thisName) // inner class (if any) for claszSymbol already tracked. + else classBTypeFromSymbol(claszSymbol) } case Select(Ident(nme.EMPTY_PACKAGE_NAME), module) => @@ -459,9 +460,10 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder { case ClazzTag => val toPush: BType = { - val kind = toTypeKind(const.typeValue) - if (kind.isPrimitive) classLiteral(kind) - else kind + toTypeKind(const.typeValue) match { + case kind: PrimitiveBType => boxedClassOfPrimitive(kind) + case kind => kind + } } mnode.visitLdcInsn(toPush.toASMType) @@ -558,7 +560,7 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder { abort(s"Erasure should have added an unboxing operation to prevent this cast. Tree: $app") } else if (r.isPrimitive) { - bc isInstance classLiteral(r) + bc isInstance boxedClassOfPrimitive(r.asPrimitiveBType) } else { assert(r.isRef, r) // ensure that it's not a method @@ -619,7 +621,7 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder { } case rt: ClassBType => - assert(exemplar(ctor.owner).c == rt, s"Symbol ${ctor.owner.fullName} is different from $rt") + assert(classBTypeFromSymbol(ctor.owner) == rt, s"Symbol ${ctor.owner.fullName} is different from $rt") mnode.visitTypeInsn(asm.Opcodes.NEW, rt.internalName) bc dup generatedType genLoadArguments(args, paramTKs(app)) @@ -632,16 +634,16 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder { case Apply(fun @ _, List(expr)) if currentRun.runDefinitions.isBox(fun.symbol) => val nativeKind = tpeTK(expr) genLoad(expr, nativeKind) - val MethodNameAndType(mname, mdesc) = asmBoxTo(nativeKind) - bc.invokestatic(BoxesRunTime.internalName, mname, mdesc) + val MethodNameAndType(mname, methodType) = asmBoxTo(nativeKind) + bc.invokestatic(BoxesRunTime.internalName, mname, methodType.descriptor) generatedType = boxResultType(fun.symbol) // was toTypeKind(fun.symbol.tpe.resultType) case Apply(fun @ _, List(expr)) if currentRun.runDefinitions.isUnbox(fun.symbol) => genLoad(expr) val boxType = unboxResultType(fun.symbol) // was toTypeKind(fun.symbol.owner.linkedClassOfClass.tpe) generatedType = boxType - val MethodNameAndType(mname, mdesc) = asmUnboxTo(boxType) - bc.invokestatic(BoxesRunTime.internalName, mname, mdesc) + val MethodNameAndType(mname, methodType) = asmUnboxTo(boxType) + bc.invokestatic(BoxesRunTime.internalName, mname, methodType.descriptor) case app @ Apply(fun, args) => val sym = fun.symbol @@ -807,7 +809,7 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder { } def adapt(from: BType, to: BType) { - if (!conforms(from, to)) { + if (!from.conformsTo(to)) { to match { case UNIT => bc drop from case _ => bc.emitT2T(from, to) @@ -948,7 +950,7 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder { def genCallMethod(method: Symbol, style: InvokeStyle, hostClass0: Symbol = null, pos: Position = NoPosition) { val siteSymbol = claszSymbol - val hostSymbol = if (hostClass0 == null) method.owner else hostClass0; + val hostSymbol = if (hostClass0 == null) method.owner else hostClass0 val methodOwner = method.owner // info calls so that types are up to date; erasure may add lateINTERFACE to traits hostSymbol.info ; methodOwner.info @@ -966,8 +968,7 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder { || methodOwner == definitions.ObjectClass ) val receiver = if (useMethodOwner) methodOwner else hostSymbol - val bmOwner = asmClassType(receiver) - val jowner = bmOwner.internalName + val jowner = internalName(receiver) val jname = method.javaSimpleName.toString val bmType = asmMethodType(method) val mdescr = bmType.descriptor @@ -977,7 +978,7 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder { if (!isModuleInitialized && jMethodName == INSTANCE_CONSTRUCTOR_NAME && jname == INSTANCE_CONSTRUCTOR_NAME && - isStaticModule(siteSymbol)) { + isStaticModuleClass(siteSymbol)) { isModuleInitialized = true mnode.visitVarInsn(asm.Opcodes.ALOAD, 0) mnode.visitFieldInsn( @@ -1100,7 +1101,7 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder { genCZJUMP(success, failure, op, ObjectReference) } else { - val tk = maxType(tpeTK(l), tpeTK(r)) + val tk = tpeTK(l).maxType(tpeTK(r)) genLoad(l, tk) genLoad(r, tk) genCJUMP(success, failure, op, tk) @@ -1206,7 +1207,7 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder { genCZJUMP(success, failure, icodes.NE, BOOL) } else { // l == r -> if (l eq null) r eq null else l.equals(r) - val eqEqTempLocal = locals.makeLocal(AnyRefReference, nme.EQEQ_LOCAL_VAR.toString) + val eqEqTempLocal = locals.makeLocal(ObjectReference, nme.EQEQ_LOCAL_VAR.toString) val lNull = new asm.Label val lNonNull = new asm.Label diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BCodeHelpers.scala b/src/compiler/scala/tools/nsc/backend/jvm/BCodeHelpers.scala index 31a392ed55..5670715cd3 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/BCodeHelpers.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/BCodeHelpers.scala @@ -8,8 +8,7 @@ package tools.nsc package backend.jvm import scala.tools.asm -import scala.annotation.switch -import scala.collection.{ immutable, mutable } +import scala.collection.mutable import scala.tools.nsc.io.AbstractFile /* @@ -19,9 +18,10 @@ import scala.tools.nsc.io.AbstractFile * @version 1.0 * */ -abstract class BCodeHelpers extends BCodeTypes with BytecodeWriters { +abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters { import global._ import bTypes._ + import coreBTypes._ /* * must-single-thread @@ -53,24 +53,6 @@ abstract class BCodeHelpers extends BCodeTypes with BytecodeWriters { // https://issues.scala-lang.org/browse/SI-3872 // ----------------------------------------------------------------------------------------- - /* - * can-multi-thread - */ - def firstCommonSuffix(as: List[Tracked], bs: List[Tracked]): ClassBType = { - var chainA = as - var chainB = bs - var fcs: Tracked = null - do { - if (chainB contains chainA.head) fcs = chainA.head - else if (chainA contains chainB.head) fcs = chainB.head - else { - chainA = chainA.tail - chainB = chainB.tail - } - } while (fcs == null) - fcs.c - } - /* An `asm.ClassWriter` that uses `jvmWiseLUB()` * The internal name of the least common ancestor of the types given by inameA and inameB. * It's what ASM needs to know in order to compute stack map frames, http://asm.ow2.org/doc/developer-guide.html#controlflow @@ -78,57 +60,17 @@ abstract class BCodeHelpers extends BCodeTypes with BytecodeWriters { final class CClassWriter(flags: Int) extends asm.ClassWriter(flags) { /** - * This method is thread re-entrant because chrs never grows during its operation (that's - * because all TypeNames being looked up have already been entered). - * To stress this point, rather than using `newTypeName()` we use `lookupTypeName()` - * - * can-multi-thread + * This method is thread-safe: it depends only on the BTypes component, which does not depend + * on global. TODO @lry move to a different place where no global is in scope, on bTypes. */ override def getCommonSuperClass(inameA: String, inameB: String): String = { - val a = ClassBType(lookupTypeName(inameA.toCharArray)) - val b = ClassBType(lookupTypeName(inameB.toCharArray)) - val lca = jvmWiseLUB(a, b) - val lcaName = lca.internalName // don't call javaName because that side-effects innerClassBuffer. - assert(lcaName != "scala/Any") - - lcaName // ASM caches the answer during the lifetime of a ClassWriter. We outlive that. Not sure whether caching on our side would improve things. - } - - } - - /** - * Finding the least upper bound in agreement with the bytecode verifier (given two internal names - * handed out by ASM) - * Background: - * http://gallium.inria.fr/~xleroy/publi/bytecode-verification-JAR.pdf - * http://comments.gmane.org/gmane.comp.java.vm.languages/2293 - * https://issues.scala-lang.org/browse/SI-3872 - * - * can-multi-thread - */ - def jvmWiseLUB(a: ClassBType, b: ClassBType): ClassBType = { - - assert(a.isNonSpecial, s"jvmWiseLUB() received a non-plain-class $a") - assert(b.isNonSpecial, s"jvmWiseLUB() received a non-plain-class $b") - - val ta = exemplars.get(a) - val tb = exemplars.get(b) - - val res = (ta.isInterface, tb.isInterface) match { - case (true, true) => - // exercised by test/files/run/t4761.scala - if (tb.isSubtypeOf(ta.c)) ta.c - else if (ta.isSubtypeOf(tb.c)) tb.c - else ObjectReference - case (true, false) => - if (tb.isSubtypeOf(a)) a else ObjectReference - case (false, true) => - if (ta.isSubtypeOf(b)) b else ObjectReference - case _ => - firstCommonSuffix(ta :: ta.superClasses, tb :: tb.superClasses) + val a = classBTypeFromInternalName(inameA) + val b = classBTypeFromInternalName(inameB) + val lub = a.jvmWiseLUB(b) + val lubName = lub.internalName + assert(lubName != "scala/Any") + lubName // ASM caches the answer during the lifetime of a ClassWriter. We outlive that. Not sure whether caching on our side would improve things. } - assert(res.isNonSpecial, "jvmWiseLUB() returned a non-plain-class.") - res } /* @@ -230,7 +172,7 @@ abstract class BCodeHelpers extends BCodeTypes with BytecodeWriters { def fieldSymbols(cls: Symbol): List[Symbol] = { for (f <- cls.info.decls.toList ; if !f.isMethod && f.isTerm && !f.isModule - ) yield f; + ) yield f } /* @@ -261,38 +203,16 @@ abstract class BCodeHelpers extends BCodeTypes with BytecodeWriters { * * can-multi-thread */ - final def addInnerClassesASM(jclass: asm.ClassVisitor, refedInnerClasses: Iterable[BType]) { - // used to detect duplicates. - val seen = mutable.Map.empty[String, String] - // result without duplicates, not yet sorted. - val result = mutable.Set.empty[InnerClassEntry] - - for(s: BType <- refedInnerClasses; - e: InnerClassEntry <- exemplars.get(s).innersChain) { - - assert(e.name != null, "saveInnerClassesFor() is broken.") // documentation - val doAdd = seen.get(e.name) match { - // TODO is it ok for prevOName to be null? (Someone should really document the invariants of the InnerClasses bytecode attribute) - case Some(prevOName) => - // this occurs e.g. when innerClassBuffer contains both class Thread$State, object Thread$State, - // i.e. for them it must be the case that oname == java/lang/Thread - assert(prevOName == e.outerName, "duplicate") - false - case None => true - } - - if (doAdd) { - seen += (e.name -> e.outerName) - result += e - } - + final def addInnerClassesASM(jclass: asm.ClassVisitor, refedInnerClasses: List[ClassBType]) { + val allNestedClasses = refedInnerClasses.flatMap(_.enclosingNestedClassesChain).distinct + + // sorting ensures nested classes are listed after their enclosing class thus satisfying the Eclipse Java compiler + for (nestedClass <- allNestedClasses.sortBy(_.internalName.toString)) { + // Extract the innerClassEntry - we know it exists, enclosingNestedClassesChain only returns nested classes. + val Some(e) = nestedClass.innerClassAttributeEntry + jclass.visitInnerClass(e.name, e.outerName, e.innerName, e.flags) } - // sorting ensures inner classes are listed after their enclosing class thus satisfying the Eclipse Java compiler - for(e <- result.toList sortBy (_.name.toString)) { - jclass.visitInnerClass(e.name, e.outerName, e.innerName, e.access) - } - - } // end of method addInnerClassesASM() + } /* * Custom attribute (JVMS 4.7.1) "ScalaSig" used as marker only @@ -323,8 +243,8 @@ abstract class BCodeHelpers extends BCodeTypes with BytecodeWriters { * can-multi-thread */ def createJAttribute(name: String, b: Array[Byte], offset: Int, len: Int): asm.Attribute = { - val dest = new Array[Byte](len); - System.arraycopy(b, offset, dest, 0, len); + val dest = new Array[Byte](len) + System.arraycopy(b, offset, dest, 0, len) new asm.CustomAttr(name, dest) } @@ -385,9 +305,9 @@ abstract class BCodeHelpers extends BCodeTypes with BytecodeWriters { def debugLevel = settings.debuginfo.indexOfChoice - val emitSource = debugLevel >= 1 - val emitLines = debugLevel >= 2 - val emitVars = debugLevel >= 3 + final val emitSource = debugLevel >= 1 + final val emitLines = debugLevel >= 2 + final val emitVars = debugLevel >= 3 /* * Contains class-symbols that: @@ -396,152 +316,135 @@ abstract class BCodeHelpers extends BCodeTypes with BytecodeWriters { * * In other words, the lifetime of `innerClassBufferASM` is associated to "the class being generated". */ - val innerClassBufferASM = mutable.Set.empty[BType] + final val innerClassBufferASM = mutable.Set.empty[ClassBType] - /* - * Tracks (if needed) the inner class given by `sym`. - * - * must-single-thread + /** + * The class internal name for a given class symbol. If the symbol describes a nested class, the + * ClassBType is added to the innerClassBufferASM. */ - final def internalName(sym: Symbol): String = asmClassType(sym).internalName + final def internalName(sym: Symbol): String = { + // For each java class, the scala compiler creates a class and a module (thus a module class). + // If the `sym` is a java module class, we use the java class instead. This ensures that we + // register the class (instead of the module class) in innerClassBufferASM. + // The two symbols have the same name, so the resulting internalName is the same. + val classSym = if (sym.isJavaDefined && sym.isModuleClass) sym.linkedClassOfClass else sym + getClassBTypeAndRegisterInnerClass(classSym).internalName + } - /* - * Tracks (if needed) the inner class given by `sym`. + private def assertClassNotArray(sym: Symbol): Unit = { + assert(sym.isClass, sym) + assert(sym != definitions.ArrayClass || isCompilingArray, sym) + } + + private def assertClassNotArrayNotPrimitive(sym: Symbol): Unit = { + assertClassNotArray(sym) + assert(!primitiveTypeMap.contains(sym) || isCompilingPrimitive, sym) + } + + /** + * The ClassBType for a class symbol. If the class is nested, the ClassBType is added to the + * innerClassBufferASM. * - * must-single-thread + * The class symbol scala.Nothing is mapped to the class scala.runtime.Nothing$. Similarly, + * scala.Null is mapped to scala.runtime.Null$. This is because there exist no class files + * for the Nothing / Null. If used for example as a parameter type, we use the runtime classes + * in the classfile method signature. + * + * Note that the referenced class symbol may be an implementation class. For example when + * compiling a mixed-in method that forwards to the static method in the implementation class, + * the class descriptor of the receiver (the implementation class) is obtained by creating the + * ClassBType. */ - final def asmClassType(sym: Symbol): ClassBType = { - assert( - hasInternalName(sym), - { - val msg0 = if (sym.isAbstractType) "An AbstractTypeSymbol (SI-7122) " else "A symbol "; - msg0 + s"has reached the bytecode emitter, for which no JVM-level internal name can be found: ${sym.fullName}" - } - ) - val phantOpt = phantomTypeMap.get(sym) - if (phantOpt.isDefined) { - return phantOpt.get - } - val tracked = exemplar(sym) - val tk = tracked.c - if (tracked.isInnerClass) { - innerClassBufferASM += tk - } + final def getClassBTypeAndRegisterInnerClass(sym: Symbol): ClassBType = { + assertClassNotArrayNotPrimitive(sym) - tk + if (sym == definitions.NothingClass) RT_NOTHING + else if (sym == definitions.NullClass) RT_NULL + else { + val r = classBTypeFromSymbol(sym) + if (r.isNestedClass) innerClassBufferASM += r + r + } } - /* - * Returns the BType for the given type. - * Tracks (if needed) the inner class given by `t`. + /** + * This method returns the BType for a type reference, for example a parameter type. * - * must-single-thread + * If the result is a ClassBType for a nested class, it is added to the innerClassBufferASM. + * + * If `t` references a class, toTypeKind ensures that the class is not an implementation class. + * See also comment on getClassBTypeAndRegisterInnerClass, which is invoked for implementation + * classes. */ final def toTypeKind(t: Type): BType = { + import definitions.ArrayClass - /* Interfaces have to be handled delicately to avoid introducing spurious errors, - * but if we treat them all as AnyRef we lose too much information. + /** + * Primitive types are represented as TypeRefs to the class symbol of, for example, scala.Int. + * The `primitiveTypeMap` maps those class symbols to the corresponding PrimitiveBType. */ - def newReference(sym0: Symbol): BType = { - assert(!primitiveTypeMap.contains(sym0), "Use primitiveTypeMap instead.") - assert(sym0 != definitions.ArrayClass, "Use arrayOf() instead.") - - if (sym0 == definitions.NullClass) return RT_NULL; - if (sym0 == definitions.NothingClass) return RT_NOTHING; - - val sym = ( - if (!sym0.isPackageClass) sym0 - else sym0.info.member(nme.PACKAGE) match { - case NoSymbol => abort(s"SI-5604: Cannot use package as value: ${sym0.fullName}") - case s => abort(s"SI-5604: found package class where package object expected: $s") - } - ) - - // Can't call .toInterface (at this phase) or we trip an assertion. - // See PackratParser#grow for a method which fails with an apparent mismatch - // between "object PackratParsers$class" and "trait PackratParsers" - if (sym.isImplClass) { - // pos/spec-List.scala is the sole failure if we don't check for NoSymbol - val traitSym = sym.owner.info.decl(tpnme.interfaceName(sym.name)) - if (traitSym != NoSymbol) { - // this tracks the inner class in innerClassBufferASM, if needed. - return asmClassType(traitSym) - } - } - - assert(hasInternalName(sym), s"Invoked for a symbol lacking JVM internal name: ${sym.fullName}") - assert(!phantomTypeMap.contains(sym), "phantom types not supposed to reach here.") - - val tracked = exemplar(sym) - val tk = tracked.c - if (tracked.isInnerClass) { - innerClassBufferASM += tk - } - - tk + def primitiveOrClassToBType(sym: Symbol): BType = { + assertClassNotArray(sym) + assert(!sym.isImplClass, sym) + primitiveTypeMap.getOrElse(sym, getClassBTypeAndRegisterInnerClass(sym)) } - def primitiveOrRefType(sym: Symbol): BType = { - assert(sym != definitions.ArrayClass, "Use primitiveOrArrayOrRefType() instead.") - - primitiveTypeMap.getOrElse(sym, newReference(sym)) - } - - def primitiveOrRefType2(sym: Symbol): BType = { - primitiveTypeMap.get(sym) match { - case Some(pt) => pt - case None => - sym match { - case definitions.NullClass => RT_NULL - case definitions.NothingClass => RT_NOTHING - case _ if sym.isClass => newReference(sym) - case _ => - assert(sym.isType, sym) // it must be compiling Array[a] - ObjectReference - } - } + /** + * When compiling Array.scala, the type parameter T is not erased and shows up in method + * signatures, e.g. `def apply(i: Int): T`. A TyperRef to T is replaced by ObjectReference. + */ + def nonClassTypeRefToBType(sym: Symbol): ClassBType = { + assert(sym.isType && isCompilingArray, sym) + ObjectReference } - import definitions.ArrayClass - - // Call to .normalize fixes #3003 (follow type aliases). Otherwise, primitiveOrArrayOrRefType() would return ObjectReference. - t.normalize match { - - case ThisType(sym) => - if (sym == ArrayClass) ObjectReference - else phantomTypeMap.getOrElse(sym, exemplar(sym).c) - - case SingleType(_, sym) => primitiveOrRefType(sym) - - case _: ConstantType => toTypeKind(t.underlying) - - case TypeRef(_, sym, args) => - if (sym == ArrayClass) ArrayBType(toTypeKind(args.head)) - else primitiveOrRefType2(sym) - - case ClassInfoType(_, _, sym) => - assert(sym != ArrayClass, "ClassInfoType to ArrayClass!") - primitiveOrRefType(sym) - - // TODO @lry check below comments / todo's - // !!! Iulian says types which make no sense after erasure should not reach here, which includes the ExistentialType, AnnotatedType, RefinedType. - case ExistentialType(_, t) => toTypeKind(t) // TODO shouldn't get here but the following does: akka-actor/src/main/scala/akka/util/WildcardTree.scala - case AnnotatedType(_, w) => toTypeKind(w) // TODO test/files/jvm/annotations.scala causes an AnnotatedType to reach here. - case RefinedType(parents, _) => parents.map(toTypeKind(_).asClassBType) reduceLeft jvmWiseLUB - - // For sure WildcardTypes shouldn't reach here either, but when debugging such situations this may come in handy. - // case WildcardType => REFERENCE(ObjectClass) - case norm => abort( - s"Unknown type: $t, $norm [${t.getClass}, ${norm.getClass}] TypeRef? ${t.isInstanceOf[TypeRef]}" - ) + t.dealiasWiden match { + case TypeRef(_, ArrayClass, List(arg)) => ArrayBType(toTypeKind(arg)) // Array type such as Array[Int] (kept by erasure) + case TypeRef(_, sym, _) if !sym.isClass => nonClassTypeRefToBType(sym) // See comment on nonClassTypeRefToBType + case TypeRef(_, sym, _) => primitiveOrClassToBType(sym) // Common reference to a type such as scala.Int or java.lang.String + case ClassInfoType(_, _, sym) => primitiveOrClassToBType(sym) // We get here, for example, for genLoadModule, which invokes toTypeKind(moduleClassSymbol.info) + + /* AnnotatedType should (probably) be eliminated by erasure. However we know it happens for + * meta-annotated annotations (@(ann @getter) val x = 0), so we don't emit a warning. + * The type in the AnnotationInfo is an AnnotatedTpe. Tested in jvm/annotations.scala. + */ + case a @ AnnotatedType(_, t) => + debuglog(s"typeKind of annotated type $a") + toTypeKind(t) + + /* ExistentialType should (probably) be eliminated by erasure. We know they get here for + * classOf constants: + * class C[T] + * class T { final val k = classOf[C[_]] } + */ + case e @ ExistentialType(_, t) => + debuglog(s"typeKind of existential type $e") + toTypeKind(t) + + /* The cases below should probably never occur. They are kept for now to avoid introducing + * new compiler crashes, but we added a warning. The compiler / library bootstrap and the + * test suite don't produce any warning. + */ + + case tp => + currentUnit.warning(tp.typeSymbol.pos, + s"an unexpected type representation reached the compiler backend while compiling $currentUnit: $tp. " + + "If possible, please file a bug on issues.scala-lang.org.") + + tp match { + case ThisType(ArrayClass) => ObjectReference // was introduced in 9b17332f11 to fix SI-999, but this code is not reached in its test, or any other test + case ThisType(sym) => getClassBTypeAndRegisterInnerClass(sym) + case SingleType(_, sym) => primitiveOrClassToBType(sym) + case ConstantType(_) => toTypeKind(t.underlying) + case RefinedType(parents, _) => parents.map(toTypeKind(_).asClassBType).reduceLeft((a, b) => a.jvmWiseLUB(b)) + } } - - } // end of method toTypeKind() + } /* * must-single-thread */ - def asmMethodType(msym: Symbol): MethodBType = { + final def asmMethodType(msym: Symbol): MethodBType = { assert(msym.isMethod, s"not a method-symbol: $msym") val resT: BType = if (msym.isClassConstructor || msym.isConstructor) UNIT @@ -549,45 +452,17 @@ abstract class BCodeHelpers extends BCodeTypes with BytecodeWriters { MethodBType(msym.tpe.paramTypes map toTypeKind, resT) } - /* - * Returns all direct member inner classes of `csym`, - * thus making sure they get entries in the InnerClasses JVM attribute - * even if otherwise not mentioned in the class being built. - * - * must-single-thread - */ - final def trackMemberClasses(csym: Symbol, lateClosuresBTs: List[BType]): List[BType] = { - val lateInnerClasses = exitingErasure { - for (sym <- List(csym, csym.linkedClassOfClass); memberc <- sym.info.decls.map(innerClassSymbolFor) if memberc.isClass) - yield memberc - } - // as a precaution, do the following outside the above `exitingErasure` otherwise funny internal names might be computed. - val result = for(memberc <- lateInnerClasses) yield { - val tracked = exemplar(memberc) - val memberCTK = tracked.c - assert(tracked.isInnerClass, s"saveInnerClassesFor() says this was no inner-class after all: ${memberc.fullName}") - - memberCTK - } - - exemplar(csym).directMemberClasses = result - - result - } - - /* - * Tracks (if needed) the inner class given by `t`. - * - * must-single-thread + /** + * The jvm descriptor of a type. If `t` references a nested class, its ClassBType is added to + * the innerClassBufferASM. */ final def descriptor(t: Type): String = { toTypeKind(t).descriptor } - /* - * Tracks (if needed) the inner class given by `sym`. - * - * must-single-thread + /** + * The jvm descriptor for a symbol. If `sym` represents a nested class, its ClassBType is added + * to the innerClassBufferASM. */ - final def descriptor(sym: Symbol): String = { asmClassType(sym).descriptor } + final def descriptor(sym: Symbol): String = { getClassBTypeAndRegisterInnerClass(sym).descriptor } } // end of trait BCInnerClassGen @@ -792,7 +667,7 @@ abstract class BCodeHelpers extends BCodeTypes with BytecodeWriters { */ // TODO: evaluate the other flags we might be dropping on the floor here. // TODO: ACC_SYNTHETIC ? - val flags = PublicStatic | ( + val flags = GenBCode.PublicStatic | ( if (m.isVarargsMethod) asm.Opcodes.ACC_VARARGS else 0 ) @@ -898,62 +773,13 @@ abstract class BCodeHelpers extends BCodeTypes with BytecodeWriters { def addSerialVUID(id: Long, jclass: asm.ClassVisitor) { // add static serialVersionUID field if `clasz` annotated with `@SerialVersionUID(uid: Long)` jclass.visitField( - PublicStaticFinal, + GenBCode.PublicStaticFinal, "serialVersionUID", "J", null, // no java-generic-signature new java.lang.Long(id) ).visitEnd() } - - /* - * @param owner internal name of the enclosing class of the class. - * - * @param name the name of the method that contains the class. - - * @param methodType the method that contains the class. - */ - case class EnclMethodEntry(owner: String, name: String, methodType: BType) - - /* - * @return null if the current class is not internal to a method - * - * Quoting from JVMS 4.7.7 The EnclosingMethod Attribute - * A class must have an EnclosingMethod attribute if and only if it is a local class or an anonymous class. - * A class may have no more than one EnclosingMethod attribute. - * - * must-single-thread - */ - def getEnclosingMethodAttribute(clazz: Symbol): EnclMethodEntry = { // JVMS 4.7.7 - - def newEEE(eClass: Symbol, m: Symbol) = { - EnclMethodEntry( - internalName(eClass), - m.javaSimpleName.toString, - asmMethodType(m) - ) - } - - var res: EnclMethodEntry = null - val sym = clazz.originalEnclosingMethod - if (sym.isMethod) { - debuglog(s"enclosing method for $clazz is $sym (in ${sym.enclClass})") - res = newEEE(sym.enclClass, sym) - } else if (clazz.isAnonymousClass) { - val enclClass = clazz.rawowner - assert(enclClass.isClass, enclClass) - val sym = enclClass.primaryConstructor - if (sym == NoSymbol) { - log(s"Ran out of room looking for an enclosing method for $clazz: no constructor here: $enclClass.") - } else { - debuglog(s"enclosing method for $clazz is $sym (in $enclClass)") - res = newEEE(enclClass, sym) - } - } - - res - } - } // end of trait BCClassGen /* basic functionality for class file building of plain, mirror, and beaninfo classes. */ @@ -982,11 +808,12 @@ abstract class BCodeHelpers extends BCodeTypes with BytecodeWriters { * * must-single-thread */ - def genMirrorClass(modsym: Symbol, cunit: CompilationUnit): asm.tree.ClassNode = { - assert(modsym.companionClass == NoSymbol, modsym) + def genMirrorClass(moduleClass: Symbol, cunit: CompilationUnit): asm.tree.ClassNode = { + assert(moduleClass.isModuleClass) + assert(moduleClass.companionClass == NoSymbol, moduleClass) innerClassBufferASM.clear() this.cunit = cunit - val moduleName = internalName(modsym) // + "$" + val moduleName = internalName(moduleClass) // + "$" val mirrorName = moduleName.substring(0, moduleName.length() - 1) val flags = (asm.Opcodes.ACC_SUPER | asm.Opcodes.ACC_PUBLIC | asm.Opcodes.ACC_FINAL) @@ -996,7 +823,7 @@ abstract class BCodeHelpers extends BCodeTypes with BytecodeWriters { flags, mirrorName, null /* no java-generic-signature */, - JAVA_LANG_OBJECT.internalName, + ObjectReference.internalName, EMPTY_STRING_ARRAY ) @@ -1005,18 +832,18 @@ abstract class BCodeHelpers extends BCodeTypes with BytecodeWriters { null /* SourceDebugExtension */) } - val ssa = getAnnotPickle(mirrorName, modsym.companionSymbol) + val ssa = getAnnotPickle(mirrorName, moduleClass.companionSymbol) mirrorClass.visitAttribute(if (ssa.isDefined) pickleMarkerLocal else pickleMarkerForeign) - emitAnnotations(mirrorClass, modsym.annotations ++ ssa) + emitAnnotations(mirrorClass, moduleClass.annotations ++ ssa) - addForwarders(isRemote(modsym), mirrorClass, mirrorName, modsym) + addForwarders(isRemote(moduleClass), mirrorClass, mirrorName, moduleClass) - innerClassBufferASM ++= trackMemberClasses(modsym, Nil /* TODO what about Late-Closure-Classes */ ) + innerClassBufferASM ++= classBTypeFromSymbol(moduleClass).info.memberClasses addInnerClassesASM(mirrorClass, innerClassBufferASM.toList) mirrorClass.visitEnd() - ("" + modsym.name) // this side-effect is necessary, really. + ("" + moduleClass.name) // this side-effect is necessary, really. mirrorClass } @@ -1039,10 +866,7 @@ abstract class BCodeHelpers extends BCodeTypes with BytecodeWriters { innerClassBufferASM.clear() - val flags = mkFlags( - javaFlags(cls), - if (isDeprecated(cls)) asm.Opcodes.ACC_DEPRECATED else 0 // ASM pseudo access flag - ) + val flags = javaFlags(cls) val beanInfoName = (internalName(cls) + "BeanInfo") val beanInfoClass = new asm.tree.ClassNode @@ -1088,9 +912,9 @@ abstract class BCodeHelpers extends BCodeTypes with BytecodeWriters { EMPTY_STRING_ARRAY // no throwable exceptions ) - val stringArrayJType: BType = ArrayBType(JAVA_LANG_STRING) + val stringArrayJType: BType = ArrayBType(StringReference) val conJType: BType = MethodBType( - exemplar(definitions.ClassClass).c :: stringArrayJType :: stringArrayJType :: Nil, + classBTypeFromSymbol(definitions.ClassClass) :: stringArrayJType :: stringArrayJType :: Nil, UNIT ) @@ -1101,7 +925,7 @@ abstract class BCodeHelpers extends BCodeTypes with BytecodeWriters { constructor.visitLdcInsn(new java.lang.Integer(fi)) if (f == null) { constructor.visitInsn(asm.Opcodes.ACONST_NULL) } else { constructor.visitLdcInsn(f) } - constructor.visitInsn(JAVA_LANG_STRING.typedOpcode(asm.Opcodes.IASTORE)) + constructor.visitInsn(StringReference.typedOpcode(asm.Opcodes.IASTORE)) fi += 1 } } @@ -1110,16 +934,16 @@ abstract class BCodeHelpers extends BCodeTypes with BytecodeWriters { constructor.visitVarInsn(asm.Opcodes.ALOAD, 0) // push the class - constructor.visitLdcInsn(exemplar(cls).c.toASMType) + constructor.visitLdcInsn(classBTypeFromSymbol(cls).toASMType) // push the string array of field information constructor.visitLdcInsn(new java.lang.Integer(fieldList.length)) - constructor.visitTypeInsn(asm.Opcodes.ANEWARRAY, JAVA_LANG_STRING.internalName) + constructor.visitTypeInsn(asm.Opcodes.ANEWARRAY, StringReference.internalName) push(fieldList) // push the string array of method information constructor.visitLdcInsn(new java.lang.Integer(methodList.length)) - constructor.visitTypeInsn(asm.Opcodes.ANEWARRAY, JAVA_LANG_STRING.internalName) + constructor.visitTypeInsn(asm.Opcodes.ANEWARRAY, StringReference.internalName) push(methodList) // invoke the superclass constructor, which will do the @@ -1130,7 +954,7 @@ abstract class BCodeHelpers extends BCodeTypes with BytecodeWriters { constructor.visitMaxs(0, 0) // just to follow protocol, dummy arguments constructor.visitEnd() - innerClassBufferASM ++= trackMemberClasses(cls, Nil /* TODO what about Late-Closure-Classes */ ) + innerClassBufferASM ++= classBTypeFromSymbol(cls).info.memberClasses addInnerClassesASM(beanInfoClass, innerClassBufferASM.toList) beanInfoClass.visitEnd() @@ -1162,11 +986,11 @@ abstract class BCodeHelpers extends BCodeTypes with BytecodeWriters { */ def legacyAddCreatorCode(clinit: asm.MethodVisitor, cnode: asm.tree.ClassNode, thisName: String) { // this tracks the inner class in innerClassBufferASM, if needed. - val androidCreatorType = asmClassType(AndroidCreatorClass) + val androidCreatorType = getClassBTypeAndRegisterInnerClass(AndroidCreatorClass) val tdesc_creator = androidCreatorType.descriptor cnode.visitField( - PublicStaticFinal, + GenBCode.PublicStaticFinal, "CREATOR", tdesc_creator, null, // no java-generic-signature @@ -1203,5 +1027,4 @@ abstract class BCodeHelpers extends BCodeTypes with BytecodeWriters { } } // end of trait JAndroidBuilder - } diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BCodeIdiomatic.scala b/src/compiler/scala/tools/nsc/backend/jvm/BCodeIdiomatic.scala index 9b7c975960..d58368b19d 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/BCodeIdiomatic.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/BCodeIdiomatic.scala @@ -19,29 +19,23 @@ import scala.collection.mutable * */ abstract class BCodeIdiomatic extends SubComponent { - protected val bCodeICodeCommon: BCodeICodeCommon[global.type] = new BCodeICodeCommon(global) - - val bTypes = new BTypes[global.type](global) { - def chrs = global.chrs - override type BTypeName = global.TypeName - override def createNewName(s: String) = global.newTypeName(s) - } + val bTypes = new BTypesFromSymbols[global.type](global) import global._ import bTypes._ + import coreBTypes._ val classfileVersion: Int = settings.target.value match { case "jvm-1.5" => asm.Opcodes.V1_5 case "jvm-1.6" => asm.Opcodes.V1_6 case "jvm-1.7" => asm.Opcodes.V1_7 + case "jvm-1.8" => asm.Opcodes.V1_8 } val majorVersion: Int = (classfileVersion & 0xFF) val emitStackMapFrame = (majorVersion >= 50) - def mkFlags(args: Int*) = args.foldLeft(0)(_ | _) - - val extraProc: Int = mkFlags( + val extraProc: Int = GenBCode.mkFlags( asm.ClassWriter.COMPUTE_MAXS, if (emitStackMapFrame) asm.ClassWriter.COMPUTE_FRAMES else 0 ) @@ -51,15 +45,6 @@ abstract class BCodeIdiomatic extends SubComponent { val CLASS_CONSTRUCTOR_NAME = "<clinit>" val INSTANCE_CONSTRUCTOR_NAME = "<init>" - val ObjectReference = ClassBType("java/lang/Object") - val AnyRefReference = ObjectReference - val objArrayReference = ArrayBType(ObjectReference) - - val JAVA_LANG_OBJECT = ObjectReference - val JAVA_LANG_STRING = ClassBType("java/lang/String") - - var StringBuilderReference: BType = null - val EMPTY_STRING_ARRAY = Array.empty[String] val EMPTY_INT_ARRAY = Array.empty[Int] val EMPTY_LABEL_ARRAY = Array.empty[asm.Label] @@ -238,7 +223,7 @@ abstract class BCodeIdiomatic extends SubComponent { final def genStringConcat(el: BType) { val jtype = - if (el.isArray || el.isClass) JAVA_LANG_OBJECT + if (el.isArray || el.isClass) ObjectReference else el val bt = MethodBType(List(jtype), StringBuilderReference) @@ -265,7 +250,7 @@ abstract class BCodeIdiomatic extends SubComponent { assert( from.isNonVoidPrimitiveType && to.isNonVoidPrimitiveType, - s"Cannot emit primitive conversion from $from to $to" + s"Cannot emit primitive conversion from $from to $to - ${global.currentUnit}" ) def pickOne(opcs: Array[Int]) { // TODO index on to.sort @@ -536,7 +521,7 @@ abstract class BCodeIdiomatic extends SubComponent { final def emitTypeBased(opcs: Array[Int], tk: BType) { assert(tk != UNIT, tk) val opc = { - if (tk.isRef) { opcs(0) } + if (tk.isRef) { opcs(0) } else if (tk.isIntSizedType) { (tk: @unchecked) match { case BOOL | BYTE => opcs(1) @@ -647,7 +632,7 @@ abstract class BCodeIdiomatic extends SubComponent { */ final def coercionTo(code: Int): BType = { import scalaPrimitives._ - (code: @scala.annotation.switch) match { + (code: @switch) match { case B2B | C2B | S2B | I2B | L2B | F2B | D2B => BYTE case B2C | C2C | S2C | I2C | L2C | F2C | D2C => CHAR case B2S | C2S | S2S | I2S | L2S | F2S | D2S => SHORT @@ -658,21 +643,6 @@ abstract class BCodeIdiomatic extends SubComponent { } } - final val typeOfArrayOp: Map[Int, BType] = { - import scalaPrimitives._ - Map( - (List(ZARRAY_LENGTH, ZARRAY_GET, ZARRAY_SET) map (_ -> BOOL)) ++ - (List(BARRAY_LENGTH, BARRAY_GET, BARRAY_SET) map (_ -> BYTE)) ++ - (List(SARRAY_LENGTH, SARRAY_GET, SARRAY_SET) map (_ -> SHORT)) ++ - (List(CARRAY_LENGTH, CARRAY_GET, CARRAY_SET) map (_ -> CHAR)) ++ - (List(IARRAY_LENGTH, IARRAY_GET, IARRAY_SET) map (_ -> INT)) ++ - (List(LARRAY_LENGTH, LARRAY_GET, LARRAY_SET) map (_ -> LONG)) ++ - (List(FARRAY_LENGTH, FARRAY_GET, FARRAY_SET) map (_ -> FLOAT)) ++ - (List(DARRAY_LENGTH, DARRAY_GET, DARRAY_SET) map (_ -> DOUBLE)) ++ - (List(OARRAY_LENGTH, OARRAY_GET, OARRAY_SET) map (_ -> ObjectReference)) : _* - ) - } - /* * Collects (in `result`) all LabelDef nodes enclosed (directly or not) by each node it visits. * diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BCodeSkelBuilder.scala b/src/compiler/scala/tools/nsc/backend/jvm/BCodeSkelBuilder.scala index 8845ffa0cd..4592031a31 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/BCodeSkelBuilder.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/BCodeSkelBuilder.scala @@ -26,6 +26,8 @@ import java.io.PrintWriter abstract class BCodeSkelBuilder extends BCodeHelpers { import global._ import bTypes._ + import coreBTypes._ + import bCodeAsmCommon._ /* * There's a dedicated PlainClassBuilder for each CompilationUnit, @@ -95,7 +97,7 @@ abstract class BCodeSkelBuilder extends BCodeHelpers { claszSymbol = cd.symbol isCZParcelable = isAndroidParcelableClass(claszSymbol) - isCZStaticModule = isStaticModule(claszSymbol) + isCZStaticModule = isStaticModuleClass(claszSymbol) isCZRemote = isRemote(claszSymbol) thisName = internalName(claszSymbol) @@ -116,7 +118,7 @@ abstract class BCodeSkelBuilder extends BCodeHelpers { addClassFields() - innerClassBufferASM ++= trackMemberClasses(claszSymbol, Nil) + innerClassBufferASM ++= classBTypeFromSymbol(claszSymbol).info.memberClasses gen(cd.impl) addInnerClassesASM(cnode, innerClassBufferASM.toList) @@ -134,40 +136,28 @@ abstract class BCodeSkelBuilder extends BCodeHelpers { private def initJClass(jclass: asm.ClassVisitor) { val ps = claszSymbol.info.parents - val superClass: String = if (ps.isEmpty) JAVA_LANG_OBJECT.internalName else internalName(ps.head.typeSymbol); - val ifaces: Array[String] = { - val arrIfacesTr: Array[Tracked] = exemplar(claszSymbol).ifaces - val arrIfaces = new Array[String](arrIfacesTr.length) - var i = 0 - while (i < arrIfacesTr.length) { - val ifaceTr = arrIfacesTr(i) - val bt = ifaceTr.c - if (ifaceTr.isInnerClass) { innerClassBufferASM += bt } - arrIfaces(i) = bt.internalName - i += 1 - } - arrIfaces + val superClass: String = if (ps.isEmpty) ObjectReference.internalName else internalName(ps.head.typeSymbol) + val interfaceNames = classBTypeFromSymbol(claszSymbol).info.interfaces map { + case classBType => + if (classBType.isNestedClass) { innerClassBufferASM += classBType } + classBType.internalName } - // `internalName()` tracks inner classes. - val flags = mkFlags( - javaFlags(claszSymbol), - if (isDeprecated(claszSymbol)) asm.Opcodes.ACC_DEPRECATED else 0 // ASM pseudo access flag - ) + val flags = javaFlags(claszSymbol) val thisSignature = getGenericSignature(claszSymbol, claszSymbol.owner) cnode.visit(classfileVersion, flags, thisName, thisSignature, - superClass, ifaces) + superClass, interfaceNames.toArray) if (emitSource) { cnode.visitSource(cunit.source.toString, null /* SourceDebugExtension */) } - val enclM = getEnclosingMethodAttribute(claszSymbol) - if (enclM != null) { - val EnclMethodEntry(className, methodName, methodType) = enclM - cnode.visitOuterClass(className, methodName, methodType.descriptor) + enclosingMethodAttribute(claszSymbol, internalName, asmMethodType(_).descriptor) match { + case Some(EnclosingMethodEntry(className, methodName, methodDescriptor)) => + cnode.visitOuterClass(className, methodName, methodDescriptor) + case _ => () } val ssa = getAnnotPickle(thisName, claszSymbol) @@ -207,7 +197,7 @@ abstract class BCodeSkelBuilder extends BCodeHelpers { */ private def addModuleInstanceField() { val fv = - cnode.visitField(PublicStaticFinal, // TODO confirm whether we really don't want ACC_SYNTHETIC nor ACC_DEPRECATED + cnode.visitField(GenBCode.PublicStaticFinal, // TODO confirm whether we really don't want ACC_SYNTHETIC nor ACC_DEPRECATED strMODULE_INSTANCE_FIELD, "L" + thisName + ";", null, // no java-generic-signature @@ -223,7 +213,7 @@ abstract class BCodeSkelBuilder extends BCodeHelpers { private def fabricateStaticInit() { val clinit: asm.MethodVisitor = cnode.visitMethod( - PublicStatic, // TODO confirm whether we really don't want ACC_SYNTHETIC nor ACC_DEPRECATED + GenBCode.PublicStatic, // TODO confirm whether we really don't want ACC_SYNTHETIC nor ACC_DEPRECATED CLASS_CONSTRUCTOR_NAME, "()V", null, // no java-generic-signature @@ -254,10 +244,7 @@ abstract class BCodeSkelBuilder extends BCodeHelpers { */ for (f <- fieldSymbols(claszSymbol)) { val javagensig = getGenericSignature(f, claszSymbol) - val flags = mkFlags( - javaFieldFlags(f), - if (isDeprecated(f)) asm.Opcodes.ACC_DEPRECATED else 0 // ASM pseudo access flag - ) + val flags = javaFieldFlags(f) val jfield = new asm.tree.FieldNode( flags, @@ -576,12 +563,11 @@ abstract class BCodeSkelBuilder extends BCodeHelpers { val isNative = methSymbol.hasAnnotation(definitions.NativeAttr) val isAbstractMethod = (methSymbol.isDeferred || methSymbol.owner.isInterface) - val flags = mkFlags( + val flags = GenBCode.mkFlags( javaFlags(methSymbol), if (claszSymbol.isInterface) asm.Opcodes.ACC_ABSTRACT else 0, if (methSymbol.isStrictFP) asm.Opcodes.ACC_STRICT else 0, - if (isNative) asm.Opcodes.ACC_NATIVE else 0, // native methods of objects are generated in mirror classes - if (isDeprecated(methSymbol)) asm.Opcodes.ACC_DEPRECATED else 0 // ASM pseudo access flag + if (isNative) asm.Opcodes.ACC_NATIVE else 0 // native methods of objects are generated in mirror classes ) // TODO needed? for(ann <- m.symbol.annotations) { ann.symbol.initialize } @@ -695,7 +681,7 @@ abstract class BCodeSkelBuilder extends BCodeHelpers { // android creator code if (isCZParcelable) { // add a static field ("CREATOR") to this class to cache android.os.Parcelable$Creator - val andrFieldDescr = asmClassType(AndroidCreatorClass).descriptor + val andrFieldDescr = getClassBTypeAndRegisterInnerClass(AndroidCreatorClass).descriptor cnode.visitField( asm.Opcodes.ACC_STATIC | asm.Opcodes.ACC_FINAL, "CREATOR", diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BCodeSyncAndTry.scala b/src/compiler/scala/tools/nsc/backend/jvm/BCodeSyncAndTry.scala index c271e7b129..7c95b7fc3b 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/BCodeSyncAndTry.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/BCodeSyncAndTry.scala @@ -21,7 +21,7 @@ import scala.tools.asm abstract class BCodeSyncAndTry extends BCodeBodyBuilder { import global._ import bTypes._ - + import coreBTypes._ /* * Functionality to lower `synchronized` and `try` expressions. diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BCodeTypes.scala b/src/compiler/scala/tools/nsc/backend/jvm/BCodeTypes.scala deleted file mode 100644 index 62dfb4917d..0000000000 --- a/src/compiler/scala/tools/nsc/backend/jvm/BCodeTypes.scala +++ /dev/null @@ -1,861 +0,0 @@ -/* NSC -- new Scala compiler - * Copyright 2005-2012 LAMP/EPFL - * @author Martin Odersky - */ - -package scala -package tools.nsc -package backend.jvm - -import scala.tools.asm -import scala.collection.{ immutable, mutable } - -/* - * Utilities to mediate between types as represented in Scala ASTs and ASM trees. - * - * @author Miguel Garcia, http://lamp.epfl.ch/~magarcia/ScalaCompilerCornerReloaded - * @version 1.0 - * - */ -abstract class BCodeTypes extends BCodeIdiomatic { - import global._ - import bTypes._ - - // when compiling the Scala library, some assertions don't hold (e.g., scala.Boolean has null superClass although it's not an interface) - val isCompilingStdLib = !(settings.sourcepath.isDefault) - - // special names - var StringReference : ClassBType = null - var ThrowableReference : ClassBType = null - var jlCloneableReference : ClassBType = null // java/lang/Cloneable - var jlNPEReference : ClassBType = null // java/lang/NullPointerException - var jioSerializableReference : ClassBType = null // java/io/Serializable - var scalaSerializableReference : ClassBType = null // scala/Serializable - var classCastExceptionReference : ClassBType = null // java/lang/ClassCastException - - /* A map from scala primitive type-symbols to BTypes */ - var primitiveTypeMap: Map[Symbol, BType] = null - /* A map from scala type-symbols for Nothing and Null to (runtime version) BTypes */ - var phantomTypeMap: Map[Symbol, ClassBType] = null - /* Maps the method symbol for a box method to the boxed type of the result. - * For example, the method symbol for `Byte.box()`) is mapped to the BType `Ljava/lang/Integer;`. */ - var boxResultType: Map[Symbol, BType] = null - /* Maps the method symbol for an unbox method to the primitive type of the result. - * For example, the method symbol for `Byte.unbox()`) is mapped to the BType BYTE. */ - var unboxResultType: Map[Symbol, BType] = null - - var hashMethodSym: Symbol = null // scala.runtime.ScalaRunTime.hash - - var AndroidParcelableInterface: Symbol = null - var AndroidCreatorClass : Symbol = null // this is an inner class, use asmType() to get hold of its BType while tracking in innerClassBufferASM - - var BeanInfoAttr: Symbol = null - - /* The Object => String overload. */ - var String_valueOf: Symbol = null - - var ArrayInterfaces: Set[Tracked] = null - - // scala.FunctionX and scala.runtim.AbstractFunctionX - val FunctionReference = new Array[Tracked](definitions.MaxFunctionArity + 1) - val AbstractFunctionReference = new Array[Tracked](definitions.MaxFunctionArity + 1) - val abstractFunctionArityMap = mutable.Map.empty[BType, Int] - - var PartialFunctionReference: ClassBType = null // scala.PartialFunction - var AbstractPartialFunctionReference: ClassBType = null // scala.runtime.AbstractPartialFunction - - var BoxesRunTime: ClassBType = null - - /* - * must-single-thread - */ - def initBCodeTypes() { - import definitions._ - - primitiveTypeMap = - Map( - UnitClass -> UNIT, - BooleanClass -> BOOL, - CharClass -> CHAR, - ByteClass -> BYTE, - ShortClass -> SHORT, - IntClass -> INT, - LongClass -> LONG, - FloatClass -> FLOAT, - DoubleClass -> DOUBLE - ) - - phantomTypeMap = - Map( - NothingClass -> RT_NOTHING, - NullClass -> RT_NULL, - NothingClass -> RT_NOTHING, // we map on purpose to RT_NOTHING, getting rid of the distinction compile-time vs. runtime for NullClass. - NullClass -> RT_NULL // ditto. - ) - - boxResultType = - for((csym, msym) <- currentRun.runDefinitions.boxMethod) - yield (msym -> classLiteral(primitiveTypeMap(csym))) - - unboxResultType = - for((csym, msym) <- currentRun.runDefinitions.unboxMethod) - yield (msym -> primitiveTypeMap(csym)) - - // boxed classes are looked up in the `exemplars` map by jvmWiseLUB(). - // Other than that, they aren't needed there (e.g., `isSubtypeOf()` special-cases boxed classes, similarly for others). - val boxedClasses = List(BoxedBooleanClass, BoxedCharacterClass, BoxedByteClass, BoxedShortClass, BoxedIntClass, BoxedLongClass, BoxedFloatClass, BoxedDoubleClass) - for(csym <- boxedClasses) { - val key = ClassBType(csym.javaBinaryName.toTypeName) - val tr = buildExemplar(key, csym) - symExemplars.put(csym, tr) - exemplars.put(tr.c, tr) - } - - // reversePrimitiveMap = (primitiveTypeMap map { case (s, pt) => (s.tpe, pt) } map (_.swap)).toMap - - hashMethodSym = getMember(ScalaRunTimeModule, nme.hash_) - - // TODO avoiding going through through missingHook for every line in the REPL: https://github.com/scala/scala/commit/8d962ed4ddd310cc784121c426a2e3f56a112540 - AndroidParcelableInterface = rootMirror.getClassIfDefined("android.os.Parcelable") - AndroidCreatorClass = rootMirror.getClassIfDefined("android.os.Parcelable$Creator") - - // the following couldn't be an eager vals in Phase constructors: - // that might cause cycles before Global has finished initialization. - BeanInfoAttr = rootMirror.getRequiredClass("scala.beans.BeanInfo") - - String_valueOf = { - getMember(StringModule, nme.valueOf) filter (sym => - sym.info.paramTypes match { - case List(pt) => pt.typeSymbol == ObjectClass - case _ => false - } - ) - } - - exemplar(JavaCloneableClass) - exemplar(JavaSerializableClass) - exemplar(SerializableClass) - - StringReference = exemplar(StringClass).c - StringBuilderReference = exemplar(StringBuilderClass).c - ThrowableReference = exemplar(ThrowableClass).c - jlCloneableReference = exemplar(JavaCloneableClass).c - jlNPEReference = exemplar(NullPointerExceptionClass).c - jioSerializableReference = exemplar(JavaSerializableClass).c - scalaSerializableReference = exemplar(SerializableClass).c - classCastExceptionReference = exemplar(ClassCastExceptionClass).c - - PartialFunctionReference = exemplar(PartialFunctionClass).c - for(idx <- 0 to definitions.MaxFunctionArity) { - FunctionReference(idx) = exemplar(FunctionClass(idx)) - AbstractFunctionReference(idx) = exemplar(AbstractFunctionClass(idx)) - abstractFunctionArityMap += (AbstractFunctionReference(idx).c -> idx) - AbstractPartialFunctionReference = exemplar(AbstractPartialFunctionClass).c - } - - BoxesRunTime = ClassBType("scala/runtime/BoxesRunTime") - } - - /* - * must-single-thread - */ - def clearBCodeTypes() { - symExemplars.clear() - exemplars.clear() - } - - val PublicStatic = asm.Opcodes.ACC_PUBLIC | asm.Opcodes.ACC_STATIC - val PublicStaticFinal = asm.Opcodes.ACC_PUBLIC | asm.Opcodes.ACC_STATIC | asm.Opcodes.ACC_FINAL - - val strMODULE_INSTANCE_FIELD = nme.MODULE_INSTANCE_FIELD.toString - - // ------------------------------------------------ - // accessory maps tracking the isInterface, innerClasses, superClass, and supportedInterfaces relations, - // allowing answering `conforms()` without resorting to typer. - // ------------------------------------------------ - - /** - * TODO @lry should probably be a map form ClassBType to Tracked - */ - val exemplars = new java.util.concurrent.ConcurrentHashMap[BType, Tracked] - - /** - * Maps class symbols to their corresponding `Tracked` instance. - */ - val symExemplars = new java.util.concurrent.ConcurrentHashMap[Symbol, Tracked] - - /** - * A `Tracked` instance stores information about a BType. This allows ansering type questions - * without resolving to the compiler, in a thread-safe manner, in particular isSubtypeOf. - * - * @param c the BType described by this `Tracked` - * @param flags the java flags for the type, computed by BCodeTypes#javaFlags - * @param sc the bytecode-level superclass if any, null otherwise - * @param ifaces the interfaces explicitly declared. Not included are those transitively - * supported, but the utility method `allLeafIfaces()` can be used for that. - * @param innersChain the containing classes for a non-package-level class `c`, null otherwise. - * - * Note: the optimizer may inline anonymous closures, thus eliding those inner classes (no - * physical class file is emitted for elided classes). Before committing `innersChain` to - * bytecode, cross-check with the list of elided classes (SI-6546). - * - * All methods of this class can-multi-thread - * - * TODO @lry c: ClassBType. rename to ClassBTypeInfo - */ - case class Tracked(c: ClassBType, flags: Int, sc: Tracked, ifaces: Array[Tracked], innersChain: Array[InnerClassEntry]) { - - // not a case-field because we initialize it only for JVM classes we emit. - // TODO @lry make it an Option[List[BType]] - // TODO: this is currently not used. a commit in the optimizer branch uses this field to - // re-compute inner classes (ee4c185). leaving it in for now. - private var _directMemberClasses: List[BType] = null - - def directMemberClasses: List[BType] = { - assert(_directMemberClasses != null, s"getter directMemberClasses() invoked too early for $c") - _directMemberClasses - } - - def directMemberClasses_=(bs: List[BType]) { - if (_directMemberClasses != null) { - // TODO we enter here when both mirror class and plain class are emitted for the same ModuleClassSymbol. - assert(_directMemberClasses.sameElements(bs)) - } - _directMemberClasses = bs - } - - /* `isCompilingStdLib` saves the day when compiling: - * (1) scala.Nothing (the test `c.isNonSpecial` fails for it) - * (2) scala.Boolean (it has null superClass and is not an interface) - */ - assert(c.isNonSpecial || isCompilingStdLib /*(1)*/, s"non well-formed plain-type: $this") - assert( - if (sc == null) { (c == ObjectReference) || isInterface || isCompilingStdLib /*(2)*/ } - else { (c != ObjectReference) && !sc.isInterface } - , "non well-formed plain-type: " + this - ) - assert(ifaces.forall(i => i.c.isNonSpecial && i.isInterface), s"non well-formed plain-type: $this") - - import asm.Opcodes._ - def hasFlags(mask: Int) = (flags & mask) != 0 - def isInterface = hasFlags(ACC_INTERFACE) - def isFinal = hasFlags(ACC_FINAL) - def isInnerClass = { innersChain != null } - def isLambda = { - // ie isLCC || isTraditionalClosureClass - isFinal && (c.simpleName.contains(tpnme.ANON_FUN_NAME.toString)) && isFunctionType(c) - } - - /* can-multi-thread */ - def superClasses: List[Tracked] = { - if (sc == null) Nil else sc :: sc.superClasses - } - - /* can-multi-thread */ - def isSubtypeOf(other: BType): Boolean = { - assert(other.isNonSpecial, "so called special cases have to be handled in BCodeTypes.conforms()") - - if (c == other) return true; - - val otherIsIface = exemplars.get(other).isInterface - - if (this.isInterface) { - if (other == ObjectReference) return true; - if (!otherIsIface) return false; - } - else { - if (sc != null && sc.isSubtypeOf(other)) return true; - if (!otherIsIface) return false; - } - - var idx = 0 - while (idx < ifaces.length) { - if (ifaces(idx).isSubtypeOf(other)) return true; - idx += 1 - } - - false - } - - /* - * The `ifaces` field lists only those interfaces declared by `c` - * From the set of all supported interfaces, this method discards those which are supertypes of others in the set. - */ - def allLeafIfaces: Set[Tracked] = { - if (sc == null) { ifaces.toSet } - else { minimizeInterfaces(ifaces.toSet ++ sc.allLeafIfaces) } - } - - /* - * This type may not support in its entirety the interface given by the argument, however it may support some of its super-interfaces. - * We visualize each such supported subset of the argument's functionality as a "branch". This method returns all such branches. - * - * In other words, let Ri be a branch supported by `ib`, - * this method returns all Ri such that this <:< Ri, where each Ri is maximally deep. - */ - def supportedBranches(ib: Tracked): Set[Tracked] = { - assert(ib.isInterface, s"Non-interface argument: $ib") - - val result: Set[Tracked] = - if (this.isSubtypeOf(ib.c)) { Set(ib) } - else { ib.ifaces.toSet[Tracked].flatMap( bi => supportedBranches(bi) ) } - - checkAllInterfaces(result) - - result - } - - override def toString = { c.toString } - - } - - /* must-single-thread */ - final def isDeprecated(sym: Symbol): Boolean = { sym.annotations exists (_ matches definitions.DeprecatedAttr) } - - /* must-single-thread */ - final def hasInternalName(sym: Symbol) = { sym.isClass || (sym.isModule && !sym.isMethod) } - - /* must-single-thread */ - def getSuperInterfaces(csym: Symbol): List[Symbol] = { - - // Additional interface parents based on annotations and other cues - def newParentForAttr(ann: AnnotationInfo): Symbol = ann.symbol match { - case definitions.RemoteAttr => definitions.RemoteInterfaceClass - case _ => NoSymbol - } - - /* Drop redundant interfaces (which are implemented by some other parent) from the immediate parents. - * In other words, no two interfaces in the result are related by subtyping. - * This method works on Symbols, a similar one (not duplicate) works on Tracked instances. - */ - def minimizeInterfaces(lstIfaces: List[Symbol]): List[Symbol] = { - var rest = lstIfaces - var leaves = List.empty[Symbol] - while (!rest.isEmpty) { - val candidate = rest.head - val nonLeaf = leaves exists { lsym => lsym isSubClass candidate } - if (!nonLeaf) { - leaves = candidate :: (leaves filterNot { lsym => candidate isSubClass lsym }) - } - rest = rest.tail - } - - leaves - } - - val superInterfaces0: List[Symbol] = csym.mixinClasses - val superInterfaces = existingSymbols(superInterfaces0 ++ csym.annotations.map(newParentForAttr)).distinct - - assert(!superInterfaces.contains(NoSymbol), s"found NoSymbol among: ${superInterfaces.mkString(", ")}") - assert(superInterfaces.forall(s => s.isInterface || s.isTrait), s"found non-interface among: ${superInterfaces.mkString(", ")}") - - minimizeInterfaces(superInterfaces) - } - - /* - * Records the superClass and supportedInterfaces relations, - * so that afterwards queries can be answered without resorting to typer. - * This method does not add to `innerClassBufferASM`, use `internalName()` or `asmType()` or `toTypeKind()` for that. - * On the other hand, this method does record the inner-class status of the argument, via `buildExemplar()`. - * - * must-single-thread - */ - final def exemplar(csym0: Symbol): Tracked = { - assert(csym0 != NoSymbol, "NoSymbol can't be tracked") - - val csym = { - if (csym0.isJavaDefined && csym0.isModuleClass) csym0.linkedClassOfClass - else if (csym0.isModule) csym0.moduleClass - else csym0 // we track only module-classes and plain-classes - } - - assert(!primitiveTypeMap.contains(csym) || isCompilingStdLib, s"primitive types not tracked here: ${csym.fullName}") - assert(!phantomTypeMap.contains(csym), s"phantom types not tracked here: ${csym.fullName}") - - val opt = symExemplars.get(csym) - if (opt != null) { - return opt - } - val key = new ClassBType(csym.javaBinaryName.toTypeName) - assert(key.isNonSpecial || isCompilingStdLib, s"Not a class to track: ${csym.fullName}") - - // TODO accomodate the fix for SI-5031 of https://github.com/scala/scala/commit/0527b2549bcada2fda2201daa630369b377d0877 - // TODO Weaken this assertion? buildExemplar() needs to be updated, too. In the meantime, pos/t5031_3 has been moved to test/disabled/pos. - val whatWasInExemplars = exemplars.get(key) - assert(whatWasInExemplars == null, "Maps `symExemplars` and `exemplars` got out of synch.") - val tr = buildExemplar(key, csym) - symExemplars.put(csym, tr) - if (csym != csym0) { symExemplars.put(csym0, tr) } - exemplars.put(tr.c, tr) // tr.c is the hash-consed, internalized, canonical representative for csym's key. - tr - } - - /* - * must-single-thread - */ - private def buildExemplar(key: ClassBType, csym: Symbol): Tracked = { - val sc = - if (csym.isImplClass) definitions.ObjectClass - else csym.superClass - assert( - if (csym == definitions.ObjectClass) - sc == NoSymbol - else if (csym.isInterface) - sc == definitions.ObjectClass - else - ((sc != NoSymbol) && !sc.isInterface) || isCompilingStdLib, - "superClass out of order" - ) - val ifacesArr = getSuperInterfaces(csym).map(exemplar).toArray - - val flags = mkFlags( - javaFlags(csym), - if (isDeprecated(csym)) asm.Opcodes.ACC_DEPRECATED else 0 // ASM pseudo access flag - ) - - val tsc = if (sc == NoSymbol) null else exemplar(sc) - - val innersChain = saveInnerClassesFor(csym, key) - - Tracked(key, flags, tsc, ifacesArr, innersChain) - } - - // ---------------- utilities around interfaces represented by Tracked instances. ---------------- - - /* Drop redundant interfaces (those which are implemented by some other). - * In other words, no two interfaces in the result are related by subtyping. - * This method works on Tracked elements, a similar one (not duplicate) works on Symbols. - */ - def minimizeInterfaces(lstIfaces: Set[Tracked]): Set[Tracked] = { - checkAllInterfaces(lstIfaces) - var rest = lstIfaces.toList - var leaves = List.empty[Tracked] - while (!rest.isEmpty) { - val candidate = rest.head - val nonLeaf = leaves exists { leaf => leaf.isSubtypeOf(candidate.c) } - if (!nonLeaf) { - leaves = candidate :: (leaves filterNot { leaf => candidate.isSubtypeOf(leaf.c) }) - } - rest = rest.tail - } - - leaves.toSet - } - - def allInterfaces(is: Iterable[Tracked]): Boolean = { is forall { i => i.isInterface } } - def nonInterfaces(is: Iterable[Tracked]): Iterable[Tracked] = { is filterNot { i => i.isInterface } } - - def checkAllInterfaces(ifaces: Iterable[Tracked]) { - assert(allInterfaces(ifaces), s"Non-interfaces: ${nonInterfaces(ifaces).mkString}") - } - - /* - * Subtype check `a <:< b` on BTypes that takes into account the JVM built-in numeric promotions (e.g. BYTE to INT). - * Its operation can be visualized more easily in terms of the Java bytecode type hierarchy. - * This method used to be called, in the ICode world, TypeKind.<:<() - * - * can-multi-thread - */ - final def conforms(a: BType, b: BType): Boolean = { - if (a.isArray) { // may be null - /* Array subtyping is covariant here, as in Java bytecode. Also necessary for Java interop. */ - if ((b == jlCloneableReference) || - (b == jioSerializableReference) || - (b == AnyRefReference)) { true } - else if (b.isArray) { conforms(a.asArrayBType.componentType, // TODO @lry change to pattern match, get rid of casts - b.asArrayBType.componentType) } - else { false } - } - else if (a.isBoxed) { // may be null - if (b.isBoxed) { a == b } - else if (b == AnyRefReference) { true } - else if (!(b.isClass)) { false } - else { exemplars.get(a).isSubtypeOf(b) } // e.g., java/lang/Double conforms to java/lang/Number - } - else if (a.isNullType) { // known to be null - if (b.isNothingType) { false } - else if (b.isPrimitive) { false } - else { true } - } - else if (a.isNothingType) { // known to be Nothing - true - } - else if (a == UNIT) { - b == UNIT - } - else if (a.isClass) { // may be null - if (a.isNothingType) { true } - else if (b.isClass) { exemplars.get(a).isSubtypeOf(b) } - else if (b.isArray) { a.isNullType } // documentation only, because `if(a.isNullType)` (above) covers this case already. - else { false } - } - else { - - def msg = s"(a: $a, b: $b)" - - assert(a.isNonVoidPrimitiveType, s"a isn't a non-Unit value type. $msg") - assert(b.isPrimitive, s"b isn't a value type. $msg") - - (a eq b) || (a match { - case BOOL | BYTE | SHORT | CHAR => b == INT || b == LONG // TODO Actually, BOOL does NOT conform to LONG. Even with adapt(). - case _ => a == b - }) - } - } - - /* The maxValueType of (Char, Byte) and of (Char, Short) is Int, to encompass the negative values of Byte and Short. See ticket #2087. - * - * can-multi-thread - */ - def maxValueType(a: BType, other: BType): BType = { - assert(a.isPrimitive, "maxValueType() is defined only for 1st arg valuetypes (2nd arg doesn't matter).") - - def uncomparable: Nothing = { - abort(s"Uncomparable BTypes: $a with $other") - } - - if (a.isNothingType) return other; - if (other.isNothingType) return a; - if (a == other) return a; - - a match { - - case UNIT => uncomparable - case BOOL => uncomparable - - case BYTE => - if (other == CHAR) INT - else if (other.isNumericType) other - else uncomparable - - case SHORT => - other match { - case BYTE => SHORT - case CHAR => INT - case INT | LONG | FLOAT | DOUBLE => other - case _ => uncomparable - } - - case CHAR => - other match { - case BYTE | SHORT => INT - case INT | LONG | FLOAT | DOUBLE => other - case _ => uncomparable - } - - case INT => - other match { - case BYTE | SHORT | CHAR => INT - case LONG | FLOAT | DOUBLE => other - case _ => uncomparable - } - - case LONG => - if (other.isIntegralType) LONG - else if (other.isRealType) DOUBLE - else uncomparable - - case FLOAT => - if (other == DOUBLE) DOUBLE - else if (other.isNumericType) FLOAT - else uncomparable - - case DOUBLE => - if (other.isNumericType) DOUBLE - else uncomparable - - case _ => uncomparable - } - } - - /* Takes promotions of numeric primitives into account. - * - * can-multi-thread - */ - final def maxType(a: BType, other: BType): BType = { - if (a.isPrimitive) { maxValueType(a, other) } - else { - if (a.isNothingType) return other; - if (other.isNothingType) return a; - if (a == other) return a; - // Approximate `lub`. The common type of two references is always AnyRef. - // For 'real' least upper bound wrt to subclassing use method 'lub'. - assert(a.isArray || a.isBoxed || a.isClass, s"This is not a valuetype and it's not something else, what is it? $a") - // TODO For some reason, ICode thinks `REFERENCE(...).maxType(BOXED(whatever))` is `uncomparable`. Here, that has maxType AnyRefReference. - // BTW, when swapping arguments, ICode says BOXED(whatever).maxType(REFERENCE(...)) == AnyRefReference, so I guess the above was an oversight in REFERENCE.maxType() - if (other.isRef) { AnyRefReference } - else { abort(s"Uncomparable BTypes: $a with $other") } - } - } - - /* - * Whether the argument is a subtype of - * scala.PartialFunction[-A, +B] extends (A => B) - * N.B.: this method returns true for a scala.runtime.AbstractPartialFunction - * - * can-multi-thread - */ - def isPartialFunctionType(t: BType): Boolean = { - (t.isClass) && exemplars.get(t).isSubtypeOf(PartialFunctionReference) - } - - /* - * Whether the argument is a subtype of scala.FunctionX where 0 <= X <= definitions.MaxFunctionArity - * - * can-multi-thread - */ - def isFunctionType(t: BType): Boolean = { - if (!t.isClass) return false - var idx = 0 - val et: Tracked = exemplars.get(t) - while (idx <= definitions.MaxFunctionArity) { - if (et.isSubtypeOf(FunctionReference(idx).c)) { - return true - } - idx += 1 - } - false - } - - /* - * must-single-thread - */ - def isTopLevelModule(sym: Symbol): Boolean = { - exitingPickler { sym.isModuleClass && !sym.isImplClass && !sym.isNestedClass } - } - - /* - * must-single-thread - */ - def isStaticModule(sym: Symbol): Boolean = { - sym.isModuleClass && !sym.isImplClass && !sym.isLifted - } - - // --------------------------------------------------------------------- - // ---------------- InnerClasses attribute (JVMS 4.7.6) ---------------- - // --------------------------------------------------------------------- - - val INNER_CLASSES_FLAGS = - (asm.Opcodes.ACC_PUBLIC | asm.Opcodes.ACC_PRIVATE | asm.Opcodes.ACC_PROTECTED | - asm.Opcodes.ACC_STATIC | asm.Opcodes.ACC_INTERFACE | asm.Opcodes.ACC_ABSTRACT | asm.Opcodes.ACC_FINAL) - - /* - * @param name the internal name of an inner class. - * @param outerName the internal name of the class to which the inner class belongs. - * May be `null` for non-member inner classes (ie for a Java local class or a Java anonymous class). - * @param innerName the (simple) name of the inner class inside its enclosing class. It's `null` for anonymous inner classes. - * @param access the access flags of the inner class as originally declared in the enclosing class. - */ - case class InnerClassEntry(name: String, outerName: String, innerName: String, access: Int) { - assert(name != null, "Null isn't good as class name in an InnerClassEntry.") - } - - /* For given symbol return a symbol corresponding to a class that should be declared as inner class. - * - * For example: - * class A { - * class B - * object C - * } - * - * then method will return: - * NoSymbol for A, - * the same symbol for A.B (corresponding to A$B class), and - * A$C$ symbol for A.C. - * - * must-single-thread - */ - def innerClassSymbolFor(s: Symbol): Symbol = - if (s.isClass) s else if (s.isModule) s.moduleClass else NoSymbol - - /* - * Computes the chain of inner-class (over the is-member-of relation) for the given argument. - * The resulting chain will be cached in `exemplars`. - * - * The chain thus cached is valid during this compiler run, see in contrast - * `innerClassBufferASM` for a cache that is valid only for the class being emitted. - * - * The argument can be any symbol, but given that this method is invoked only from `buildExemplar()`, - * in practice it has been vetted to be a class-symbol. - * - * Returns: - * - * - a non-empty array of entries for an inner-class argument. - * The array's first element is the outermost top-level class, - * the array's last element corresponds to csym. - * - * - null otherwise. - * - * This method does not add to `innerClassBufferASM`, use instead `exemplar()` for that. - * - * must-single-thread - */ - final def saveInnerClassesFor(csym: Symbol, csymTK: BType): Array[InnerClassEntry] = { - - val ics = innerClassSymbolFor(csym) - if (ics == NoSymbol) { - return null - } - assert(ics == csym, s"Disagreement between innerClassSymbolFor() and exemplar()'s tracked symbol for the same input: ${csym.fullName}") - - var chain: List[Symbol] = Nil - var x = ics - while (x ne NoSymbol) { - assert(x.isClass, s"not a class symbol: ${x.fullName}") - val isInner = !x.rawowner.isPackageClass - if (isInner) { - chain ::= x - x = innerClassSymbolFor(x.rawowner) - } else { - x = NoSymbol - } - } - - if (chain.isEmpty) null - else chain.map(toInnerClassEntry).toArray - } - - /* - * must-single-thread - */ - private def toInnerClassEntry(innerSym: Symbol): InnerClassEntry = { - - /* The outer name for this inner class. Note that it returns null - * when the inner class should not get an index in the constant pool. - * That means non-member classes (anonymous). See Section 4.7.5 in the JVMS. - */ - def outerName(innerSym: Symbol): Name = { - if (innerSym.originalEnclosingMethod != NoSymbol) - null - else { - val outerName = innerSym.rawowner.javaBinaryName - if (isTopLevelModule(innerSym.rawowner)) nme.stripModuleSuffix(outerName) - else outerName - } - } - - def innerName(innerSym: Symbol): String = { - if (innerSym.isAnonymousClass || innerSym.isAnonymousFunction) - null - else - innerSym.rawname + innerSym.moduleSuffix - } - - val flagsWithFinal: Int = mkFlags( - if (innerSym.rawowner.hasModuleFlag) asm.Opcodes.ACC_STATIC else 0, - javaFlags(innerSym), - if (isDeprecated(innerSym)) asm.Opcodes.ACC_DEPRECATED else 0 // ASM pseudo-access flag - ) & (INNER_CLASSES_FLAGS | asm.Opcodes.ACC_DEPRECATED) - val flags = if (innerSym.isModuleClass) flagsWithFinal & ~asm.Opcodes.ACC_FINAL else flagsWithFinal // For SI-5676, object overriding. - - val jname = innerSym.javaBinaryName.toString // never null - val oname = { // null when method-enclosed - val on = outerName(innerSym) - if (on == null) null else on.toString - } - val iname = { // null for anonymous inner class - val in = innerName(innerSym) - if (in == null) null else in.toString - } - - InnerClassEntry(jname, oname, iname, flags) - } - - // -------------------------------------------- - // ---------------- Java flags ---------------- - // -------------------------------------------- - - /* - * can-multi-thread - */ - final def hasPublicBitSet(flags: Int) = ((flags & asm.Opcodes.ACC_PUBLIC) != 0) - - /* - * must-single-thread - */ - final def isRemote(s: Symbol) = (s hasAnnotation definitions.RemoteAttr) - - /* - * Return the Java modifiers for the given symbol. - * Java modifiers for classes: - * - public, abstract, final, strictfp (not used) - * for interfaces: - * - the same as for classes, without 'final' - * for fields: - * - public, private (*) - * - static, final - * for methods: - * - the same as for fields, plus: - * - abstract, synchronized (not used), strictfp (not used), native (not used) - * - * (*) protected cannot be used, since inner classes 'see' protected members, - * and they would fail verification after lifted. - * - * must-single-thread - */ - def javaFlags(sym: Symbol): Int = { - // constructors of module classes should be private - // PP: why are they only being marked private at this stage and not earlier? - val privateFlag = - sym.isPrivate || (sym.isPrimaryConstructor && isTopLevelModule(sym.owner)) - - // Final: the only fields which can receive ACC_FINAL are eager vals. - // Neither vars nor lazy vals can, because: - // - // Source: http://docs.oracle.com/javase/specs/jls/se7/html/jls-17.html#jls-17.5.3 - // "Another problem is that the specification allows aggressive - // optimization of final fields. Within a thread, it is permissible to - // reorder reads of a final field with those modifications of a final - // field that do not take place in the constructor." - // - // A var or lazy val which is marked final still has meaning to the - // scala compiler. The word final is heavily overloaded unfortunately; - // for us it means "not overridable". At present you can't override - // vars regardless; this may change. - // - // The logic does not check .isFinal (which checks flags for the FINAL flag, - // and includes symbols marked lateFINAL) instead inspecting rawflags so - // we can exclude lateFINAL. Such symbols are eligible for inlining, but to - // avoid breaking proxy software which depends on subclassing, we do not - // emit ACC_FINAL. - // Nested objects won't receive ACC_FINAL in order to allow for their overriding. - - val finalFlag = ( - (((sym.rawflags & symtab.Flags.FINAL) != 0) || isTopLevelModule(sym)) - && !sym.enclClass.isInterface - && !sym.isClassConstructor - && !sym.isMutable // lazy vals and vars both - ) - - // Primitives are "abstract final" to prohibit instantiation - // without having to provide any implementations, but that is an - // illegal combination of modifiers at the bytecode level so - // suppress final if abstract if present. - import asm.Opcodes._ - mkFlags( - if (privateFlag) ACC_PRIVATE else ACC_PUBLIC, - if (sym.isDeferred || sym.hasAbstractFlag) ACC_ABSTRACT else 0, - if (sym.isInterface) ACC_INTERFACE else 0, - if (finalFlag && !sym.hasAbstractFlag) ACC_FINAL else 0, - if (sym.isStaticMember) ACC_STATIC else 0, - if (sym.isBridge) ACC_BRIDGE | ACC_SYNTHETIC else 0, - if (sym.isArtifact) ACC_SYNTHETIC else 0, - if (sym.isClass && !sym.isInterface) ACC_SUPER else 0, - if (sym.hasEnumFlag) ACC_ENUM else 0, - if (sym.isVarargsMethod) ACC_VARARGS else 0, - if (sym.hasFlag(symtab.Flags.SYNCHRONIZED)) ACC_SYNCHRONIZED else 0 - ) - } - - /* - * must-single-thread - */ - def javaFieldFlags(sym: Symbol) = { - javaFlags(sym) | mkFlags( - if (sym hasAnnotation definitions.TransientAttr) asm.Opcodes.ACC_TRANSIENT else 0, - if (sym hasAnnotation definitions.VolatileAttr) asm.Opcodes.ACC_VOLATILE else 0, - if (sym.isMutable) 0 else asm.Opcodes.ACC_FINAL - ) - } - -} // end of class BCodeTypes diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BTypes.scala b/src/compiler/scala/tools/nsc/backend/jvm/BTypes.scala index 5b0fa6f7a8..7bf61b4f51 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/BTypes.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/BTypes.scala @@ -1,37 +1,57 @@ +/* NSC -- new Scala compiler + * Copyright 2005-2014 LAMP/EPFL + * @author Martin Odersky + */ + package scala.tools.nsc package backend.jvm -import scala.collection.immutable -import scala.annotation.switch import scala.tools.asm import asm.Opcodes -import scala.collection.mutable.ListBuffer /** - * BTypes is a backend component that defines the class BType, a number of basic instances and - * some utilities. - * - * A BType is essentially an slice of the array `chrs` denoting the name of the type, and a field - * denoting the kind (object, array, method, or one of the primitive types). + * The BTypes component defines The BType class hierarchy. BTypes encapsulates all type information + * that is required after building the ASM nodes. This includes optimizations, geneartion of + * InnerClass attributes and generation of stack map frames. * - * BTypes depends on Global just because it re-uses hash-consing of Name. It would be cleaner to - * create an interface for BTypeName and extend it in scala.reflect.internal.Names#Name, that - * would simplify testing BTypes (no Global needed). + * This representation is immutable and independent of the compiler data structures, hence it can + * be queried by concurrent threads. */ -abstract class BTypes[G <: Global](val __global_dont_use: G) { - def chrs: Array[Char] +abstract class BTypes { + /** + * A map from internal names to ClassBTypes. Every ClassBType is added to this map on its + * construction. + * + * This map is used when computing stack map frames. The asm.ClassWriter invokes the method + * `getCommonSuperClass`. In this method we need to obtain the ClassBType for a given internal + * name. The method assumes that every class type that appears in the bytecode exists in the map. + * + * Concurrent because stack map frames are computed when in the class writer, which might run + * on multiple classes concurrently. + */ + protected val classBTypeFromInternalNameMap: collection.concurrent.Map[String, ClassBType] /** - * Interface for names stored in `chrs` + * The string represented by the `offset` / `length` values of a ClassBType, see comment of that + * class. */ - type BTypeName <: __global_dont_use.Name + protected def internalNameString(offset: Int, lenght: Int): String /** - * Create a new name in `chrs`. Names are assumed to be hash-consed. Equality on BType will use - * reference equality to compare the names. + * Obtain a previously constructed ClassBType for a given internal name. */ - def createNewName(s: String): BTypeName + def classBTypeFromInternalName(internalName: String) = classBTypeFromInternalNameMap(internalName) + + // Some core BTypes are required here, in class BType, where no Global instance is available. + // The Global is only available in the subclass BTypesFromSymbols. We cannot depend on the actual + // implementation (CoreBTypesProxy) here because it has members that refer to global.Symbol. + val coreBTypes: CoreBTypesProxyGlobalIndependent[this.type] + import coreBTypes._ + /** + * A BType is either a primitve type, a ClassBType, an ArrayBType of one of these, or a MethodType + * referring to BTypes. + */ /*sealed*/ trait BType { // Not sealed for now due to SI-8546 final override def toString: String = this match { case UNIT => "V" @@ -43,9 +63,9 @@ abstract class BTypes[G <: Global](val __global_dont_use: G) { case FLOAT => "F" case LONG => "J" case DOUBLE => "D" - case c @ ClassBType(_, _) => "L" + c.internalName + ";" - case ArrayBType(component) => "[" + component - case MethodBType(args, res) => "(" + args.mkString + ")" + res + case ClassBType(internalName) => "L" + internalName + ";" + case ArrayBType(component) => "[" + component + case MethodBType(args, res) => "(" + args.mkString + ")" + res } /** @@ -73,18 +93,11 @@ abstract class BTypes[G <: Global](val __global_dont_use: G) { final def isMethod: Boolean = this.isInstanceOf[MethodBType] final def isNonVoidPrimitiveType = isPrimitive && this != UNIT - // TODO @lry should also include !isMethod in isNonSpecial? in this case it would be equivalent to isClass, so we could get rid of it. - final def isNonSpecial = !isPrimitive && !isArray && !isPhantomType - final def isNullType = this == RT_NULL || this == CT_NULL - final def isNothingType = this == RT_NOTHING || this == CT_NOTHING - final def isPhantomType = isNullType || isNothingType - - final def isBoxed = this match { - case BOXED_UNIT | BOXED_BOOLEAN | BOXED_CHAR | - BOXED_BYTE | BOXED_SHORT | BOXED_INT | - BOXED_FLOAT | BOXED_LONG | BOXED_DOUBLE => true - case _ => false - } + + final def isNullType = this == RT_NULL + final def isNothingType = this == RT_NOTHING + + final def isBoxed = this.isClass && boxedClasses(this.asClassBType) final def isIntSizedType = this == BOOL || this == CHAR || this == BYTE || this == SHORT || this == INT @@ -94,6 +107,72 @@ abstract class BTypes[G <: Global](val __global_dont_use: G) { final def isNumericType = isIntegralType || isRealType final def isWideType = size == 2 + /* + * Subtype check `this <:< other` on BTypes that takes into account the JVM built-in numeric + * promotions (e.g. BYTE to INT). Its operation can be visualized more easily in terms of the + * Java bytecode type hierarchy. + */ + final def conformsTo(other: BType): Boolean = { + assert(isRef || isPrimitive, s"conformsTo cannot handle $this") + assert(other.isRef || other.isPrimitive, s"conformsTo cannot handle $other") + + this match { + case ArrayBType(component) => + if (other == ObjectReference || other == jlCloneableReference || other == jioSerializableReference) true + else other match { + case ArrayBType(otherComponoent) => component.conformsTo(otherComponoent) + case _ => false + } + + case classType: ClassBType => + if (isBoxed) { + if (other.isBoxed) this == other + else if (other == ObjectReference) true + else other match { + case otherClassType: ClassBType => classType.isSubtypeOf(otherClassType) // e.g., java/lang/Double conforms to java/lang/Number + case _ => false + } + } else if (isNullType) { + if (other.isNothingType) false + else if (other.isPrimitive) false + else true // Null conforms to all classes (except Nothing) and arrays. + } else if (isNothingType) { + true + } else other match { + case otherClassType: ClassBType => classType.isSubtypeOf(otherClassType) + // case ArrayBType(_) => this.isNullType // documentation only, because `if (isNullType)` above covers this case + case _ => + // isNothingType || // documentation only, because `if (isNothingType)` above covers this case + false + } + + case UNIT => + other == UNIT + case BOOL | BYTE | SHORT | CHAR => + this == other || other == INT || other == LONG // TODO Actually, BOOL does NOT conform to LONG. Even with adapt(). + case _ => + assert(isPrimitive && other.isPrimitive, s"Expected primitive types $this - $other") + this == other + } + } + + /** + * Compute the upper bound of two types. + * Takes promotions of numeric primitives into account. + */ + final def maxType(other: BType): BType = this match { + case pt: PrimitiveBType => pt.maxValueType(other) + + case _: ArrayBType | _: ClassBType => + if (isNothingType) return other + if (other.isNothingType) return this + if (this == other) return this + + assert(other.isRef, s"Cannot compute maxType: $this, $other") + // Approximate `lub`. The common type of two references is always ObjectReference. + ObjectReference + } + /** * See documentation of [[typedOpcode]]. * The numbers are taken from asm.Type.VOID_TYPE ff., the values are those shifted by << 8. @@ -160,50 +239,82 @@ abstract class BTypes[G <: Global](val __global_dont_use: G) { case FLOAT => asm.Type.FLOAT_TYPE case LONG => asm.Type.LONG_TYPE case DOUBLE => asm.Type.DOUBLE_TYPE - case c @ ClassBType(_, _) => asm.Type.getObjectType(c.internalName) // (*) - case a @ ArrayBType(_) => asm.Type.getObjectType(a.descriptor) - case m @ MethodBType(_, _) => asm.Type.getMethodType(m.descriptor) + case ClassBType(internalName) => asm.Type.getObjectType(internalName) // see (*) above + case a: ArrayBType => asm.Type.getObjectType(a.descriptor) + case m: MethodBType => asm.Type.getMethodType(m.descriptor) } - def asRefBType : RefBType = this.asInstanceOf[RefBType] - def asArrayBType: ArrayBType = this.asInstanceOf[ArrayBType] - def asClassBType: ClassBType = this.asInstanceOf[ClassBType] + def asRefBType : RefBType = this.asInstanceOf[RefBType] + def asArrayBType : ArrayBType = this.asInstanceOf[ArrayBType] + def asClassBType : ClassBType = this.asInstanceOf[ClassBType] + def asPrimitiveBType : PrimitiveBType = this.asInstanceOf[PrimitiveBType] } - object BType { + sealed trait PrimitiveBType extends BType { + /** - * @param chars The character array containing the descriptor - * @param start The position where the descriptor starts - * @return The BType and the index of the first character after the consumed descriptor + * The upper bound of two primitive types. The `other` type has to be either a primitive + * type or Nothing. + * + * The maxValueType of (Char, Byte) and of (Char, Short) is Int, to encompass the negative + * values of Byte and Short. See ticket #2087. */ - private[BTypes] def fromNonMethodDescriptor(chars: Array[Char], start: Int): (BType, Int) = { - chars(start) match { - case 'L' => - var i = start - while (chars(i) != ';') { i += 1 } - // Example: chars = "IILpkg/Cls;I" - // ^ ^ - // start=2 i=10 - // `start + 1` to exclude the 'L', `i - start - 1` excludes the ';' - (new ClassBType(new String(chars, start + 1, i - start - 1)), i + 1) - case '[' => - val (res, next) = fromNonMethodDescriptor(chars, start + 1) - (ArrayBType(res), next) - case 'V' => (UNIT, start + 1) - case 'Z' => (BOOL, start + 1) - case 'C' => (CHAR, start + 1) - case 'B' => (BYTE, start + 1) - case 'S' => (SHORT, start + 1) - case 'I' => (INT, start + 1) - case 'F' => (FLOAT, start + 1) - case 'J' => (LONG, start + 1) - case 'D' => (DOUBLE, start + 1) + final def maxValueType(other: BType): BType = { + + def uncomparable: Nothing = throw new AssertionError(s"Cannot compute maxValueType: $this, $other") + + if (!other.isPrimitive && !other.isNothingType) uncomparable + + if (other.isNothingType) return this + if (this == other) return this + + this match { + case BYTE => + if (other == CHAR) INT + else if (other.isNumericType) other + else uncomparable + + case SHORT => + other match { + case BYTE => SHORT + case CHAR => INT + case INT | LONG | FLOAT | DOUBLE => other + case _ => uncomparable + } + + case CHAR => + other match { + case BYTE | SHORT => INT + case INT | LONG | FLOAT | DOUBLE => other + case _ => uncomparable + } + + case INT => + other match { + case BYTE | SHORT | CHAR => INT + case LONG | FLOAT | DOUBLE => other + case _ => uncomparable + } + + case LONG => + if (other.isIntegralType) LONG + else if (other.isRealType) DOUBLE + else uncomparable + + case FLOAT => + if (other == DOUBLE) DOUBLE + else if (other.isNumericType) FLOAT + else uncomparable + + case DOUBLE => + if (other.isNumericType) DOUBLE + else uncomparable + + case UNIT | BOOL => uncomparable } } } - sealed trait PrimitiveBType extends BType - case object UNIT extends PrimitiveBType case object BOOL extends PrimitiveBType case object CHAR extends PrimitiveBType @@ -227,57 +338,408 @@ abstract class BTypes[G <: Global](val __global_dont_use: G) { * This can be verified for example using javap or ASMifier. */ def classOrArrayType: String = this match { - case c: ClassBType => c.internalName - case a: ArrayBType => a.descriptor + case ClassBType(internalName) => internalName + case a: ArrayBType => a.descriptor } } /** - * Class or Interface type. + * InnerClass and EnclosingMethod attributes (EnclosingMethod is displayed as OUTERCLASS in asm). + * + * In this summary, "class" means "class or interface". + * + * JLS: http://docs.oracle.com/javase/specs/jls/se8/html/index.html + * JVMS: http://docs.oracle.com/javase/specs/jvms/se8/html/index.html + * + * Terminology + * ----------- + * + * - Nested class (JLS 8): class whose declaration occurs within the body of another class + * + * - Top-level class (JLS 8): non-nested class + * + * - Inner class (JLS 8.1.3): nested class that is not (explicitly or implicitly) static + * + * - Member class (JLS 8.5): class directly enclosed in the body of a class (and not, for + * example, defined in a method). Member classes cannot be anonymous. May be static. + * + * - Local class (JLS 14.3): nested, non-anonymous class that is not a member of a class + * - cannot be static (therefore they are "inner" classes) + * - can be defined in a method, a constructor or in an initializer block + * + * - Initializer block (JLS 8.6 / 8.7): block of statements in a java class + * - static initializer: executed before constructor body + * - instance initializer: exectued when class is initialized (instance creation, static + * field access, ...) + * + * - A static nested class can be defined as + * - a static member class (explicitly static), or + * - a member class of an interface (implicitly static) + * - local classes are never static, even if they are defined in a static method. + * + * Note: it is NOT the case that all inner classes (non-static) have an outer pointer. Example: + * class C { static void foo { class D {} } } + * The class D is an inner class (non-static), but javac does not add an outer pointer to it. + * + * InnerClass + * ---------- + * + * The JVMS 4.7.6 requires an entry for every class mentioned in a CONSTANT_Class_info in the + * constant pool (CP) that is not a member of a package (JLS 7.1). + * + * The JLS 13.1, points 9. / 10. requires: a class must reference (in the CP) + * - its immediately enclosing class + * - all of its member classes + * - all local and anonymous classes that are referenced (or declared) elsewhere (method, + * constructor, initializer block, field initializer) + * + * In a comment, the 4.7.6 spec says: this implies an entry in the InnerClass attribute for + * - All enclosing classes (except the outermost, which is top-level) + * - My comment: not sure how this is implied, below (*) a Java counter-example. + * In any case, the Java compiler seems to add all enclosing classes, even if they are not + * otherwise mentioned in the CP. So we should do the same. + * - All nested classes (including anonymous and local, but not transitively) + * + * Fields in the InnerClass entries: + * - inner class: the (nested) class C we are talking about + * - outer class: the class of which C is a member. Has to be null for non-members, i.e. for + * local and anonymous classes. NOTE: this co-incides with the presence of an + * EnclosingMethod attribute (see below) + * - inner name: A string with the simple name of the inner class. Null for anonymous classes. + * - flags: access property flags, details in JVMS, table in 4.7.6. Static flag: see + * discussion below. + * + * + * Note 1: when a nested class is present in the InnerClass attribute, all of its enclosing + * classes have to be present as well (by the rules above). Example: + * + * class Outer { class I1 { class I2 { } } } + * class User { Outer.I1.I2 foo() { } } + * + * The return type "Outer.I1.I2" puts "Outer$I1$I2" in the CP, therefore the class is added to the + * InnerClass attribute. For this entry, the "outer class" field will be "Outer$I1". This in turn + * adds "Outer$I1" to the CP, which requires adding that class to the InnerClass attribute. + * (For local / anonymous classes this would not be the case, since the "outer class" attribute + * would be empty. However, no class (other than the enclosing class) can refer to them, as they + * have no name.) + * + * In the current implementation of the Scala compiler, when adding a class to the InnerClass + * attribute, all of its enclosing classes will be added as well. Javac seems to do the same, + * see (*). + * + * + * Note 2: If a class name is mentioned only in a CONSTANT_Utf8_info, but not in a + * CONSTANT_Class_info, the JVMS does not require an entry in the InnerClass attribute. However, + * the Java compiler seems to add such classes anyway. For example, when using an annotation, the + * annotation class is stored as a CONSTANT_Utf8_info in the CP: + * + * @O.Ann void foo() { } + * + * adds "const #13 = Asciz LO$Ann;;" in the constant pool. The "RuntimeInvisibleAnnotations" + * attribute refers to that constant pool entry. Even though there is no other reference to + * `O.Ann`, the java compiler adds an entry for that class to the InnerClass attribute (which + * entails adding a CONSTANT_Class_info for the class). + * + * + * + * EnclosingMethod + * --------------- + * + * JVMS 4.7.7: the attribute must be present "if and only if it represents a local class + * or an anonymous class" (i.e. not for member classes). + * + * The attribute is mis-named, it should be called "EnclosingClass". It has to be defined for all + * local and anonymous classes, no matter if there is an enclosing method or not. Accordingly, the + * "class" field (see below) must be always defined, while the "method" field may be null. + * + * NOTE: When a EnclosingMethod attribute is requried (local and anonymous classes), the "outer" + * field in the InnerClass table must be null. + * + * Fields: + * - class: the enclosing class + * - method: the enclosing method (or constructor). Null if the class is not enclosed by a + * method, i.e. for + * - local or anonymous classes defined in (static or non-static) initializer blocks + * - anonymous classes defined in initializer blocks or field initializers + * + * Note: the field is required for anonymous classes defined within local variable + * initializers (within a method), Java example below (**). + * + * For local and anonymous classes in initializer blocks or field initializers, and + * class-level anonymous classes, the scala compiler sets the "method" field to null. + * + * + * (*) + * public class Test { + * void foo() { + * class Foo1 { + * // constructor statement block + * { + * class Foo2 { + * class Foo3 { } + * } + * } + * } + * } + * } + * + * The class file Test$1Foo1$1Foo2$Foo3 has no reference to the class Test$1Foo1, however it + * still contains an InnerClass attribute for Test$1Foo1. + * Maybe this is just because the Java compiler follows the JVMS comment ("InnerClasses + * information for each enclosing class"). + * + * + * (**) + * void foo() { + * // anonymous class defined in local variable initializer expression. + * Runnable x = true ? (new Runnable() { + * public void run() { return; } + * }) : null; + * } + * + * The EnclosingMethod attribute of the anonymous class mentions "foo" in the "method" field. + * + * + * Java Compatibility + * ------------------ + * + * In the InnerClass entry for classes in top-level modules, the "outer class" is emitted as the + * mirror class (or the existing companion class), i.e. C1 is nested in T (not T$). + * For classes nested in a nested object, the "outer class" is the module class: C2 is nested in T$N$ + * object T { + * class C1 + * object N { class C2 } + * } + * + * Reason: java compat. It's a "best effort" "solution". If you want to use "C1" from Java, you + * can write "T.C1", and the Java compiler will translate that to the classfile T$C1. + * + * If we would emit the "outer class" of C1 as "T$", then in Java you'd need to write "T$.C1" + * because the java compiler looks at the InnerClass attribute to find if an inner class exists. + * However, the Java compiler would then translate the '.' to '$' and you'd get the class name + * "T$$C1". This class file obviously does not exist. + * + * Directly using the encoded class name "T$C1" in Java does not work: since the classfile + * describes a nested class, the Java compiler hides it from the classpath and will report + * "cannot find symbol T$C1". This means that the class T.N.C2 cannot be referenced from a + * Java source file in any way. + * + * + * STATIC flag + * ----------- + * + * Java: static member classes have the static flag in the InnerClass attribute, for example B in + * class A { static class B { } } + * + * The spec is not very clear about when the static flag should be emitted. It says: "Marked or + * implicitly static in source." + * + * The presence of the static flag does NOT coincide with the absence of an "outer" field in the + * class. The java compiler never puts the static flag for local classes, even if they don't have + * an outer pointer: + * + * class A { + * void f() { class B {} } + * static void g() { calss C {} } + * } * - * Classes are represented using their name as a slice of the `chrs` array. This representation is - * efficient because the JVM class name is initially created using `classSymbol.javaBinaryName`. - * This already adds the necessary string to the `chrs` array, so it makes sense to reuse the same - * name table in the backend. + * B has an outer pointer, C doesn't. Both B and C are NOT marked static in the InnerClass table. * - * Not a case class because that would expose the (Int, Int) constructor (didn't find a way to - * make it private, also the factory in the companion). + * It seems sane to follow the same principle in the Scala compiler. So: + * + * package p + * object O1 { + * class C1 // static inner class + * object O2 { // static inner module + * def f = { + * class C2 { // non-static inner class, even though there's no outer pointer + * class C3 // non-static, has an outer pointer + * } + * } + * } + * } + * + * Mirror Classes + * -------------- + * + * TODO: innerclass attributes on mirror class, bean info class */ - class ClassBType private(val offset: Int, val length: Int) extends RefBType { - /** - * Construct a ClassBType for a given (intenred) class name. - * - * @param n The class name as a slice of the `chrs` array, without the surrounding 'L' and ';'. - * Note that `classSymbol.javaBinaryName` returns exactly such a name. - */ - def this(n: BTypeName) = this(n.start, n.length) + /** + * A ClassBType represents a class or interface type. The necessary information to build a + * ClassBType is extracted from compiler symbols and types, see BTypesFromSymbols. + * + * The `offset` and `length` fields are used to represent the internal name of the class. They + * are indices into some character array. The internal name can be obtained through the method + * `internalNameString`, which is abstract in this component. Name creation is assumed to be + * hash-consed, so if two ClassBTypes have the same internal name, they NEED to have the same + * `offset` and `length`. + * + * The actual implementation in subclass BTypesFromSymbols uses the global `chrs` array from the + * name table. This representation is efficient because the JVM class name is obtained through + * `classSymbol.javaBinaryName`. This already adds the necessary string to the `chrs` array, + * so it makes sense to reuse the same name table in the backend. + * + * ClassBType is not a case class because we want a custom equals method, and because the + * extractor extracts the internalName, which is what you typically need. + */ + final class ClassBType(val offset: Int, val length: Int) extends RefBType { /** - * Construct a ClassBType for a given java class name. + * Write-once variable allows initializing a cyclic graph of infos. This is required for + * nested classes. Example: for the definition `class A { class B }` we have * - * @param s A class name of the form "java/lang/String", without the surrounding 'L' and ';'. + * B.info.nestedInfo.outerClass == A + * A.info.memberClasses contains B */ - def this(s: String) = this({ - assert(!(s.head == 'L' && s.last == ';'), s"Descriptor instead of internal name: $s") - createNewName(s) - }) + private var _info: ClassInfo = null + + def info: ClassInfo = { + assert(_info != null, s"ClassBType.info not yet assigned: $this") + _info + } + + def info_=(i: ClassInfo): Unit = { + assert(_info == null, s"Cannot set ClassBType.info multiple times: $this") + _info = i + checkInfoConsistency() + } + + classBTypeFromInternalNameMap(internalName) = this + + private def checkInfoConsistency(): Unit = { + // we assert some properties. however, some of the linked ClassBType (members, superClass, + // interfaces) may not yet have an `_info` (initialization of cyclic structures). so we do a + // best-effort verification. + def ifInit(c: ClassBType)(p: ClassBType => Boolean): Boolean = c._info == null || p(c) + + def isJLO(t: ClassBType) = t.internalName == "java/lang/Object" + + assert(!ClassBType.isInternalPhantomType(internalName), s"Cannot create ClassBType for phantom type $this") + + assert( + if (info.superClass.isEmpty) { isJLO(this) || (isCompilingPrimitive && ClassBType.hasNoSuper(internalName)) } + else if (isInterface) isJLO(info.superClass.get) + else !isJLO(this) && ifInit(info.superClass.get)(!_.isInterface), + s"Invalid superClass in $this: ${info.superClass}" + ) + assert( + info.interfaces.forall(c => ifInit(c)(_.isInterface)), + s"Invalid interfaces in $this: ${info.interfaces}" + ) + + assert(info.memberClasses.forall(c => ifInit(c)(_.isNestedClass)), info.memberClasses) + } /** * The internal name of a class is the string returned by java.lang.Class.getName, with all '.' * replaced by '/'. For example "java/lang/String". */ - def internalName: String = new String(chrs, offset, length) + def internalName: String = internalNameString(offset, length) /** * @return The class name without the package prefix */ def simpleName: String = internalName.split("/").last + def isInterface = (info.flags & asm.Opcodes.ACC_INTERFACE) != 0 + + def superClassesTransitive: List[ClassBType] = info.superClass match { + case None => Nil + case Some(sc) => sc :: sc.superClassesTransitive + } + + def isNestedClass = info.nestedInfo.isDefined + + def enclosingNestedClassesChain: List[ClassBType] = + if (isNestedClass) this :: info.nestedInfo.get.enclosingClass.enclosingNestedClassesChain + else Nil + + def innerClassAttributeEntry: Option[InnerClassEntry] = info.nestedInfo map { + case NestedInfo(_, outerName, innerName, isStaticNestedClass) => + InnerClassEntry( + internalName, + outerName.orNull, + innerName.orNull, + GenBCode.mkFlags( + info.flags, + if (isStaticNestedClass) asm.Opcodes.ACC_STATIC else 0 + ) & ClassBType.INNER_CLASSES_FLAGS + ) + } + + def isSubtypeOf(other: ClassBType): Boolean = { + if (this == other) return true + + if (isInterface) { + if (other == ObjectReference) return true // interfaces conform to Object + if (!other.isInterface) return false // this is an interface, the other is some class other than object. interfaces cannot extend classes, so the result is false. + // else: this and other are both interfaces. continue to (*) + } else { + val sc = info.superClass + if (sc.isDefined && sc.get.isSubtypeOf(other)) return true // the superclass of this class conforms to other + if (!other.isInterface) return false // this and other are both classes, and the superclass of this does not conform + // else: this is a class, the other is an interface. continue to (*) + } + + // (*) check if some interface of this class conforms to other. + info.interfaces.exists(_.isSubtypeOf(other)) + } + + /** + * Finding the least upper bound in agreement with the bytecode verifier + * Background: + * http://gallium.inria.fr/~xleroy/publi/bytecode-verification-JAR.pdf + * http://comments.gmane.org/gmane.comp.java.vm.languages/2293 + * https://issues.scala-lang.org/browse/SI-3872 + */ + def jvmWiseLUB(other: ClassBType): ClassBType = { + def isNotNullOrNothing(c: ClassBType) = !c.isNullType && !c.isNothingType + assert(isNotNullOrNothing(this) && isNotNullOrNothing(other), s"jvmWiseLub for null or nothing: $this - $other") + + val res: ClassBType = (this.isInterface, other.isInterface) match { + case (true, true) => + // exercised by test/files/run/t4761.scala + if (other.isSubtypeOf(this)) this + else if (this.isSubtypeOf(other)) other + else ObjectReference + + case (true, false) => + if (other.isSubtypeOf(this)) this else ObjectReference + + case (false, true) => + if (this.isSubtypeOf(other)) other else ObjectReference + + case _ => + // TODO @lry I don't really understand the reasoning here. + // Both this and other are classes. The code takes (transitively) all superclasses and + // finds the first common one. + firstCommonSuffix(this :: this.superClassesTransitive, other :: other.superClassesTransitive) + } + + assert(isNotNullOrNothing(res), s"jvmWiseLub computed: $res") + res + } + + private def firstCommonSuffix(as: List[ClassBType], bs: List[ClassBType]): ClassBType = { + var chainA = as + var chainB = bs + var fcs: ClassBType = null + do { + if (chainB contains chainA.head) fcs = chainA.head + else if (chainA contains chainB.head) fcs = chainB.head + else { + chainA = chainA.tail + chainB = chainB.tail + } + } while (fcs == null) + fcs + } + /** - * Custom equals / hashCode are needed because this is not a case class. + * Custom equals / hashCode: we only compare the name (offset / length) */ override def equals(o: Any): Boolean = (this eq o.asInstanceOf[Object]) || (o match { - case ClassBType(`offset`, `length`) => true + case c: ClassBType => c.offset == this.offset && c.length == this.length case _ => false }) @@ -291,14 +753,92 @@ abstract class BTypes[G <: Global](val __global_dont_use: G) { } object ClassBType { - def apply(n: BTypeName): ClassBType = new ClassBType(n) - def apply(s: String): ClassBType = new ClassBType(s) - - def unapply(c: ClassBType): Option[(Int, Int)] = + /** + * Pattern matching on a ClassBType extracts the `internalName` of the class. + */ + def unapply(c: ClassBType): Option[String] = if (c == null) None - else Some((c.offset, c.length)) + else Some(c.internalName) + + /** + * Valid flags for InnerClass attribute entry. + * See http://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4.7.6 + */ + private val INNER_CLASSES_FLAGS = { + asm.Opcodes.ACC_PUBLIC | asm.Opcodes.ACC_PRIVATE | asm.Opcodes.ACC_PROTECTED | + asm.Opcodes.ACC_STATIC | asm.Opcodes.ACC_FINAL | asm.Opcodes.ACC_INTERFACE | + asm.Opcodes.ACC_ABSTRACT | asm.Opcodes.ACC_SYNTHETIC | asm.Opcodes.ACC_ANNOTATION | + asm.Opcodes.ACC_ENUM + } + + // Primitive classes have no super class. A ClassBType for those is only created when + // they are actually being compiled (e.g., when compiling scala/Boolean.scala). + private val hasNoSuper = Set( + "scala/Unit", + "scala/Boolean", + "scala/Char", + "scala/Byte", + "scala/Short", + "scala/Int", + "scala/Float", + "scala/Long", + "scala/Double" + ) + + private val isInternalPhantomType = Set( + "scala/Null", + "scala/Nothing" + ) } + /** + * The type info for a class. Used for symboltable-independent subtype checks in the backend. + * + * @param superClass The super class, not defined for class java/lang/Object. + * @param interfaces All transitively implemented interfaces, except for those inherited + * through the superclass. + * @param flags The java flags, obtained through `javaFlags`. Used also to derive + * the flags for InnerClass entries. + * @param memberClasses Classes nested in this class. Those need to be added to the + * InnerClass table, see the InnerClass spec summary above. + * @param nestedInfo If this describes a nested class, information for the InnerClass table. + */ + case class ClassInfo(superClass: Option[ClassBType], interfaces: List[ClassBType], flags: Int, + memberClasses: List[ClassBType], nestedInfo: Option[NestedInfo]) + + /** + * Information required to add a class to an InnerClass table. + * The spec summary above explains what information is required for the InnerClass entry. + * + * @param enclosingClass The enclosing class, if it is also nested. When adding a class + * to the InnerClass table, enclosing nested classes are also added. + * @param outerName The outerName field in the InnerClass entry, may be None. + * @param innerName The innerName field, may be None. + * @param isStaticNestedClass True if this is a static nested class (not inner class) (*) + * + * (*) Note that the STATIC flag in ClassInfo.flags, obtained through javaFlags(classSym), is not + * correct for the InnerClass entry, see javaFlags. The static flag in the InnerClass describes + * a source-level propety: if the class is in a static context (does not have an outer pointer). + * This is checked when building the NestedInfo. + */ + case class NestedInfo(enclosingClass: ClassBType, + outerName: Option[String], + innerName: Option[String], + isStaticNestedClass: Boolean) + + /** + * This class holds the data for an entry in the InnerClass table. See the InnerClass summary + * above in this file. + * + * There's some overlap with the class NestedInfo, but it's not exactly the same and cleaner to + * keep separate. + * @param name The internal name of the class. + * @param outerName The internal name of the outer class, may be null. + * @param innerName The simple name of the inner class, may be null. + * @param flags The flags for this class in the InnerClass entry. + */ + case class InnerClassEntry(name: String, outerName: String, innerName: String, flags: Int) + case class ArrayBType(componentType: BType) extends RefBType { def dimension: Int = componentType match { case a: ArrayBType => 1 + a.dimension @@ -311,107 +851,23 @@ abstract class BTypes[G <: Global](val __global_dont_use: G) { } } - case class MethodBType(argumentTypes: List[BType], returnType: BType) extends BType { - private def this(types: (List[BType], BType)) = this(types._1, types._2) - def this(descriptor: String) = this(MethodBType.decomposeMethodDescriptor(descriptor)) - } + case class MethodBType(argumentTypes: List[BType], returnType: BType) extends BType - object MethodBType { - private def decomposeMethodDescriptor(descriptor: String): (List[BType], BType) = { - val chars = descriptor.toCharArray - assert(chars(0) == '(', s"Not a valid method descriptor: $descriptor") - var i = 1 - val argTypes = new ListBuffer[BType] - while (chars(i) != ')') { - val (argType, next) = BType.fromNonMethodDescriptor(chars, i) - argTypes += argType - i = next - } - val (resType, _) = BType.fromNonMethodDescriptor(chars, i + 1) // `i + 1` to skip the ')' - (argTypes.toList, resType) - } - def apply(descriptor: String) = { - val (argTypes, resType) = decomposeMethodDescriptor(descriptor) - new MethodBType(argTypes, resType) - } - } - - val BOXED_UNIT = ClassBType("java/lang/Void") - val BOXED_BOOLEAN = ClassBType("java/lang/Boolean") - val BOXED_BYTE = ClassBType("java/lang/Byte") - val BOXED_SHORT = ClassBType("java/lang/Short") - val BOXED_CHAR = ClassBType("java/lang/Character") - val BOXED_INT = ClassBType("java/lang/Integer") - val BOXED_LONG = ClassBType("java/lang/Long") - val BOXED_FLOAT = ClassBType("java/lang/Float") - val BOXED_DOUBLE = ClassBType("java/lang/Double") - - /* - * RT_NOTHING and RT_NULL exist at run-time only. They are the bytecode-level manifestation (in - * method signatures only) of what shows up as NothingClass resp. NullClass in Scala ASTs. - * - * Therefore, when RT_NOTHING or RT_NULL are to be emitted, a mapping is needed: the internal - * names of NothingClass and NullClass can't be emitted as-is. + /* Some definitions that are required for the implementation of BTypes. They are abstract because + * initializing them requires information from types / symbols, which is not accessible here in + * BTypes. + * + * They are defs (not vals) because they are implemented using vars (see comment on CoreBTypes). */ - val RT_NOTHING = ClassBType("scala/runtime/Nothing$") - val RT_NULL = ClassBType("scala/runtime/Null$") - val CT_NOTHING = ClassBType("scala/Nothing") - val CT_NULL = ClassBType("scala/Null") - - val srBooleanRef = ClassBType("scala/runtime/BooleanRef") - val srByteRef = ClassBType("scala/runtime/ByteRef") - val srCharRef = ClassBType("scala/runtime/CharRef") - val srIntRef = ClassBType("scala/runtime/IntRef") - val srLongRef = ClassBType("scala/runtime/LongRef") - val srFloatRef = ClassBType("scala/runtime/FloatRef") - val srDoubleRef = ClassBType("scala/runtime/DoubleRef") /** - * Map from type kinds to the Java reference types. - * Useful when pushing class literals onto the operand stack (ldc instruction taking a class - * literal). - * @see Predef.classOf - * @see genConstant() - * - * TODO @lry rename to "boxedClassOfPrimitive" or so, check usages + * Just a named pair, used in CoreBTypes.asmBoxTo/asmUnboxTo. */ - val classLiteral = immutable.Map[BType, ClassBType]( - UNIT -> BOXED_UNIT, - BOOL -> BOXED_BOOLEAN, - BYTE -> BOXED_BYTE, - SHORT -> BOXED_SHORT, - CHAR -> BOXED_CHAR, - INT -> BOXED_INT, - LONG -> BOXED_LONG, - FLOAT -> BOXED_FLOAT, - DOUBLE -> BOXED_DOUBLE - ) - - case class MethodNameAndType(name: String, descriptor: String) - - val asmBoxTo: immutable.Map[BType, MethodNameAndType] = { - Map( - BOOL -> MethodNameAndType("boxToBoolean", "(Z)Ljava/lang/Boolean;" ) , - BYTE -> MethodNameAndType("boxToByte", "(B)Ljava/lang/Byte;" ) , - CHAR -> MethodNameAndType("boxToCharacter", "(C)Ljava/lang/Character;") , - SHORT -> MethodNameAndType("boxToShort", "(S)Ljava/lang/Short;" ) , - INT -> MethodNameAndType("boxToInteger", "(I)Ljava/lang/Integer;" ) , - LONG -> MethodNameAndType("boxToLong", "(J)Ljava/lang/Long;" ) , - FLOAT -> MethodNameAndType("boxToFloat", "(F)Ljava/lang/Float;" ) , - DOUBLE -> MethodNameAndType("boxToDouble", "(D)Ljava/lang/Double;" ) - ) - } + final case class MethodNameAndType(name: String, methodType: MethodBType) - val asmUnboxTo: immutable.Map[BType, MethodNameAndType] = { - Map( - BOOL -> MethodNameAndType("unboxToBoolean", "(Ljava/lang/Object;)Z") , - BYTE -> MethodNameAndType("unboxToByte", "(Ljava/lang/Object;)B") , - CHAR -> MethodNameAndType("unboxToChar", "(Ljava/lang/Object;)C") , - SHORT -> MethodNameAndType("unboxToShort", "(Ljava/lang/Object;)S") , - INT -> MethodNameAndType("unboxToInt", "(Ljava/lang/Object;)I") , - LONG -> MethodNameAndType("unboxToLong", "(Ljava/lang/Object;)J") , - FLOAT -> MethodNameAndType("unboxToFloat", "(Ljava/lang/Object;)F") , - DOUBLE -> MethodNameAndType("unboxToDouble", "(Ljava/lang/Object;)D") - ) - } + /** + * True if the current compilation unit is of a primitive class (scala.Boolean et al). + * Used only in assertions. Abstract here because its implementation depends on global. + */ + def isCompilingPrimitive: Boolean } diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BTypesFromSymbols.scala b/src/compiler/scala/tools/nsc/backend/jvm/BTypesFromSymbols.scala new file mode 100644 index 0000000000..0e2f938602 --- /dev/null +++ b/src/compiler/scala/tools/nsc/backend/jvm/BTypesFromSymbols.scala @@ -0,0 +1,396 @@ +/* NSC -- new Scala compiler + * Copyright 2005-2014 LAMP/EPFL + * @author Martin Odersky + */ + +package scala.tools.nsc +package backend.jvm + +import scala.tools.asm + +/** + * This class mainly contains the method classBTypeFromSymbol, which extracts the necessary + * information from a symbol and its type to create the correpsonding ClassBType. It requires + * access to the compiler (global parameter). + * + * The mixin CoreBTypes defines core BTypes that are used in the backend. Building these BTypes + * uses classBTypeFromSymbol, hence requires access to the compiler (global). + * + * BTypesFromSymbols extends BTypes because the implementation of BTypes requires access to some + * of the core btypes. They are declared in BTypes as abstract members. Note that BTypes does + * not have access to the compiler instance. + */ +class BTypesFromSymbols[G <: Global](val global: G) extends BTypes { + import global._ + import definitions._ + + val bCodeICodeCommon: BCodeICodeCommon[global.type] = new BCodeICodeCommon(global) + val bCodeAsmCommon: BCodeAsmCommon[global.type] = new BCodeAsmCommon(global) + import bCodeAsmCommon._ + + // Why the proxy, see documentation of class [[CoreBTypes]]. + val coreBTypes = new CoreBTypesProxy[this.type](this) + import coreBTypes._ + + final def intializeCoreBTypes(): Unit = { + coreBTypes.setBTypes(new CoreBTypes[this.type](this)) + } + + def internalNameString(offset: Int, length: Int) = new String(global.chrs, offset, length) + + protected val classBTypeFromInternalNameMap = { + global.perRunCaches.recordCache(collection.concurrent.TrieMap.empty[String, ClassBType]) + } + + /** + * Cache for the method classBTypeFromSymbol. + */ + private val convertedClasses = perRunCaches.newMap[Symbol, ClassBType]() + + // helpers that need access to global. + // TODO @lry create a separate component, they don't belong to BTypesFromSymbols + + final val strMODULE_INSTANCE_FIELD = nme.MODULE_INSTANCE_FIELD.toString + + private val primitiveCompilationUnits = Set( + "Unit.scala", + "Boolean.scala", + "Char.scala", + "Byte.scala", + "Short.scala", + "Int.scala", + "Float.scala", + "Long.scala", + "Double.scala" + ) + + /** + * True if the current compilation unit is of a primitive class (scala.Boolean et al). + * Used only in assertions. + */ + def isCompilingPrimitive = { + primitiveCompilationUnits(currentUnit.source.file.name) + } + + def isCompilingArray = { + currentUnit.source.file.name == "Array.scala" + } + + // end helpers + + /** + * The ClassBType for a class symbol `sym`. + */ + final def classBTypeFromSymbol(classSym: Symbol): ClassBType = { + assert(classSym != NoSymbol, "Cannot create ClassBType from NoSymbol") + assert(classSym.isClass, s"Cannot create ClassBType from non-class symbol $classSym") + assert( + (!primitiveTypeMap.contains(classSym) || isCompilingPrimitive) && + (classSym != NothingClass && classSym != NullClass), + s"Cannot create ClassBType for special class symbol ${classSym.fullName}") + + convertedClasses.getOrElse(classSym, { + val internalName = classSym.javaBinaryName.toTypeName + // We first create and add the ClassBType to the hash map before computing its info. This + // allows initializing cylic dependencies, see the comment on variable ClassBType._info. + val classBType = new ClassBType(internalName.start, internalName.length) + convertedClasses(classSym) = classBType + setClassInfo(classSym, classBType) + }) + } + + private def setClassInfo(classSym: Symbol, classBType: ClassBType): ClassBType = { + val superClassSym = if (classSym.isImplClass) ObjectClass else classSym.superClass + assert( + if (classSym == ObjectClass) + superClassSym == NoSymbol + else if (classSym.isInterface) + superClassSym == ObjectClass + else + // A ClassBType for a primitive class (scala.Boolean et al) is only created when compiling these classes. + ((superClassSym != NoSymbol) && !superClassSym.isInterface) || (isCompilingPrimitive && primitiveTypeMap.contains(classSym)), + s"Bad superClass for $classSym: $superClassSym" + ) + val superClass = if (superClassSym == NoSymbol) None + else Some(classBTypeFromSymbol(superClassSym)) + + val interfaces = getSuperInterfaces(classSym).map(classBTypeFromSymbol) + + val flags = javaFlags(classSym) + + /* The InnerClass table of a class C must contain all nested classes of C, even if they are only + * declared but not otherwise referenced in C (from the bytecode or a method / field signature). + * We collect them here. + * + * Nested classes that are also referenced in C will be added to the innerClassBufferASM during + * code generation, but those duplicates will be eliminated when emitting the InnerClass + * attribute. + * + * Why doe we need to collect classes into innerClassBufferASM at all? To collect references to + * nested classes, but NOT nested in C, that are used within C. + */ + val nestedClassSymbols = { + // The lambdalift phase lifts all nested classes to the enclosing class, so if we collect + // member classes right after lambdalift, we obtain all nested classes, including local and + // anonymous ones. + val nestedClasses = exitingPhase(currentRun.lambdaliftPhase)(memberClassesOf(classSym)) + + // If this is a top-level class, and it has a companion object, the member classes of the + // companion are added as members of the class. For example: + // class C { } + // object C { + // class D + // def f = { class E } + // } + // The class D is added as a member of class C. The reason is that the InnerClass attribute + // for D will containt class "C" and NOT the module class "C$" as the outer class of D. + // This is done by buildNestedInfo, the reason is Java compatibility, see comment in BTypes. + // For consistency, the InnerClass entry for D needs to be present in C - to Java it looks + // like D is a member of C, not C$. + val linkedClass = exitingPickler(classSym.linkedClassOfClass) // linkedCoC does not work properly in late phases + val companionModuleMembers = { + // phase travel to exitingPickler: this makes sure that memberClassesOf only sees member classes, + // not local classes of the companion module (E in the exmaple) that were lifted by lambdalift. + if (isTopLevelModuleClass(linkedClass)) exitingPickler(memberClassesOf(linkedClass)) + else Nil + } + + nestedClasses ++ companionModuleMembers + } + + /** + * For nested java classes, the scala compiler creates both a class and a module (and therefore + * a module class) symbol. For example, in `class A { class B {} }`, the nestedClassSymbols + * for A contain both the class B and the module class B. + * Here we get rid of the module class B, making sure that the class B is present. + */ + val nestedClassSymbolsNoJavaModuleClasses = nestedClassSymbols.filter(s => { + if (s.isJavaDefined && s.isModuleClass) { + // We could also search in nestedClassSymbols for s.linkedClassOfClass, but sometimes that + // returns NoSymbol, so it doesn't work. + val nb = nestedClassSymbols.count(mc => mc.name == s.name && mc.owner == s.owner) + assert(nb == 2, s"Java member module without member class: $s - $nestedClassSymbols") + false + } else true + }) + + val memberClasses = nestedClassSymbolsNoJavaModuleClasses.map(classBTypeFromSymbol) + + val nestedInfo = buildNestedInfo(classSym) + + classBType.info = ClassInfo(superClass, interfaces, flags, memberClasses, nestedInfo) + classBType + } + + /** + * All interfaces implemented by a class, except for those inherited through the superclass. + * + * TODO @lry share code with GenASM + */ + private def getSuperInterfaces(classSym: Symbol): List[Symbol] = { + + // Additional interface parents based on annotations and other cues + def newParentForAnnotation(ann: AnnotationInfo): Symbol = ann.symbol match { + case RemoteAttr => RemoteInterfaceClass + case _ => NoSymbol + } + + /** + * Drop redundant interfaces (which are implemented by some other parent) from the immediate + * parents. In other words, no two interfaces in the result are related by subtyping. + */ + def dropRedundantInterfaces(lstIfaces: List[Symbol]): List[Symbol] = { + var rest = lstIfaces + var leaves = List.empty[Symbol] + while (!rest.isEmpty) { + val candidate = rest.head + val nonLeaf = leaves exists { lsym => lsym isSubClass candidate } + if (!nonLeaf) { + leaves = candidate :: (leaves filterNot { lsym => candidate isSubClass lsym }) + } + rest = rest.tail + } + + leaves + } + + val superInterfaces0: List[Symbol] = classSym.mixinClasses + val superInterfaces = existingSymbols(superInterfaces0 ++ classSym.annotations.map(newParentForAnnotation)).distinct + + assert(!superInterfaces.contains(NoSymbol), s"found NoSymbol among: ${superInterfaces.mkString(", ")}") + assert(superInterfaces.forall(s => s.isInterface || s.isTrait), s"found non-interface among: ${superInterfaces.mkString(", ")}") + + dropRedundantInterfaces(superInterfaces) + } + + private def buildNestedInfo(innerClassSym: Symbol): Option[NestedInfo] = { + assert(innerClassSym.isClass, s"Cannot build NestedInfo for non-class symbol $innerClassSym") + + val isNested = !innerClassSym.rawowner.isPackageClass + if (!isNested) None + else { + // See comment in BTypes, when is a class marked static in the InnerClass table. + val isStaticNestedClass = isOriginallyStaticOwner(innerClassSym.originalOwner) + + // After lambdalift (which is where we are), the rawowoner field contains the enclosing class. + val enclosingClassSym = { + if (innerClassSym.isJavaDefined && innerClassSym.rawowner.isModuleClass) { + // Example java source: class C { static class D { } } + // The Scala compiler creates a class and a module symbol for C. Because D is a static + // nested class, the symbol for D is nested in the module class C (not in the class C). + // For the InnerClass attribute, we use the class symbol C, which represents the situation + // in the source code. + + // Cannot use innerClassSym.isStatic: this method looks at the owner, which is a package + // at this pahse (after lambdalift, flatten). + assert(isOriginallyStaticOwner(innerClassSym.originalOwner), innerClassSym.originalOwner) + + // phase travel for linkedCoC - does not always work in late phases + exitingPickler(innerClassSym.rawowner.linkedClassOfClass) + } + else innerClassSym.rawowner + } + val enclosingClass: ClassBType = classBTypeFromSymbol(enclosingClassSym) + + val outerName: Option[String] = { + if (isAnonymousOrLocalClass(innerClassSym)) { + None + } else { + val outerName = innerClassSym.rawowner.javaBinaryName + // Java compatibility. See the big comment in BTypes that summarizes the InnerClass spec. + val outerNameModule = if (isTopLevelModuleClass(innerClassSym.rawowner)) outerName.dropModule + else outerName + Some(outerNameModule.toString) + } + } + + val innerName: Option[String] = { + if (innerClassSym.isAnonymousClass || innerClassSym.isAnonymousFunction) None + else Some(innerClassSym.rawname + innerClassSym.moduleSuffix) // moduleSuffix for module classes + } + + Some(NestedInfo(enclosingClass, outerName, innerName, isStaticNestedClass)) + } + } + + /** + * True for module classes of package level objects. The backend will generate a mirror class for + * such objects. + */ + final def isTopLevelModuleClass(sym: Symbol): Boolean = exitingPickler { + // phase travel to pickler required for isNestedClass (looks at owner) + val r = sym.isModuleClass && !sym.isNestedClass + // The mixin phase adds the `lateMODULE` flag to trait implementation classes. Since the flag + // is late, it should not be visible here inside the time travel. We check this. + if (r) assert(!sym.isImplClass, s"isModuleClass should be false for impl class $sym") + r + } + + /** + * True for module classes of modules that are top-level or owned only by objects. Module classes + * for such objects will get a MODULE$ flag and a corresponding static initializer. + */ + final def isStaticModuleClass(sym: Symbol): Boolean = { + /* (1) Phase travel to to pickler is required to exclude implementation classes; they have the + * lateMODULEs after mixin, so isModuleClass would be true. + * (2) isStaticModuleClass is a source-level property. See comment on isOriginallyStaticOwner. + */ + exitingPickler { // (1) + sym.isModuleClass && + isOriginallyStaticOwner(sym.originalOwner) // (2) + } + } + + // legacy, to be removed when the @remote annotation gets removed + final def isRemote(s: Symbol) = (s hasAnnotation definitions.RemoteAttr) + final def hasPublicBitSet(flags: Int) = ((flags & asm.Opcodes.ACC_PUBLIC) != 0) + + /** + * Return the Java modifiers for the given symbol. + * Java modifiers for classes: + * - public, abstract, final, strictfp (not used) + * for interfaces: + * - the same as for classes, without 'final' + * for fields: + * - public, private (*) + * - static, final + * for methods: + * - the same as for fields, plus: + * - abstract, synchronized (not used), strictfp (not used), native (not used) + * for all: + * - deprecated + * + * (*) protected cannot be used, since inner classes 'see' protected members, + * and they would fail verification after lifted. + */ + final def javaFlags(sym: Symbol): Int = { + // constructors of module classes should be private. introduced in b06edbc, probably to prevent + // creating module instances from java. for nested modules, the constructor needs to be public + // since they are created by the outer class and stored in a field. a java client can create + // new instances via outerClassInstance.new InnerModuleClass$(). + // TODO: do this early, mark the symbol private. + val privateFlag = + sym.isPrivate || (sym.isPrimaryConstructor && isTopLevelModuleClass(sym.owner)) + + // Symbols marked in source as `final` have the FINAL flag. (In the past, the flag was also + // added to modules and module classes, not anymore since 296b706). + // Note that the presence of the `FINAL` flag on a symbol does not correspond 1:1 to emitting + // ACC_FINAL in bytecode. + // + // Top-level modules are marked ACC_FINAL in bytecode (even without the FINAL flag). Nested + // objects don't get the flag to allow overriding (under -Yoverride-objects, SI-5676). + // + // For fields, only eager val fields can receive ACC_FINAL. vars or lazy vals can't: + // Source: http://docs.oracle.com/javase/specs/jls/se7/html/jls-17.html#jls-17.5.3 + // "Another problem is that the specification allows aggressive + // optimization of final fields. Within a thread, it is permissible to + // reorder reads of a final field with those modifications of a final + // field that do not take place in the constructor." + // + // A var or lazy val which is marked final still has meaning to the + // scala compiler. The word final is heavily overloaded unfortunately; + // for us it means "not overridable". At present you can't override + // vars regardless; this may change. + // + // The logic does not check .isFinal (which checks flags for the FINAL flag, + // and includes symbols marked lateFINAL) instead inspecting rawflags so + // we can exclude lateFINAL. Such symbols are eligible for inlining, but to + // avoid breaking proxy software which depends on subclassing, we do not + // emit ACC_FINAL. + + val finalFlag = ( + (((sym.rawflags & symtab.Flags.FINAL) != 0) || isTopLevelModuleClass(sym)) + && !sym.enclClass.isInterface + && !sym.isClassConstructor + && !sym.isMutable // lazy vals and vars both + ) + + // Primitives are "abstract final" to prohibit instantiation + // without having to provide any implementations, but that is an + // illegal combination of modifiers at the bytecode level so + // suppress final if abstract if present. + import asm.Opcodes._ + GenBCode.mkFlags( + if (privateFlag) ACC_PRIVATE else ACC_PUBLIC, + if (sym.isDeferred || sym.hasAbstractFlag) ACC_ABSTRACT else 0, + if (sym.isInterface) ACC_INTERFACE else 0, + if (finalFlag && !sym.hasAbstractFlag) ACC_FINAL else 0, + if (sym.isStaticMember) ACC_STATIC else 0, + if (sym.isBridge) ACC_BRIDGE | ACC_SYNTHETIC else 0, + if (sym.isArtifact) ACC_SYNTHETIC else 0, + if (sym.isClass && !sym.isInterface) ACC_SUPER else 0, + if (sym.hasEnumFlag) ACC_ENUM else 0, + if (sym.isVarargsMethod) ACC_VARARGS else 0, + if (sym.hasFlag(symtab.Flags.SYNCHRONIZED)) ACC_SYNCHRONIZED else 0, + if (sym.isDeprecated) asm.Opcodes.ACC_DEPRECATED else 0 + ) + } + + def javaFieldFlags(sym: Symbol) = { + javaFlags(sym) | GenBCode.mkFlags( + if (sym hasAnnotation TransientAttr) asm.Opcodes.ACC_TRANSIENT else 0, + if (sym hasAnnotation VolatileAttr) asm.Opcodes.ACC_VOLATILE else 0, + if (sym.isMutable) 0 else asm.Opcodes.ACC_FINAL + ) + } +} diff --git a/src/compiler/scala/tools/nsc/backend/jvm/CoreBTypes.scala b/src/compiler/scala/tools/nsc/backend/jvm/CoreBTypes.scala new file mode 100644 index 0000000000..fac3c93be2 --- /dev/null +++ b/src/compiler/scala/tools/nsc/backend/jvm/CoreBTypes.scala @@ -0,0 +1,293 @@ +package scala.tools.nsc +package backend.jvm + +import scala.annotation.switch + +/** + * Core BTypes and some other definitions. The initialization of these definitions requies access + * to symbols / types (global). + * + * The symbols used to initialize the ClassBTypes may change from one compiler run to the next. To + * make sure the definitions are consistent with the symbols in the current run, the + * `intializeCoreBTypes` method in BTypesFromSymbols creates a new instance of CoreBTypes in each + * compiler run. + * + * The class BTypesFromSymbols does not directly reference CoreBTypes, but CoreBTypesProxy. The + * reason is that having a `var bTypes: CoreBTypes` would not allow `import bTypes._`. Instead, the + * proxy class holds a `CoreBTypes` in a variable field and forwards to this instance. + * + * The definitions in `CoreBTypes` need to be lazy vals to break an initialization cycle. When + * creating a new instance to assign to the proxy, the `classBTypeFromSymbol` invoked in the + * constructor will actucally go through the proxy. The lazy vals make sure the instance is assigned + * in the proxy before the fields are initialized. + * + * Note: if we did not re-create the core BTypes on each compiler run, BType.classBTypeFromInternalNameMap + * could not be a perRunCache anymore: the classes defeined here need to be in that map, they are + * added when the ClassBTypes are created. The per run cache removes them, so they would be missing + * in the second run. + */ +class CoreBTypes[BTFS <: BTypesFromSymbols[_ <: Global]](val bTypes: BTFS) { + import bTypes._ + import global._ + import rootMirror.{requiredClass, getClassIfDefined} + import definitions._ + + /** + * Maps primitive types to their corresponding PrimitiveBType. The map is defined lexically above + * the first use of `classBTypeFromSymbol` because that method looks at the map. + */ + lazy val primitiveTypeMap: Map[Symbol, PrimitiveBType] = Map( + UnitClass -> UNIT, + BooleanClass -> BOOL, + CharClass -> CHAR, + ByteClass -> BYTE, + ShortClass -> SHORT, + IntClass -> INT, + LongClass -> LONG, + FloatClass -> FLOAT, + DoubleClass -> DOUBLE + ) + + lazy val BOXED_UNIT : ClassBType = classBTypeFromSymbol(requiredClass[java.lang.Void]) + lazy val BOXED_BOOLEAN : ClassBType = classBTypeFromSymbol(BoxedBooleanClass) + lazy val BOXED_BYTE : ClassBType = classBTypeFromSymbol(BoxedByteClass) + lazy val BOXED_SHORT : ClassBType = classBTypeFromSymbol(BoxedShortClass) + lazy val BOXED_CHAR : ClassBType = classBTypeFromSymbol(BoxedCharacterClass) + lazy val BOXED_INT : ClassBType = classBTypeFromSymbol(BoxedIntClass) + lazy val BOXED_LONG : ClassBType = classBTypeFromSymbol(BoxedLongClass) + lazy val BOXED_FLOAT : ClassBType = classBTypeFromSymbol(BoxedFloatClass) + lazy val BOXED_DOUBLE : ClassBType = classBTypeFromSymbol(BoxedDoubleClass) + + /** + * Map from primitive types to their boxed class type. Useful when pushing class literals onto the + * operand stack (ldc instruction taking a class literal), see genConstant. + */ + lazy val boxedClassOfPrimitive: Map[PrimitiveBType, ClassBType] = Map( + UNIT -> BOXED_UNIT, + BOOL -> BOXED_BOOLEAN, + BYTE -> BOXED_BYTE, + SHORT -> BOXED_SHORT, + CHAR -> BOXED_CHAR, + INT -> BOXED_INT, + LONG -> BOXED_LONG, + FLOAT -> BOXED_FLOAT, + DOUBLE -> BOXED_DOUBLE + ) + + lazy val boxedClasses: Set[ClassBType] = boxedClassOfPrimitive.values.toSet + + /** + * Maps the method symbol for a box method to the boxed type of the result. For example, the + * method symbol for `Byte.box()` is mapped to the ClassBType `java/lang/Byte`. + */ + lazy val boxResultType: Map[Symbol, ClassBType] = { + for ((valueClassSym, boxMethodSym) <- currentRun.runDefinitions.boxMethod) + yield boxMethodSym -> boxedClassOfPrimitive(primitiveTypeMap(valueClassSym)) + } + + /** + * Maps the method symbol for an unbox method to the primitive type of the result. + * For example, the method symbol for `Byte.unbox()`) is mapped to the PrimitiveBType BYTE. */ + lazy val unboxResultType: Map[Symbol, PrimitiveBType] = { + for ((valueClassSym, unboxMethodSym) <- currentRun.runDefinitions.unboxMethod) + yield unboxMethodSym -> primitiveTypeMap(valueClassSym) + } + + /* + * RT_NOTHING and RT_NULL exist at run-time only. They are the bytecode-level manifestation (in + * method signatures only) of what shows up as NothingClass resp. NullClass in Scala ASTs. + * + * Therefore, when RT_NOTHING or RT_NULL are to be emitted, a mapping is needed: the internal + * names of NothingClass and NullClass can't be emitted as-is. + * TODO @lry Once there's a 2.11.3 starr, use the commented argument list. The current starr crashes on the type literal `scala.runtime.Nothing$` + */ + lazy val RT_NOTHING : ClassBType = classBTypeFromSymbol(rootMirror.getRequiredClass("scala.runtime.Nothing$")) // (requiredClass[scala.runtime.Nothing$]) + lazy val RT_NULL : ClassBType = classBTypeFromSymbol(rootMirror.getRequiredClass("scala.runtime.Null$")) // (requiredClass[scala.runtime.Null$]) + + lazy val ObjectReference : ClassBType = classBTypeFromSymbol(ObjectClass) + lazy val objArrayReference : ArrayBType = ArrayBType(ObjectReference) + + lazy val StringReference : ClassBType = classBTypeFromSymbol(StringClass) + lazy val StringBuilderReference : ClassBType = classBTypeFromSymbol(StringBuilderClass) + lazy val ThrowableReference : ClassBType = classBTypeFromSymbol(ThrowableClass) + lazy val jlCloneableReference : ClassBType = classBTypeFromSymbol(JavaCloneableClass) // java/lang/Cloneable + lazy val jlNPEReference : ClassBType = classBTypeFromSymbol(NullPointerExceptionClass) // java/lang/NullPointerException + lazy val jioSerializableReference : ClassBType = classBTypeFromSymbol(JavaSerializableClass) // java/io/Serializable + lazy val scalaSerializableReference : ClassBType = classBTypeFromSymbol(SerializableClass) // scala/Serializable + lazy val classCastExceptionReference : ClassBType = classBTypeFromSymbol(ClassCastExceptionClass) // java/lang/ClassCastException + + lazy val srBooleanRef : ClassBType = classBTypeFromSymbol(requiredClass[scala.runtime.BooleanRef]) + lazy val srByteRef : ClassBType = classBTypeFromSymbol(requiredClass[scala.runtime.ByteRef]) + lazy val srCharRef : ClassBType = classBTypeFromSymbol(requiredClass[scala.runtime.CharRef]) + lazy val srIntRef : ClassBType = classBTypeFromSymbol(requiredClass[scala.runtime.IntRef]) + lazy val srLongRef : ClassBType = classBTypeFromSymbol(requiredClass[scala.runtime.LongRef]) + lazy val srFloatRef : ClassBType = classBTypeFromSymbol(requiredClass[scala.runtime.FloatRef]) + lazy val srDoubleRef : ClassBType = classBTypeFromSymbol(requiredClass[scala.runtime.DoubleRef]) + + lazy val hashMethodSym: Symbol = getMember(ScalaRunTimeModule, nme.hash_) + + // TODO @lry avoiding going through through missingHook for every line in the REPL: https://github.com/scala/scala/commit/8d962ed4ddd310cc784121c426a2e3f56a112540 + lazy val AndroidParcelableInterface : Symbol = getClassIfDefined("android.os.Parcelable") + lazy val AndroidCreatorClass : Symbol = getClassIfDefined("android.os.Parcelable$Creator") + + lazy val BeanInfoAttr: Symbol = requiredClass[scala.beans.BeanInfo] + + /* The Object => String overload. */ + lazy val String_valueOf: Symbol = { + getMember(StringModule, nme.valueOf) filter (sym => sym.info.paramTypes match { + case List(pt) => pt.typeSymbol == ObjectClass + case _ => false + }) + } + + // scala.FunctionX and scala.runtim.AbstractFunctionX + lazy val FunctionReference : Vector[ClassBType] = (0 to MaxFunctionArity).map(i => classBTypeFromSymbol(FunctionClass(i)))(collection.breakOut) + lazy val AbstractFunctionReference : Vector[ClassBType] = (0 to MaxFunctionArity).map(i => classBTypeFromSymbol(AbstractFunctionClass(i)))(collection.breakOut) + lazy val AbstractFunctionArityMap : Map[ClassBType, Int] = AbstractFunctionReference.zipWithIndex.toMap + + lazy val PartialFunctionReference : ClassBType = classBTypeFromSymbol(PartialFunctionClass) + lazy val AbstractPartialFunctionReference : ClassBType = classBTypeFromSymbol(AbstractPartialFunctionClass) + + lazy val BoxesRunTime: ClassBType = classBTypeFromSymbol(requiredClass[scala.runtime.BoxesRunTime]) + + /** + * Methods in scala.runtime.BoxesRuntime + */ + lazy val asmBoxTo : Map[BType, MethodNameAndType] = Map( + BOOL -> MethodNameAndType("boxToBoolean", MethodBType(List(BOOL), BOXED_BOOLEAN)), + BYTE -> MethodNameAndType("boxToByte", MethodBType(List(BYTE), BOXED_BYTE)), + CHAR -> MethodNameAndType("boxToCharacter", MethodBType(List(CHAR), BOXED_CHAR)), + SHORT -> MethodNameAndType("boxToShort", MethodBType(List(SHORT), BOXED_SHORT)), + INT -> MethodNameAndType("boxToInteger", MethodBType(List(INT), BOXED_INT)), + LONG -> MethodNameAndType("boxToLong", MethodBType(List(LONG), BOXED_LONG)), + FLOAT -> MethodNameAndType("boxToFloat", MethodBType(List(FLOAT), BOXED_FLOAT)), + DOUBLE -> MethodNameAndType("boxToDouble", MethodBType(List(DOUBLE), BOXED_DOUBLE)) + ) + + lazy val asmUnboxTo: Map[BType, MethodNameAndType] = Map( + BOOL -> MethodNameAndType("unboxToBoolean", MethodBType(List(ObjectReference), BOOL)), + BYTE -> MethodNameAndType("unboxToByte", MethodBType(List(ObjectReference), BYTE)), + CHAR -> MethodNameAndType("unboxToChar", MethodBType(List(ObjectReference), CHAR)), + SHORT -> MethodNameAndType("unboxToShort", MethodBType(List(ObjectReference), SHORT)), + INT -> MethodNameAndType("unboxToInt", MethodBType(List(ObjectReference), INT)), + LONG -> MethodNameAndType("unboxToLong", MethodBType(List(ObjectReference), LONG)), + FLOAT -> MethodNameAndType("unboxToFloat", MethodBType(List(ObjectReference), FLOAT)), + DOUBLE -> MethodNameAndType("unboxToDouble", MethodBType(List(ObjectReference), DOUBLE)) + ) + + lazy val typeOfArrayOp: Map[Int, BType] = { + import scalaPrimitives._ + Map( + (List(ZARRAY_LENGTH, ZARRAY_GET, ZARRAY_SET) map (_ -> BOOL)) ++ + (List(BARRAY_LENGTH, BARRAY_GET, BARRAY_SET) map (_ -> BYTE)) ++ + (List(SARRAY_LENGTH, SARRAY_GET, SARRAY_SET) map (_ -> SHORT)) ++ + (List(CARRAY_LENGTH, CARRAY_GET, CARRAY_SET) map (_ -> CHAR)) ++ + (List(IARRAY_LENGTH, IARRAY_GET, IARRAY_SET) map (_ -> INT)) ++ + (List(LARRAY_LENGTH, LARRAY_GET, LARRAY_SET) map (_ -> LONG)) ++ + (List(FARRAY_LENGTH, FARRAY_GET, FARRAY_SET) map (_ -> FLOAT)) ++ + (List(DARRAY_LENGTH, DARRAY_GET, DARRAY_SET) map (_ -> DOUBLE)) ++ + (List(OARRAY_LENGTH, OARRAY_GET, OARRAY_SET) map (_ -> ObjectReference)) : _* + ) + } +} + +/** + * This trait make some core BTypes availalbe that don't depend on a Global instance. Some core + * BTypes are required to be accessible in the BTypes trait, which does not have access to Global. + * + * BTypes cannot refer to CoreBTypesProxy because some of its members depend on global, for example + * the type Symbol in + * def primitiveTypeMap: Map[Symbol, PrimitiveBType] + */ +trait CoreBTypesProxyGlobalIndependent[BTS <: BTypes] { + val bTypes: BTS + import bTypes._ + + def boxedClasses: Set[ClassBType] + + def RT_NOTHING : ClassBType + def RT_NULL : ClassBType + + def ObjectReference : ClassBType + def jlCloneableReference : ClassBType + def jioSerializableReference : ClassBType +} + +/** + * See comment in class [[CoreBTypes]]. + */ +final class CoreBTypesProxy[BTFS <: BTypesFromSymbols[_ <: Global]](val bTypes: BTFS) extends CoreBTypesProxyGlobalIndependent[BTFS] { + import bTypes._ + import global._ + + private[this] var _coreBTypes: CoreBTypes[bTypes.type] = _ + def setBTypes(coreBTypes: CoreBTypes[BTFS]): Unit = { + _coreBTypes = coreBTypes.asInstanceOf[CoreBTypes[bTypes.type]] + } + + def primitiveTypeMap: Map[Symbol, PrimitiveBType] = _coreBTypes.primitiveTypeMap + + def BOXED_UNIT : ClassBType = _coreBTypes.BOXED_UNIT + def BOXED_BOOLEAN : ClassBType = _coreBTypes.BOXED_BOOLEAN + def BOXED_BYTE : ClassBType = _coreBTypes.BOXED_BYTE + def BOXED_SHORT : ClassBType = _coreBTypes.BOXED_SHORT + def BOXED_CHAR : ClassBType = _coreBTypes.BOXED_CHAR + def BOXED_INT : ClassBType = _coreBTypes.BOXED_INT + def BOXED_LONG : ClassBType = _coreBTypes.BOXED_LONG + def BOXED_FLOAT : ClassBType = _coreBTypes.BOXED_FLOAT + def BOXED_DOUBLE : ClassBType = _coreBTypes.BOXED_DOUBLE + + def boxedClasses: Set[ClassBType] = _coreBTypes.boxedClasses + + def boxedClassOfPrimitive: Map[PrimitiveBType, ClassBType] = _coreBTypes.boxedClassOfPrimitive + + def boxResultType: Map[Symbol, ClassBType] = _coreBTypes.boxResultType + + def unboxResultType: Map[Symbol, PrimitiveBType] = _coreBTypes.unboxResultType + + def RT_NOTHING : ClassBType = _coreBTypes.RT_NOTHING + def RT_NULL : ClassBType = _coreBTypes.RT_NULL + + def ObjectReference : ClassBType = _coreBTypes.ObjectReference + def objArrayReference : ArrayBType = _coreBTypes.objArrayReference + + def StringReference : ClassBType = _coreBTypes.StringReference + def StringBuilderReference : ClassBType = _coreBTypes.StringBuilderReference + def ThrowableReference : ClassBType = _coreBTypes.ThrowableReference + def jlCloneableReference : ClassBType = _coreBTypes.jlCloneableReference + def jlNPEReference : ClassBType = _coreBTypes.jlNPEReference + def jioSerializableReference : ClassBType = _coreBTypes.jioSerializableReference + def scalaSerializableReference : ClassBType = _coreBTypes.scalaSerializableReference + def classCastExceptionReference : ClassBType = _coreBTypes.classCastExceptionReference + + def srBooleanRef : ClassBType = _coreBTypes.srBooleanRef + def srByteRef : ClassBType = _coreBTypes.srByteRef + def srCharRef : ClassBType = _coreBTypes.srCharRef + def srIntRef : ClassBType = _coreBTypes.srIntRef + def srLongRef : ClassBType = _coreBTypes.srLongRef + def srFloatRef : ClassBType = _coreBTypes.srFloatRef + def srDoubleRef : ClassBType = _coreBTypes.srDoubleRef + + def hashMethodSym: Symbol = _coreBTypes.hashMethodSym + + def AndroidParcelableInterface : Symbol = _coreBTypes.AndroidParcelableInterface + def AndroidCreatorClass : Symbol = _coreBTypes.AndroidCreatorClass + + def BeanInfoAttr: Symbol = _coreBTypes.BeanInfoAttr + + def String_valueOf: Symbol = _coreBTypes.String_valueOf + + def FunctionReference : Vector[ClassBType] = _coreBTypes.FunctionReference + def AbstractFunctionReference : Vector[ClassBType] = _coreBTypes.AbstractFunctionReference + def AbstractFunctionArityMap : Map[ClassBType, Int] = _coreBTypes.AbstractFunctionArityMap + + def PartialFunctionReference : ClassBType = _coreBTypes.PartialFunctionReference + def AbstractPartialFunctionReference : ClassBType = _coreBTypes.AbstractPartialFunctionReference + + def BoxesRunTime: ClassBType = _coreBTypes.BoxesRunTime + + def asmBoxTo : Map[BType, MethodNameAndType] = _coreBTypes.asmBoxTo + def asmUnboxTo: Map[BType, MethodNameAndType] = _coreBTypes.asmUnboxTo + + def typeOfArrayOp: Map[Int, BType] = _coreBTypes.typeOfArrayOp +} diff --git a/src/compiler/scala/tools/nsc/backend/jvm/GenASM.scala b/src/compiler/scala/tools/nsc/backend/jvm/GenASM.scala index b0fb3069c1..2593903b9d 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/GenASM.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/GenASM.scala @@ -26,6 +26,9 @@ abstract class GenASM extends SubComponent with BytecodeWriters with GenJVMASM { import icodes.opcodes._ import definitions._ + val bCodeAsmCommon: BCodeAsmCommon[global.type] = new BCodeAsmCommon(global) + import bCodeAsmCommon._ + // Strangely I can't find this in the asm code // 255, but reserving 1 for "this" final val MaximumJvmParameters = 254 @@ -381,6 +384,7 @@ abstract class GenASM extends SubComponent with BytecodeWriters with GenJVMASM { case "jvm-1.5" => asm.Opcodes.V1_5 case "jvm-1.6" => asm.Opcodes.V1_6 case "jvm-1.7" => asm.Opcodes.V1_7 + case "jvm-1.8" => asm.Opcodes.V1_8 } private val majorVersion: Int = (classfileVersion & 0xFF) @@ -620,7 +624,7 @@ abstract class GenASM extends SubComponent with BytecodeWriters with GenJVMASM { * That means non-member classes (anonymous). See Section 4.7.5 in the JVMS. */ def outerName(innerSym: Symbol): String = { - if (innerSym.originalEnclosingMethod != NoSymbol) + if (isAnonymousOrLocalClass(innerSym)) null else { val outerName = javaName(innerSym.rawowner) @@ -635,10 +639,16 @@ abstract class GenASM extends SubComponent with BytecodeWriters with GenJVMASM { else innerSym.rawname + innerSym.moduleSuffix - // add inner classes which might not have been referenced yet - exitingErasure { - for (sym <- List(csym, csym.linkedClassOfClass); m <- sym.info.decls.map(innerClassSymbolFor) if m.isClass) - innerClassBuffer += m + // This collects all inner classes of csym, including local and anonymous: lambdalift makes + // them members of their enclosing class. + innerClassBuffer ++= exitingPhase(currentRun.lambdaliftPhase)(memberClassesOf(csym)) + + // Add members of the companion object (if top-level). why, see comment in BTypes.scala. + val linkedClass = exitingPickler(csym.linkedClassOfClass) // linkedCoC does not work properly in late phases + if (isTopLevelModule(linkedClass)) { + // phase travel to exitingPickler: this makes sure that memberClassesOf only sees member classes, + // not local classes that were lifted by lambdalift. + innerClassBuffer ++= exitingPickler(memberClassesOf(linkedClass)) } val allInners: List[Symbol] = innerClassBuffer.toList filterNot deadCode.elidedClosures @@ -652,7 +662,8 @@ abstract class GenASM extends SubComponent with BytecodeWriters with GenJVMASM { // sort them so inner classes succeed their enclosing class to satisfy the Eclipse Java compiler for (innerSym <- allInners sortBy (_.name.length)) { // TODO why not sortBy (_.name.toString()) ?? val flagsWithFinal: Int = mkFlags( - if (innerSym.rawowner.hasModuleFlag) asm.Opcodes.ACC_STATIC else 0, + // See comment in BTypes, when is a class marked static in the InnerClass table. + if (isOriginallyStaticOwner(innerSym.originalOwner)) asm.Opcodes.ACC_STATIC else 0, javaFlags(innerSym), if(isDeprecated(innerSym)) asm.Opcodes.ACC_DEPRECATED else 0 // ASM pseudo-access flag ) & (INNER_CLASSES_FLAGS | asm.Opcodes.ACC_DEPRECATED) @@ -1218,10 +1229,10 @@ abstract class GenASM extends SubComponent with BytecodeWriters with GenJVMASM { null /* SourceDebugExtension */) } - val enclM = getEnclosingMethodAttribute() - if(enclM != null) { - val EnclMethodEntry(className, methodName, methodType) = enclM - jclass.visitOuterClass(className, methodName, methodType.getDescriptor) + enclosingMethodAttribute(clasz.symbol, javaName, javaType(_).getDescriptor) match { + case Some(EnclosingMethodEntry(className, methodName, methodDescriptor)) => + jclass.visitOuterClass(className, methodName, methodDescriptor) + case _ => () } // typestate: entering mode with valid call sequences: @@ -1282,45 +1293,6 @@ abstract class GenASM extends SubComponent with BytecodeWriters with GenJVMASM { writeIfNotTooBig("" + c.symbol.name, thisName, jclass, c.symbol) } - /** - * @param owner internal name of the enclosing class of the class. - * - * @param name the name of the method that contains the class. - - * @param methodType the method that contains the class. - */ - case class EnclMethodEntry(owner: String, name: String, methodType: asm.Type) - - /** - * @return null if the current class is not internal to a method - * - * Quoting from JVMS 4.7.7 The EnclosingMethod Attribute - * A class must have an EnclosingMethod attribute if and only if it is a local class or an anonymous class. - * A class may have no more than one EnclosingMethod attribute. - * - */ - private def getEnclosingMethodAttribute(): EnclMethodEntry = { // JVMS 4.7.7 - var res: EnclMethodEntry = null - val clazz = clasz.symbol - val sym = clazz.originalEnclosingMethod - if (sym.isMethod) { - debuglog("enclosing method for %s is %s (in %s)".format(clazz, sym, sym.enclClass)) - res = EnclMethodEntry(javaName(sym.enclClass), javaName(sym), javaType(sym)) - } else if (clazz.isAnonymousClass) { - val enclClass = clazz.rawowner - assert(enclClass.isClass, enclClass) - val sym = enclClass.primaryConstructor - if (sym == NoSymbol) { - log("Ran out of room looking for an enclosing method for %s: no constructor here.".format(enclClass)) - } else { - debuglog("enclosing method for %s is %s (in %s)".format(clazz, sym, enclClass)) - res = EnclMethodEntry(javaName(enclClass), javaName(sym), javaType(sym)) - } - } - - res - } - def genField(f: IField) { debuglog("Adding field: " + f.symbol.fullName) diff --git a/src/compiler/scala/tools/nsc/backend/jvm/GenBCode.scala b/src/compiler/scala/tools/nsc/backend/jvm/GenBCode.scala index a401de05e5..0a7c894a69 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/GenBCode.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/GenBCode.scala @@ -46,6 +46,9 @@ import scala.tools.asm abstract class GenBCode extends BCodeSyncAndTry { import global._ + import bTypes._ + import coreBTypes._ + val phaseName = "jvm" override def newPhase(prev: Phase) = new BCodePhase(prev) @@ -130,7 +133,7 @@ abstract class GenBCode extends BCodeSyncAndTry { return } else { - try { visit(item) } + try { withCurrentUnit(item.cunit)(visit(item)) } catch { case ex: Throwable => ex.printStackTrace() @@ -165,7 +168,7 @@ abstract class GenBCode extends BCodeSyncAndTry { // -------------- mirror class, if needed -------------- val mirrorC = - if (isStaticModule(claszSymbol) && isTopLevelModule(claszSymbol)) { + if (isTopLevelModuleClass(claszSymbol)) { if (claszSymbol.companionClass == NoSymbol) { mirrorCodeGen.genMirrorClass(claszSymbol, cunit) } else { @@ -271,8 +274,8 @@ abstract class GenBCode extends BCodeSyncAndTry { override def run() { arrivalPos = 0 // just in case - scalaPrimitives.init - initBCodeTypes() + scalaPrimitives.init() + bTypes.intializeCoreBTypes() // initBytecodeWriter invokes fullName, thus we have to run it before the typer-dependent thread is activated. bytecodeWriter = initBytecodeWriter(cleanup.getEntryPoints) @@ -297,9 +300,6 @@ abstract class GenBCode extends BCodeSyncAndTry { * (2) if requested, check-java-signatures, over and beyond the syntactic checks in `getGenericSignature()` * */ - - // clearing maps - clearBCodeTypes() } /* @@ -385,3 +385,10 @@ abstract class GenBCode extends BCodeSyncAndTry { } // end of class BCodePhase } // end of class GenBCode + +object GenBCode { + def mkFlags(args: Int*) = args.foldLeft(0)(_ | _) + + final val PublicStatic = asm.Opcodes.ACC_PUBLIC | asm.Opcodes.ACC_STATIC + final val PublicStaticFinal = asm.Opcodes.ACC_PUBLIC | asm.Opcodes.ACC_STATIC | asm.Opcodes.ACC_FINAL +} diff --git a/src/compiler/scala/tools/nsc/javac/JavaScanners.scala b/src/compiler/scala/tools/nsc/javac/JavaScanners.scala index bddcf6567c..ac86dfd665 100644 --- a/src/compiler/scala/tools/nsc/javac/JavaScanners.scala +++ b/src/compiler/scala/tools/nsc/javac/JavaScanners.scala @@ -861,7 +861,7 @@ trait JavaScanners extends ast.parser.ScannersCommon { in = new JavaCharArrayReader(unit.source.content, !settings.nouescape.value, syntaxError) init() def error (pos: Int, msg: String) = reporter.error(pos, msg) - def incompleteInputError(pos: Int, msg: String) = currentRun.reporting.incompleteInputError(pos, msg) + def incompleteInputError(pos: Int, msg: String) = currentRun.parsing.incompleteInputError(pos, msg) def deprecationWarning(pos: Int, msg: String) = currentRun.reporting.deprecationWarning(pos, msg) implicit def g2p(pos: Int): Position = Position.offset(unit.source, pos) } diff --git a/src/compiler/scala/tools/nsc/plugins/Plugins.scala b/src/compiler/scala/tools/nsc/plugins/Plugins.scala index 12f9aeba27..6e3d013e52 100644 --- a/src/compiler/scala/tools/nsc/plugins/Plugins.scala +++ b/src/compiler/scala/tools/nsc/plugins/Plugins.scala @@ -33,7 +33,7 @@ trait Plugins { global: Global => } val maybes = Plugin.loadAllFrom(paths, dirs, settings.disable.value) val (goods, errors) = maybes partition (_.isSuccess) - // Explicit parameterization of recover to suppress -Xlint warning about inferred Any + // Explicit parameterization of recover to avoid -Xlint warning about inferred Any errors foreach (_.recover[Any] { // legacy behavior ignores altogether, so at least warn devs case e: MissingPluginException => if (global.isDeveloper) warning(e.getMessage) diff --git a/src/compiler/scala/tools/nsc/reporters/AbstractReporter.scala b/src/compiler/scala/tools/nsc/reporters/AbstractReporter.scala index 6c592ead0d..5e4914fa83 100644 --- a/src/compiler/scala/tools/nsc/reporters/AbstractReporter.scala +++ b/src/compiler/scala/tools/nsc/reporters/AbstractReporter.scala @@ -30,6 +30,7 @@ abstract class AbstractReporter extends Reporter { private def isVerbose = settings.verbose.value private def noWarnings = settings.nowarnings.value private def isPromptSet = settings.prompt.value + private def isDebug = settings.debug protected def info0(pos: Position, msg: String, severity: Severity, force: Boolean) { if (severity == INFO) { @@ -46,7 +47,7 @@ abstract class AbstractReporter extends Reporter { severity.count += 1 display(pos, msg, severity) } - else if (settings.debug) { + else if (isDebug) { severity.count += 1 display(pos, "[ suppressed ] " + msg, severity) } @@ -57,6 +58,7 @@ abstract class AbstractReporter extends Reporter { } } + /** Logs a position and returns true if it was already logged. * @note Two positions are considered identical for logging if they have the same point. */ diff --git a/src/compiler/scala/tools/nsc/reporters/Reporter.scala b/src/compiler/scala/tools/nsc/reporters/Reporter.scala index 5b576a547d..3d688efae1 100644 --- a/src/compiler/scala/tools/nsc/reporters/Reporter.scala +++ b/src/compiler/scala/tools/nsc/reporters/Reporter.scala @@ -13,8 +13,8 @@ import scala.reflect.internal.util._ * This describes the internal interface for issuing information, warnings and errors. * The only abstract method in this class must be info0. * - * TODO: Move external clients (sbt/ide/partest) to reflect.internal.Reporter - * This interface should be considered private to the compiler. + * TODO: Move external clients (sbt/ide/partest) to reflect.internal.Reporter, + * and remove this class. */ abstract class Reporter extends scala.reflect.internal.Reporter { /** Informational messages. If `!force`, they may be suppressed. */ diff --git a/src/compiler/scala/tools/nsc/settings/AbsScalaSettings.scala b/src/compiler/scala/tools/nsc/settings/AbsScalaSettings.scala index 38a7525862..d0b8fd70ed 100644 --- a/src/compiler/scala/tools/nsc/settings/AbsScalaSettings.scala +++ b/src/compiler/scala/tools/nsc/settings/AbsScalaSettings.scala @@ -29,7 +29,7 @@ trait AbsScalaSettings { def ChoiceSetting(name: String, helpArg: String, descr: String, choices: List[String], default: String): ChoiceSetting def IntSetting(name: String, descr: String, default: Int, range: Option[(Int, Int)], parser: String => Option[Int]): IntSetting def MultiStringSetting(name: String, helpArg: String, descr: String): MultiStringSetting - def MultiChoiceSetting(name: String, helpArg: String, descr: String, choices: List[String]): MultiChoiceSetting + def MultiChoiceSetting(name: String, helpArg: String, descr: String, choices: List[String], default: Option[() => Unit])(helper: MultiChoiceSetting => String): MultiChoiceSetting def OutputSetting(outputDirs: OutputDirs, default: String): OutputSetting def PathSetting(name: String, descr: String, default: String): PathSetting def PhasesSetting(name: String, descr: String, default: String): PhasesSetting diff --git a/src/compiler/scala/tools/nsc/settings/MutableSettings.scala b/src/compiler/scala/tools/nsc/settings/MutableSettings.scala index 54e444decf..f26192f88a 100644 --- a/src/compiler/scala/tools/nsc/settings/MutableSettings.scala +++ b/src/compiler/scala/tools/nsc/settings/MutableSettings.scala @@ -211,11 +211,10 @@ class MutableSettings(val errorFn: String => Unit) add(new ChoiceSetting(name, helpArg, descr, choices, default)) def IntSetting(name: String, descr: String, default: Int, range: Option[(Int, Int)], parser: String => Option[Int]) = add(new IntSetting(name, descr, default, range, parser)) def MultiStringSetting(name: String, arg: String, descr: String) = add(new MultiStringSetting(name, arg, descr)) - def MultiChoiceSetting(name: String, helpArg: String, descr: String, choices: List[String]): MultiChoiceSetting = { - val fullChoix = choices.mkString(": ", ",", ".") - val fullDescr = s"$descr$fullChoix" - add(new MultiChoiceSetting(name, helpArg, fullDescr, choices)) - } + def MultiChoiceSetting(name: String, helpArg: String, descr: String, choices: List[String], default: Option[() => Unit] = None)( + helper: MultiChoiceSetting => String = _ => choices.mkString(f"$descr:%n", f"%n ", f"%n") + ) = + add(new MultiChoiceSetting(name, helpArg, descr, choices, default, helper)) def OutputSetting(outputDirs: OutputDirs, default: String) = add(new OutputSetting(outputDirs, default)) def PhasesSetting(name: String, descr: String, default: String = "") = add(new PhasesSetting(name, descr, default)) def StringSetting(name: String, arg: String, descr: String, default: String) = add(new StringSetting(name, arg, descr, default)) @@ -444,7 +443,7 @@ class MutableSettings(val errorFn: String => Unit) value = s.equalsIgnoreCase("true") } override def tryToSetColon(args: List[String]) = args match { - case Nil => tryToSet(Nil) + case Nil => tryToSet(Nil) case List(x) => if (x.equalsIgnoreCase("true")) { value = true @@ -452,7 +451,8 @@ class MutableSettings(val errorFn: String => Unit) } else if (x.equalsIgnoreCase("false")) { value = false Some(Nil) - } else errorAndValue("'" + x + "' is not a valid choice for '" + name + "'", None) + } else errorAndValue(s"'$x' is not a valid choice for '$name'", None) + case _ => errorAndValue(s"'$name' accepts only one boolean value", None) } } @@ -553,12 +553,58 @@ class MutableSettings(val errorFn: String => Unit) } } + /** A setting that receives any combination of enumerated values, + * including "_" to mean all values and "help" for verbose info. + * In non-colonated mode, stops consuming args at the first + * non-value, instead of at the next option, as for a multi-string. + */ class MultiChoiceSetting private[nsc]( name: String, arg: String, descr: String, - override val choices: List[String]) - extends MultiStringSetting(name, arg, descr) + override val choices: List[String], + val default: Option[() => Unit], + helper: MultiChoiceSetting => String + ) extends MultiStringSetting(name, s"_,$arg,-$arg", s"$descr: `_' for all, `$name:help' to list") { + + private def badChoice(s: String, n: String) = errorFn(s"'$s' is not a valid choice for '$name'") + private def choosing = choices.nonEmpty + private def isChoice(s: String) = (s == "_") || (choices contains (s stripPrefix "-")) + + private var sawHelp = false + private var sawAll = false + private val adderAll = () => sawAll = true + private val noargs = () => errorFn(s"'$name' requires an option. See '$name:help'.") + + override protected def tts(args: List[String], halting: Boolean) = { + val added = collection.mutable.ListBuffer.empty[String] + def tryArg(arg: String) = arg match { + case "_" if choosing => addAll() + case "help" if choosing => sawHelp = true + case s if !choosing || isChoice(s) => added += s + case s => badChoice(s, name) + } + def stoppingAt(arg: String) = (arg startsWith "-") || (choosing && !isChoice(arg)) + def loop(args: List[String]): List[String] = args match { + case arg :: _ if halting && stoppingAt(arg) => args + case arg :: rest => tryArg(arg) ; loop(rest) + case Nil => Nil + } + val rest = loop(args) + if (rest.size == args.size) + (default getOrElse noargs)() // if no arg consumed, trigger default action or error + else + value ++= added.toList // update all new settings at once + Some(rest) + } + + def isHelping: Boolean = sawHelp + def help: String = helper(this) + def addAll(): Unit = (default getOrElse adderAll)() + + // the semantics is: s is enabled, i.e., either s or (_ but not -s) + override def contains(s: String) = isChoice(s) && (value contains s) || (sawAll && !(value contains s"-$s")) + } /** A setting that accumulates all strings supplied to it, * until it encounters one starting with a '-'. @@ -570,24 +616,23 @@ class MutableSettings(val errorFn: String => Unit) extends Setting(name, descr) with Clearable { type T = List[String] protected var v: T = Nil - def appendToValue(str: String) { value ++= List(str) } - def badChoice(s: String, n: String) = errorFn(s"'$s' is not a valid choice for '$name'") - - def tryToSet(args: List[String]) = { - val (strings, rest) = args span (x => !x.startsWith("-")) - strings foreach { - case "_" if choices.nonEmpty => choices foreach appendToValue - case s if choices.isEmpty || (choices contains s) => appendToValue(s) - case s => badChoice(s, name) + def appendToValue(str: String) = value ++= List(str) + + // try to set. halting means halt at first non-arg + protected def tts(args: List[String], halting: Boolean) = { + def loop(args: List[String]): List[String] = args match { + case arg :: rest => if (halting && (arg startsWith "-")) args else { appendToValue(arg) ; loop(rest) } + case Nil => Nil } - Some(rest) + Some(loop(args)) } - override def tryToSetColon(args: List[String]) = tryToSet(args) + def tryToSet(args: List[String]) = tts(args, halting = true) + override def tryToSetColon(args: List[String]) = tts(args, halting = false) override def tryToSetFromPropertyValue(s: String) = tryToSet(s.trim.split(',').toList) // used from ide - def clear(): Unit = (v = Nil) - def unparse: List[String] = value map (name + ":" + _) - def contains(s: String) = value contains s + def clear(): Unit = (v = Nil) + def unparse: List[String] = value map (name + ":" + _) + def contains(s: String) = value contains s withHelpSyntax(name + ":<" + arg + ">") } @@ -606,10 +651,8 @@ class MutableSettings(val errorFn: String => Unit) protected var v: T = default def indexOfChoice: Int = choices indexOf value - private def usageErrorMessage = { - "Usage: %s:<%s>\n where <%s> choices are %s (default: %s)\n".format( - name, helpArg, helpArg, choices mkString ", ", default) - } + private def usageErrorMessage = f"Usage: $name:<$helpArg>%n where <$helpArg> choices are ${choices mkString ", "} (default: $default)%n" + def tryToSet(args: List[String]) = errorAndValue(usageErrorMessage, None) override def tryToSetColon(args: List[String]) = args match { diff --git a/src/compiler/scala/tools/nsc/settings/ScalaSettings.scala b/src/compiler/scala/tools/nsc/settings/ScalaSettings.scala index d22dcacad6..8e69598614 100644 --- a/src/compiler/scala/tools/nsc/settings/ScalaSettings.scala +++ b/src/compiler/scala/tools/nsc/settings/ScalaSettings.scala @@ -44,8 +44,11 @@ trait ScalaSettings extends AbsScalaSettings /** If any of these settings is enabled, the compiler should print a message and exit. */ def infoSettings = List[Setting](version, help, Xhelp, Yhelp, showPlugins, showPhases, genPhaseGraph) + /** Any -multichoice:help? Nicer if any option could report that it had help to offer. */ + private def multihelp = allSettings exists { case s: MultiChoiceSetting => s.isHelping case _ => false } + /** Is an info setting set? */ - def isInfo = infoSettings exists (_.isSetByUser) + def isInfo = (infoSettings exists (_.isSetByUser)) || multihelp /** Disable a setting */ def disable(s: Setting) = allSettings -= s @@ -67,8 +70,27 @@ trait ScalaSettings extends AbsScalaSettings // Would be nice to build this dynamically from scala.languageFeature. // The two requirements: delay error checking until you have symbols, and let compiler command build option-specific help. val language = { - val features = List("dynamics", "postfixOps", "reflectiveCalls", "implicitConversions", "higherKinds", "existentials", "experimental.macros") - MultiChoiceSetting("-language", "feature", "Enable one or more language features", features) + val features = List( + "dynamics" -> "Allow direct or indirect subclasses of scala.Dynamic", + "postfixOps" -> "Allow postfix operator notation, such as `1 to 10 toList'", + "reflectiveCalls" -> "Allow reflective access to members of structural types", + "implicitConversions" -> "Allow definition of implicit functions called views", + "higherKinds" -> "Allow higher-kinded types", // "Ask Adriaan, but if you have to ask..." + "existentials" -> "Existential types (besides wildcard types) can be written and inferred", + "experimental.macros" -> "Allow macro defintion (besides implementation and application)" + ) + val description = "Enable or disable language features" + MultiChoiceSetting( + name = "-language", + helpArg = "feature", + descr = description, + choices = features map (_._1) + ) { s => + val helpline: ((String, String)) => String = { + case (name, text) => f" $name%-25s $text%n" + } + features map helpline mkString (f"$description:%n", "", f"%n") + } } /* @@ -185,7 +207,6 @@ trait ScalaSettings extends AbsScalaSettings val YmethodInfer = BooleanSetting ("-Yinfer-argument-types", "Infer types for arguments of overriden methods.") val etaExpandKeepsStar = BooleanSetting ("-Yeta-expand-keeps-star", "Eta-expand varargs methods to T* rather than Seq[T]. This is a temporary option to ease transition.").withDeprecationMessage(removalIn212) val inferByName = BooleanSetting ("-Yinfer-by-name", "Allow inference of by-name types. This is a temporary option to ease transition. See SI-7899.").withDeprecationMessage(removalIn212) - val Yinvalidate = StringSetting ("-Yinvalidate", "classpath-entry", "Invalidate classpath entry before run", "") val YvirtClasses = false // too embryonic to even expose as a -Y //BooleanSetting ("-Yvirtual-classes", "Support virtual classes") val YdisableUnreachablePrevention = BooleanSetting("-Ydisable-unreachable-prevention", "Disable the prevention of unreachable blocks in code generation.") val YnoLoadImplClass = BooleanSetting ("-Yno-load-impl-class", "Do not load $class.class files.") diff --git a/src/compiler/scala/tools/nsc/settings/StandardScalaSettings.scala b/src/compiler/scala/tools/nsc/settings/StandardScalaSettings.scala index 37dfafb01c..d42c0dd730 100644 --- a/src/compiler/scala/tools/nsc/settings/StandardScalaSettings.scala +++ b/src/compiler/scala/tools/nsc/settings/StandardScalaSettings.scala @@ -39,7 +39,7 @@ trait StandardScalaSettings { val optimise: BooleanSetting // depends on post hook which mutates other settings val print = BooleanSetting ("-print", "Print program with Scala-specific features removed.") val target = ChoiceSetting ("-target", "target", "Target platform for object files. All JVM 1.5 targets are deprecated.", - List("jvm-1.5", "jvm-1.6", "jvm-1.7"), "jvm-1.6") + List("jvm-1.5", "jvm-1.6", "jvm-1.7", "jvm-1.8"), "jvm-1.6") val unchecked = BooleanSetting ("-unchecked", "Enable additional warnings where generated code depends on assumptions.") val uniqid = BooleanSetting ("-uniqid", "Uniquely tag all identifiers in debugging output.") val usejavacp = BooleanSetting ("-usejavacp", "Utilize the java.class.path in classpath resolution.") diff --git a/src/compiler/scala/tools/nsc/settings/Warnings.scala b/src/compiler/scala/tools/nsc/settings/Warnings.scala index 1509ad13b8..4c37633301 100644 --- a/src/compiler/scala/tools/nsc/settings/Warnings.scala +++ b/src/compiler/scala/tools/nsc/settings/Warnings.scala @@ -17,12 +17,15 @@ trait Warnings { // Warning semantics. val fatalWarnings = BooleanSetting("-Xfatal-warnings", "Fail the compilation if there are any warnings.") - // These warnings are all so noisy as to be useless in their + // These additional warnings are all so noisy as to be useless in their // present form, but have the potential to offer useful info. protected def allWarnings = lintWarnings ++ List( warnDeadCode, warnValueDiscard, - warnNumericWiden + warnNumericWiden, + warnUnused, // SI-7712, SI-7707 warnUnused not quite ready for prime-time + warnUnusedImport, // currently considered too noisy for general use + warnValueOverrides // currently turned off as experimental ) // These warnings should be pretty quiet unless you're doing // something inadvisable. @@ -31,27 +34,130 @@ trait Warnings { warnNullaryOverride, warnNullaryUnit, warnAdaptedArgs, - warnInferAny - // warnUnused SI-7712, SI-7707 warnUnused not quite ready for prime-time - // warnUnusedImport currently considered too noisy for general use + warnInferAny, + warnMissingInterpolator, + warnDocDetached, + warnPrivateShadow, + warnTypeParameterShadow, + warnPolyImplicitOverload, + warnOptionImplicit, + warnDelayedInit, + warnByNameRightAssociative, + warnPackageObjectClasses, + warnUnsoundMatch ) - private lazy val warnSelectNullable = BooleanSetting("-Xcheck-null", "This option is obsolete and does nothing.") + // Individual warnings. They can be set with -Ywarn. + private def nonlintflag(name: String, text: String): BooleanSetting = BooleanSetting(name, text) + + val warnDeadCode = nonlintflag("-Ywarn-dead-code", + "Warn when dead code is identified.") + val warnValueDiscard = nonlintflag("-Ywarn-value-discard", + "Warn when non-Unit expression results are unused.") + val warnNumericWiden = nonlintflag("-Ywarn-numeric-widen", + "Warn when numerics are widened.") + val warnUnused = nonlintflag("-Ywarn-unused", + "Warn when local and private vals, vars, defs, and types are are unused") + val warnUnusedImport = nonlintflag("-Ywarn-unused-import", + "Warn when imports are unused") + + // Lint warnings that have no -Y avatar, created with new instead of the autoregistering factory method. + // They evaluate true if set to true or else are unset but -Xlint is true + private def lintflag(name: String, text: String): BooleanSetting = + new BooleanSetting(name, text) { + override def value = if (isSetByUser) super.value else xlint + } + + val warnAdaptedArgs = lintflag("adapted-args", + "Warn if an argument list is modified to match the receiver.") + val warnNullaryUnit = lintflag("nullary-unit", + "Warn when nullary methods return Unit.") + val warnInaccessible = lintflag("inaccessible", + "Warn about inaccessible types in method signatures.") + val warnNullaryOverride = lintflag("nullary-override", + "Warn when non-nullary `def f()' overrides nullary `def f'.") + val warnInferAny = lintflag("infer-any", + "Warn when a type argument is inferred to be `Any`.") + val warnMissingInterpolator = lintflag("missing-interpolator", + "A string literal appears to be missing an interpolator id.") + val warnDocDetached = lintflag("doc-detached", + "A ScalaDoc comment appears to be detached from its element.") + val warnPrivateShadow = lintflag("private-shadow", + "A private field (or class parameter) shadows a superclass field.") + val warnTypeParameterShadow = lintflag("type-parameter-shadow", + "A local type parameter shadows a type already in scope.") + val warnPolyImplicitOverload = lintflag("poly-implicit-overload", + "Parameterized overloaded implicit methods are not visible as view bounds") + val warnOptionImplicit = lintflag("option-implicit", + "Option.apply used implicit view.") + val warnDelayedInit = lintflag("delayedinit-select", + "Selecting member of DelayedInit") + val warnByNameRightAssociative = lintflag("by-name-right-associative", + "By-name parameter of right associative operator") + val warnPackageObjectClasses = lintflag("package-object-classes", + "Class or object defined in package object") + val warnUnsoundMatch = lintflag("unsound-match", + "Pattern match may not be typesafe") - // Individual warnings. - val warnAdaptedArgs = BooleanSetting ("-Ywarn-adapted-args", "Warn if an argument list is modified to match the receiver.") - val warnDeadCode = BooleanSetting ("-Ywarn-dead-code", "Warn when dead code is identified.") - val warnValueDiscard = BooleanSetting ("-Ywarn-value-discard", "Warn when non-Unit expression results are unused.") - val warnNumericWiden = BooleanSetting ("-Ywarn-numeric-widen", "Warn when numerics are widened.") - val warnNullaryUnit = BooleanSetting ("-Ywarn-nullary-unit", "Warn when nullary methods return Unit.") - val warnInaccessible = BooleanSetting ("-Ywarn-inaccessible", "Warn about inaccessible types in method signatures.") - val warnNullaryOverride = BooleanSetting ("-Ywarn-nullary-override", "Warn when non-nullary overrides nullary, e.g. `def foo()` over `def foo`.") - val warnInferAny = BooleanSetting ("-Ywarn-infer-any", "Warn when a type argument is inferred to be `Any`.") - val warnUnused = BooleanSetting ("-Ywarn-unused", "Warn when local and private vals, vars, defs, and types are are unused") - val warnUnusedImport = BooleanSetting ("-Ywarn-unused-import", "Warn when imports are unused") - - // Warning groups. - val lint = BooleanSetting("-Xlint", "Enable recommended additional warnings.") enablingIfNotSetByUser lintWarnings + // Experimental lint warnings that are turned off, but which could be turned on programmatically. + // These warnings are said to blind those who dare enable them. + // They are not activated by -Xlint and can't be enabled on the command line. + val warnValueOverrides = { + val flag = lintflag("value-overrides", "Generated value class method overrides an implementation") + flag.value = false + flag + } + + // The Xlint warning group. + private val xlint = new BooleanSetting("-Zunused", "True if -Xlint or -Xlint:_") + // On -Xlint or -Xlint:_, set xlint, otherwise set the lint warning unless already set true + val lint = { + val description = "Enable or disable specific warnings" + val choices = (lintWarnings map (_.name)).sorted + MultiChoiceSetting( + name = "-Xlint", + helpArg = "warning", + descr = description, + choices = choices, + default = Some(() => xlint.value = true) + ) { s => + def helpline(n: String) = lintWarnings.find(_.name == n).map(w => f" ${w.name}%-25s ${w.helpDescription}%n") + choices flatMap (helpline(_)) mkString (f"$description:%n", "", f"%n") + } withPostSetHook { x => + val Neg = "-" + def setPolitely(b: BooleanSetting, v: Boolean) = if (!b.isSetByUser || !b) b.value = v + def set(w: String, v: Boolean) = lintWarnings find (_.name == w) foreach (setPolitely(_, v)) + def propagate(ss: List[String]): Unit = ss match { + case w :: rest => if (w startsWith Neg) set(w stripPrefix Neg, false) else set(w, true) ; propagate(rest) + case Nil => () + } + propagate(x.value) + } + } + + // Lint warnings that are currently -Y, but deprecated in that usage + @deprecated("Use warnAdaptedArgs", since="2.11.2") + val YwarnAdaptedArgs = BooleanSetting("-Ywarn-adapted-args", + "Warn if an argument list is modified to match the receiver.") enabling List(warnAdaptedArgs) + //withDeprecationMessage "Enable -Xlint:adapted-args" + @deprecated("Use warnNullaryUnit", since="2.11.2") + val YwarnNullaryUnit = BooleanSetting("-Ywarn-nullary-unit", + "Warn when nullary methods return Unit.") enabling List(warnNullaryUnit) + //withDeprecationMessage "Enable -Xlint:nullary-unit" + @deprecated("Use warnInaccessible", since="2.11.2") + val YwarnInaccessible = BooleanSetting("-Ywarn-inaccessible", + "Warn about inaccessible types in method signatures.") enabling List(warnInaccessible) + //withDeprecationMessage "Enable -Xlint:inaccessible" + @deprecated("Use warnNullaryOverride", since="2.11.2") + val YwarnNullaryOverride = BooleanSetting("-Ywarn-nullary-override", + "Warn when non-nullary `def f()' overrides nullary `def f'.") enabling List(warnNullaryOverride) + //withDeprecationMessage "Enable -Xlint:nullary-override" + @deprecated("Use warnInferAny", since="2.11.2") + val YwarnInferAny = BooleanSetting("-Ywarn-infer-any", + "Warn when a type argument is inferred to be `Any`.") enabling List(warnInferAny) + //withDeprecationMessage "Enable -Xlint:infer-any" + + private lazy val warnSelectNullable = BooleanSetting("-Xcheck-null", "This option is obsolete and does nothing.") // Backward compatibility. @deprecated("Use fatalWarnings", "2.11.0") def Xwarnfatal = fatalWarnings // used by sbt diff --git a/src/compiler/scala/tools/nsc/symtab/SymbolLoaders.scala b/src/compiler/scala/tools/nsc/symtab/SymbolLoaders.scala index 447fa66ae4..82c2a4d6ed 100644 --- a/src/compiler/scala/tools/nsc/symtab/SymbolLoaders.scala +++ b/src/compiler/scala/tools/nsc/symtab/SymbolLoaders.scala @@ -240,6 +240,12 @@ abstract class SymbolLoaders { } } + private def phaseBeforeRefchecks: Phase = { + var resPhase = phase + while (resPhase.refChecked) resPhase = resPhase.prev + resPhase + } + /** * Load contents of a package */ @@ -248,19 +254,24 @@ abstract class SymbolLoaders { protected def doComplete(root: Symbol) { assert(root.isPackageClass, root) - root.setInfo(new PackageClassInfoType(newScope, root)) - - if (!root.isRoot) { - for (classRep <- classpath.classes) { - initializeFromClassPath(root, classRep) - } - } - if (!root.isEmptyPackageClass) { - for (pkg <- classpath.packages) { - enterPackage(root, pkg.name, new PackageLoader(pkg)) + // Time travel to a phase before refchecks avoids an initialization issue. `openPackageModule` + // creates a module symbol and invokes invokes `companionModule` while the `infos` field is + // still null. This calls `isModuleNotMethod`, which forces the `info` if run after refchecks. + enteringPhase(phaseBeforeRefchecks) { + root.setInfo(new PackageClassInfoType(newScope, root)) + + if (!root.isRoot) { + for (classRep <- classpath.classes) { + initializeFromClassPath(root, classRep) + } } + if (!root.isEmptyPackageClass) { + for (pkg <- classpath.packages) { + enterPackage(root, pkg.name, new PackageLoader(pkg)) + } - openPackageModule(root) + openPackageModule(root) + } } } } @@ -290,7 +301,13 @@ abstract class SymbolLoaders { protected def doComplete(root: Symbol) { val start = if (Statistics.canEnable) Statistics.startTimer(classReadNanos) else null - classfileParser.parse(classfile, root) + + // Running the classfile parser after refchecks can lead to "illegal class file dependency" + // errors. More concretely, the classfile parser calls "sym.companionModule", which calls + // "isModuleNotMethod" on the companion. After refchecks, this method forces the info, which + // may run the classfile parser. This produces the error. + enteringPhase(phaseBeforeRefchecks)(classfileParser.parse(classfile, root)) + if (root.associatedFile eq NoAbstractFile) { root match { // In fact, the ModuleSymbol forwards its setter to the module class diff --git a/src/compiler/scala/tools/nsc/transform/Delambdafy.scala b/src/compiler/scala/tools/nsc/transform/Delambdafy.scala index 1f8ab7e887..9cc8d779a0 100644 --- a/src/compiler/scala/tools/nsc/transform/Delambdafy.scala +++ b/src/compiler/scala/tools/nsc/transform/Delambdafy.scala @@ -37,6 +37,15 @@ abstract class Delambdafy extends Transform with TypingTransformers with ast.Tre /** the following two members override abstract members in Transform */ val phaseName: String = "delambdafy" + override def newPhase(prev: scala.tools.nsc.Phase): StdPhase = { + if (settings.Ydelambdafy.value == "method") new Phase(prev) + else new SkipPhase(prev) + } + + class SkipPhase(prev: scala.tools.nsc.Phase) extends StdPhase(prev) { + def apply(unit: global.CompilationUnit): Unit = () + } + protected def newTransformer(unit: CompilationUnit): Transformer = new DelambdafyTransformer(unit) @@ -252,39 +261,39 @@ abstract class Delambdafy extends Transform with TypingTransformers with ast.Tre captureProxies2 += ((capture, sym)) } - // the Optional proxy that will hold a reference to the 'this' - // object used by the lambda, if any. NoSymbol if there is no this proxy - val thisProxy = { - val target = targetMethod(originalFunction) - if (thisReferringMethods contains target) { - val sym = anonClass.newVariable(nme.FAKE_LOCAL_THIS, originalFunction.pos, SYNTHETIC) - sym.info = oldClass.tpe - sym - } else NoSymbol - } + // the Optional proxy that will hold a reference to the 'this' + // object used by the lambda, if any. NoSymbol if there is no this proxy + val thisProxy = { + val target = targetMethod(originalFunction) + if (thisReferringMethods contains target) { + val sym = anonClass.newVariable(nme.FAKE_LOCAL_THIS, originalFunction.pos, SYNTHETIC) + sym.info = oldClass.tpe + sym + } else NoSymbol + } - val decapturify = new DeCapturifyTransformer(captureProxies2, unit, oldClass, anonClass, originalFunction.symbol.pos, thisProxy) + val decapturify = new DeCapturifyTransformer(captureProxies2, unit, oldClass, anonClass, originalFunction.symbol.pos, thisProxy) - val accessorMethod = createAccessorMethod(thisProxy, originalFunction) + val accessorMethod = createAccessorMethod(thisProxy, originalFunction) - val decapturedFunction = decapturify.transform(originalFunction).asInstanceOf[Function] + val decapturedFunction = decapturify.transform(originalFunction).asInstanceOf[Function] - val members = (optionSymbol(thisProxy).toList ++ (captureProxies2 map (_._2))) map {member => - anonClass.info.decls enter member - ValDef(member, gen.mkZero(member.tpe)) setPos decapturedFunction.pos - } + val members = (optionSymbol(thisProxy).toList ++ (captureProxies2 map (_._2))) map {member => + anonClass.info.decls enter member + ValDef(member, gen.mkZero(member.tpe)) setPos decapturedFunction.pos + } - // constructor - val constr = createConstructor(anonClass, members) + // constructor + val constr = createConstructor(anonClass, members) - // apply method with same arguments and return type as original lambda. - val applyMethodDef = createApplyMethod(anonClass, decapturedFunction, accessorMethod, thisProxy) + // apply method with same arguments and return type as original lambda. + val applyMethodDef = createApplyMethod(anonClass, decapturedFunction, accessorMethod, thisProxy) - val bridgeMethod = createBridgeMethod(anonClass, originalFunction, applyMethodDef) + val bridgeMethod = createBridgeMethod(anonClass, originalFunction, applyMethodDef) - def fulldef(sym: Symbol) = - if (sym == NoSymbol) sym.toString - else s"$sym: ${sym.tpe} in ${sym.owner}" + def fulldef(sym: Symbol) = + if (sym == NoSymbol) sym.toString + else s"$sym: ${sym.tpe} in ${sym.owner}" bridgeMethod foreach (bm => // TODO SI-6260 maybe just create the apply method with the signature (Object => Object) in all cases diff --git a/src/compiler/scala/tools/nsc/transform/Erasure.scala b/src/compiler/scala/tools/nsc/transform/Erasure.scala index ec4deb6be0..3d8b2f02f3 100644 --- a/src/compiler/scala/tools/nsc/transform/Erasure.scala +++ b/src/compiler/scala/tools/nsc/transform/Erasure.scala @@ -468,8 +468,12 @@ abstract class Erasure extends AddInterfaces if (!bridgeNeeded) return - val newFlags = (member.flags | BRIDGE | ARTIFACT) & ~(ACCESSOR | DEFERRED | LAZY | lateDEFERRED) - val bridge = other.cloneSymbolImpl(root, newFlags) setPos root.pos + var newFlags = (member.flags | BRIDGE | ARTIFACT) & ~(ACCESSOR | DEFERRED | LAZY | lateDEFERRED) + // If `member` is a ModuleSymbol, the bridge should not also be a ModuleSymbol. Otherwise we + // end up with two module symbols with the same name in the same scope, which is surprising + // when implementing later phases. + if (member.isModule) newFlags = (newFlags | METHOD) & ~(MODULE | lateMETHOD | STABLE) + val bridge = other.cloneSymbolImpl(root, newFlags) setPos root.pos debuglog("generating bridge from %s (%s): %s to %s: %s".format( other, flagsToString(newFlags), @@ -1133,7 +1137,7 @@ abstract class Erasure extends AddInterfaces val tree2 = mixinTransformer.transform(tree1) // debuglog("tree after addinterfaces: \n" + tree2) - newTyper(rootContext(unit, tree, erasedTypes = true)).typed(tree2) + newTyper(rootContextPostTyper(unit, tree)).typed(tree2) } } } diff --git a/src/compiler/scala/tools/nsc/transform/Flatten.scala b/src/compiler/scala/tools/nsc/transform/Flatten.scala index c3fbfae322..fa53ef48b5 100644 --- a/src/compiler/scala/tools/nsc/transform/Flatten.scala +++ b/src/compiler/scala/tools/nsc/transform/Flatten.scala @@ -76,8 +76,17 @@ abstract class Flatten extends InfoTransform { for (sym <- decls) { if (sym.isTerm && !sym.isStaticModule) { decls1 enter sym - if (sym.isModule) + if (sym.isModule) { + // Nested, non-static moduls are transformed to methods. + assert(sym.isMethod, s"Non-static $sym should have the lateMETHOD flag from RefChecks") + // Note that module classes are not entered into the 'decls' of the ClassInfoType + // of the outer class, only the module symbols are. So the current loop does + // not visit module classes. Therefore we set the LIFTED flag here for module + // classes. + // TODO: should we also set the LIFTED flag for static, nested module classes? + // currently they don't get the flag, even though they are lifted to the package sym.moduleClass setFlag LIFTED + } } else if (sym.isClass) liftSymbol(sym) } diff --git a/src/compiler/scala/tools/nsc/transform/LambdaLift.scala b/src/compiler/scala/tools/nsc/transform/LambdaLift.scala index f85d8222f0..d69c9d9a65 100644 --- a/src/compiler/scala/tools/nsc/transform/LambdaLift.scala +++ b/src/compiler/scala/tools/nsc/transform/LambdaLift.scala @@ -449,6 +449,8 @@ abstract class LambdaLift extends InfoTransform { if (sym.isClass) sym.owner = sym.owner.toInterface if (sym.isMethod) sym setFlag LIFTED liftedDefs(sym.owner) ::= tree + // TODO: this modifies the ClassInfotype of the enclosing class, which is associated with another phase (explicitouter). + // This breaks type history: in a phase travel to before lambda lift, the ClassInfoType will contain lifted classes. sym.owner.info.decls enterUnique sym debuglog("lifted: " + sym + " from " + oldOwner + " to " + sym.owner) EmptyTree diff --git a/src/compiler/scala/tools/nsc/transform/LazyVals.scala b/src/compiler/scala/tools/nsc/transform/LazyVals.scala index b71d14a04f..38671ebaae 100644 --- a/src/compiler/scala/tools/nsc/transform/LazyVals.scala +++ b/src/compiler/scala/tools/nsc/transform/LazyVals.scala @@ -192,13 +192,15 @@ abstract class LazyVals extends Transform with TypingTransformers with ast.TreeD def mkSlowPathDef(clazz: Symbol, lzyVal: Symbol, cond: Tree, syncBody: List[Tree], stats: List[Tree], retVal: Tree): Tree = { + // Q: is there a reason to first set owner to `clazz` (by using clazz.newMethod), and then + // changing it to lzyVal.owner very soon after? Could we just do lzyVal.owner.newMethod? val defSym = clazz.newMethod(nme.newLazyValSlowComputeName(lzyVal.name.toTermName), lzyVal.pos, STABLE | PRIVATE) defSym setInfo MethodType(List(), lzyVal.tpe.resultType) defSym.owner = lzyVal.owner debuglog(s"crete slow compute path $defSym with owner ${defSym.owner} for lazy val $lzyVal") if (bitmaps.contains(lzyVal)) bitmaps(lzyVal).map(_.owner = defSym) - val rhs: Tree = (gen.mkSynchronizedCheck(clazz, cond, syncBody, stats)).changeOwner(currentOwner -> defSym) + val rhs: Tree = gen.mkSynchronizedCheck(clazz, cond, syncBody, stats).changeOwner(currentOwner -> defSym) DefDef(defSym, addBitmapDefs(lzyVal, BLOCK(rhs, retVal))) } diff --git a/src/compiler/scala/tools/nsc/transform/TypingTransformers.scala b/src/compiler/scala/tools/nsc/transform/TypingTransformers.scala index 3feadcd9b2..dc3313e2e4 100644 --- a/src/compiler/scala/tools/nsc/transform/TypingTransformers.scala +++ b/src/compiler/scala/tools/nsc/transform/TypingTransformers.scala @@ -17,9 +17,9 @@ trait TypingTransformers { abstract class TypingTransformer(unit: CompilationUnit) extends Transformer { var localTyper: analyzer.Typer = if (phase.erasedTypes) - erasure.newTyper(erasure.rootContext(unit, EmptyTree, erasedTypes = true)).asInstanceOf[analyzer.Typer] - else - analyzer.newTyper(analyzer.rootContext(unit, EmptyTree, true)) + erasure.newTyper(erasure.rootContextPostTyper(unit, EmptyTree)).asInstanceOf[analyzer.Typer] + else // TODO: AM: should some phases use a regular rootContext instead of a post-typer one?? + analyzer.newTyper(analyzer.rootContextPostTyper(unit, EmptyTree)) protected var curTree: Tree = _ override final def atOwner[A](owner: Symbol)(trans: => A): A = atOwner(curTree, owner)(trans) diff --git a/src/compiler/scala/tools/nsc/transform/UnCurry.scala b/src/compiler/scala/tools/nsc/transform/UnCurry.scala index 2209aac00f..9b85f1b36a 100644 --- a/src/compiler/scala/tools/nsc/transform/UnCurry.scala +++ b/src/compiler/scala/tools/nsc/transform/UnCurry.scala @@ -69,7 +69,6 @@ abstract class UnCurry extends InfoTransform private val byNameArgs = mutable.HashSet[Tree]() private val noApply = mutable.HashSet[Tree]() private val newMembers = mutable.Map[Symbol, mutable.Buffer[Tree]]() - private val repeatedParams = mutable.Map[Symbol, List[ValDef]]() /** Add a new synthetic member for `currentOwner` */ private def addNewMember(t: Tree): Unit = @@ -428,7 +427,7 @@ abstract class UnCurry extends InfoTransform treeCopy.ValDef(p, p.mods, p.name, p.tpt, EmptyTree) }) - if (dd.symbol hasAnnotation VarargsClass) saveRepeatedParams(dd) + if (dd.symbol hasAnnotation VarargsClass) validateVarargs(dd) withNeedLift(needLift = false) { if (dd.symbol.isClassConstructor) { @@ -699,19 +698,12 @@ abstract class UnCurry extends InfoTransform } } - - /* Analyzes repeated params if method is annotated as `varargs`. - * If the repeated params exist, it saves them into the `repeatedParams` map, - * which is used later. - */ - private def saveRepeatedParams(dd: DefDef): Unit = + private def validateVarargs(dd: DefDef): Unit = if (dd.symbol.isConstructor) reporter.error(dd.symbol.pos, "A constructor cannot be annotated with a `varargs` annotation.") - else treeInfo.repeatedParams(dd) match { - case Nil => - reporter.error(dd.symbol.pos, "A method without repeated parameters cannot be annotated with the `varargs` annotation.") - case reps => - repeatedParams(dd.symbol) = reps + else { + val hasRepeated = mexists(dd.symbol.paramss)(sym => definitions.isRepeatedParamType(sym.tpe)) + if (!hasRepeated) reporter.error(dd.symbol.pos, "A method without repeated parameters cannot be annotated with the `varargs` annotation.") } /* Called during post transform, after the method argument lists have been flattened. @@ -719,7 +711,7 @@ abstract class UnCurry extends InfoTransform * varargs forwarder. */ private def addJavaVarargsForwarders(dd: DefDef, flatdd: DefDef): DefDef = { - if (!dd.symbol.hasAnnotation(VarargsClass) || !repeatedParams.contains(dd.symbol)) + if (!dd.symbol.hasAnnotation(VarargsClass) || !enteringUncurry(mexists(dd.symbol.paramss)(sym => definitions.isRepeatedParamType(sym.tpe)))) return flatdd def toArrayType(tp: Type): Type = { @@ -735,19 +727,18 @@ abstract class UnCurry extends InfoTransform ) } - val reps = repeatedParams(dd.symbol) - val rpsymbols = reps.map(_.symbol).toSet val theTyper = typer.atOwner(dd, currentClass) - val flatparams = flatdd.vparamss.head + val flatparams = flatdd.symbol.paramss.head + val isRepeated = enteringUncurry(dd.symbol.info.paramss.flatten.map(sym => definitions.isRepeatedParamType(sym.tpe))) // create the type - val forwformals = flatparams map { - case p if rpsymbols(p.symbol) => toArrayType(p.symbol.tpe) - case p => p.symbol.tpe + val forwformals = map2(flatparams, isRepeated) { + case (p, true) => toArrayType(p.tpe) + case (p, false)=> p.tpe } val forwresult = dd.symbol.tpe_*.finalResultType val forwformsyms = map2(forwformals, flatparams)((tp, oldparam) => - currentClass.newValueParameter(oldparam.name, oldparam.symbol.pos).setInfo(tp) + currentClass.newValueParameter(oldparam.name.toTermName, oldparam.pos).setInfo(tp) ) def mono = MethodType(forwformsyms, forwresult) val forwtype = dd.symbol.tpe match { @@ -761,13 +752,13 @@ abstract class UnCurry extends InfoTransform // create the tree val forwtree = theTyper.typedPos(dd.pos) { - val locals = map2(forwParams, flatparams) { - case (_, fp) if !rpsymbols(fp.symbol) => null - case (argsym, fp) => + val locals = map3(forwParams, flatparams, isRepeated) { + case (_, fp, false) => null + case (argsym, fp, true) => Block(Nil, gen.mkCast( gen.mkWrapArray(Ident(argsym), elementType(ArrayClass, argsym.tpe)), - seqType(elementType(SeqClass, fp.symbol.tpe)) + seqType(elementType(SeqClass, fp.tpe)) ) ) } diff --git a/src/compiler/scala/tools/nsc/transform/patmat/MatchAnalysis.scala b/src/compiler/scala/tools/nsc/transform/patmat/MatchAnalysis.scala index 6f81cbe152..b2dc6e4e52 100644 --- a/src/compiler/scala/tools/nsc/transform/patmat/MatchAnalysis.scala +++ b/src/compiler/scala/tools/nsc/transform/patmat/MatchAnalysis.scala @@ -50,7 +50,7 @@ trait TreeAndTypeAnalysis extends Debugging { // but the annotation didn't bubble up... // This is a pretty poor approximation. def unsoundAssumptionUsed = binder.name != nme.WILDCARD && !(pt <:< pat.tpe) - if (settings.lint && unsoundAssumptionUsed) + if (settings.warnUnsoundMatch && unsoundAssumptionUsed) reporter.warning(pat.pos, sm"""The value matched by $pat is bound to ${binder.name}, which may be used under the |unsound assumption that it has type ${pat.tpe}, whereas we can only safely diff --git a/src/compiler/scala/tools/nsc/typechecker/Adaptations.scala b/src/compiler/scala/tools/nsc/typechecker/Adaptations.scala index 37c39c07be..2f4d228347 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Adaptations.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Adaptations.scala @@ -82,7 +82,8 @@ trait Adaptations { } else if (settings.warnAdaptedArgs) context.warning(t.pos, adaptWarningMessage(s"Adapting argument list by creating a ${args.size}-tuple: this may not be what you want.")) - !settings.noAdaptedArgs || !(args.isEmpty && settings.future) + // return `true` if the adaptation should be kept + !(settings.noAdaptedArgs || (args.isEmpty && settings.future)) } } } diff --git a/src/compiler/scala/tools/nsc/typechecker/ContextErrors.scala b/src/compiler/scala/tools/nsc/typechecker/ContextErrors.scala index 9715fdaf00..20e462bbce 100644 --- a/src/compiler/scala/tools/nsc/typechecker/ContextErrors.scala +++ b/src/compiler/scala/tools/nsc/typechecker/ContextErrors.scala @@ -27,6 +27,16 @@ trait ContextErrors { override def toString() = "[Type error at:" + errPos + "] " + errMsg } + abstract class AbsAmbiguousTypeError extends AbsTypeError + + case class AmbiguousTypeError(errPos: Position, errMsg: String) + extends AbsAmbiguousTypeError + + case class AmbiguousImplicitTypeError(underlyingTree: Tree, errMsg: String) + extends AbsAmbiguousTypeError { + def errPos = underlyingTree.pos + } + sealed abstract class TreeTypeError extends AbsTypeError { def underlyingTree: Tree def errPos = underlyingTree.pos @@ -38,9 +48,6 @@ trait ContextErrors { case class AccessTypeError(underlyingTree: Tree, errMsg: String) extends TreeTypeError - case class AmbiguousTypeError(errPos: Position, errMsg: String) - extends AbsTypeError - case class SymbolTypeError(underlyingSym: Symbol, errMsg: String) extends AbsTypeError { @@ -75,8 +82,6 @@ trait ContextErrors { s"diverging implicit expansion for type ${pt}\nstarting with ${sym.fullLocationString}" } - case class AmbiguousImplicitTypeError(underlyingTree: Tree, errMsg: String) - extends TreeTypeError case class PosAndMsgTypeError(errPos: Position, errMsg: String) extends AbsTypeError @@ -90,10 +95,6 @@ trait ContextErrors { issueTypeError(SymbolTypeError(sym, msg)) } - def issueAmbiguousTypeError(pre: Type, sym1: Symbol, sym2: Symbol, err: AmbiguousTypeError)(implicit context: Context) { - context.issueAmbiguousError(pre, sym1, sym2, err) - } - def issueTypeError(err: AbsTypeError)(implicit context: Context) { context.issue(err) } def typeErrorMsg(found: Type, req: Type) = "type mismatch" + foundReqMsg(found, req) @@ -123,6 +124,36 @@ trait ContextErrors { import ErrorUtils._ + private def MacroIncompatibleEngineError(friendlyMessage: String, internalMessage: String) = { + def debugDiagnostic = s"(internal diagnostic: $internalMessage)" + val message = if (macroDebugLite || macroDebugVerbose) s"$friendlyMessage $debugDiagnostic" else friendlyMessage + // TODO: clean this up! (This is a more explicit version of what the code use to do, to reveal the issue.) + throw new TypeError(analyzer.lastTreeToTyper.pos, message) + } + + def MacroCantExpand210xMacrosError(internalMessage: String) = + MacroIncompatibleEngineError("can't expand macros compiled by previous versions of Scala", internalMessage) + + def MacroCantExpandIncompatibleMacrosError(internalMessage: String) = + MacroIncompatibleEngineError("macro cannot be expanded, because it was compiled by an incompatible macro engine", internalMessage) + + def NoImplicitFoundError(tree: Tree, param: Symbol)(implicit context: Context): Unit = { + def errMsg = { + val paramName = param.name + val paramTp = param.tpe + def evOrParam = ( + if (paramName startsWith nme.EVIDENCE_PARAM_PREFIX) + "evidence parameter of type" + else + s"parameter $paramName:") + paramTp.typeSymbolDirect match { + case ImplicitNotFoundMsg(msg) => msg.format(paramName, paramTp) + case _ => s"could not find implicit value for $evOrParam $paramTp" + } + } + issueNormalTypeError(tree, errMsg) + } + trait TyperContextErrors { self: Typer => @@ -141,24 +172,6 @@ trait ContextErrors { setError(tree) } - def NoImplicitFoundError(tree: Tree, param: Symbol) = { - def errMsg = { - val paramName = param.name - val paramTp = param.tpe - def evOrParam = ( - if (paramName startsWith nme.EVIDENCE_PARAM_PREFIX) - "evidence parameter of type" - else - s"parameter $paramName:" - ) - paramTp.typeSymbolDirect match { - case ImplicitNotFoundMsg(msg) => msg.format(paramName, paramTp) - case _ => s"could not find implicit value for $evOrParam $paramTp" - } - } - issueNormalTypeError(tree, errMsg) - } - def AdaptTypeError(tree: Tree, found: Type, req: Type) = { // SI-3971 unwrapping to the outermost Apply helps prevent confusion with the // error message point. @@ -733,17 +746,6 @@ trait ContextErrors { NormalTypeError(expandee, "too many argument lists for " + fun) } - private def MacroIncompatibleEngineError(friendlyMessage: String, internalMessage: String) = { - def debugDiagnostic = s"(internal diagnostic: $internalMessage)" - val message = if (macroDebugLite || macroDebugVerbose) s"$friendlyMessage $debugDiagnostic" else friendlyMessage - issueNormalTypeError(lastTreeToTyper, message) - } - - def MacroCantExpand210xMacrosError(internalMessage: String) = - MacroIncompatibleEngineError("can't expand macros compiled by previous versions of Scala", internalMessage) - - def MacroCantExpandIncompatibleMacrosError(internalMessage: String) = - MacroIncompatibleEngineError("macro cannot be expanded, because it was compiled by an incompatible macro engine", internalMessage) case object MacroExpansionException extends Exception with scala.util.control.ControlThrowable @@ -883,19 +885,21 @@ trait ContextErrors { val WrongNumber, NoParams, ArgsDoNotConform = Value } - private def ambiguousErrorMsgPos(pos: Position, pre: Type, sym1: Symbol, sym2: Symbol, rest: String) = - if (sym1.hasDefault && sym2.hasDefault && sym1.enclClass == sym2.enclClass) { - val methodName = nme.defaultGetterToMethod(sym1.name) - (sym1.enclClass.pos, - "in "+ sym1.enclClass +", multiple overloaded alternatives of " + methodName + - " define default arguments") - } else { - (pos, - ("ambiguous reference to overloaded definition,\n" + - "both " + sym1 + sym1.locationString + " of type " + pre.memberType(sym1) + - "\nand " + sym2 + sym2.locationString + " of type " + pre.memberType(sym2) + - "\nmatch " + rest) - ) + private def issueAmbiguousTypeErrorUnlessErroneous(pos: Position, pre: Type, sym1: Symbol, sym2: Symbol, rest: String): Unit = + if (!(pre.isErroneous || sym1.isErroneous || sym2.isErroneous)) { + if (sym1.hasDefault && sym2.hasDefault && sym1.enclClass == sym2.enclClass) { + val methodName = nme.defaultGetterToMethod(sym1.name) + context.issueAmbiguousError(AmbiguousTypeError(sym1.enclClass.pos, + "in "+ sym1.enclClass +", multiple overloaded alternatives of " + methodName + + " define default arguments")) + } else { + context.issueAmbiguousError(AmbiguousTypeError(pos, + ("ambiguous reference to overloaded definition,\n" + + "both " + sym1 + sym1.locationString + " of type " + pre.memberType(sym1) + + "\nand " + sym2 + sym2.locationString + " of type " + pre.memberType(sym2) + + "\nmatch " + rest) + )) + } } def AccessError(tree: Tree, sym: Symbol, ctx: Context, explanation: String): AbsTypeError = @@ -952,8 +956,7 @@ trait ContextErrors { val msg0 = "argument types " + argtpes.mkString("(", ",", ")") + (if (pt == WildcardType) "" else " and expected result type " + pt) - val (pos, msg) = ambiguousErrorMsgPos(tree.pos, pre, best, firstCompeting, msg0) - issueAmbiguousTypeError(pre, best, firstCompeting, AmbiguousTypeError(pos, msg)) + issueAmbiguousTypeErrorUnlessErroneous(tree.pos, pre, best, firstCompeting, msg0) setErrorOnLastTry(lastTry, tree) } else setError(tree) // do not even try further attempts because they should all fail // even if this is not the last attempt (because of the SO's possibility on the horizon) @@ -966,8 +969,7 @@ trait ContextErrors { } def AmbiguousExprAlternativeError(tree: Tree, pre: Type, best: Symbol, firstCompeting: Symbol, pt: Type, lastTry: Boolean) = { - val (pos, msg) = ambiguousErrorMsgPos(tree.pos, pre, best, firstCompeting, "expected type " + pt) - issueAmbiguousTypeError(pre, best, firstCompeting, AmbiguousTypeError(pos, msg)) + issueAmbiguousTypeErrorUnlessErroneous(tree.pos, pre, best, firstCompeting, "expected type " + pt) setErrorOnLastTry(lastTry, tree) } diff --git a/src/compiler/scala/tools/nsc/typechecker/Contexts.scala b/src/compiler/scala/tools/nsc/typechecker/Contexts.scala index 72ca9b879a..a79f162140 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Contexts.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Contexts.scala @@ -9,6 +9,7 @@ package typechecker import scala.collection.{ immutable, mutable } import scala.annotation.tailrec import scala.reflect.internal.util.shortClassOfInstance +import scala.tools.nsc.reporters.Reporter /** * @author Martin Odersky @@ -98,7 +99,7 @@ trait Contexts { self: Analyzer => } - def rootContext(unit: CompilationUnit, tree: Tree = EmptyTree, erasedTypes: Boolean = false): Context = { + def rootContext(unit: CompilationUnit, tree: Tree = EmptyTree, throwing: Boolean = false, checking: Boolean = false): Context = { val rootImportsContext = (startContext /: rootImports(unit))((c, sym) => c.make(gen.mkWildcardImport(sym))) // there must be a scala.xml package when xml literals were parsed in this unit @@ -113,18 +114,21 @@ trait Contexts { self: Analyzer => else rootImportsContext.make(gen.mkImport(ScalaXmlPackage, nme.TopScope, nme.dollarScope)) val c = contextWithXML.make(tree, unit = unit) - if (erasedTypes) c.setThrowErrors() else c.setReportErrors() - c(EnrichmentEnabled | ImplicitsEnabled) = !erasedTypes + + c.initRootContext(throwing, checking) c } + def rootContextPostTyper(unit: CompilationUnit, tree: Tree = EmptyTree): Context = + rootContext(unit, tree, throwing = true) + def resetContexts() { startContext.enclosingContextChain foreach { context => context.tree match { case Import(qual, _) => qual setType singleType(qual.symbol.owner.thisType, qual.symbol) case _ => } - context.reportBuffer.clearAll() + context.reporter.clearAll() } } @@ -178,7 +182,8 @@ trait Contexts { self: Analyzer => * @param _outer The next outer context. */ class Context private[typechecker](val tree: Tree, val owner: Symbol, val scope: Scope, - val unit: CompilationUnit, _outer: Context) { + val unit: CompilationUnit, _outer: Context, + private[this] var _reporter: ContextReporter = new ThrowingReporter) { private def outerIsNoContext = _outer eq null final def outer: Context = if (outerIsNoContext) NoContext else _outer @@ -254,8 +259,6 @@ trait Contexts { self: Analyzer => def macrosEnabled = this(MacrosEnabled) def enrichmentEnabled_=(value: Boolean) = this(EnrichmentEnabled) = value def enrichmentEnabled = this(EnrichmentEnabled) - def checking_=(value: Boolean) = this(Checking) = value - def checking = this(Checking) def retyping_=(value: Boolean) = this(ReTyping) = value def retyping = this(ReTyping) def inSecondTry = this(SecondTry) @@ -265,8 +268,9 @@ trait Contexts { self: Analyzer => def defaultModeForTyped: Mode = if (inTypeConstructorAllowed) Mode.NOmode else Mode.EXPRmode - /** These messages are printed when issuing an error */ - var diagnostic: List[String] = Nil + /** To enrich error messages involving default arguments. + When extending the notion, group diagnostics in an object. */ + var diagUsedDefaults: Boolean = false /** Saved type bounds for type parameters which are narrowed in a GADT. */ var savedTypeBounds: List[(Symbol, Type)] = List() @@ -310,7 +314,7 @@ trait Contexts { self: Analyzer => */ def savingUndeterminedTypeParams[A](reportAmbiguous: Boolean = ambiguousErrors)(body: => A): A = { withMode() { - this(AmbiguousErrors) = reportAmbiguous + setAmbiguousErrors(reportAmbiguous) val saved = extractUndetparams() try body finally undetparams = saved @@ -321,54 +325,59 @@ trait Contexts { self: Analyzer => // Error reporting policies and buffer. // - private var _reportBuffer: ReportBuffer = new ReportBuffer - /** A buffer for errors and warnings, used with `this.bufferErrors == true` */ - def reportBuffer = _reportBuffer - /** Discard the current report buffer, and replace with an empty one */ - def useFreshReportBuffer() = _reportBuffer = new ReportBuffer - /** Discard the current report buffer, and replace with `other` */ - def restoreReportBuffer(other: ReportBuffer) = _reportBuffer = other - - /** The first error, if any, in the report buffer */ - def firstError: Option[AbsTypeError] = reportBuffer.firstError - def errors: Seq[AbsTypeError] = reportBuffer.errors - /** Does the report buffer contain any errors? */ - def hasErrors = reportBuffer.hasErrors - - def reportErrors = this(ReportErrors) - def bufferErrors = this(BufferErrors) + // the reporter for this context + def reporter: ContextReporter = _reporter + + // if set, errors will not be reporter/thrown + def bufferErrors = reporter.isBuffering + def reportErrors = !bufferErrors + + // whether to *report* (which is separate from buffering/throwing) ambiguity errors def ambiguousErrors = this(AmbiguousErrors) - def throwErrors = contextMode.inNone(ReportErrors | BufferErrors) - - def setReportErrors(): Unit = set(enable = ReportErrors | AmbiguousErrors, disable = BufferErrors) - def setBufferErrors(): Unit = set(enable = BufferErrors, disable = ReportErrors | AmbiguousErrors) - def setThrowErrors(): Unit = this(ReportErrors | AmbiguousErrors | BufferErrors) = false - def setAmbiguousErrors(report: Boolean): Unit = this(AmbiguousErrors) = report - - /** Append the given errors to the report buffer */ - def updateBuffer(errors: Traversable[AbsTypeError]) = reportBuffer ++= errors - /** Clear all errors from the report buffer */ - def flushBuffer() { reportBuffer.clearAllErrors() } - /** Return and clear all errors from the report buffer */ - def flushAndReturnBuffer(): immutable.Seq[AbsTypeError] = { - val current = reportBuffer.errors - reportBuffer.clearAllErrors() - current - } - /** Issue and clear all warnings from the report buffer */ - def flushAndIssueWarnings() { - reportBuffer.warnings foreach { - case (pos, msg) => reporter.warning(pos, msg) + private def setAmbiguousErrors(report: Boolean): Unit = this(AmbiguousErrors) = report + + /** + * Try inference twice: once without views and once with views, + * unless views are already disabled. + */ + abstract class TryTwice { + def tryOnce(isLastTry: Boolean): Unit + + final def apply(): Unit = { + val doLastTry = + // do first try if implicits are enabled + if (implicitsEnabled) { + // We create a new BufferingReporter to + // distinguish errors that occurred before entering tryTwice + // and our first attempt in 'withImplicitsDisabled'. If the + // first attempt fails, we try with implicits on + // and the original reporter. + // immediate reporting of ambiguous errors is suppressed, so that they are buffered + inSilentMode { + try { + set(disable = ImplicitsEnabled | EnrichmentEnabled) // restored by inSilentMode + tryOnce(false) + reporter.hasErrors + } catch { + case ex: CyclicReference => throw ex + case ex: TypeError => true // recoverable cyclic references? + } + } + } else true + + // do last try if try with implicits enabled failed + // (or if it was not attempted because they were disabled) + if (doLastTry) + tryOnce(true) } - reportBuffer.clearAllWarnings() } // // Temporary mode adjustment // - @inline def withMode[T](enabled: ContextMode = NOmode, disabled: ContextMode = NOmode)(op: => T): T = { + @inline final def withMode[T](enabled: ContextMode = NOmode, disabled: ContextMode = NOmode)(op: => T): T = { val saved = contextMode set(enabled, disabled) try op @@ -402,12 +411,18 @@ trait Contexts { self: Analyzer => // See comment on FormerNonStickyModes. @inline final def withOnlyStickyModes[T](op: => T): T = withMode(disabled = FormerNonStickyModes)(op) - /** @return true if the `expr` evaluates to true within a silent Context that incurs no errors */ + // inliner note: this has to be a simple method for inlining to work -- moved the `&& !reporter.hasErrors` out @inline final def inSilentMode(expr: => Boolean): Boolean = { - withMode() { // withMode with no arguments to restore the mode mutated by `setBufferErrors`. - setBufferErrors() - try expr && !hasErrors - finally reportBuffer.clearAll() + val savedContextMode = contextMode + val savedReporter = reporter + + setAmbiguousErrors(false) + _reporter = new BufferingReporter + + try expr + finally { + contextMode = savedContextMode + _reporter = savedReporter } } @@ -423,7 +438,8 @@ trait Contexts { self: Analyzer => * `Context#imports`. */ def make(tree: Tree = tree, owner: Symbol = owner, - scope: Scope = scope, unit: CompilationUnit = unit): Context = { + scope: Scope = scope, unit: CompilationUnit = unit, + reporter: ContextReporter = this.reporter): Context = { val isTemplateOrPackage = tree match { case _: Template | _: PackageDef => true case _ => false @@ -446,16 +462,15 @@ trait Contexts { self: Analyzer => // The blank canvas val c = if (isImport) - new Context(tree, owner, scope, unit, this) with ImportContext + new Context(tree, owner, scope, unit, this, reporter) with ImportContext else - new Context(tree, owner, scope, unit, this) + new Context(tree, owner, scope, unit, this, reporter) // Fields that are directly propagated c.variance = variance - c.diagnostic = diagnostic + c.diagUsedDefaults = diagUsedDefaults c.openImplicits = openImplicits c.contextMode = contextMode // note: ConstructorSuffix, a bit within `mode`, is conditionally overwritten below. - c._reportBuffer = reportBuffer // Fields that may take on a different value in the child c.prefix = prefixInChild @@ -470,22 +485,38 @@ trait Contexts { self: Analyzer => c } + /** Use reporter (possibly buffered) for errors/warnings and enable implicit conversion **/ + def initRootContext(throwing: Boolean = false, checking: Boolean = false): Unit = { + _reporter = + if (checking) new CheckingReporter + else if (throwing) new ThrowingReporter + else new ImmediateReporter + + setAmbiguousErrors(!throwing) + this(EnrichmentEnabled | ImplicitsEnabled) = !throwing + } + def make(tree: Tree, owner: Symbol, scope: Scope): Context = // TODO SI-7345 Moving this optimization into the main overload of `make` causes all tests to fail. - // even if it is extened to check that `unit == this.unit`. Why is this? + // even if it is extended to check that `unit == this.unit`. Why is this? if (tree == this.tree && owner == this.owner && scope == this.scope) this else make(tree, owner, scope, unit) /** Make a child context that represents a new nested scope */ - def makeNewScope(tree: Tree, owner: Symbol): Context = - make(tree, owner, newNestedScope(scope)) + def makeNewScope(tree: Tree, owner: Symbol, reporter: ContextReporter = this.reporter): Context = + make(tree, owner, newNestedScope(scope), reporter = reporter) /** Make a child context that buffers errors and warnings into a fresh report buffer. */ def makeSilent(reportAmbiguousErrors: Boolean = ambiguousErrors, newtree: Tree = tree): Context = { - val c = make(newtree) - c.setBufferErrors() + // A fresh buffer so as not to leak errors/warnings into `this`. + val c = make(newtree, reporter = new BufferingReporter) c.setAmbiguousErrors(reportAmbiguousErrors) - c._reportBuffer = new ReportBuffer // A fresh buffer so as not to leak errors/warnings into `this`. + c + } + + def makeNonSilent(newtree: Tree): Context = { + val c = make(newtree, reporter = reporter.makeImmediate) + c.setAmbiguousErrors(true) c } @@ -508,7 +539,9 @@ trait Contexts { self: Analyzer => */ def makeConstructorContext = { val baseContext = enclClass.outer.nextEnclosing(!_.tree.isInstanceOf[Template]) - val argContext = baseContext.makeNewScope(tree, owner) + // must propagate reporter! + // (caught by neg/t3649 when refactoring reporting to be specified only by this.reporter and not also by this.contextMode) + val argContext = baseContext.makeNewScope(tree, owner, reporter = this.reporter) argContext.contextMode = contextMode argContext.inSelfSuperCall = true def enterElems(c: Context) { @@ -533,65 +566,16 @@ trait Contexts { self: Analyzer => // Error and warning issuance // - private def addDiagString(msg: String) = { - val ds = - if (diagnostic.isEmpty) "" - else diagnostic.mkString("\n","\n", "") - if (msg endsWith ds) msg else msg + ds - } - - private def unitError(pos: Position, msg: String): Unit = - if (checking) onTreeCheckerError(pos, msg) else reporter.error(pos, msg) - - @inline private def issueCommon(err: AbsTypeError)(pf: PartialFunction[AbsTypeError, Unit]) { - // TODO: are errors allowed to have pos == NoPosition?? - // if not, Jason suggests doing: val pos = err.errPos.orElse( { devWarning("Que?"); context.tree.pos }) - if (settings.Yissuedebug) { - log("issue error: " + err.errMsg) - (new Exception).printStackTrace() - } - if (pf isDefinedAt err) pf(err) - else if (bufferErrors) { reportBuffer += err } - else throw new TypeError(err.errPos, err.errMsg) - } - /** Issue/buffer/throw the given type error according to the current mode for error reporting. */ - def issue(err: AbsTypeError) { - issueCommon(err) { case _ if reportErrors => - unitError(err.errPos, addDiagString(err.errMsg)) - } - } - - /** Issue/buffer/throw the given implicit ambiguity error according to the current mode for error reporting. */ - def issueAmbiguousError(pre: Type, sym1: Symbol, sym2: Symbol, err: AbsTypeError) { - issueCommon(err) { case _ if ambiguousErrors => - if (!pre.isErroneous && !sym1.isErroneous && !sym2.isErroneous) - unitError(err.errPos, err.errMsg) - } - } - + private[typechecker] def issue(err: AbsTypeError) = reporter.issue(err)(this) /** Issue/buffer/throw the given implicit ambiguity error according to the current mode for error reporting. */ - def issueAmbiguousError(err: AbsTypeError) { - issueCommon(err) { case _ if ambiguousErrors => unitError(err.errPos, addDiagString(err.errMsg)) } - } - - /** Issue/throw the given `err` according to the current mode for error reporting. */ - def error(pos: Position, err: Throwable) = - if (reportErrors) unitError(pos, addDiagString(err.getMessage())) - else throw err - + private[typechecker] def issueAmbiguousError(err: AbsAmbiguousTypeError) = reporter.issueAmbiguousError(err)(this) /** Issue/throw the given error message according to the current mode for error reporting. */ - def error(pos: Position, msg: String) = { - val msg1 = addDiagString(msg) - if (reportErrors) unitError(pos, msg1) - else throw new TypeError(pos, msg1) - } - + def error(pos: Position, msg: String) = reporter.error(pos, msg) /** Issue/throw the given error message according to the current mode for error reporting. */ - def warning(pos: Position, msg: String, force: Boolean = false) { - if (reportErrors || force) reporter.warning(pos, msg) - else if (bufferErrors) reportBuffer += (pos -> msg) - } + def warning(pos: Position, msg: String) = reporter.warning(pos, msg) + def echo(pos: Position, msg: String) = reporter.echo(pos, msg) + def deprecationWarning(pos: Position, sym: Symbol, msg: String): Unit = currentRun.reporting.deprecationWarning(pos, sym, msg) @@ -601,7 +585,6 @@ trait Contexts { self: Analyzer => def featureWarning(pos: Position, featureName: String, featureDesc: String, featureTrait: Symbol, construct: => String = "", required: Boolean): Unit = currentRun.reporting.featureWarning(pos, featureName, featureDesc, featureTrait, construct, required) - def echo(pos: Position, msg: String): Unit = reporter.echo(pos, msg) // nextOuter determines which context is searched next for implicits // (after `this`, which contributes `newImplicits` below.) In @@ -1238,61 +1221,176 @@ trait Contexts { self: Analyzer => override final def toString = super.toString + " with " + s"ImportContext { $impInfo; outer.owner = ${outer.owner} }" } - /** A buffer for warnings and errors that are accumulated during speculative type checking. */ - final class ReportBuffer { + /** A reporter for use during type checking. It has multiple modes for handling errors. + * + * The default (immediate mode) is to send the error to the global reporter. + * When switched into buffering mode via makeBuffering, errors and warnings are buffered and not be reported + * (there's a special case for ambiguity errors for some reason: those are force to the reporter when context.ambiguousErrors, + * or else they are buffered -- TODO: can we simplify this?) + * + * When using the type checker after typers, an error results in a TypeError being thrown. TODO: get rid of this mode. + * + * To handle nested contexts, reporters share buffers. TODO: only buffer in BufferingReporter, emit immediately in ImmediateReporter + */ + abstract class ContextReporter(private[this] var _errorBuffer: mutable.LinkedHashSet[AbsTypeError] = null, private[this] var _warningBuffer: mutable.LinkedHashSet[(Position, String)] = null) extends Reporter { type Error = AbsTypeError type Warning = (Position, String) - private def newBuffer[A] = mutable.LinkedHashSet.empty[A] // Important to use LinkedHS for stable results. + def issue(err: AbsTypeError)(implicit context: Context): Unit = handleError(err.errPos, addDiagString(err.errMsg)) - // [JZ] Contexts, pre- the SI-7345 refactor, avoided allocating the buffers until needed. This - // is replicated here out of conservatism. - private var _errorBuffer: mutable.LinkedHashSet[Error] = _ - private def errorBuffer = {if (_errorBuffer == null) _errorBuffer = newBuffer; _errorBuffer} - def errors: immutable.Seq[Error] = errorBuffer.toVector + protected def handleError(pos: Position, msg: String): Unit + protected def handleSuppressedAmbiguous(err: AbsAmbiguousTypeError): Unit = () + protected def handleWarning(pos: Position, msg: String): Unit = reporter.warning(pos, msg) - private var _warningBuffer: mutable.LinkedHashSet[Warning] = _ - private def warningBuffer = {if (_warningBuffer == null) _warningBuffer = newBuffer; _warningBuffer} - def warnings: immutable.Seq[Warning] = warningBuffer.toVector + def makeImmediate: ContextReporter = this + def makeBuffering: ContextReporter = this + def isBuffering: Boolean = false - def +=(error: AbsTypeError): this.type = { - errorBuffer += error - this - } - def ++=(errors: Traversable[AbsTypeError]): this.type = { - errorBuffer ++= errors - this - } - def +=(warning: Warning): this.type = { - warningBuffer += warning - this + /** Emit an ambiguous error according to context.ambiguousErrors + * + * - when true, use global.reporter regardless of whether we're buffering (TODO: can we change this?) + * - else, let this context reporter decide + */ + final def issueAmbiguousError(err: AbsAmbiguousTypeError)(implicit context: Context): Unit = + if (context.ambiguousErrors) reporter.error(err.errPos, addDiagString(err.errMsg)) // force reporting... see TODO above + else handleSuppressedAmbiguous(err) + + @inline final def withFreshErrorBuffer[T](expr: => T): T = { + val previousBuffer = _errorBuffer + _errorBuffer = newBuffer + val res = expr // expr will read _errorBuffer + _errorBuffer = previousBuffer + res } - def clearAll(): this.type = { - clearAllErrors(); clearAllWarnings(); + @inline final def propagatingErrorsTo[T](target: ContextReporter)(expr: => T): T = { + val res = expr // TODO: make sure we're okay skipping the try/finally overhead + if ((this ne target) && hasErrors) { // `this eq target` in e.g., test/files/neg/divergent-implicit.scala + // assert(target.errorBuffer ne _errorBuffer) + target ++= errors + // TODO: is clearAllErrors necessary? (no tests failed when dropping it) + // NOTE: even though `this ne target`, it may still be that `target.errorBuffer eq _errorBuffer`, + // so don't clear the buffer, but null out the reference so that a new one will be created when necessary (should be never??) + // (we should refactor error buffering to avoid mutation on shared buffers) + clearAllErrors() + } + res } - def clearAllErrors(): this.type = { - errorBuffer.clear() - this - } - def clearErrors(removeF: PartialFunction[AbsTypeError, Boolean]): this.type = { - errorBuffer.retain(!PartialFunction.cond(_)(removeF)) - this + protected final def info0(pos: Position, msg: String, severity: Severity, force: Boolean): Unit = + severity match { + case ERROR => handleError(pos, msg) + case WARNING => handleWarning(pos, msg) + case INFO => reporter.echo(pos, msg) + } + + final override def hasErrors = super.hasErrors || errorBuffer.nonEmpty + + // TODO: everything below should be pushed down to BufferingReporter (related to buffering) + // Implicit relies on this most heavily, but there you know reporter.isInstanceOf[BufferingReporter] + // can we encode this statically? + + // have to pass in context because multiple contexts may share the same ReportBuffer + def reportFirstDivergentError(fun: Tree, param: Symbol, paramTp: Type)(implicit context: Context): Unit = + errors.collectFirst { + case dte: DivergentImplicitTypeError => dte + } match { + case Some(divergent) => + // DivergentImplicit error has higher priority than "no implicit found" + // no need to issue the problem again if we are still in silent mode + if (context.reportErrors) { + context.issue(divergent.withPt(paramTp)) + errorBuffer.retain { + case dte: DivergentImplicitTypeError => false + case _ => true + } + } + case _ => + NoImplicitFoundError(fun, param)(context) + } + + def retainDivergentErrorsExcept(saved: DivergentImplicitTypeError) = + errorBuffer.retain { + case err: DivergentImplicitTypeError => err ne saved + case _ => false + } + + def propagateImplicitTypeErrorsTo(target: ContextReporter) = { + errors foreach { + case err@(_: DivergentImplicitTypeError | _: AmbiguousImplicitTypeError) => + target.errorBuffer += err + case _ => + } + // debuglog("propagateImplicitTypeErrorsTo: " + errors) } - def retainErrors(leaveF: PartialFunction[AbsTypeError, Boolean]): this.type = { - errorBuffer.retain(PartialFunction.cond(_)(leaveF)) - this + + protected def addDiagString(msg: String)(implicit context: Context): String = { + val diagUsedDefaultsMsg = "Error occurred in an application involving default arguments." + if (context.diagUsedDefaults && !(msg endsWith diagUsedDefaultsMsg)) msg + "\n" + diagUsedDefaultsMsg + else msg } - def clearAllWarnings(): this.type = { - warningBuffer.clear() - this + + final def emitWarnings() = if (_warningBuffer != null) { + _warningBuffer foreach { + case (pos, msg) => reporter.warning(pos, msg) + } + _warningBuffer = null } - def hasErrors = errorBuffer.nonEmpty - def firstError = errorBuffer.headOption + // [JZ] Contexts, pre- the SI-7345 refactor, avoided allocating the buffers until needed. This + // is replicated here out of conservatism. + private def newBuffer[A] = mutable.LinkedHashSet.empty[A] // Important to use LinkedHS for stable results. + final protected def errorBuffer = { if (_errorBuffer == null) _errorBuffer = newBuffer; _errorBuffer } + final protected def warningBuffer = { if (_warningBuffer == null) _warningBuffer = newBuffer; _warningBuffer } + + final def errors: immutable.Seq[Error] = errorBuffer.toVector + final def warnings: immutable.Seq[Warning] = warningBuffer.toVector + final def firstError: Option[AbsTypeError] = errorBuffer.headOption + + // TODO: remove ++= and clearAll* entirely in favor of more high-level combinators like withFreshErrorBuffer + final private[typechecker] def ++=(errors: Traversable[AbsTypeError]): Unit = errorBuffer ++= errors + + // null references to buffers instead of clearing them, + // as the buffers may be shared between different reporters + final def clearAll(): Unit = { _errorBuffer = null; _warningBuffer = null } + final def clearAllErrors(): Unit = { _errorBuffer = null } + } + + private[typechecker] class ImmediateReporter(_errorBuffer: mutable.LinkedHashSet[AbsTypeError] = null, _warningBuffer: mutable.LinkedHashSet[(Position, String)] = null) extends ContextReporter(_errorBuffer, _warningBuffer) { + override def makeBuffering: ContextReporter = new BufferingReporter(errorBuffer, warningBuffer) + protected def handleError(pos: Position, msg: String): Unit = reporter.error(pos, msg) + } + + + private[typechecker] class BufferingReporter(_errorBuffer: mutable.LinkedHashSet[AbsTypeError] = null, _warningBuffer: mutable.LinkedHashSet[(Position, String)] = null) extends ContextReporter(_errorBuffer, _warningBuffer) { + override def isBuffering = true + + override def issue(err: AbsTypeError)(implicit context: Context): Unit = errorBuffer += err + + // this used to throw new TypeError(pos, msg) -- buffering lets us report more errors (test/files/neg/macro-basic-mamdmi) + // the old throwing behavior was relied on by diagnostics in manifestOfType + protected def handleError(pos: Position, msg: String): Unit = errorBuffer += TypeErrorWrapper(new TypeError(pos, msg)) + override protected def handleSuppressedAmbiguous(err: AbsAmbiguousTypeError): Unit = errorBuffer += err + override protected def handleWarning(pos: Position, msg: String): Unit = warningBuffer += ((pos, msg)) + + // TODO: emit all buffered errors, warnings + override def makeImmediate: ContextReporter = new ImmediateReporter(errorBuffer, warningBuffer) } + /** Used after typer (specialization relies on TypeError being thrown, among other post-typer phases). + * + * TODO: get rid of it, use ImmediateReporter and a check for reporter.hasErrors where necessary + */ + private[typechecker] class ThrowingReporter extends ContextReporter { + protected def handleError(pos: Position, msg: String): Unit = throw new TypeError(pos, msg) + } + + /** Used during a run of [[scala.tools.nsc.typechecker.TreeCheckers]]? */ + private[typechecker] class CheckingReporter extends ContextReporter { + protected def handleError(pos: Position, msg: String): Unit = onTreeCheckerError(pos, msg) + } + + class ImportInfo(val tree: Import, val depth: Int) { def pos = tree.pos def posOf(sel: ImportSelector) = tree.pos withPoint sel.namePos @@ -1385,8 +1483,6 @@ object ContextMode { def apply(bits: Int): ContextMode = new ContextMode(bits) final val NOmode: ContextMode = 0 - final val ReportErrors: ContextMode = 1 << 0 - final val BufferErrors: ContextMode = 1 << 1 final val AmbiguousErrors: ContextMode = 1 << 2 /** Are we in a secondary constructor after the this constructor call? */ @@ -1409,8 +1505,6 @@ object ContextMode { /** To selectively allow enrichment in patterns, where other kinds of implicit conversions are not allowed */ final val EnrichmentEnabled: ContextMode = 1 << 8 - /** Are we in a run of [[scala.tools.nsc.typechecker.TreeCheckers]]? */ - final val Checking: ContextMode = 1 << 9 /** Are we retypechecking arguments independently from the function applied to them? See `Typer.tryTypedApply` * TODO - iron out distinction/overlap with SecondTry. @@ -1447,17 +1541,14 @@ object ContextMode { PatternAlternative | StarPatterns | SuperInit | SecondTry | ReturnExpr | TypeConstructorAllowed ) - final val DefaultMode: ContextMode = MacrosEnabled + final val DefaultMode: ContextMode = MacrosEnabled private val contextModeNameMap = Map( - ReportErrors -> "ReportErrors", - BufferErrors -> "BufferErrors", AmbiguousErrors -> "AmbiguousErrors", ConstructorSuffix -> "ConstructorSuffix", SelfSuperCall -> "SelfSuperCall", ImplicitsEnabled -> "ImplicitsEnabled", MacrosEnabled -> "MacrosEnabled", - Checking -> "Checking", ReTyping -> "ReTyping", PatternAlternative -> "PatternAlternative", StarPatterns -> "StarPatterns", diff --git a/src/compiler/scala/tools/nsc/typechecker/Implicits.scala b/src/compiler/scala/tools/nsc/typechecker/Implicits.scala index 73c3e6f016..b85c8e6d42 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Implicits.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Implicits.scala @@ -71,13 +71,10 @@ trait Implicits { typingStack.printTyping(tree, "typing implicit: %s %s".format(tree, context.undetparamsString)) val implicitSearchContext = context.makeImplicit(reportAmbiguous) val result = new ImplicitSearch(tree, pt, isView, implicitSearchContext, pos).bestImplicit - if (result.isFailure && saveAmbiguousDivergent && implicitSearchContext.hasErrors) { - context.updateBuffer(implicitSearchContext.reportBuffer.errors.collect { - case dte: DivergentImplicitTypeError => dte - case ate: AmbiguousImplicitTypeError => ate - }) - debuglog("update buffer: " + implicitSearchContext.reportBuffer.errors) - } + + if (result.isFailure && saveAmbiguousDivergent && implicitSearchContext.reporter.hasErrors) + implicitSearchContext.reporter.propagateImplicitTypeErrorsTo(context.reporter) + // SI-7944 undetermined type parameters that result from inference within typedImplicit land in // `implicitSearchContext.undetparams`, *not* in `context.undetparams` // Here, we copy them up to parent context (analogously to the way the errors are copied above), @@ -99,7 +96,7 @@ trait Implicits { def wrapper(inference: => SearchResult) = wrapper1(inference) val result = wrapper(inferImplicit(tree, pt, reportAmbiguous = true, isView = isView, context = context, saveAmbiguousDivergent = !silent, pos = pos)) if (result.isFailure && !silent) { - val err = context.firstError + val err = context.reporter.firstError val errPos = err.map(_.errPos).getOrElse(pos) val errMsg = err.map(_.errMsg).getOrElse("implicit search has failed. to find out the reason, turn on -Xlog-implicits") onError(errPos, errMsg) @@ -635,7 +632,7 @@ trait Implicits { } case _ => fallback } - context.firstError match { // using match rather than foreach to avoid non local return. + context.reporter.firstError match { // using match rather than foreach to avoid non local return. case Some(err) => log("implicit adapt failed: " + err.errMsg) return fail(err.errMsg) @@ -658,8 +655,8 @@ trait Implicits { } } - if (context.hasErrors) - fail("hasMatchingSymbol reported error: " + context.firstError.get.errMsg) + if (context.reporter.hasErrors) + fail("hasMatchingSymbol reported error: " + context.reporter.firstError.get.errMsg) else if (itree3.isErroneous) fail("error typechecking implicit candidate") else if (isLocalToCallsite && !hasMatchingSymbol(itree2)) @@ -677,7 +674,7 @@ trait Implicits { // #2421: check that we correctly instantiated type parameters outside of the implicit tree: checkBounds(itree3, NoPrefix, NoSymbol, undetParams, targs, "inferred ") - context.firstError match { + context.reporter.firstError match { case Some(err) => return fail("type parameters weren't correctly instantiated outside of the implicit tree: " + err.errMsg) case None => @@ -716,7 +713,7 @@ trait Implicits { case t => t } - context.firstError match { + context.reporter.firstError match { case Some(err) => fail("typing TypeApply reported errors for the implicit tree: " + err.errMsg) case None => @@ -857,13 +854,11 @@ trait Implicits { SearchFailure } else { if (search.isFailure) { - // We don't want errors that occur during checking implicit info + // Discard the divergentError we saved (if any), as well as all errors that are not of type DivergentImplicitTypeError + // We don't want errors that occur while checking the implicit info // to influence the check of further infos, but we should retain divergent implicit errors // (except for the one we already squirreled away) - val saved = divergentError.getOrElse(null) - context.reportBuffer.retainErrors { - case err: DivergentImplicitTypeError => err ne saved - } + context.reporter.retainDivergentErrorsExcept(divergentError.getOrElse(null)) } search } @@ -909,7 +904,7 @@ trait Implicits { // the first `DivergentImplicitTypeError` that is being propagated // from a nested implicit search; this one will be // re-issued if this level of the search fails. - DivergentImplicitRecovery(typedFirstPending, firstPending, context.errors) match { + DivergentImplicitRecovery(typedFirstPending, firstPending, context.reporter.errors) match { case sr if sr.isDivergent => Nil case sr if sr.isFailure => rankImplicits(otherPending, acc) case newBest => @@ -1146,7 +1141,7 @@ trait Implicits { try { val tree1 = typedPos(pos.focus)(arg) - context.firstError match { + context.reporter.firstError match { case Some(err) => processMacroExpansionError(err.errPos, err.errMsg) case None => new SearchResult(tree1, EmptyTreeTypeSubstituter, Nil) } @@ -1278,19 +1273,20 @@ trait Implicits { if (tagInScope.isEmpty) mot(tp, Nil, Nil) else { if (ReflectRuntimeUniverse == NoSymbol) { - // todo. write a test for this - context.error(pos, + // TODO: write a test for this (the next error message is already checked by neg/interop_typetags_without_classtags_arenot_manifests.scala) + // TODO: this was using context.error, and implicit search always runs in silent mode, thus it was actually throwing a TypeError + // with the new strategy-based reporting, a BufferingReporter buffers instead of throwing + // it would be good to rework this logic to fit into the regular context.error mechanism + throw new TypeError(pos, sm"""to create a manifest here, it is necessary to interoperate with the type tag `$tagInScope` in scope. |however typetag -> manifest conversion requires Scala reflection, which is not present on the classpath. |to proceed put scala-reflect.jar on your compilation classpath and recompile.""") - return SearchFailure } if (resolveClassTag(pos, tp, allowMaterialization = true) == EmptyTree) { - context.error(pos, + throw new TypeError(pos, sm"""to create a manifest here, it is necessary to interoperate with the type tag `$tagInScope` in scope. |however typetag -> manifest conversion requires a class tag for the corresponding type to be present. |to proceed add a class tag to the type `$tp` (e.g. by introducing a context bound) and recompile.""") - return SearchFailure } val cm = typed(Ident(ReflectRuntimeCurrentMirror)) val internal = gen.mkAttributedSelect(gen.mkAttributedRef(ReflectRuntimeUniverse), UniverseInternal) @@ -1346,52 +1342,66 @@ trait Implicits { * If all fails return SearchFailure */ def bestImplicit: SearchResult = { - val failstart = if (Statistics.canEnable) Statistics.startTimer(inscopeFailNanos) else null - val succstart = if (Statistics.canEnable) Statistics.startTimer(inscopeSucceedNanos) else null + val stats = Statistics.canEnable + val failstart = if (stats) Statistics.startTimer(inscopeFailNanos) else null + val succstart = if (stats) Statistics.startTimer(inscopeSucceedNanos) else null var result = searchImplicit(context.implicitss, isLocalToCallsite = true) - if (result.isFailure) { - if (Statistics.canEnable) Statistics.stopTimer(inscopeFailNanos, failstart) - } else { - if (Statistics.canEnable) Statistics.stopTimer(inscopeSucceedNanos, succstart) - if (Statistics.canEnable) Statistics.incCounter(inscopeImplicitHits) + if (stats) { + if (result.isFailure) Statistics.stopTimer(inscopeFailNanos, failstart) + else { + Statistics.stopTimer(inscopeSucceedNanos, succstart) + Statistics.incCounter(inscopeImplicitHits) + } } + if (result.isFailure) { - val previousErrs = context.flushAndReturnBuffer() - val failstart = if (Statistics.canEnable) Statistics.startTimer(oftypeFailNanos) else null - val succstart = if (Statistics.canEnable) Statistics.startTimer(oftypeSucceedNanos) else null + val failstart = if (stats) Statistics.startTimer(oftypeFailNanos) else null + val succstart = if (stats) Statistics.startTimer(oftypeSucceedNanos) else null + + // SI-6667, never search companions after an ambiguous error in in-scope implicits + val wasAmbigious = result.isAmbiguousFailure + + // TODO: encapsulate + val previousErrs = context.reporter.errors + context.reporter.clearAllErrors() - val wasAmbigious = result.isAmbiguousFailure // SI-6667, never search companions after an ambiguous error in in-scope implicits result = materializeImplicit(pt) + // `materializeImplicit` does some preprocessing for `pt` // is it only meant for manifests/tags or we need to do the same for `implicitsOfExpectedType`? if (result.isFailure && !wasAmbigious) result = searchImplicit(implicitsOfExpectedType, isLocalToCallsite = false) - if (result.isFailure) { - context.updateBuffer(previousErrs) - if (Statistics.canEnable) Statistics.stopTimer(oftypeFailNanos, failstart) - } else { - if (Statistics.canEnable) Statistics.stopTimer(oftypeSucceedNanos, succstart) - if (Statistics.canEnable) Statistics.incCounter(oftypeImplicitHits) + if (result.isFailure) + context.reporter ++= previousErrs + + if (stats) { + if (result.isFailure) Statistics.stopTimer(oftypeFailNanos, failstart) + else { + Statistics.stopTimer(oftypeSucceedNanos, succstart) + Statistics.incCounter(oftypeImplicitHits) + } } } if (result.isSuccess && isView) { def maybeInvalidConversionError(msg: String) { // We have to check context.ambiguousErrors even though we are calling "issueAmbiguousError" // which ostensibly does exactly that before issuing the error. Why? I have no idea. Test is pos/t7690. + // AM: I would guess it's because ambiguous errors will be buffered in silent mode if they are not reported if (context.ambiguousErrors) context.issueAmbiguousError(AmbiguousImplicitTypeError(tree, msg)) } pt match { case Function1(_, out) => - def prohibit(sym: Symbol) = if (sym.tpe <:< out) { - maybeInvalidConversionError(s"the result type of an implicit conversion must be more specific than ${sym.name}") - result = SearchFailure + // must inline to avoid capturing result + def prohibit(sym: Symbol) = (sym.tpe <:< out) && { + maybeInvalidConversionError(s"the result type of an implicit conversion must be more specific than ${sym.name}") + true } - prohibit(AnyRefClass) - if (settings.isScala211) prohibit(AnyValClass) + if (prohibit(AnyRefClass) || (settings.isScala211 && prohibit(AnyValClass))) + result = SearchFailure case _ => false } if (settings.isScala211 && isInvalidConversionSource(pt)) { @@ -1399,8 +1409,9 @@ trait Implicits { result = SearchFailure } } - if (result.isFailure) - debuglog("no implicits found for "+pt+" "+pt.typeSymbol.info.baseClasses+" "+implicitsOfExpectedType) + + if (result.isFailure && settings.debug) // debuglog is not inlined for some reason + log("no implicits found for "+pt+" "+pt.typeSymbol.info.baseClasses+" "+implicitsOfExpectedType) result } @@ -1422,20 +1433,19 @@ trait Implicits { val eligible = new ImplicitComputation(iss, isLocalToCallsite).eligible eligible.toList.flatMap { (ii: ImplicitInfo) => - // each ImplicitInfo contributes a distinct set of constraints (generated indirectly by typedImplicit) - // thus, start each type var off with a fresh for every typedImplicit - resetTVars() - // any previous errors should not affect us now - context.flushBuffer() - - val res = typedImplicit(ii, ptChecked = false, isLocalToCallsite) - if (res.tree ne EmptyTree) List((res, tvars map (_.constr))) - else Nil + // each ImplicitInfo contributes a distinct set of constraints (generated indirectly by typedImplicit) + // thus, start each type var off with a fresh for every typedImplicit + resetTVars() + // any previous errors should not affect us now + context.reporter.clearAllErrors() + val res = typedImplicit(ii, ptChecked = false, isLocalToCallsite) + if (res.tree ne EmptyTree) List((res, tvars map (_.constr))) + else Nil + } } - } eligibleInfos(context.implicitss, isLocalToCallsite = true) ++ eligibleInfos(implicitsOfExpectedType, isLocalToCallsite = false) - } + } } object ImplicitNotFoundMsg { diff --git a/src/compiler/scala/tools/nsc/typechecker/Infer.scala b/src/compiler/scala/tools/nsc/typechecker/Infer.scala index a3f1da60ce..fb7651ffd6 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Infer.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Infer.scala @@ -199,8 +199,6 @@ trait Infer extends Checkable { def getContext = context - def issue(err: AbsTypeError): Unit = context.issue(err) - def explainTypes(tp1: Type, tp2: Type) = { if (context.reportErrors) withDisambiguation(List(), tp1, tp2)(global.explainTypes(tp1, tp2)) @@ -781,7 +779,7 @@ trait Infer extends Checkable { def applicableExpectingPt(pt: Type): Boolean = { val silent = context.makeSilent(reportAmbiguousErrors = false) val result = newTyper(silent).infer.isApplicable(undetparams, ftpe, argtpes0, pt) - if (silent.hasErrors && !pt.isWildcard) + if (silent.reporter.hasErrors && !pt.isWildcard) applicableExpectingPt(WildcardType) // second try else result @@ -1266,33 +1264,36 @@ trait Infer extends Checkable { * If no alternative matches `pt`, take the parameterless one anyway. */ def inferExprAlternative(tree: Tree, pt: Type): Tree = { - def tryOurBests(pre: Type, alts: List[Symbol], isSecondTry: Boolean): Unit = { - val alts0 = alts filter (alt => isWeaklyCompatible(pre memberType alt, pt)) - val alts1 = if (alts0.isEmpty) alts else alts0 - val bests = bestAlternatives(alts1) { (sym1, sym2) => - val tp1 = pre memberType sym1 - val tp2 = pre memberType sym2 - - ( (tp2 eq ErrorType) - || isWeaklyCompatible(tp1, pt) && !isWeaklyCompatible(tp2, pt) - || isStrictlyMoreSpecific(tp1, tp2, sym1, sym2) - ) - } - // todo: missing test case for bests.isEmpty - bests match { - case best :: Nil => tree setSymbol best setType (pre memberType best) - case best :: competing :: _ if alts0.nonEmpty => - // SI-6912 Don't give up and leave an OverloadedType on the tree. - // Originally I wrote this as `if (secondTry) ... `, but `tryTwice` won't attempt the second try - // unless an error is issued. We're not issuing an error, in the assumption that it would be - // spurious in light of the erroneous expected type - if (pt.isErroneous) setError(tree) - else AmbiguousExprAlternativeError(tree, pre, best, competing, pt, isSecondTry) - case _ => if (bests.isEmpty || alts0.isEmpty) NoBestExprAlternativeError(tree, pt, isSecondTry) + val c = context + class InferTwice(pre: Type, alts: List[Symbol]) extends c.TryTwice { + def tryOnce(isSecondTry: Boolean): Unit = { + val alts0 = alts filter (alt => isWeaklyCompatible(pre memberType alt, pt)) + val alts1 = if (alts0.isEmpty) alts else alts0 + val bests = bestAlternatives(alts1) { (sym1, sym2) => + val tp1 = pre memberType sym1 + val tp2 = pre memberType sym2 + + ( (tp2 eq ErrorType) + || isWeaklyCompatible(tp1, pt) && !isWeaklyCompatible(tp2, pt) + || isStrictlyMoreSpecific(tp1, tp2, sym1, sym2) + ) + } + // todo: missing test case for bests.isEmpty + bests match { + case best :: Nil => tree setSymbol best setType (pre memberType best) + case best :: competing :: _ if alts0.nonEmpty => + // SI-6912 Don't give up and leave an OverloadedType on the tree. + // Originally I wrote this as `if (secondTry) ... `, but `tryTwice` won't attempt the second try + // unless an error is issued. We're not issuing an error, in the assumption that it would be + // spurious in light of the erroneous expected type + if (pt.isErroneous) setError(tree) + else AmbiguousExprAlternativeError(tree, pre, best, competing, pt, isSecondTry) + case _ => if (bests.isEmpty || alts0.isEmpty) NoBestExprAlternativeError(tree, pt, isSecondTry) + } } } tree.tpe match { - case OverloadedType(pre, alts) => tryTwice(tryOurBests(pre, alts, _)) ; tree + case OverloadedType(pre, alts) => (new InferTwice(pre, alts)).apply() ; tree case _ => tree } } @@ -1370,70 +1371,41 @@ trait Infer extends Checkable { * @pre tree.tpe is an OverloadedType. */ def inferMethodAlternative(tree: Tree, undetparams: List[Symbol], argtpes0: List[Type], pt0: Type): Unit = { - val OverloadedType(pre, alts) = tree.tpe - var varargsStar = false - val argtpes = argtpes0 mapConserve { - case RepeatedType(tp) => varargsStar = true ; tp - case tp => tp - } - def followType(sym: Symbol) = followApply(pre memberType sym) - def bestForExpectedType(pt: Type, isLastTry: Boolean): Unit = { - val applicable0 = alts filter (alt => context inSilentMode isApplicable(undetparams, followType(alt), argtpes, pt)) - val applicable = overloadsToConsiderBySpecificity(applicable0, argtpes, varargsStar) - val ranked = bestAlternatives(applicable)((sym1, sym2) => - isStrictlyMoreSpecific(followType(sym1), followType(sym2), sym1, sym2) - ) - ranked match { - case best :: competing :: _ => AmbiguousMethodAlternativeError(tree, pre, best, competing, argtpes, pt, isLastTry) // ambiguous - case best :: Nil => tree setSymbol best setType (pre memberType best) // success - case Nil if pt.isWildcard => NoBestMethodAlternativeError(tree, argtpes, pt, isLastTry) // failed - case Nil => bestForExpectedType(WildcardType, isLastTry) // failed, but retry with WildcardType - } - } - // This potentially makes up to four attempts: tryTwice may execute + // This potentially makes up to four attempts: tryOnce may execute // with and without views enabled, and bestForExpectedType will try again // with pt = WildcardType if it fails with pt != WildcardType. - tryTwice { isLastTry => - val pt = if (pt0.typeSymbol == UnitClass) WildcardType else pt0 - debuglog(s"infer method alt ${tree.symbol} with alternatives ${alts map pre.memberType} argtpes=$argtpes pt=$pt") - bestForExpectedType(pt, isLastTry) - } - } + val c = context + class InferMethodAlternativeTwice extends c.TryTwice { + private[this] val OverloadedType(pre, alts) = tree.tpe + private[this] var varargsStar = false + private[this] val argtpes = argtpes0 mapConserve { + case RepeatedType(tp) => varargsStar = true ; tp + case tp => tp + } - /** Try inference twice, once without views and once with views, - * unless views are already disabled. - */ - def tryTwice(infer: Boolean => Unit): Unit = { - if (context.implicitsEnabled) { - val savedContextMode = context.contextMode - var fallback = false - context.setBufferErrors() - // We cache the current buffer because it is impossible to - // distinguish errors that occurred before entering tryTwice - // and our first attempt in 'withImplicitsDisabled'. If the - // first attempt fails we try with implicits on *and* clean - // buffer but that would also flush any pre-tryTwice valid - // errors, hence some manual buffer tweaking is necessary. - val errorsToRestore = context.flushAndReturnBuffer() - try { - context.withImplicitsDisabled(infer(false)) - if (context.hasErrors) { - fallback = true - context.contextMode = savedContextMode - context.flushBuffer() - infer(true) + private def followType(sym: Symbol) = followApply(pre memberType sym) + // separate method to help the inliner + private def isAltApplicable(pt: Type)(alt: Symbol) = context inSilentMode { isApplicable(undetparams, followType(alt), argtpes, pt) && !context.reporter.hasErrors } + private def rankAlternatives(sym1: Symbol, sym2: Symbol) = isStrictlyMoreSpecific(followType(sym1), followType(sym2), sym1, sym2) + private def bestForExpectedType(pt: Type, isLastTry: Boolean): Unit = { + val applicable = overloadsToConsiderBySpecificity(alts filter isAltApplicable(pt), argtpes, varargsStar) + val ranked = bestAlternatives(applicable)(rankAlternatives) + ranked match { + case best :: competing :: _ => AmbiguousMethodAlternativeError(tree, pre, best, competing, argtpes, pt, isLastTry) // ambiguous + case best :: Nil => tree setSymbol best setType (pre memberType best) // success + case Nil if pt.isWildcard => NoBestMethodAlternativeError(tree, argtpes, pt, isLastTry) // failed + case Nil => bestForExpectedType(WildcardType, isLastTry) // failed, but retry with WildcardType } - } catch { - case ex: CyclicReference => throw ex - case ex: TypeError => // recoverable cyclic references - context.contextMode = savedContextMode - if (!fallback) infer(true) else () - } finally { - context.contextMode = savedContextMode - context.updateBuffer(errorsToRestore) + } + + private[this] val pt = if (pt0.typeSymbol == UnitClass) WildcardType else pt0 + def tryOnce(isLastTry: Boolean): Unit = { + debuglog(s"infer method alt ${tree.symbol} with alternatives ${alts map pre.memberType} argtpes=$argtpes pt=$pt") + bestForExpectedType(pt, isLastTry) } } - else infer(true) + + (new InferMethodAlternativeTwice).apply() } /** Assign `tree` the type of all polymorphic alternatives diff --git a/src/compiler/scala/tools/nsc/typechecker/Macros.scala b/src/compiler/scala/tools/nsc/typechecker/Macros.scala index 9c22688581..da7b8b09aa 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Macros.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Macros.scala @@ -226,7 +226,8 @@ trait Macros extends MacroRuntimes with Traces with Helpers { val Apply(_, pickledPayload) = wrapped val payload = pickledPayload.map{ case Assign(k, v) => (unpickleAtom(k), unpickleAtom(v)) }.toMap - import typer.TyperErrorGen._ + // TODO: refactor error handling: fail always throws a TypeError, + // and uses global state (analyzer.lastTreeToTyper) to determine the position for the error def fail(msg: String) = MacroCantExpandIncompatibleMacrosError(msg) def unpickle[T](field: String, clazz: Class[T]): T = { def failField(msg: String) = fail(s"$field $msg") @@ -576,7 +577,10 @@ trait Macros extends MacroRuntimes with Traces with Helpers { // also see http://groups.google.com/group/scala-internals/browse_thread/thread/492560d941b315cc val expanded1 = try onSuccess(duplicateAndKeepPositions(expanded)) finally popMacroContext() if (!hasMacroExpansionAttachment(expanded1)) linkExpandeeAndExpanded(expandee, expanded1) - if (settings.Ymacroexpand.value == settings.MacroExpand.Discard) expandee.setType(expanded1.tpe) + if (settings.Ymacroexpand.value == settings.MacroExpand.Discard) { + suppressMacroExpansion(expandee) + expandee.setType(expanded1.tpe) + } else expanded1 case Fallback(fallback) => onFallback(fallback) case Delayed(delayed) => onDelayed(delayed) @@ -621,7 +625,7 @@ trait Macros extends MacroRuntimes with Traces with Helpers { // `macroExpandApply` is called from `adapt`, where implicit conversions are disabled // therefore we need to re-enable the conversions back temporarily val result = typer.context.withImplicitsEnabled(typer.typed(tree, mode, pt)) - if (result.isErrorTyped && macroDebugVerbose) println(s"$label has failed: ${typer.context.reportBuffer.errors}") + if (result.isErrorTyped && macroDebugVerbose) println(s"$label has failed: ${typer.context.reporter.errors}") result } } diff --git a/src/compiler/scala/tools/nsc/typechecker/Namers.scala b/src/compiler/scala/tools/nsc/typechecker/Namers.scala index 1328119aac..fdff2f3076 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Namers.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Namers.scala @@ -717,7 +717,7 @@ trait Namers extends MethodSynthesis { m.updateAttachment(new ConstructorDefaultsAttachment(tree, null)) } val owner = tree.symbol.owner - if (settings.lint && owner.isPackageObjectClass && !mods.isImplicit) { + if (settings.warnPackageObjectClasses && owner.isPackageObjectClass && !mods.isImplicit) { reporter.warning(tree.pos, "it is not recommended to define classes/objects inside of package objects.\n" + "If possible, define " + tree.symbol + " in " + owner.skipPackageObject + " instead." @@ -1494,8 +1494,7 @@ trait Namers extends MethodSynthesis { case defn: MemberDef => val ainfos = defn.mods.annotations filterNot (_ eq null) map { ann => val ctx = typer.context - val annCtx = ctx.make(ann) - annCtx.setReportErrors() + val annCtx = ctx.makeNonSilent(ann) // need to be lazy, #1782. beforeTyper to allow inferView in annotation args, SI-5892. AnnotationInfo lazily { enteringTyper(newTyper(annCtx) typedAnnotation ann) diff --git a/src/compiler/scala/tools/nsc/typechecker/NamesDefaults.scala b/src/compiler/scala/tools/nsc/typechecker/NamesDefaults.scala index 43902d1c65..b6387fd56b 100644 --- a/src/compiler/scala/tools/nsc/typechecker/NamesDefaults.scala +++ b/src/compiler/scala/tools/nsc/typechecker/NamesDefaults.scala @@ -379,18 +379,37 @@ trait NamesDefaults { self: Analyzer => def makeNamedTypes(syms: List[Symbol]) = syms map (sym => NamedType(sym.name, sym.tpe)) - def missingParams[T](args: List[T], params: List[Symbol], argName: T => Option[Name] = nameOfNamedArg _): (List[Symbol], Boolean) = { - val namedArgs = args.dropWhile(arg => { - val n = argName(arg) - n.isEmpty || params.forall(p => p.name != n.get) - }) - val namedParams = params.drop(args.length - namedArgs.length) - // missing: keep those with a name which doesn't exist in namedArgs - val missingParams = namedParams.filter(p => namedArgs.forall(arg => { + /** + * Returns the parameter symbols of an invocation expression that are not defined by the list + * of arguments. + * + * @param args The list of arguments + * @param params The list of parameter sybols of the invoked method + * @param argName A function that extracts the name of an argument expression, if it is a named argument. + */ + def missingParams[T](args: List[T], params: List[Symbol], argName: T => Option[Name]): (List[Symbol], Boolean) = { + // The argument list contains first a mix of positional args and named args that are on the + // right parameter position, and then a number or named args on different positions. + + // collect all named arguments whose position does not match the parameter they define + val namedArgsOnChangedPosition = args.zip(params) dropWhile { + case (arg, param) => + val n = argName(arg) + // drop the argument if + // - it's not named, or + // - it's named, but defines the parameter on its current position, or + // - it's named, but none of the parameter names matches (treated as a positional argument, an assignment expression) + n.isEmpty || n.get == param.name || params.forall(_.name != n.get) + } map (_._1) + + val paramsWithoutPositionalArg = params.drop(args.length - namedArgsOnChangedPosition.length) + + // missing parameters: those with a name which is not specified in one of the namedArgsOnChangedPosition + val missingParams = paramsWithoutPositionalArg.filter(p => namedArgsOnChangedPosition.forall { arg => val n = argName(arg) n.isEmpty || n.get != p.name - })) - val allPositional = missingParams.length == namedParams.length + }) + val allPositional = missingParams.length == paramsWithoutPositionalArg.length (missingParams, allPositional) } @@ -407,7 +426,7 @@ trait NamesDefaults { self: Analyzer => previousArgss: List[List[Tree]], params: List[Symbol], pos: scala.reflect.internal.util.Position, context: Context): (List[Tree], List[Symbol]) = { if (givenArgs.length < params.length) { - val (missing, positional) = missingParams(givenArgs, params) + val (missing, positional) = missingParams(givenArgs, params, nameOfNamedArg) if (missing forall (_.hasDefault)) { val defaultArgs = missing flatMap (p => { val defGetter = defaultGetter(p, context) diff --git a/src/compiler/scala/tools/nsc/typechecker/PatternTypers.scala b/src/compiler/scala/tools/nsc/typechecker/PatternTypers.scala index cf3f265f0c..da0e67a2a5 100644 --- a/src/compiler/scala/tools/nsc/typechecker/PatternTypers.scala +++ b/src/compiler/scala/tools/nsc/typechecker/PatternTypers.scala @@ -261,7 +261,7 @@ trait PatternTypers { def doTypedUnapply(tree: Tree, fun0: Tree, fun: Tree, args: List[Tree], mode: Mode, pt: Type): Tree = { def duplErrTree = setError(treeCopy.Apply(tree, fun0, args)) - def duplErrorTree(err: AbsTypeError) = { issue(err); duplErrTree } + def duplErrorTree(err: AbsTypeError) = { context.issue(err); duplErrTree } if (args.length > MaxTupleArity) return duplErrorTree(TooManyArgsPatternError(fun)) diff --git a/src/compiler/scala/tools/nsc/typechecker/RefChecks.scala b/src/compiler/scala/tools/nsc/typechecker/RefChecks.scala index 1b3da26bf2..1cb775959d 100644 --- a/src/compiler/scala/tools/nsc/typechecker/RefChecks.scala +++ b/src/compiler/scala/tools/nsc/typechecker/RefChecks.scala @@ -167,7 +167,7 @@ abstract class RefChecks extends InfoTransform with scala.reflect.internal.trans } // This has become noisy with implicit classes. - if (settings.lint && settings.developer) { + if (settings.warnPolyImplicitOverload && settings.developer) { clazz.info.decls filter (x => x.isImplicit && x.typeParams.nonEmpty) foreach { sym => // implicit classes leave both a module symbol and a method symbol as residue val alts = clazz.info.decl(sym.name).alternatives filterNot (_.isModule) @@ -954,7 +954,7 @@ abstract class RefChecks extends InfoTransform with scala.reflect.internal.trans def apply(tp: Type) = mapOver(tp).normalize } - def checkImplicitViewOptionApply(pos: Position, fn: Tree, args: List[Tree]): Unit = if (settings.lint) (fn, args) match { + def checkImplicitViewOptionApply(pos: Position, fn: Tree, args: List[Tree]): Unit = if (settings.warnOptionImplicit) (fn, args) match { case (tap@TypeApply(fun, targs), List(view: ApplyImplicitView)) if fun.symbol == currentRun.runDefinitions.Option_apply => reporter.warning(pos, s"Suspicious application of an implicit view (${view.fun}) in the argument to Option.apply.") // SI-6567 case _ => @@ -1320,7 +1320,7 @@ abstract class RefChecks extends InfoTransform with scala.reflect.internal.trans && !qual.tpe.isInstanceOf[ThisType] && sym.accessedOrSelf.isVal ) - if (settings.lint.value && isLikelyUninitialized) + if (settings.warnDelayedInit && isLikelyUninitialized) reporter.warning(pos, s"Selecting ${sym} from ${sym.owner}, which extends scala.DelayedInit, is likely to yield an uninitialized value") } @@ -1387,7 +1387,7 @@ abstract class RefChecks extends InfoTransform with scala.reflect.internal.trans private def checkByNameRightAssociativeDef(tree: DefDef) { tree match { case DefDef(_, name, _, params :: _, _, _) => - if (settings.lint && !treeInfo.isLeftAssoc(name.decodedName) && params.exists(p => isByName(p.symbol))) + if (settings.warnByNameRightAssociative && !treeInfo.isLeftAssoc(name.decodedName) && params.exists(p => isByName(p.symbol))) reporter.warning(tree.pos, "by-name parameters will be evaluated eagerly when called as a right-associative infix operator. For more details, see SI-1980.") case _ => @@ -1633,7 +1633,7 @@ abstract class RefChecks extends InfoTransform with scala.reflect.internal.trans if (settings.warnNullaryUnit) checkNullaryMethodReturnType(sym) if (settings.warnInaccessible) { - if (!sym.isConstructor && !sym.isEffectivelyFinal && !sym.isSynthetic) + if (!sym.isConstructor && !sym.isEffectivelyFinalOrNotOverridden && !sym.isSynthetic) checkAccessibilityOfReferencedTypes(tree) } tree match { diff --git a/src/compiler/scala/tools/nsc/typechecker/SuperAccessors.scala b/src/compiler/scala/tools/nsc/typechecker/SuperAccessors.scala index d3a41b9570..38b00a015b 100644 --- a/src/compiler/scala/tools/nsc/typechecker/SuperAccessors.scala +++ b/src/compiler/scala/tools/nsc/typechecker/SuperAccessors.scala @@ -247,7 +247,7 @@ abstract class SuperAccessors extends transform.Transform with transform.TypingT // also exists in a superclass, because they may be surprised // to find out that a constructor parameter will shadow a // field. See SI-4762. - if (settings.lint) { + if (settings.warnPrivateShadow) { if (sym.isPrivateLocal && sym.paramss.isEmpty) { qual.symbol.ancestors foreach { parent => parent.info.decls filterNot (x => x.isPrivate || x.isLocalToThis) foreach { m2 => diff --git a/src/compiler/scala/tools/nsc/typechecker/SyntheticMethods.scala b/src/compiler/scala/tools/nsc/typechecker/SyntheticMethods.scala index 9516f94135..d0237fb468 100644 --- a/src/compiler/scala/tools/nsc/typechecker/SyntheticMethods.scala +++ b/src/compiler/scala/tools/nsc/typechecker/SyntheticMethods.scala @@ -339,12 +339,11 @@ trait SyntheticMethods extends ast.TreeDSL { !hasOverridingImplementation(m) || { clazz.isDerivedValueClass && (m == Any_hashCode || m == Any_equals) && { // Without a means to suppress this warning, I've thought better of it. - // - // if (settings.lint) { - // (clazz.info nonPrivateMember m.name) filter (m => (m.owner != AnyClass) && (m.owner != clazz) && !m.isDeferred) andAlso { m => - // currentUnit.warning(clazz.pos, s"Implementation of ${m.name} inherited from ${m.owner} overridden in $clazz to enforce value class semantics") - // } - // } + if (settings.warnValueOverrides) { + (clazz.info nonPrivateMember m.name) filter (m => (m.owner != AnyClass) && (m.owner != clazz) && !m.isDeferred) andAlso { m => + currentUnit.warning(clazz.pos, s"Implementation of ${m.name} inherited from ${m.owner} overridden in $clazz to enforce value class semantics") + } + } true } } diff --git a/src/compiler/scala/tools/nsc/typechecker/TreeCheckers.scala b/src/compiler/scala/tools/nsc/typechecker/TreeCheckers.scala index 399a4ca8d5..743bbe53bd 100644 --- a/src/compiler/scala/tools/nsc/typechecker/TreeCheckers.scala +++ b/src/compiler/scala/tools/nsc/typechecker/TreeCheckers.scala @@ -208,8 +208,7 @@ abstract class TreeCheckers extends Analyzer { } def check(unit: CompilationUnit) { informProgress("checking "+unit) - val context = rootContext(unit) - context.checking = true + val context = rootContext(unit, checking = true) tpeOfTree.clear() SymbolTracker.check(phase, unit) val checker = new TreeChecker(context) diff --git a/src/compiler/scala/tools/nsc/typechecker/TypeDiagnostics.scala b/src/compiler/scala/tools/nsc/typechecker/TypeDiagnostics.scala index 7440f69e93..8d29d28908 100644 --- a/src/compiler/scala/tools/nsc/typechecker/TypeDiagnostics.scala +++ b/src/compiler/scala/tools/nsc/typechecker/TypeDiagnostics.scala @@ -572,11 +572,11 @@ trait TypeDiagnostics { } else f } def apply(tree: Tree): Tree = { - // Error suppression will squash some of these warnings unless we circumvent it. + // Error suppression (in context.warning) would squash some of these warnings. // It is presumed if you are using a -Y option you would really like to hear - // the warnings you've requested. + // the warnings you've requested; thus, use reporter.warning. if (settings.warnDeadCode && context.unit.exists && treeOK(tree) && exprOK) - context.warning(tree.pos, "dead code following this construct", force = true) + reporter.warning(tree.pos, "dead code following this construct") tree } @@ -600,6 +600,23 @@ trait TypeDiagnostics { ) } + // warn about class/method/type-members' type parameters that shadow types already in scope + def warnTypeParameterShadow(tparams: List[TypeDef], sym: Symbol): Unit = + if (settings.warnTypeParameterShadow && !isPastTyper && !sym.isSynthetic) { + def enclClassOrMethodOrTypeMember(c: Context): Context = + if (!c.owner.exists || c.owner.isClass || c.owner.isMethod || (c.owner.isType && !c.owner.isParameter)) c + else enclClassOrMethodOrTypeMember(c.outer) + + val tt = tparams.filter(_.name != typeNames.WILDCARD).foreach { tp => + // we don't care about type params shadowing other type params in the same declaration + enclClassOrMethodOrTypeMember(context).outer.lookupSymbol(tp.name, s => s != tp.symbol && s.hasRawInfo && reallyExists(s)) match { + case LookupSucceeded(_, sym2) => context.warning(tp.pos, + s"type parameter ${tp.name} defined in $sym shadows $sym2 defined in ${sym2.owner}. You may want to rename your type parameter, or possibly remove it.") + case _ => + } + } + } + /** Report a type error. * * @param pos The position where to report the error @@ -629,7 +646,7 @@ trait TypeDiagnostics { throw new FatalError("cannot redefine root "+sym) } case _ => - context0.error(ex.pos, ex) + context0.error(ex.pos, ex.msg) } } } diff --git a/src/compiler/scala/tools/nsc/typechecker/Typers.scala b/src/compiler/scala/tools/nsc/typechecker/Typers.scala index 2c31ef2e8e..422b940cd3 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Typers.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Typers.scala @@ -155,21 +155,7 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper } else { mkArg = gen.mkNamedArg // don't pass the default argument (if any) here, but start emitting named arguments for the following args if (!param.hasDefault && !paramFailed) { - context.reportBuffer.errors.collectFirst { - case dte: DivergentImplicitTypeError => dte - } match { - case Some(divergent) => - // DivergentImplicit error has higher priority than "no implicit found" - // no need to issue the problem again if we are still in silent mode - if (context.reportErrors) { - context.issue(divergent.withPt(paramTp)) - context.reportBuffer.clearErrors { - case dte: DivergentImplicitTypeError => true - } - } - case _ => - NoImplicitFoundError(fun, param) - } + context.reporter.reportFirstDivergentError(fun, param, paramTp)(context) paramFailed = true } /* else { @@ -478,20 +464,8 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper if (cond) typerWithLocalContext(c)(f) else f(this) @inline - final def typerWithLocalContext[T](c: Context)(f: Typer => T): T = { - val res = f(newTyper(c)) - if (c.hasErrors) - context.updateBuffer(c.flushAndReturnBuffer()) - res - } - - @inline - final def withSavedContext[T](c: Context)(f: => T) = { - val savedErrors = c.flushAndReturnBuffer() - val res = f - c.updateBuffer(savedErrors) - res - } + final def typerWithLocalContext[T](c: Context)(f: Typer => T): T = + c.reporter.propagatingErrorsTo(context.reporter)(f(newTyper(c))) /** The typer for a label definition. If this is part of a template we * first have to enter the label definition. @@ -684,6 +658,12 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper if (Statistics.canEnable) Statistics.stopCounter(subtypeFailed, subtypeStart) if (Statistics.canEnable) Statistics.stopTimer(failedSilentNanos, failedSilentStart) } + @inline def wrapResult(reporter: ContextReporter, result: T) = + if (reporter.hasErrors) { + stopStats() + SilentTypeError(reporter.errors: _*) + } else SilentResultValue(result) + try { if (context.reportErrors || reportAmbiguousErrors != context.ambiguousErrors || @@ -697,20 +677,17 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper context.undetparams = context1.undetparams context.savedTypeBounds = context1.savedTypeBounds context.namedApplyBlockInfo = context1.namedApplyBlockInfo - if (context1.hasErrors) { - stopStats() - SilentTypeError(context1.errors: _*) - } else { - // If we have a successful result, emit any warnings it created. - context1.flushAndIssueWarnings() - SilentResultValue(result) - } + + // If we have a successful result, emit any warnings it created. + if (!context1.reporter.hasErrors) + context1.reporter.emitWarnings() + + wrapResult(context1.reporter, result) } else { assert(context.bufferErrors || isPastTyper, "silent mode is not available past typer") - withSavedContext(context){ - val res = op(this) - val errorsToReport = context.flushAndReturnBuffer() - if (errorsToReport.isEmpty) SilentResultValue(res) else SilentTypeError(errorsToReport.head) + + context.reporter.withFreshErrorBuffer { + wrapResult(context.reporter, op(this)) } } } catch { @@ -816,14 +793,14 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper } // avoid throwing spurious DivergentImplicit errors - if (context.hasErrors) + if (context.reporter.hasErrors) setError(tree) else withCondConstrTyper(treeInfo.isSelfOrSuperConstrCall(tree))(typer1 => if (original != EmptyTree && pt != WildcardType) ( typer1 silent { tpr => val withImplicitArgs = tpr.applyImplicitArgs(tree) - if (tpr.context.hasErrors) tree // silent will wrap it in SilentTypeError anyway + if (tpr.context.reporter.hasErrors) tree // silent will wrap it in SilentTypeError anyway else tpr.typed(withImplicitArgs, mode, pt) } orElse { _ => @@ -1057,7 +1034,7 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper val silentContext = context.makeImplicit(context.ambiguousErrors) val res = newTyper(silentContext).typed( new ApplyImplicitView(coercion, List(tree)) setPos tree.pos, mode, pt) - silentContext.firstError match { + silentContext.reporter.firstError match { case Some(err) => context.issue(err) case None => return res } @@ -1749,6 +1726,8 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper |you want, you must write the annotation class in Java.""".stripMargin) } + warnTypeParameterShadow(tparams1, clazz) + if (!isPastTyper) { for (ann <- clazz.getAnnotation(DeprecatedAttr)) { val m = companionSymbolOf(clazz, context) @@ -2151,6 +2130,8 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper val tparams1 = ddef.tparams mapConserve typedTypeDef val vparamss1 = ddef.vparamss mapConserve (_ mapConserve typedValDef) + warnTypeParameterShadow(tparams1, meth) + meth.annotations.map(_.completeInfo()) for (vparams1 <- vparamss1; vparam1 <- vparams1 dropRight 1) @@ -2227,6 +2208,8 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper val typedMods = typedModifiers(tdef.mods) tdef.symbol.annotations.map(_.completeInfo()) + warnTypeParameterShadow(tparams1, tdef.symbol) + // @specialized should not be pickled when compiling with -no-specialize if (settings.nospecialization && currentRun.compiles(tdef.symbol)) { tdef.symbol.removeAnnotation(definitions.SpecializedClass) @@ -2990,7 +2973,7 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper ConstructorsOrderError(stat) } - if (treeInfo.isPureExprForWarningPurposes(result)) context.warning(stat.pos, + if (!isPastTyper && treeInfo.isPureExprForWarningPurposes(result)) context.warning(stat.pos, "a pure expression does nothing in statement position; " + "you may be omitting necessary parentheses" ) @@ -3149,7 +3132,7 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper def doTypedApply(tree: Tree, fun0: Tree, args: List[Tree], mode: Mode, pt: Type): Tree = { // TODO_NMT: check the assumption that args nonEmpty def duplErrTree = setError(treeCopy.Apply(tree, fun0, args)) - def duplErrorTree(err: AbsTypeError) = { issue(err); duplErrTree } + def duplErrorTree(err: AbsTypeError) = { context.issue(err); duplErrTree } def preSelectOverloaded(fun: Tree): Tree = { if (fun.hasSymbolField && fun.symbol.isOverloaded) { @@ -3229,7 +3212,7 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper (arg1, arg1.tpe.deconst) }.unzip } - if (context.hasErrors) + if (context.reporter.hasErrors) setError(tree) else { inferMethodAlternative(fun, undetparams, argTpes, pt) @@ -3248,25 +3231,25 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper * to that. This is the last thing which is tried (after * default arguments) */ - def tryTupleApply: Tree = ( + def tryTupleApply: Tree = { if (eligibleForTupleConversion(paramTypes, argslen) && !phase.erasedTypes) { val tupleArgs = List(atPos(tree.pos.makeTransparent)(gen.mkTuple(args))) // expected one argument, but got 0 or >1 ==> try applying to tuple // the inner "doTypedApply" does "extractUndetparams" => restore when it fails val savedUndetparams = context.undetparams silent(_.doTypedApply(tree, fun, tupleArgs, mode, pt)) map { t => - // Depending on user options, may warn or error here if - // a Unit or tuple was inserted. - val keepTree = ( - !mode.typingExprNotFun - || t.symbol == null - || checkValidAdaptation(t, args) - ) - if (keepTree) t else EmptyTree + // Depending on user options, may warn or error here if + // a Unit or tuple was inserted. + val keepTree = ( + !mode.typingExprNotFun // why? introduced in 4e488a60, doc welcome + || t.symbol == null // ditto + || checkValidAdaptation(t, args) + ) + if (keepTree) t else EmptyTree } orElse { _ => context.undetparams = savedUndetparams ; EmptyTree } } else EmptyTree - ) + } /* Treats an application which uses named or default arguments. * Also works if names + a vararg used: when names are used, the vararg @@ -3357,8 +3340,7 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper duplErrTree } else if (lencmp2 == 0) { // useful when a default doesn't match parameter type, e.g. def f[T](x:T="a"); f[Int]() - val note = "Error occurred in an application involving default arguments." - if (!(context.diagnostic contains note)) context.diagnostic = note :: context.diagnostic + context.diagUsedDefaults = true doTypedApply(tree, if (blockIsEmpty) fun else fun1, allArgs, mode, pt) } else { rollbackNamesDefaultsOwnerChanges() @@ -4331,7 +4313,7 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper c.retyping = true try { val res = newTyper(c).typedArgs(args, mode) - if (c.hasErrors) None else Some(res) + if (c.reporter.hasErrors) None else Some(res) } catch { case ex: CyclicReference => throw ex @@ -4395,7 +4377,7 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper case _ => () } } - typeErrors foreach issue + typeErrors foreach context.issue setError(treeCopy.Apply(tree, fun, args)) } @@ -4449,7 +4431,7 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper doTypedApply(tree, fun2, args, mode, pt) case err: SilentTypeError => onError({ - err.reportableErrors foreach issue + err.reportableErrors foreach context.issue args foreach (arg => typed(arg, mode, ErrorType)) setError(tree) }) @@ -4686,7 +4668,7 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper else // before failing due to access, try a dynamic call. asDynamicCall getOrElse { - issue(accessibleError.get) + context.issue(accessibleError.get) setError(tree) } case _ => @@ -5099,7 +5081,7 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper def isPlausible(m: Symbol) = m.alternatives exists (m => requiresNoArgs(m.info)) def maybeWarn(s: String): Unit = { - def warn(message: String) = context.warning(lit.pos, s"$message Did you forget the interpolator?") + def warn(message: String) = context.warning(lit.pos, s"possible missing interpolator: $message") def suspiciousSym(name: TermName) = context.lookupSymbol(name, _ => true).symbol def suspiciousExpr = InterpolatorCodeRegex findFirstIn s def suspiciousIdents = InterpolatorIdentRegex findAllIn s map (s => suspiciousSym(s drop 1)) @@ -5107,9 +5089,9 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper // heuristics - no warning on e.g. a string with only "$asInstanceOf" if (s contains ' ') ( if (suspiciousExpr.nonEmpty) - warn("That looks like an interpolated expression!") // "${...}" + warn("detected an interpolated expression") // "${...}" else - suspiciousIdents find isPlausible foreach (sym => warn(s"`$$${sym.name}` looks like an interpolated identifier!")) // "$id" + suspiciousIdents find isPlausible foreach (sym => warn(s"detected interpolated identifier `$$${sym.name}`")) // "$id" ) } lit match { @@ -5119,7 +5101,7 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper } def typedLiteral(tree: Literal) = { - if (settings.lint) warnMissingInterpolator(tree) + if (settings.warnMissingInterpolator) warnMissingInterpolator(tree) tree setType (if (tree.value.tag == UnitTag) UnitTpe else ConstantType(tree.value)) } diff --git a/src/compiler/scala/tools/reflect/FormatInterpolator.scala b/src/compiler/scala/tools/reflect/FormatInterpolator.scala index 57be1afdfe..b445f1e2bb 100644 --- a/src/compiler/scala/tools/reflect/FormatInterpolator.scala +++ b/src/compiler/scala/tools/reflect/FormatInterpolator.scala @@ -182,13 +182,23 @@ abstract class FormatInterpolator { case (part, n) => copyPart(part, n) } - //q"{..$evals; ${fstring.toString}.format(..$ids)}" - locally { + //q"{..$evals; new StringOps(${fstring.toString}).format(..$ids)}" + val format = fstring.toString + if (ids.isEmpty && !format.contains("%")) Literal(Constant(format)) + else { + val scalaPackage = Select(Ident(nme.ROOTPKG), TermName("scala")) + val newStringOps = Select( + New(Select(Select(Select(scalaPackage, + TermName("collection")), TermName("immutable")), TypeName("StringOps"))), + termNames.CONSTRUCTOR + ) val expr = Apply( Select( - Literal(Constant(fstring.toString)), - newTermName("format")), + Apply( + newStringOps, + List(Literal(Constant(format)))), + TermName("format")), ids.toList ) val p = c.macroApplication.pos diff --git a/src/compiler/scala/tools/reflect/ToolBoxFactory.scala b/src/compiler/scala/tools/reflect/ToolBoxFactory.scala index 923297bafb..1643e0061f 100644 --- a/src/compiler/scala/tools/reflect/ToolBoxFactory.scala +++ b/src/compiler/scala/tools/reflect/ToolBoxFactory.scala @@ -142,7 +142,7 @@ abstract class ToolBoxFactory[U <: JavaUniverse](val u: U) { factorySelf => run.symSource(ownerClass) = NoAbstractFile // need to set file to something different from null, so that currentRun.defines works phase = run.typerPhase // need to set a phase to something <= typerPhase, otherwise implicits in typedSelect will be disabled globalPhase = run.typerPhase // amazing... looks like phase and globalPhase are different things, so we need to set them separately - currentTyper.context.setReportErrors() // need to manually set context mode, otherwise typer.silent will throw exceptions + currentTyper.context.initRootContext() // need to manually set context mode, otherwise typer.silent will throw exceptions reporter.reset() val expr3 = withContext(transform(currentTyper, expr2)) diff --git a/src/compiler/scala/tools/reflect/quasiquotes/Parsers.scala b/src/compiler/scala/tools/reflect/quasiquotes/Parsers.scala index b68022afd9..392b7fc881 100644 --- a/src/compiler/scala/tools/reflect/quasiquotes/Parsers.scala +++ b/src/compiler/scala/tools/reflect/quasiquotes/Parsers.scala @@ -69,9 +69,16 @@ trait Parsers { self: Quasiquotes => override def makeTupleType(trees: List[Tree]): Tree = TupleTypePlaceholder(trees) // q"{ $x }" - override def makeBlock(stats: List[Tree]): Tree = stats match { - case (head @ Ident(name)) :: Nil if isHole(name) => Block(Nil, head) - case _ => super.makeBlock(stats) + override def makeBlock(stats: List[Tree]): Tree = method match { + case nme.apply => + stats match { + // we don't want to eagerly flatten trees with placeholders as they + // might have to be wrapped into a block depending on their value + case (head @ Ident(name)) :: Nil if isHole(name) => Block(Nil, head) + case _ => gen.mkBlock(stats, doFlatten = true) + } + case nme.unapply => gen.mkBlock(stats, doFlatten = false) + case other => global.abort("unreachable") } // tq"$a => $b" |