diff options
Diffstat (limited to 'src')
90 files changed, 2934 insertions, 2704 deletions
diff --git a/src/compiler/scala/tools/cmd/gen/AnyVals.scala b/src/compiler/scala/tools/cmd/gen/AnyVals.scala index 842851b4f6..e78589908c 100644 --- a/src/compiler/scala/tools/cmd/gen/AnyVals.scala +++ b/src/compiler/scala/tools/cmd/gen/AnyVals.scala @@ -111,8 +111,8 @@ import scala.language.implicitConversions""" " */"), Op(">>", "/**\n" + - " * Returns this value bit-shifted left by the specified number of bits,\n" + - " * filling in the right bits with the same value as the left-most bit of this.\n" + + " * Returns this value bit-shifted right by the specified number of bits,\n" + + " * filling in the left bits with the same value as the left-most bit of this.\n" + " * The effect of this is to retain the sign of the value.\n" + " * @example {{{\n" + " * -21 >> 3 == -3\n" + 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 3ded456378..9b8e9fa330 100644 --- a/src/compiler/scala/tools/nsc/CompilerCommand.scala +++ b/src/compiler/scala/tools/nsc/CompilerCommand.scala @@ -107,7 +107,7 @@ class CompilerCommand(arguments: List[String], val settings: Settings) { else { val sb = new StringBuilder allSettings foreach { - case s: MultiChoiceSetting if s.isHelping => sb append s.help + 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 5bf0d8d9f7..452081cff1 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" @@ -968,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. @@ -1169,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") @@ -1361,7 +1372,7 @@ class Global(var currentSettings: Settings, var reporter: Reporter) runCheckers() // output collected statistics - if (settings.Ystatistics) + if (settings.YstatisticsEnabled) statistics.print(phase) advancePhase() diff --git a/src/compiler/scala/tools/nsc/MainBench.scala b/src/compiler/scala/tools/nsc/MainBench.scala index 03190a63f3..f01de0cbe1 100644 --- a/src/compiler/scala/tools/nsc/MainBench.scala +++ b/src/compiler/scala/tools/nsc/MainBench.scala @@ -24,7 +24,7 @@ object MainBench extends Driver with EvalLoop { var start = System.nanoTime() for (i <- 0 until NIter) { if (i == NIter-1) { - theCompiler.settings.Ystatistics.value = true + theCompiler.settings.Ystatistics.default.get foreach theCompiler.settings.Ystatistics.add Statistics.enabled = true } process(args) 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/Reporting.scala b/src/compiler/scala/tools/nsc/Reporting.scala index 0263586418..c9782de7c8 100644 --- a/src/compiler/scala/tools/nsc/Reporting.scala +++ b/src/compiler/scala/tools/nsc/Reporting.scala @@ -33,7 +33,7 @@ trait Reporting extends scala.reflect.internal.Reporting { self: ast.Positions w if (option) reporter.warning(pos, msg) else if (!(warnings contains pos)) warnings += ((pos, msg)) def summarize() = - if (warnings.nonEmpty && (option.isDefault || settings.fatalWarnings)) { + if (warnings.nonEmpty && (option.isDefault || option)) { val numWarnings = warnings.size val warningVerb = if (numWarnings == 1) "was" else "were" val warningCount = countElementsAsString(numWarnings, s"$what warning") @@ -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/ScriptRunner.scala b/src/compiler/scala/tools/nsc/ScriptRunner.scala index c2d62db558..7d5c6f6fff 100644 --- a/src/compiler/scala/tools/nsc/ScriptRunner.scala +++ b/src/compiler/scala/tools/nsc/ScriptRunner.scala @@ -6,7 +6,7 @@ package scala package tools.nsc -import io.{ Directory, File, Path } +import io.{ AbstractFile, Directory, File, Path } import java.io.IOException import scala.tools.nsc.reporters.{Reporter,ConsoleReporter} import util.Exceptional.unwrap @@ -111,6 +111,12 @@ class ScriptRunner extends HasCompileSocket { else None } + def hasClassToRun(d: Directory): Boolean = { + import util.ClassPath.{ DefaultJavaContext => ctx } + val cp = ctx.newClassPath(AbstractFile.getDirectory(d)) + cp.findClass(mainClass).isDefined + } + /* The script runner calls sys.exit to communicate a return value, but this must * not take place until there are no non-daemon threads running. Tickets #1955, #2006. */ @@ -124,15 +130,21 @@ class ScriptRunner extends HasCompileSocket { compile match { case Some(compiledPath) => - try io.Jar.create(jarFile, compiledPath, mainClass) - catch { case _: Exception => jarFile.delete() } - - if (jarOK) { - compiledPath.deleteRecursively() - handler(jarFile.toAbsolute.path) + if (!hasClassToRun(compiledPath)) { + // it compiled ok, but there is nothing to run; + // running an empty script should succeed + true + } else { + try io.Jar.create(jarFile, compiledPath, mainClass) + catch { case _: Exception => jarFile.delete() } + + if (jarOK) { + compiledPath.deleteRecursively() + handler(jarFile.toAbsolute.path) + } + // jar failed; run directly from the class files + else handler(compiledPath.path) } - // jar failed; run directly from the class files - else handler(compiledPath.path) case _ => false } } @@ -140,8 +152,8 @@ class ScriptRunner extends HasCompileSocket { if (jarOK) handler(jarFile.toAbsolute.path) // pre-compiled jar is current else recompile() // jar old - recompile the script. } - // don't use a cache jar at all--just use the class files - else compile exists (cp => handler(cp.path)) + // don't use a cache jar at all--just use the class files, if they exist + else compile exists (cp => !hasClassToRun(cp) || handler(cp.path)) } } diff --git a/src/compiler/scala/tools/nsc/ast/DocComments.scala b/src/compiler/scala/tools/nsc/ast/DocComments.scala index 6d9b41ec45..02a199f7ac 100755 --- a/src/compiler/scala/tools/nsc/ast/DocComments.scala +++ b/src/compiler/scala/tools/nsc/ast/DocComments.scala @@ -59,14 +59,21 @@ trait DocComments { self: Global => comment.defineVariables(sym) } + + def replaceInheritDocToInheritdoc(docStr: String):String = { + docStr.replaceAll("""\{@inheritDoc\p{Zs}*\}""", "@inheritdoc") + } + /** The raw doc comment of symbol `sym`, minus usecase and define sections, augmented by * missing sections of an inherited doc comment. * If a symbol does not have a doc comment but some overridden version of it does, * the doc comment of the overridden version is copied instead. */ def cookedDocComment(sym: Symbol, docStr: String = ""): String = cookedDocComments.getOrElseUpdate(sym, { - val ownComment = if (docStr.length == 0) docComments get sym map (_.template) getOrElse "" + var ownComment = if (docStr.length == 0) docComments get sym map (_.template) getOrElse "" else DocComment(docStr).template + ownComment = replaceInheritDocToInheritdoc(ownComment) + superComment(sym) match { case None => if (ownComment.indexOf("@inheritdoc") != -1) diff --git a/src/compiler/scala/tools/nsc/ast/parser/Parsers.scala b/src/compiler/scala/tools/nsc/ast/parser/Parsers.scala index c90f0d0173..8d810d456e 100644 --- a/src/compiler/scala/tools/nsc/ast/parser/Parsers.scala +++ b/src/compiler/scala/tools/nsc/ast/parser/Parsers.scala @@ -232,7 +232,7 @@ self => 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, diff --git a/src/compiler/scala/tools/nsc/ast/parser/Scanners.scala b/src/compiler/scala/tools/nsc/ast/parser/Scanners.scala index 0248806fff..9ebc94b5fc 100644 --- a/src/compiler/scala/tools/nsc/ast/parser/Scanners.scala +++ b/src/compiler/scala/tools/nsc/ast/parser/Scanners.scala @@ -1260,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 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 2d1030121e..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 @@ -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 de1587c7c3..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,155 +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" - // TODO @lry do we have a test for that? - 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) - } - } - - // TODO @lry: code duplication between here and method asmClassType. - - 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 primitiveOrRefType(sym: Symbol): BType = { - assert(sym != definitions.ArrayClass, "Use primitiveOrArrayOrRefType() instead.") - - primitiveTypeMap.getOrElse(sym, newReference(sym)) + def primitiveOrClassToBType(sym: Symbol): BType = { + assertClassNotArray(sym) + assert(!sym.isImplClass, sym) + primitiveTypeMap.getOrElse(sym, getClassBTypeAndRegisterInnerClass(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 @@ -552,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 @@ -795,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 ) @@ -901,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. */ @@ -985,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) @@ -999,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 ) @@ -1008,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 } @@ -1042,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 @@ -1091,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 ) @@ -1104,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 } } @@ -1113,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 @@ -1133,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() @@ -1165,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 @@ -1206,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 2343d378db..d58368b19d 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/BCodeIdiomatic.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/BCodeIdiomatic.scala @@ -19,16 +19,11 @@ 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 @@ -40,9 +35,7 @@ abstract class BCodeIdiomatic extends SubComponent { 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 ) @@ -52,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] @@ -239,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) @@ -266,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 @@ -537,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) @@ -648,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 @@ -659,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 0d67a07e0f..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, @@ -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 b373f8d74d..0000000000 --- a/src/compiler/scala/tools/nsc/backend/jvm/BCodeTypes.scala +++ /dev/null @@ -1,930 +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._ - - // Used only for assertions. When compiling the Scala library, some assertions don't hold - // (e.g., scala.Boolean has null superClass although it's not an interface) - private 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. - // ------------------------------------------------ - - /** - * Type information for classBTypes. - * - * TODO rename Tracked - */ - val exemplars = new java.util.concurrent.ConcurrentHashMap[ClassBType, Tracked] - - /** - * Maps class symbols to their corresponding `Tracked` instance. - * - * This map is only used during the first backend phase (Worker1) where ClassDef trees are - * transformed into ClassNode asm trees. In this phase, ClassBTypes and their Tracked are created - * and added to the `exemplars` map. The `symExemplars` map is only used to know if a symbol has - * already been visited. - * - * TODO move this map to the builder class. it's only used during building. can be gc'd with the builder. - */ - 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.isModuleNotMethod - - /* 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 - * - * True for module classes of package level objects. The backend will generate a mirror class for - * such objects. - */ - 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 - } - - /** - * must-single-thread - * - * 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. - */ - def isStaticModuleClass(sym: Symbol): Boolean = { - /* The implementation of this method is tricky because it is a source-level property. Various - * phases changed the symbol's properties in the meantime. - * - * (1) Phase travel to to pickler is required to exclude implementation classes; they have the - * lateMODULEs after mixin, so isModuleClass would be true. - * - * (2) We cannot use `sym.isStatic` because lambdalift modified (destructively) the owner. 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. - * So we basically re-implement `sym.isStaticOwner`, but using the original owner chain. - */ - - def isOriginallyStaticOwner(sym: Symbol): Boolean = { - sym.isPackageClass || sym.isModuleClass && isOriginallyStaticOwner(sym.originalOwner) - } - - exitingPickler { // (1) - sym.isModuleClass && - isOriginallyStaticOwner(sym.originalOwner) // (2) - } - } - - - // --------------------------------------------------------------------- - // ---------------- 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}") - // Uses `rawowner` because `owner` reflects changes in the owner chain due to flattening. - // The owner chain of a class only contains classes. This is because the lambdalift phase - // changes the `rawowner` destructively to point to the enclosing class. Before, the owner - // might be for example a method. - 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 (isTopLevelModuleClass(innerSym.rawowner)) nme.stripModuleSuffix(outerName) - else outerName - } - } - - def innerName(innerSym: Symbol): String = { - if (innerSym.isAnonymousClass || innerSym.isAnonymousFunction) - null - else - innerSym.rawname + innerSym.moduleSuffix - } - - // TODO @lry compare with table in spec: for example, deprecated should not be there it seems. - // http://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4.7.6-300-D.1-D.1 - // including "deprecated" was added in the initial commit of GenASM, but it was never in GenJVM. - val flags: Int = mkFlags( - // TODO @lry adding "static" whenever the class is owned by a module seems wrong. - // class C { object O { class I } } - // here, I is marked static in the InnerClass attribute. But the I constructor takes an outer instance. - // was added in 0469d41 - // what should it be? check what would make sense for java reflection. - // member of top-level object should be static? how about anonymous / local class that has - // been lifted to a top-level object? - // member that is only nested in objects should be static? - // verify: will ICodeReader still work after that? the code was introduced because of icode reader. - 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 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. 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._ - 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 - ) - // TODO @lry should probably also check / add "deprectated" - // all call sites of "javaFlags" seem to check for deprecation rigth after. - // Exception: the call below in javaFieldFlags. However, the caller of javaFieldFlags then - // does the check. - } - - /* - * 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 15bc068533..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. + * 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. * - * 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). - * - * 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" @@ -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. @@ -165,45 +244,77 @@ abstract class BTypes[G <: Global](val __global_dont_use: G) { 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 @@ -261,6 +372,14 @@ abstract class BTypes[G <: Global](val __global_dont_use: G) { * - 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 * ---------- @@ -271,8 +390,8 @@ abstract class BTypes[G <: Global](val __global_dont_use: G) { * 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 appear elsewhere (method, constructor, initializer - * block, field initializer) + * - 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) @@ -284,9 +403,11 @@ abstract class BTypes[G <: Global](val __global_dont_use: G) { * 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. + * 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. + * - 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 @@ -327,6 +448,13 @@ abstract class BTypes[G <: Global](val __global_dont_use: G) { * 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 @@ -337,9 +465,8 @@ abstract class BTypes[G <: Global](val __global_dont_use: G) { * Note: the field is required for anonymous classes defined within local variable * initializers (within a method), Java example below (**). * - * Currently, the Scala compiler sets "method" to the class constructor for classes - * defined in initializer blocks or field initializers. This is probably OK, since the - * Scala compiler desugars these statements into to the primary constructor. + * 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. * * * (*) @@ -401,30 +528,37 @@ abstract class BTypes[G <: Global](val __global_dont_use: G) { * STATIC flag * ----------- * - * Java: static nested classes have the "static" flag in the InnerClass attribute. This is not the - * case for local classes defined within a static method, even though such classes, as they are - * defined in a static context, don't take an "outer" instance. - * Non-static nested classes (inner classes, including local classes defined in a non-static - * method) take an "outer" instance on construction. + * 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 {} } + * } + * + * B has an outer pointer, C doesn't. Both B and C are NOT marked static in the InnerClass table. + * + * It seems sane to follow the same principle in the Scala compiler. So: * - * Scala: Explicitouter adds an "outer" parameter to nested classes, except for classes defined - * in a static context, i.e. when all outer classes are module classes. * package p * object O1 { - * class C1 // static - * object O2 { + * class C1 // static inner class + * object O2 { // static inner module * def f = { - * class C2 { // static - * class C3 // non-static, needs outer + * class C2 { // non-static inner class, even though there's no outer pointer + * class C3 // non-static, has an outer pointer * } * } * } * } * - * Int the InnerClass attribute, the `static` flag is added for all classes defined in a static - * context, i.e. also for C2. This is different than in Java. - * - * * Mirror Classes * -------------- * @@ -432,64 +566,177 @@ abstract class BTypes[G <: Global](val __global_dont_use: G) { */ /** - * Class or Interface type. - * - * The information for creating a ClassBType (superClass, interfaces, etc) is obtained - * - either from a ClassSymbol, for classes being compiled or referenced from source (see - * BCodeTypes) - * - or, during inlining, from ASM ClassNodes that are parsed from class files. - * - * The class name is represented as a slice of the `chrs` array. 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. - * - * Not a case class because that would expose the constructor that takes (offset, length) - * parameters (I didn't find a way to make it private, also the factory in the companion). - * - * @param offset See below - * @param length The class name is represented as offset and length in the `chrs` array. - * The (public) constructors of ClassBType take a BTypeName, which are - * hash-consed. This ensures that two ClassBType instances for the same name - * have the same offset and length. - * - * 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). + * 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. */ - class ClassBType private(val offset: Int, val length: Int) extends RefBType { + final class ClassBType(val offset: Int, val length: Int) extends RefBType { /** - * Construct a ClassBType from the (intenred) internal name of a class. + * 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 internalName The internal name as a slice of the `chrs` array. The internal name does - * not have the surrounding 'L' and ';'. Note that - * `classSymbol.javaBinaryName` returns exactly such a name. + * B.info.nestedInfo.outerClass == A + * A.info.memberClasses contains B */ - def this(internalName: BTypeName) = this(internalName.start, internalName.length) + private var _info: ClassInfo = null - /** - * Construct a ClassBType from the internal name of a class. - * - * @param internalName The internal name of a class has the form "java/lang/String", without the - * surrounding 'L' and ';'. - */ - def this(internalName: String) = this({ - assert(!(internalName.head == 'L' && internalName.last == ';'), s"Descriptor instead of internal name: $internalName") - createNewName(internalName) - }) + 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)) + } + /** - * Custom equals / hashCode are needed because this is not a case class. + * 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: we only compare the name (offset / length) */ override def equals(o: Any): Boolean = (this eq o.asInstanceOf[Object]) || (o match { case c: ClassBType => c.offset == this.offset && c.length == this.length @@ -506,17 +753,92 @@ abstract class BTypes[G <: Global](val __global_dont_use: G) { } object ClassBType { - def apply(internalName: BTypeName): ClassBType = new ClassBType(internalName) - def apply(internalName: String): ClassBType = new ClassBType(internalName) - /** * Pattern matching on a ClassBType extracts the `internalName` of the class. */ def unapply(c: ClassBType): Option[String] = if (c == null) None 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 @@ -529,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)) - } - - 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) - } - } + case class MethodBType(argumentTypes: List[BType], returnType: BType) extends BType - 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 2392033760..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 @@ -621,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) @@ -636,13 +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 - // TODO @lry according to the spec, all nested classes should be added, also local and - // anonymous. This seems to add only member classes - or not? it's exitingErasure, so maybe - // local / anonymous classes have been lifted by lambdalift. are they in the "decls" though? - 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 @@ -656,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) @@ -1222,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: @@ -1286,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 89866b7ce9..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() @@ -272,7 +275,7 @@ abstract class GenBCode extends BCodeSyncAndTry { arrivalPos = 0 // just in case scalaPrimitives.init() - initBCodeTypes() + 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/backend/opt/Inliners.scala b/src/compiler/scala/tools/nsc/backend/opt/Inliners.scala index 8df3969c49..351eb23c4c 100644 --- a/src/compiler/scala/tools/nsc/backend/opt/Inliners.scala +++ b/src/compiler/scala/tools/nsc/backend/opt/Inliners.scala @@ -283,7 +283,7 @@ abstract class Inliners extends SubComponent { } val tfa = new analysis.MTFAGrowable() - tfa.stat = global.settings.Ystatistics.value + tfa.stat = global.settings.YstatisticsEnabled val staleOut = new mutable.ListBuffer[BasicBlock] val splicedBlocks = mutable.Set.empty[BasicBlock] val staleIn = mutable.Set.empty[BasicBlock] 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/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 d0b8fd70ed..6b339b2a6d 100644 --- a/src/compiler/scala/tools/nsc/settings/AbsScalaSettings.scala +++ b/src/compiler/scala/tools/nsc/settings/AbsScalaSettings.scala @@ -7,20 +7,24 @@ package scala package tools.nsc package settings +import scala.language.higherKinds + trait AbsScalaSettings { self: AbsSettings => + type MultiChoiceEnumeration <: Enumeration + type Setting <: AbsSetting - type BooleanSetting <: Setting { type T = Boolean } - type ChoiceSetting <: Setting { type T = String } - type IntSetting <: Setting { type T = Int } - type MultiStringSetting <: Setting { type T = List[String] } - type MultiChoiceSetting <: Setting { type T = List[String] } - type PathSetting <: Setting { type T = String } - type PhasesSetting <: Setting { type T = List[String] } - type StringSetting <: Setting { type T = String } - type PrefixSetting <: Setting { type T = List[String] } + type BooleanSetting <: Setting { type T = Boolean } + type ChoiceSetting <: Setting { type T = String } + type IntSetting <: Setting { type T = Int } + type MultiStringSetting <: Setting { type T = List[String] } + type MultiChoiceSetting[E <: MultiChoiceEnumeration] <: Setting { type T <: E#ValueSet } + type PathSetting <: Setting { type T = String } + type PhasesSetting <: Setting { type T = List[String] } + type StringSetting <: Setting { type T = String } + type PrefixSetting <: Setting { type T = List[String] } type OutputDirs type OutputSetting <: Setting @@ -29,7 +33,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], default: Option[() => Unit])(helper: MultiChoiceSetting => String): MultiChoiceSetting + def MultiChoiceSetting[E <: MultiChoiceEnumeration](name: String, helpArg: String, descr: String, domain: E, default: Option[List[String]]): MultiChoiceSetting[E] 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 f26192f88a..bbe21477cb 100644 --- a/src/compiler/scala/tools/nsc/settings/MutableSettings.scala +++ b/src/compiler/scala/tools/nsc/settings/MutableSettings.scala @@ -209,12 +209,11 @@ class MutableSettings(val errorFn: String => Unit) def BooleanSetting(name: String, descr: String) = add(new BooleanSetting(name, descr)) def ChoiceSetting(name: String, helpArg: String, descr: String, choices: List[String], default: String) = 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 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], 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 MultiChoiceSetting[E <: MultiChoiceEnumeration](name: String, helpArg: String, descr: String, domain: E, default: Option[List[String]] = None) = + add(new MultiChoiceSetting[E](name, helpArg, descr, domain, default)) 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)) @@ -367,7 +366,7 @@ class MutableSettings(val errorFn: String => Unit) def withDeprecationMessage(msg: String): this.type = { _deprecationMessage = Some(msg) ; this } } - /** A setting represented by an integer */ + /** A setting represented by an integer. */ class IntSetting private[nsc]( name: String, descr: String, @@ -553,57 +552,193 @@ 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. + /** + * Each [[MultiChoiceSetting]] takes a MultiChoiceEnumeration as domain. The enumeration may + * use the Choice class to define values, or simply use the default `Value` constructor: + * + * object SettingDomain extends MultiChoiceEnumeration { val arg1, arg2 = Value } + * + * Or + * + * object SettingDomain extends MultiChoiceEnumeration { + * val arg1 = Choice("arg1", "help") + * val arg2 = Choice("arg2", "help") + * } + * + * Choices with a non-empty `expandsTo` enable other options. Note that expanding choices are + * not present in the multiChoiceSetting.value set, only their expansion. + */ + abstract class MultiChoiceEnumeration extends Enumeration { + case class Choice(name: String, help: String = "", expandsTo: List[Choice] = Nil) extends Val(name) + } + + /** + * A Setting that collects string-valued settings from an enumerated domain. + * - These choices can be turned on or off: "-option:on,-off" + * - If an option is set both on and off, then the option is on + * - The choice "_" enables all choices that have not been explicitly disabled + * + * Arguments can be provided in colonated or non-colonated mode, i.e. "-option a b" or + * "-option:a,b". Note that arguments starting with a "-" can only be provided in colonated mode, + * otherwise they are interpreted as a new option. + * + * In non-colonated mode, the setting stops consuming arguments at the first non-choice, + * i.e. "-option a b c" only consumes "a" and "b" if "c" is not a valid choice. + * + * @param name command-line setting name, eg "-Xlint" + * @param helpArg help description for the kind of arguments it takes, eg "warning" + * @param descr description of the setting + * @param domain enumeration of choices implementing MultiChoice, or the string value is + * taken for the name + * @param default If Some(args), the default options if none are provided. If None, an + * error is printed if there are no arguments. */ - class MultiChoiceSetting private[nsc]( + class MultiChoiceSetting[E <: MultiChoiceEnumeration] private[nsc]( name: String, - arg: String, + helpArg: String, descr: String, - 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") { + val domain: E, + val default: Option[List[String]] + ) extends Setting(name, s"$descr: `_' for all, `$name:help' to list") with Clearable { - 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 "-")) + withHelpSyntax(s"$name:<_,$helpArg,-$helpArg>") - 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'.") + object ChoiceOrVal { + def unapply(a: domain.Value): Option[(String, String, List[domain.Choice])] = a match { + case c: domain.Choice => Some((c.name, c.help, c.expandsTo)) + case v: domain.Value => Some((v.toString, "", Nil)) + } + } + + type T = domain.ValueSet + protected var v: T = domain.ValueSet.empty + + // Explicitly enabled or disabled. Yeas may contain expanding options, nays may not. + private var yeas = domain.ValueSet.empty + private var nays = domain.ValueSet.empty + + // Asked for help + private var sawHelp = false + // Wildcard _ encountered + private var sawAll = false + + private def badChoice(s: String) = errorFn(s"'$s' is not a valid choice for '$name'") + private def isChoice(s: String) = (s == "_") || (choices contains pos(s)) + + private def pos(s: String) = s stripPrefix "-" + private def isPos(s: String) = !(s startsWith "-") + + override val choices: List[String] = domain.values.toList map { + case ChoiceOrVal(name, _, _) => name + } + + def descriptions: List[String] = domain.values.toList map { + case ChoiceOrVal(_, "", x :: xs) => "Enables the options "+ (x :: xs).map(_.name).mkString(", ") + case ChoiceOrVal(_, descr, _) => descr + case _ => "" + } - override protected def tts(args: List[String], halting: Boolean) = { + /** (Re)compute from current yeas, nays, wildcard status. */ + def compute() = { + def simple(v: domain.Value) = v match { + case ChoiceOrVal(_, _, Nil) => true + case _ => false + } + + /** + * Expand an expanding option, if necessary recursively. Expanding options are not included in + * the result (consistent with "_", which is not in `value` either). + * + * Note: by precondition, options in nays are not expanding, they can only be leaves. + */ + def expand(vs: domain.ValueSet): domain.ValueSet = vs flatMap { + case c @ ChoiceOrVal(_, _, Nil) => domain.ValueSet(c) + case ChoiceOrVal(_, _, others) => expand(domain.ValueSet(others: _*)) + } + + // yeas from _ or expansions are weak: an explicit nay will disable them + val weakYeas = if (sawAll) domain.values filter simple else expand(yeas filterNot simple) + value = (yeas filter simple) | (weakYeas &~ nays) + } + + /** Add a named choice to the multichoice value. */ + def add(arg: String) = arg match { + case _ if !isChoice(arg) => + badChoice(arg) + case "_" => + sawAll = true + compute() + case _ if isPos(arg) => + yeas += domain withName arg + compute() + case _ => + val choice = domain withName pos(arg) + choice match { + case ChoiceOrVal(_, _, _ :: _) => errorFn(s"'${pos(arg)}' cannot be negated, it enables other arguments") + case _ => + } + nays += choice + compute() + } + + def tryToSet(args: List[String]) = tryToSetArgs(args, halting = true) + override def tryToSetColon(args: List[String]) = tryToSetArgs(args, halting = false) + override def tryToSetFromPropertyValue(s: String) = tryToSet(s.trim.split(',').toList) // used from ide + + /** Try to set args, handling "help" and default. + * The "halting" parameter means args were "-option a b c -else" so halt + * on "-else" or other non-choice. Otherwise, args were "-option:a,b,c,d", + * so process all and report non-choices as errors. + * @param args args to process + * @param halting stop on non-arg + */ + private def tryToSetArgs(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) + case "help" => sawHelp = true + case s if isChoice(s) => added += s // this case also adds "_" + case s => badChoice(s) } - 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 + case arg :: _ if halting && (!isPos(arg) || !isChoice(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 + + // if no arg consumed, use defaults or error; otherwise, add what they added + if (rest.size == args.size) default match { + case Some(defaults) => defaults foreach add + case None => errorFn(s"'$name' requires an option. See '$name:help'.") + } else { + added foreach add + } + Some(rest) } + def contains(choice: domain.Value): Boolean = value contains choice + 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")) + def help: String = { + val choiceLength = choices.map(_.length).max + 1 + val formatStr = s" %-${choiceLength}s %s" + choices.zipAll(descriptions, "", "").map { + case (arg, descr) => formatStr.format(arg, descr) + } mkString (f"$descr%n", f"%n", "") + } + + def clear(): Unit = { + v = domain.ValueSet.empty + yeas = domain.ValueSet.empty + nays = domain.ValueSet.empty + sawAll = false + sawHelp = false + } + def unparse: List[String] = value.toList map (s => s"$name:$s") + def contains(s: String) = domain.values.find(_.toString == s).exists(value.contains) } /** A setting that accumulates all strings supplied to it, @@ -619,15 +754,15 @@ class MutableSettings(val errorFn: String => Unit) 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) = { + protected def tryToSetArgs(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(loop(args)) } - def tryToSet(args: List[String]) = tts(args, halting = true) - override def tryToSetColon(args: List[String]) = tts(args, halting = false) + def tryToSet(args: List[String]) = tryToSetArgs(args, halting = true) + override def tryToSetColon(args: List[String]) = tryToSetArgs(args, halting = false) override def tryToSetFromPropertyValue(s: String) = tryToSet(s.trim.split(',').toList) // used from ide def clear(): Unit = (v = Nil) diff --git a/src/compiler/scala/tools/nsc/settings/ScalaSettings.scala b/src/compiler/scala/tools/nsc/settings/ScalaSettings.scala index 8e69598614..91b03869e5 100644 --- a/src/compiler/scala/tools/nsc/settings/ScalaSettings.scala +++ b/src/compiler/scala/tools/nsc/settings/ScalaSettings.scala @@ -45,7 +45,7 @@ trait ScalaSettings extends AbsScalaSettings 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 } + private def multihelp = allSettings exists { case s: MultiChoiceSetting[_] => s.isHelping case _ => false } /** Is an info setting set? */ def isInfo = (infoSettings exists (_.isSetByUser)) || multihelp @@ -69,28 +69,23 @@ 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. + object languageFeatures extends MultiChoiceEnumeration { + val dynamics = Choice("dynamics", "Allow direct or indirect subclasses of scala.Dynamic") + val postfixOps = Choice("postfixOps", "Allow postfix operator notation, such as `1 to 10 toList'") + val reflectiveCalls = Choice("reflectiveCalls", "Allow reflective access to members of structural types") + val implicitConversions = Choice("implicitConversions", "Allow definition of implicit functions called views") + val higherKinds = Choice("higherKinds", "Allow higher-kinded types") + val existentials = Choice("existentials", "Existential types (besides wildcard types) can be written and inferred") + val macros = Choice("experimental.macros", "Allow macro defintion (besides implementation and application)") + } val language = { - 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") - } + domain = languageFeatures + ) } /* @@ -193,7 +188,6 @@ trait ScalaSettings extends AbsScalaSettings val Ygenjavap = StringSetting ("-Ygen-javap", "dir", "Generate a parallel output directory of .javap files.", "") val Ygenasmp = StringSetting ("-Ygen-asmp", "dir", "Generate a parallel output directory of .asmp files (ie ASM Textifier output).", "") val Ydumpclasses = StringSetting ("-Ydump-classes", "dir", "Dump the generated bytecode to .class files (useful for reflective compilation that utilizes in-memory classloaders).", "") - val Ystatistics = BooleanSetting ("-Ystatistics", "Print compiler statistics.") andThen (scala.reflect.internal.util.Statistics.enabled = _) val stopAfter = PhasesSetting ("-Ystop-after", "Stop after") withAbbreviation ("-stop") // backward compat val stopBefore = PhasesSetting ("-Ystop-before", "Stop before") val Yrangepos = BooleanSetting ("-Yrangepos", "Use range positions for syntax trees.") @@ -217,6 +211,20 @@ trait ScalaSettings extends AbsScalaSettings private def removalIn212 = "This flag is scheduled for removal in 2.12. If you have a case where you need this flag then please report a bug." + object YstatisticsPhases extends MultiChoiceEnumeration { val parser, typer, patmat, erasure, cleanup = Value } + val Ystatistics = { + val description = "Print compiler statistics for specific phases" + MultiChoiceSetting( + name = "-Ystatistics", + helpArg = "phase", + descr = description, + domain = YstatisticsPhases, + default = Some(List("_")) + ) withPostSetHook { _ => scala.reflect.internal.util.Statistics.enabled = true } + } + + def YstatisticsEnabled = Ystatistics.value.nonEmpty + /** Area-specific debug output. */ val Ydocdebug = BooleanSetting("-Ydoc-debug", "Trace all scaladoc activity.") diff --git a/src/compiler/scala/tools/nsc/settings/Warnings.scala b/src/compiler/scala/tools/nsc/settings/Warnings.scala index bec068b56a..c400e8c29c 100644 --- a/src/compiler/scala/tools/nsc/settings/Warnings.scala +++ b/src/compiler/scala/tools/nsc/settings/Warnings.scala @@ -17,142 +17,94 @@ trait Warnings { // Warning semantics. val fatalWarnings = BooleanSetting("-Xfatal-warnings", "Fail the compilation if there are any warnings.") - // 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, - 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. - protected def lintWarnings = List( - warnInaccessible, - warnNullaryOverride, - warnNullaryUnit, - warnAdaptedArgs, - warnInferAny, - warnMissingInterpolator, - warnDocDetached, - warnPrivateShadow, - warnPolyImplicitOverload, - warnOptionImplicit, - warnDelayedInit, - warnByNameRightAssociative, - warnPackageObjectClasses, - warnUnsoundMatch - ) - - // 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 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") + // Non-lint warnings + + 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.") + // SI-7712, SI-7707 warnUnused not quite ready for prime-time + val warnUnused = BooleanSetting("-Ywarn-unused", "Warn when local and private vals, vars, defs, and types are are unused.") + // currently considered too noisy for general use + val warnUnusedImport = BooleanSetting("-Ywarn-unused-import", "Warn when imports are unused.") // 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") + val warnValueOverrides = { // currently turned off as experimental. creaded using constructor (new BS), so not available on the command line. + val flag = new BooleanSetting("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 + + object LintWarnings extends MultiChoiceEnumeration { + class LintWarning(name: String, help: String, val yAliased: Boolean) extends Choice(name, help) + def LintWarning(name: String, help: String, yAliased: Boolean = false) = new LintWarning(name, help, yAliased) + + val AdaptedArgs = LintWarning("adapted-args", "Warn if an argument list is modified to match the receiver.", true) + val NullaryUnit = LintWarning("nullary-unit", "Warn when nullary methods return Unit.", true) + val Inaccessible = LintWarning("inaccessible", "Warn about inaccessible types in method signatures.", true) + val NullaryOverride = LintWarning("nullary-override", "Warn when non-nullary `def f()' overrides nullary `def f'.", true) + val InferAny = LintWarning("infer-any", "Warn when a type argument is inferred to be `Any`.", true) + val MissingInterpolator = LintWarning("missing-interpolator", "A string literal appears to be missing an interpolator id.") + val DocDetached = LintWarning("doc-detached", "A ScalaDoc comment appears to be detached from its element.") + val PrivateShadow = LintWarning("private-shadow", "A private field (or class parameter) shadows a superclass field.") + val TypeParameterShadow = LintWarning("type-parameter-shadow", "A local type parameter shadows a type already in scope.") + val PolyImplicitOverload = LintWarning("poly-implicit-overload", "Parameterized overloaded implicit methods are not visible as view bounds.") + val OptionImplicit = LintWarning("option-implicit", "Option.apply used implicit view.") + val DelayedInitSelect = LintWarning("delayedinit-select", "Selecting member of DelayedInit") + val ByNameRightAssociative = LintWarning("by-name-right-associative", "By-name parameter of right associative operator.") + val PackageObjectClasses = LintWarning("package-object-classes", "Class or object defined in package object.") + val UnsoundMatch = LintWarning("unsound-match", "Pattern match may not be typesafe.") + + def allLintWarnings = values.toSeq.asInstanceOf[Seq[LintWarning]] } + import LintWarnings._ + + def warnAdaptedArgs = lint contains AdaptedArgs + def warnNullaryUnit = lint contains NullaryUnit + def warnInaccessible = lint contains Inaccessible + def warnNullaryOverride = lint contains NullaryOverride + def warnInferAny = lint contains InferAny + def warnMissingInterpolator = lint contains MissingInterpolator + def warnDocDetached = lint contains DocDetached + def warnPrivateShadow = lint contains PrivateShadow + def warnTypeParameterShadow = lint contains TypeParameterShadow + def warnPolyImplicitOverload = lint contains PolyImplicitOverload + def warnOptionImplicit = lint contains OptionImplicit + def warnDelayedInit = lint contains DelayedInitSelect + def warnByNameRightAssociative = lint contains ByNameRightAssociative + def warnPackageObjectClasses = lint contains PackageObjectClasses + def warnUnsoundMatch = lint contains UnsoundMatch // 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" + def YwarnAdaptedArgs = warnAdaptedArgs @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" + def YwarnNullaryUnit = warnNullaryUnit @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" + def YwarnInaccessible = warnInaccessible @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" + def YwarnNullaryOverride = warnNullaryOverride @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" + def YwarnInferAny = warnInferAny + + // The Xlint warning group. + val lint = MultiChoiceSetting( + name = "-Xlint", + helpArg = "warning", + descr = "Enable or disable specific warnings", + domain = LintWarnings, + default = Some(List("_"))) + + allLintWarnings foreach { + case w if w.yAliased => + BooleanSetting(s"-Ywarn-${w.name}", {w.help}) withPostSetHook { s => + lint.add(if (s) w.name else s"-${w.name}") + } // withDeprecationMessage s"Enable -Xlint:${c._1}" + case _ => + } private lazy val warnSelectNullable = BooleanSetting("-Xcheck-null", "This option is obsolete and does nothing.") 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/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/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/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..421021163e 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)) @@ -554,7 +552,7 @@ trait Infer extends Checkable { } case _ => context.tree.pos } - if (settings.warnInferAny.value && context.reportErrors && canWarnAboutAny) { + if (settings.warnInferAny && context.reportErrors && canWarnAboutAny) { foreachWithIndex(targs) ((targ, idx) => targ.typeSymbol match { case sym @ (AnyClass | AnyValClass) => @@ -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 33d3432ae2..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") @@ -624,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 7bbd81118a..fdff2f3076 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Namers.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Namers.scala @@ -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/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 47465875e9..af4e9e8927 100644 --- a/src/compiler/scala/tools/nsc/typechecker/RefChecks.scala +++ b/src/compiler/scala/tools/nsc/typechecker/RefChecks.scala @@ -1287,6 +1287,7 @@ abstract class RefChecks extends InfoTransform with scala.reflect.internal.trans private def checkUndesiredProperties(sym: Symbol, pos: Position) { // If symbol is deprecated, and the point of reference is not enclosed // in either a deprecated member or a scala bridge method, issue a warning. + // TODO: x.hasBridgeAnnotation doesn't seem to be needed here... if (sym.isDeprecated && !currentOwner.ownerChain.exists(x => x.isDeprecated || x.hasBridgeAnnotation)) currentRun.reporting.deprecationWarning(pos, sym) @@ -1305,7 +1306,7 @@ abstract class RefChecks extends InfoTransform with scala.reflect.internal.trans reporter.warning(pos, s"${sym.fullLocationString} has changed semantics in version ${sym.migrationVersion.get}:\n${sym.migrationMessage.get}") } // See an explanation of compileTimeOnly in its scaladoc at scala.annotation.compileTimeOnly. - if (sym.isCompileTimeOnly) { + if (sym.isCompileTimeOnly && !currentOwner.ownerChain.exists(x => x.isCompileTimeOnly)) { def defaultMsg = sm"""Reference to ${sym.fullLocationString} should not have survived past type checking, |it should have been processed and eliminated during expansion of an enclosing macro.""" @@ -1633,7 +1634,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 38b00a015b..db81eecdf5 100644 --- a/src/compiler/scala/tools/nsc/typechecker/SuperAccessors.scala +++ b/src/compiler/scala/tools/nsc/typechecker/SuperAccessors.scala @@ -82,11 +82,11 @@ abstract class SuperAccessors extends transform.Transform with transform.TypingT val buf = accDefs.getOrElse(clazz, sys.error("no acc def buf for "+clazz)) buf += typers(clazz) typed tree } - private def ensureAccessor(sel: Select) = { + private def ensureAccessor(sel: Select, mixName: TermName = nme.EMPTY) = { val Select(qual, name) = sel val sym = sel.symbol val clazz = qual.symbol - val supername = nme.superName(name) + val supername = nme.superName(name, mixName) val superAcc = clazz.info.decl(supername).suchThat(_.alias == sym) orElse { debuglog(s"add super acc ${sym.fullLocationString} to $clazz") val acc = clazz.newMethod(supername, sel.pos, SUPERACCESSOR | PRIVATE | ARTIFACT) setAlias sym @@ -150,8 +150,20 @@ abstract class SuperAccessors extends transform.Transform with transform.TypingT } } - if (name.isTermName && mix == tpnme.EMPTY && (clazz.isTrait || clazz != currentClass || !validCurrentOwner)) - ensureAccessor(sel) + def mixIsTrait = sup.tpe match { + case SuperType(thisTpe, superTpe) => superTpe.typeSymbol.isTrait + } + + val needAccessor = name.isTermName && { + mix.isEmpty && (clazz.isTrait || clazz != currentClass || !validCurrentOwner) || + // SI-8803. If we access super[A] from an inner class (!= currentClass) or closure (validCurrentOwner), + // where A is the superclass we need an accessor. If A is a parent trait we don't: in this case mixin + // will re-route the super call directly to the impl class (it's statically known). + !mix.isEmpty && (clazz != currentClass || !validCurrentOwner) && !mixIsTrait + } + + if (needAccessor) + ensureAccessor(sel, mix.toTermName) else sel } 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 70f44c4fc6..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) @@ -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 _ => diff --git a/src/compiler/scala/tools/nsc/util/StatisticsInfo.scala b/src/compiler/scala/tools/nsc/util/StatisticsInfo.scala index 225f6ca68e..be245347a8 100644 --- a/src/compiler/scala/tools/nsc/util/StatisticsInfo.scala +++ b/src/compiler/scala/tools/nsc/util/StatisticsInfo.scala @@ -14,12 +14,10 @@ abstract class StatisticsInfo { import global._ import scala.reflect.internal.TreesStats.nodeByType - val phasesShown = List("parser", "typer", "patmat", "erasure", "cleanup") - val retainedCount = Statistics.newCounter("#retained tree nodes") val retainedByType = Statistics.newByClass("#retained tree nodes by type")(Statistics.newCounter("")) - def print(phase: Phase) = if (phasesShown contains phase.name) { + def print(phase: Phase) = if (settings.Ystatistics contains phase.name) { inform("*** Cumulative statistics at phase " + phase) retainedCount.value = 0 for (c <- retainedByType.keys) 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/eclipse/repl/.classpath b/src/eclipse/repl/.classpath index 601a231aeb..8ff9aabfbf 100644 --- a/src/eclipse/repl/.classpath +++ b/src/eclipse/repl/.classpath @@ -1,11 +1,11 @@ <?xml version="1.0" encoding="UTF-8"?> <classpath> - <classpathentry kind="src" path="repl"/> - <classpathentry combineaccessrules="false" kind="src" path="/asm"/> - <classpathentry kind="var" path="M2_REPO/jline/jline/2.11/jline-2.11.jar"/> - <classpathentry kind="con" path="org.scala-ide.sdt.launching.SCALA_CONTAINER"/> - <classpathentry kind="con" path="org.scala-ide.sdt.launching.SCALA_COMPILER_CONTAINER"/> - <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/> - <classpathentry kind="var" path="SCALA_BASEDIR/build/deps/repl/jline-2.11.jar"/> - <classpathentry kind="output" path="build-quick-repl"/> + <classpathentry kind="src" path="repl"/> + <classpathentry combineaccessrules="false" kind="src" path="/asm"/> + <classpathentry kind="var" path="M2_REPO/jline/jline/2.12/jline-2.12.jar"/> + <!-- <classpathentry kind="var" path="SCALA_BASEDIR/build/deps/repl/jline-2.12.jar"/> --> + <classpathentry combineaccessrules="false" kind="src" path="/scala-compiler"/> + <classpathentry combineaccessrules="false" kind="src" path="/scala-library"/> + <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/> + <classpathentry kind="output" path="build-quick-repl"/> </classpath> diff --git a/src/interactive/scala/tools/nsc/interactive/Global.scala b/src/interactive/scala/tools/nsc/interactive/Global.scala index 95027a26b1..174254d523 100644 --- a/src/interactive/scala/tools/nsc/interactive/Global.scala +++ b/src/interactive/scala/tools/nsc/interactive/Global.scala @@ -142,8 +142,6 @@ class Global(settings: Settings, _reporter: Reporter, projectName: String = "") // don't keep the original owner in presentation compiler runs // (the map will grow indefinitely, and the only use case is the backend) override protected def saveOriginalOwner(sym: Symbol) { } - override protected def originalEnclosingMethod(sym: Symbol) = - abort("originalOwner is not kept in presentation compiler runs.") override def forInteractive = true override protected def synchronizeNames = true diff --git a/src/library/scala/Option.scala b/src/library/scala/Option.scala index 905e925f57..66900e7258 100644 --- a/src/library/scala/Option.scala +++ b/src/library/scala/Option.scala @@ -211,6 +211,17 @@ sealed abstract class Option[+A] extends Product with Serializable { /** Tests whether the option contains a given value as an element. * + * @example {{{ + * // Returns true because Some instance contains string "something" which equals "something". + * Some("something") contains "something" + * + * // Returns false because "something" != "anything". + * Some("something") contains "anything" + * + * // Returns false when method called on None. + * None contains "anything" + * }}} + * * @param elem the element to test. * @return `true` if the option has an element that is equal (as * determined by `==`) to `elem`, `false` otherwise. @@ -251,6 +262,17 @@ sealed abstract class Option[+A] extends Product with Serializable { * nonempty '''and''' `pf` is defined for that value. * Returns $none otherwise. * + * @example {{{ + * // Returns Some(HTTP) because the partial function covers the case. + * Some("http") collect {case "http" => "HTTP"} + * + * // Returns None because the partial function doesn't cover the case. + * Some("ftp") collect {case "http" => "HTTP"} + * + * // Returns None because None is passed to the collect method. + * None collect {case value => value} + * }}} + * * @param pf the partial function. * @return the result of applying `pf` to this $option's * value (if possible), or $none. diff --git a/src/library/scala/PartialFunction.scala b/src/library/scala/PartialFunction.scala index 7f4a9dc45d..fba759eb32 100644 --- a/src/library/scala/PartialFunction.scala +++ b/src/library/scala/PartialFunction.scala @@ -20,6 +20,11 @@ package scala * {{{ * val f: PartialFunction[Int, Any] = { case _ => 1/0 } * }}} + * + * It is the responsibility of the caller to call `isDefinedAt` before + * calling `apply`, because if `isDefinedAt` is false, it is not guaranteed + * `apply` will throw an exception to indicate an error condition. If an + * exception is not thrown, evaluation may result in an arbitrary value. * * The main distinction between `PartialFunction` and [[scala.Function1]] is * that the user of a `PartialFunction` may choose to do something different diff --git a/src/library/scala/Predef.scala b/src/library/scala/Predef.scala index faeb1dcbe2..7f717aa6e4 100644 --- a/src/library/scala/Predef.scala +++ b/src/library/scala/Predef.scala @@ -303,7 +303,7 @@ object Predef extends LowPriorityImplicits with DeprecatedPredef { @inline implicit def augmentString(x: String): StringOps = new StringOps(x) @inline implicit def unaugmentString(x: StringOps): String = x.repr - // printing and reading ----------------------------------------------- + // printing ----------------------------------------------------------- def print(x: Any) = Console.print(x) def println() = Console.println() diff --git a/src/library/scala/StringContext.scala b/src/library/scala/StringContext.scala index 2d2601c6fb..2632994a34 100644 --- a/src/library/scala/StringContext.scala +++ b/src/library/scala/StringContext.scala @@ -38,7 +38,7 @@ import scala.annotation.tailrec * To provide your own string interpolator, create an implicit class * which adds a method to `StringContext`. Here's an example: * {{{ - * implicit class JsonHelper(val sc: StringContext) extends AnyVal { + * implicit class JsonHelper(private val sc: StringContext) extends AnyVal { * def json(args: Any*): JSONObject = ... * } * val x: JSONObject = json"{ a: $a }" @@ -163,7 +163,7 @@ case class StringContext(parts: String*) { */ // The implementation is hardwired to `scala.tools.reflect.MacroImplementations.macro_StringInterpolation_f` // Using the mechanism implemented in `scala.tools.reflect.FastTrack` - def f(args: Any*): String = macro ??? + def f[A >: Any](args: A*): String = macro ??? } object StringContext { diff --git a/src/library/scala/collection/GenMapLike.scala b/src/library/scala/collection/GenMapLike.scala index 4e7d359251..bce9740522 100644 --- a/src/library/scala/collection/GenMapLike.scala +++ b/src/library/scala/collection/GenMapLike.scala @@ -102,7 +102,7 @@ trait GenMapLike[A, +B, +Repr] extends GenIterableLike[(A, B), Repr] with Equals */ def mapValues[C](f: B => C): GenMap[A, C] - /** Compares two maps structurally; i.e. checks if all mappings + /** Compares two maps structurally; i.e., checks if all mappings * contained in this map are also contained in the other map, * and vice versa. * diff --git a/src/library/scala/collection/Iterator.scala b/src/library/scala/collection/Iterator.scala index f6f46e158f..660cc5a42a 100644 --- a/src/library/scala/collection/Iterator.scala +++ b/src/library/scala/collection/Iterator.scala @@ -1088,6 +1088,9 @@ trait Iterator[+A] extends TraversableOnce[A] { } /** Returns this iterator with patched values. + * Patching at negative indices is the same as patching starting at 0. + * Patching at indices at or larger than the length of the original iterator appends the patch to the end. + * If more values are replaced than actually exist, the excess is ignored. * * @param from The start index from which to patch * @param patchElems The iterator of patch values @@ -1096,18 +1099,33 @@ trait Iterator[+A] extends TraversableOnce[A] { */ def patch[B >: A](from: Int, patchElems: Iterator[B], replaced: Int): Iterator[B] = new AbstractIterator[B] { private var origElems = self - private var i = 0 - def hasNext: Boolean = - if (i < from) origElems.hasNext - else patchElems.hasNext || origElems.hasNext + private var i = (if (from > 0) from else 0) // Counts down, switch to patch on 0, -1 means use patch first + def hasNext: Boolean = { + if (i == 0) { + origElems = origElems drop replaced + i = -1 + } + origElems.hasNext || patchElems.hasNext + } def next(): B = { - // We have to do this *first* just in case from = 0. - if (i == from) origElems = origElems drop replaced - val result: B = - if (i < from || !patchElems.hasNext) origElems.next() - else patchElems.next() - i += 1 - result + if (i == 0) { + origElems = origElems drop replaced + i = -1 + } + if (i < 0) { + if (patchElems.hasNext) patchElems.next() + else origElems.next() + } + else { + if (origElems.hasNext) { + i -= 1 + origElems.next() + } + else { + i = -1 + patchElems.next() + } + } } } diff --git a/src/library/scala/collection/SeqViewLike.scala b/src/library/scala/collection/SeqViewLike.scala index 5e31ac4a53..e719f19c78 100644 --- a/src/library/scala/collection/SeqViewLike.scala +++ b/src/library/scala/collection/SeqViewLike.scala @@ -154,17 +154,27 @@ trait SeqViewLike[+A, } } + // Note--for this to work, must ensure 0 <= from and 0 <= replaced + // Must also take care to allow patching inside an infinite stream + // (patching in an infinite stream is not okay) trait Patched[B >: A] extends Transformed[B] { protected[this] val from: Int protected[this] val patch: GenSeq[B] protected[this] val replaced: Int private lazy val plen = patch.length override def iterator: Iterator[B] = self.iterator patch (from, patch.iterator, replaced) - def length: Int = self.length + plen - replaced - def apply(idx: Int): B = - if (idx < from) self.apply(idx) - else if (idx < from + plen) patch.apply(idx - from) + def length: Int = { + val len = self.length + val pre = math.min(from, len) + val post = math.max(0, len - pre - replaced) + pre + plen + post + } + def apply(idx: Int): B = { + val actualFrom = if (self.lengthCompare(from) < 0) self.length else from + if (idx < actualFrom) self.apply(idx) + else if (idx < actualFrom + plen) patch.apply(idx - actualFrom) else self.apply(idx - plen + replaced) + } final override protected[this] def viewIdentifier = "P" } @@ -210,7 +220,10 @@ trait SeqViewLike[+A, override def reverse: This = newReversed.asInstanceOf[This] override def patch[B >: A, That](from: Int, patch: GenSeq[B], replaced: Int)(implicit bf: CanBuildFrom[This, B, That]): That = { - newPatched(from, patch, replaced).asInstanceOf[That] + // Be careful to not evaluate the entire sequence! Patch should work (slowly, perhaps) on infinite streams. + val nonNegFrom = math.max(0,from) + val nonNegRep = math.max(0,replaced) + newPatched(nonNegFrom, patch, nonNegRep).asInstanceOf[That] // was: val b = bf(repr) // if (b.isInstanceOf[NoBuilder[_]]) newPatched(from, patch, replaced).asInstanceOf[That] // else super.patch[B, That](from, patch, replaced)(bf) diff --git a/src/library/scala/collection/TraversableLike.scala b/src/library/scala/collection/TraversableLike.scala index d3a7db6968..a8731a51b1 100644 --- a/src/library/scala/collection/TraversableLike.scala +++ b/src/library/scala/collection/TraversableLike.scala @@ -253,7 +253,7 @@ trait TraversableLike[+A, +Repr] extends Any b.result } - private def filterImpl(p: A => Boolean, isFlipped: Boolean): Repr = { + private[scala] def filterImpl(p: A => Boolean, isFlipped: Boolean): Repr = { val b = newBuilder for (x <- this) if (p(x) != isFlipped) b += x diff --git a/src/library/scala/collection/TraversableOnce.scala b/src/library/scala/collection/TraversableOnce.scala index a8c4e047ab..13cd99d910 100644 --- a/src/library/scala/collection/TraversableOnce.scala +++ b/src/library/scala/collection/TraversableOnce.scala @@ -75,7 +75,7 @@ trait TraversableOnce[+A] extends Any with GenTraversableOnce[A] { // at least indirectly. Currently, these are `ArrayOps` and `StringOps`. // It is also implemented in `TraversableOnce[A]`. /** A version of this collection with all - * of the operations implemented sequentially (i.e. in a single-threaded manner). + * of the operations implemented sequentially (i.e., in a single-threaded manner). * * This method returns a reference to this collection. In parallel collections, * it is redefined to return a sequential implementation of this collection. In diff --git a/src/library/scala/collection/immutable/Stream.scala b/src/library/scala/collection/immutable/Stream.scala index d3ff5e8abf..1f97c4c769 100644 --- a/src/library/scala/collection/immutable/Stream.scala +++ b/src/library/scala/collection/immutable/Stream.scala @@ -468,8 +468,18 @@ self => ) else super.flatMap(f)(bf) + override private[scala] def filterImpl(p: A => Boolean, isFlipped: Boolean): Stream[A] = { + // optimization: drop leading prefix of elems for which f returns false + // var rest = this dropWhile (!p(_)) - forget DRY principle - GC can't collect otherwise + var rest = this + while (!rest.isEmpty && p(rest.head) == isFlipped) rest = rest.tail + // private utility func to avoid `this` on stack (would be needed for the lazy arg) + if (rest.nonEmpty) Stream.filteredTail(rest, p, isFlipped) + else Stream.Empty + } + /** Returns all the elements of this `Stream` that satisfy the predicate `p` - * in a new `Stream` - i.e. it is still a lazy data structure. The order of + * in a new `Stream` - i.e., it is still a lazy data structure. The order of * the elements is preserved * * @param p the predicate used to filter the stream. @@ -481,15 +491,7 @@ self => * // produces * }}} */ - override def filter(p: A => Boolean): Stream[A] = { - // optimization: drop leading prefix of elems for which f returns false - // var rest = this dropWhile (!p(_)) - forget DRY principle - GC can't collect otherwise - var rest = this - while (!rest.isEmpty && !p(rest.head)) rest = rest.tail - // private utility func to avoid `this` on stack (would be needed for the lazy arg) - if (rest.nonEmpty) Stream.filteredTail(rest, p) - else Stream.Empty - } + override def filter(p: A => Boolean): Stream[A] = filterImpl(p, isFlipped = false) // This override is only left in 2.11 because of binary compatibility, see PR #3925 override final def withFilter(p: A => Boolean): StreamWithFilter = new StreamWithFilter(p) @@ -1187,8 +1189,8 @@ object Stream extends SeqFactory[Stream] { else cons(start, range(start + step, end, step)) } - private[immutable] def filteredTail[A](stream: Stream[A], p: A => Boolean) = { - cons(stream.head, stream.tail filter p) + private[immutable] def filteredTail[A](stream: Stream[A], p: A => Boolean, isFlipped: Boolean) = { + cons(stream.head, stream.tail.filterImpl(p, isFlipped)) } private[immutable] def collectedTail[A, B, That](head: B, stream: Stream[A], pf: PartialFunction[A, B], bf: CanBuildFrom[Stream[A], B, That]) = { diff --git a/src/library/scala/collection/immutable/StringLike.scala b/src/library/scala/collection/immutable/StringLike.scala index 8e1d950d00..738b294ce6 100644 --- a/src/library/scala/collection/immutable/StringLike.scala +++ b/src/library/scala/collection/immutable/StringLike.scala @@ -121,14 +121,14 @@ self => } /** Return all lines in this string in an iterator, excluding trailing line - * end characters, i.e. apply `.stripLineEnd` to all lines + * end characters, i.e., apply `.stripLineEnd` to all lines * returned by `linesWithSeparators`. */ def lines: Iterator[String] = linesWithSeparators map (line => new WrappedString(line).stripLineEnd) /** Return all lines in this string in an iterator, excluding trailing line - * end characters, i.e. apply `.stripLineEnd` to all lines + * end characters, i.e., apply `.stripLineEnd` to all lines * returned by `linesWithSeparators`. */ @deprecated("Use `lines` instead.","2.11.0") diff --git a/src/library/scala/concurrent/Future.scala b/src/library/scala/concurrent/Future.scala index 4ed0687334..e93a3284dc 100644 --- a/src/library/scala/concurrent/Future.scala +++ b/src/library/scala/concurrent/Future.scala @@ -102,7 +102,7 @@ trait Future[+T] extends Awaitable[T] { /* Callbacks */ - /** When this future is completed successfully (i.e. with a value), + /** When this future is completed successfully (i.e., with a value), * apply the provided partial function to the value if the partial function * is defined at that value. * @@ -118,7 +118,7 @@ trait Future[+T] extends Awaitable[T] { case _ => } - /** When this future is completed with a failure (i.e. with a throwable), + /** When this future is completed with a failure (i.e., with a throwable), * apply the provided callback to the throwable. * * $caughtThrowables diff --git a/src/library/scala/concurrent/duration/Deadline.scala b/src/library/scala/concurrent/duration/Deadline.scala index 61cbe47530..a25a478602 100644 --- a/src/library/scala/concurrent/duration/Deadline.scala +++ b/src/library/scala/concurrent/duration/Deadline.scala @@ -25,15 +25,15 @@ package scala.concurrent.duration */ case class Deadline private (time: FiniteDuration) extends Ordered[Deadline] { /** - * Return a deadline advanced (i.e. moved into the future) by the given duration. + * Return a deadline advanced (i.e., moved into the future) by the given duration. */ def +(other: FiniteDuration): Deadline = copy(time = time + other) /** - * Return a deadline moved backwards (i.e. towards the past) by the given duration. + * Return a deadline moved backwards (i.e., towards the past) by the given duration. */ def -(other: FiniteDuration): Deadline = copy(time = time - other) /** - * Calculate time difference between this and the other deadline, where the result is directed (i.e. may be negative). + * Calculate time difference between this and the other deadline, where the result is directed (i.e., may be negative). */ def -(other: Deadline): FiniteDuration = time - other.time /** diff --git a/src/library/scala/math/BigDecimal.scala b/src/library/scala/math/BigDecimal.scala index bcbed645a7..5a81710986 100644 --- a/src/library/scala/math/BigDecimal.scala +++ b/src/library/scala/math/BigDecimal.scala @@ -617,10 +617,10 @@ extends ScalaNumber with ScalaNumericConversions with Serializable { */ def abs: BigDecimal = if (signum < 0) unary_- else this - /** Returns the sign of this BigDecimal, i.e. + /** Returns the sign of this BigDecimal; * -1 if it is less than 0, - * +1 if it is greater than 0 - * 0 if it is equal to 0 + * +1 if it is greater than 0, + * 0 if it is equal to 0. */ def signum: Int = this.bigDecimal.signum() diff --git a/src/library/scala/math/BigInt.scala b/src/library/scala/math/BigInt.scala index 689fc0c3e1..abc7371d9f 100644 --- a/src/library/scala/math/BigInt.scala +++ b/src/library/scala/math/BigInt.scala @@ -282,10 +282,10 @@ final class BigInt(val bigInteger: BigInteger) extends ScalaNumber with ScalaNum */ def abs: BigInt = new BigInt(this.bigInteger.abs()) - /** Returns the sign of this BigInt, i.e. + /** Returns the sign of this BigInt; * -1 if it is less than 0, - * +1 if it is greater than 0 - * 0 if it is equal to 0 + * +1 if it is greater than 0, + * 0 if it is equal to 0. */ def signum: Int = this.bigInteger.signum() diff --git a/src/library/scala/math/Ordering.scala b/src/library/scala/math/Ordering.scala index d1a4e7c35c..0d7ea8bce2 100644 --- a/src/library/scala/math/Ordering.scala +++ b/src/library/scala/math/Ordering.scala @@ -26,7 +26,7 @@ import scala.language.{implicitConversions, higherKinds} * val pairs = Array(("a", 5, 2), ("c", 3, 1), ("b", 1, 3)) * * // sort by 2nd element - * Sorting.quickSort(pairs)(Ordering.by[(String, Int, Int), Int](_._2) + * Sorting.quickSort(pairs)(Ordering.by[(String, Int, Int), Int](_._2)) * * // sort by the 3rd element, then 1st * Sorting.quickSort(pairs)(Ordering[(Int, String)].on(x => (x._3, x._1))) diff --git a/src/library/scala/math/PartialOrdering.scala b/src/library/scala/math/PartialOrdering.scala index 9e35381528..8d7fc32535 100644 --- a/src/library/scala/math/PartialOrdering.scala +++ b/src/library/scala/math/PartialOrdering.scala @@ -15,17 +15,24 @@ package math * latter. * * A [[http://en.wikipedia.org/wiki/Partial_order partial ordering]] is a - * binary relation on a type `T` that is also an equivalence relation on - * values of type `T`. This relation is exposed as the `lteq` method of - * the `PartialOrdering` trait. This relation must be: + * binary relation on a type `T`, exposed as the `lteq` method of this trait. + * This relation must be: * * - reflexive: `lteq(x, x) == '''true'''`, for any `x` of type `T`. - * - anti-symmetric: `lteq(x, y) == '''true'''` and `lteq(y, x) == true` - * then `equiv(x, y)`, for any `x` and `y` of type `T`. + * - anti-symmetric: if `lteq(x, y) == '''true'''` and + * `lteq(y, x) == '''true'''` + * then `equiv(x, y) == '''true'''`, for any `x` and `y` of type `T`. * - transitive: if `lteq(x, y) == '''true'''` and * `lteq(y, z) == '''true'''` then `lteq(x, z) == '''true'''`, * for any `x`, `y`, and `z` of type `T`. * + * Additionally, a partial ordering induces an + * [[http://en.wikipedia.org/wiki/Equivalence_relation equivalence relation]] + * on a type `T`: `x` and `y` of type `T` are equivalent if and only if + * `lteq(x, y) && lteq(y, x) == '''true'''`. This equivalence relation is + * exposed as the `equiv` method, inherited from the + * [[scala.math.Equiv Equiv]] trait. + * * @author Geoffrey Washburn * @version 1.0, 2008-04-0-3 * @since 2.7 diff --git a/src/library/scala/runtime/ScalaRunTime.scala b/src/library/scala/runtime/ScalaRunTime.scala index 5fb24f2a36..f50059ce54 100644 --- a/src/library/scala/runtime/ScalaRunTime.scala +++ b/src/library/scala/runtime/ScalaRunTime.scala @@ -62,7 +62,7 @@ object ScalaRunTime { } /** Return the class object representing an unboxed value type, - * e.g. classOf[int], not classOf[java.lang.Integer]. The compiler + * e.g., classOf[int], not classOf[java.lang.Integer]. The compiler * rewrites expressions like 5.getClass to come here. */ def anyValClass[T <: AnyVal : ClassTag](value: T): jClass[T] = diff --git a/src/library/scala/runtime/SeqCharSequence.scala b/src/library/scala/runtime/SeqCharSequence.scala index ce7d7afc9e..74e67bb9e7 100644 --- a/src/library/scala/runtime/SeqCharSequence.scala +++ b/src/library/scala/runtime/SeqCharSequence.scala @@ -44,5 +44,10 @@ final class ArrayCharSequence(val xs: Array[Char], start: Int, end: Int) extends new ArrayCharSequence(xs, start1, start1 + newlen) } } - override def toString = xs drop start take length mkString "" + override def toString = { + val start = math.max(this.start, 0) + val end = math.min(xs.length, start + length) + + if (start >= end) "" else new String(xs, start, end - start) + } } diff --git a/src/library/scala/sys/Prop.scala b/src/library/scala/sys/Prop.scala index 04c7b5108c..17ae8cb69c 100644 --- a/src/library/scala/sys/Prop.scala +++ b/src/library/scala/sys/Prop.scala @@ -20,7 +20,7 @@ package sys * @since 2.9 */ trait Prop[+T] { - /** The full name of the property, e.g. "java.awt.headless". + /** The full name of the property, e.g., "java.awt.headless". */ def key: String diff --git a/src/library/scala/util/Properties.scala b/src/library/scala/util/Properties.scala index 2daa4de9a6..8835730d95 100644 --- a/src/library/scala/util/Properties.scala +++ b/src/library/scala/util/Properties.scala @@ -107,7 +107,7 @@ private[scala] trait PropertiesTrait { val versionString = "version " + scalaPropOrElse("version.number", "(unknown)") val copyrightString = scalaPropOrElse("copyright.string", "Copyright 2002-2013, LAMP/EPFL") - /** This is the encoding to use reading in source files, overridden with -encoding + /** This is the encoding to use reading in source files, overridden with -encoding. * Note that it uses "prop" i.e. looks in the scala jar, not the system properties. */ def sourceEncoding = scalaPropOrElse("file.encoding", "UTF-8") diff --git a/src/library/scala/util/matching/Regex.scala b/src/library/scala/util/matching/Regex.scala index f35ea566ba..5c4e706dc1 100644 --- a/src/library/scala/util/matching/Regex.scala +++ b/src/library/scala/util/matching/Regex.scala @@ -6,7 +6,6 @@ ** |/ ** \* */ - /** * This package is concerned with regular expression (regex) matching against strings, * with the main goal of pulling out information from those matches, or replacing @@ -28,117 +27,127 @@ * into a [[java.lang.String]]. * */ -package scala -package util.matching +package scala.util.matching import scala.collection.AbstractIterator import java.util.regex.{ Pattern, Matcher } -/** This class provides methods for creating and using regular expressions. - * It is based on the regular expressions of the JDK since 1.4. +/** A regular expression is used to determine whether a string matches a pattern + * and, if it does, to extract or transform the parts that match. * - * Its main goal is to extract strings that match a pattern, or the subgroups - * that make it up. For that reason, it is usually used with for comprehensions - * and matching (see methods for examples). + * This class delegates to the [[java.util.regex]] package of the Java Platform. + * See the documentation for [[java.util.regex.Pattern]] for details about + * the regular expression syntax for pattern strings. * - * A Regex is created from a [[java.lang.String]] representation of the - * regular expression pattern^1^. That pattern is compiled - * during construction, so frequently used patterns should be declared outside - * loops if performance is of concern. Possibly, they might be declared on a - * companion object, so that they need only to be initialized once. + * An instance of `Regex` represents a compiled regular expression pattern. + * Since compilation is expensive, frequently used `Regex`es should be constructed + * once, outside of loops and perhaps in a companion object. * - * The canonical way of creating regex patterns is by using the method `r`, provided - * on [[java.lang.String]] through an implicit conversion into - * [[scala.collection.immutable.WrappedString]]. Using triple quotes to write these - * strings avoids having to quote the backslash character (`\`). + * The canonical way to create a `Regex` is by using the method `r`, provided + * implicitly for strings: * - * Using the constructor directly, on the other hand, makes - * it possible to declare names for subgroups in the pattern. + * {{{ + * val date = """(\d\d\d\d)-(\d\d)-(\d\d)""".r + * }}} * - * For example, both declarations below generate the same regex, but the second - * one associate names with the subgroups. + * Since escapes are not processed in multi-line string literals, using triple quotes + * avoids having to escape the backslash character, so that `"\\d"` can be written `"""\d"""`. + * + * To extract the capturing groups when a `Regex` is matched, use it as + * an extractor in a pattern match: * * {{{ - * val dateP1 = """(\d\d\d\d)-(\d\d)-(\d\d)""".r - * val dateP2 = new scala.util.matching.Regex("""(\d\d\d\d)-(\d\d)-(\d\d)""", "year", "month", "day") + * "2004-01-20" match { + * case date(year, month, day) => s"$year was a good year for PLs." + * } * }}} * - * There are two ways of using a `Regex` to find a pattern: calling methods on - * Regex, such as `findFirstIn` or `findAllIn`, or using it as an extractor in a - * pattern match. + * To check only whether the `Regex` matches, ignoring any groups, + * use a sequence wildcard: + * + * {{{ + * "2004-01-20" match { + * case date(_*) => "It's a date!" + * } + * }}} * - * Note that, when calling `findAllIn`, the resulting [[scala.util.matching.Regex.MatchIterator]] - * needs to be initialized (by calling `hasNext` or `next()`, or causing these to be - * called) before information about a match can be retrieved: + * That works because a `Regex` extractor produces a sequence of strings. + * Extracting only the year from a date could also be expressed with + * a sequence wildcard: * * {{{ - * val msg = "I love Scala" + * "2004-01-20" match { + * case date(year, _*) => s"$year was a good year for PLs." + * } + * }}} * - * // val start = " ".r.findAllIn(msg).start // throws an IllegalStateException + * In a pattern match, `Regex` normally matches the entire input. + * However, an unanchored `Regex` finds the pattern anywhere + * in the input. * - * val matches = " ".r.findAllIn(msg) - * matches.hasNext // initializes the matcher - * val start = matches.start + * {{{ + * val embeddedDate = date.unanchored + * "Date: 2004-01-20 17:25:18 GMT (10 years, 28 weeks, 5 days, 17 hours and 51 minutes ago)" match { + * case embeddedDate("2004", "01", "20") => "A Scala is born." + * } * }}} * - * When Regex is used as an extractor in a pattern match, note that it - * only succeeds if the whole text can be matched. For this reason, one usually - * calls a method to find the matching substrings, and then use it as an extractor - * to break match into subgroups. + * To find or replace matches of the pattern, use the various find and replace methods. + * There is a flavor of each method that produces matched strings and + * another that produces `Match` objects. * - * As an example, the above patterns can be used like this: + * For example, pattern matching with an unanchored `Regex`, as in the previous example, + * is the same as using `findFirstMatchIn`, except that the findFirst methods return an `Option`, + * or `None` for no match: * * {{{ - * val dateP1(year, month, day) = "2011-07-15" + * val dates = "Important dates in history: 2004-01-20, 1958-09-05, 2010-10-06, 2011-07-15" + * val firstDate = date findFirstIn dates getOrElse "No date found." + * val firstYear = for (m <- date findFirstMatchIn dates) yield m group 1 + * }}} * - * // val dateP1(year, month, day) = "Date 2011-07-15" // throws an exception at runtime + * To find all matches: * - * val copyright: String = dateP1 findFirstIn "Date of this document: 2011-07-15" match { - * case Some(dateP1(year, month, day)) => "Copyright "+year - * case None => "No copyright" - * } + * {{{ + * val allYears = for (m <- date findAllMatchIn dates) yield m group 1 + * }}} * - * val copyright: Option[String] = for { - * dateP1(year, month, day) <- dateP1 findFirstIn "Last modified 2011-07-15" - * } yield year - - * def getYears(text: String): Iterator[String] = for (dateP1(year, _, _) <- dateP1 findAllIn text) yield year - * def getFirstDay(text: String): Option[String] = for (m <- dateP2 findFirstMatchIn text) yield m group "day" + * But `findAllIn` returns a special iterator of strings that can be queried for the `MatchData` + * of the last match: + * + * {{{ + * val mi = date findAllIn dates + * val oldies = mi filter (_ => (mi group 1).toInt < 1960) map (s => s"$s: An oldie but goodie.") * }}} * - * Regex does not provide a method that returns a [[scala.Boolean]]. One can - * use [[java.lang.String]] `matches` method, or, if `Regex` is preferred, - * either ignore the return value or test the `Option` for emptyness. For example: + * Note that `findAllIn` finds matches that don't overlap. (See [[findAllIn]] for more examples.) * * {{{ - * def hasDate(text: String): Boolean = (dateP1 findFirstIn text).nonEmpty - * def printLinesWithDates(lines: Traversable[String]) { - * lines foreach { line => - * dateP1 findFirstIn line foreach { _ => println(line) } - * } - * } + * val num = """(\d+)""".r + * val all = (num findAllIn "123").toList // List("123"), not List("123", "23", "3") * }}} * - * There are also methods that can be used to replace the patterns - * on a text. The substitutions can be simple replacements, or more - * complex functions. For example: + * Text replacement can be performed unconditionally or as a function of the current match: * * {{{ - * val months = Map( 1 -> "Jan", 2 -> "Feb", 3 -> "Mar", - * 4 -> "Apr", 5 -> "May", 6 -> "Jun", - * 7 -> "Jul", 8 -> "Aug", 9 -> "Sep", - * 10 -> "Oct", 11 -> "Nov", 12 -> "Dec") - * - * import scala.util.matching.Regex.Match - * def reformatDate(text: String) = dateP2 replaceAllIn ( text, (m: Match) => - * "%s %s, %s" format (months(m group "month" toInt), m group "day", m group "year") - * ) + * val redacted = date replaceAllIn (dates, "XXXX-XX-XX") + * val yearsOnly = date replaceAllIn (dates, m => m group 1) + * val months = (0 to 11) map { i => val c = Calendar.getInstance; c.set(2014, i, 1); f"$c%tb" } + * val reformatted = date replaceAllIn (dates, _ match { case date(y,m,d) => f"${months(m.toInt - 1)} $d, $y" }) * }}} * - * You can use special pattern syntax constructs like `(?idmsux-idmsux)`¹ to switch - * various regex compilation options like `CASE_INSENSITIVE` or `UNICODE_CASE`. + * Pattern matching the `Match` against the `Regex` that created it does not reapply the `Regex`. + * In the expression for `reformatted`, each `date` match is computed once. But it is possible to apply a + * `Regex` to a `Match` resulting from a different pattern: + * + * {{{ + * val docSpree = """2011(?:-\d{2}){2}""".r + * val docView = date replaceAllIn (dates, _ match { + * case docSpree() => "Historic doc spree!" + * case _ => "Something else happened" + * }) + * }}} * - * @note ¹ A detailed description is available in [[java.util.regex.Pattern]]. * @see [[java.util.regex.Pattern]] * * @author Thibaud Hottelier @@ -154,9 +163,8 @@ import java.util.regex.{ Pattern, Matcher } * interpreted as a reference to a group in the matched pattern, with numbers * 1 through 9 corresponding to the first nine groups, and 0 standing for the * whole match. Any other character is an error. The backslash (`\`) character - * will be interpreted as an escape character, and can be used to escape the - * dollar sign. One can use [[scala.util.matching.Regex]]'s `quoteReplacement` - * to automatically escape these characters. + * will be interpreted as an escape character and can be used to escape the + * dollar sign. Use `Regex.quoteReplacement` to escape these characters. */ @SerialVersionUID(-2094783597747625537L) class Regex private[matching](val pattern: Pattern, groupNames: String*) extends Serializable { @@ -164,51 +172,84 @@ class Regex private[matching](val pattern: Pattern, groupNames: String*) extends import Regex._ - /** - * @param regex A string representing a regular expression - * @param groupNames A mapping from names to indices in capture groups - */ + /** Compile a regular expression, supplied as a string, into a pattern that + * can be matched against inputs. + * + * If group names are supplied, they can be used this way: + * + * {{{ + * val namedDate = new Regex("""(\d\d\d\d)-(\d\d)-(\d\d)""", "year", "month", "day") + * val namedYears = for (m <- namedDate findAllMatchIn dates) yield m group "year" + * }}} + * + * This constructor does not support options as flags, which must be + * supplied as inline flags in the pattern string: `(?idmsux-idmsux)`. + * + * @param regex The regular expression to compile. + * @param groupNames Names of capturing groups. + */ def this(regex: String, groupNames: String*) = this(Pattern.compile(regex), groupNames: _*) /** Tries to match a [[java.lang.CharSequence]]. + * * If the match succeeds, the result is a list of the matching * groups (or a `null` element if a group did not match any input). * If the pattern specifies no groups, then the result will be an empty list * on a successful match. * * This method attempts to match the entire input by default; to find the next - * matching subsequence, use an unanchored Regex. - + * matching subsequence, use an unanchored `Regex`. + * * For example: * * {{{ * val p1 = "ab*c".r * val p1Matches = "abbbc" match { - * case p1() => true + * case p1() => true // no groups * case _ => false * } * val p2 = "a(b*)c".r + * val p2Matches = "abbbc" match { + * case p2(_*) => true // any groups + * case _ => false + * } * val numberOfB = "abbbc" match { - * case p2(b) => Some(b.length) + * case p2(b) => Some(b.length) // one group * case _ => None * } * val p3 = "b*".r.unanchored * val p3Matches = "abbbc" match { - * case p3() => true + * case p3() => true // find the b's * case _ => false * } + * val p4 = "a(b*)(c+)".r + * val p4Matches = "abbbcc" match { + * case p4(_*) => true // multiple groups + * case _ => false + * } + * val allGroups = "abbbcc" match { + * case p4(all @ _*) => all mkString "/" // "bbb/cc" + * case _ => "" + * } + * val cGroup = "abbbcc" match { + * case p4(_, c) => c + * case _ => "" + * } * }}} * * @param s The string to match * @return The matches */ - def unapplySeq(s: CharSequence): Option[List[String]] = { - val m = pattern matcher s - if (runMatcher(m)) Some((1 to m.groupCount).toList map m.group) - else None + def unapplySeq(s: CharSequence): Option[List[String]] = s match { + case null => None + case _ => + val m = pattern matcher s + if (runMatcher(m)) Some((1 to m.groupCount).toList map m.group) + else None } /** Tries to match the String representation of a [[scala.Char]]. + * * If the match succeeds, the result is the first matching * group if any groups are defined, or an empty Sequence otherwise. * @@ -247,13 +288,16 @@ class Regex private[matching](val pattern: Pattern, groupNames: String*) extends } /** Tries to match on a [[scala.util.matching.Regex.Match]]. + * * A previously failed match results in None. + * * If a successful match was made against the current pattern, then that result is used. + * * Otherwise, this Regex is applied to the previously matched input, * and the result of that match is used. */ def unapplySeq(m: Match): Option[List[String]] = - if (m.matched == null) None + if (m == null || m.matched == null) None else if (m.matcher.pattern == this.pattern) Some((1 to m.groupCount).toList map m.group) else unapplySeq(m.matched) @@ -274,30 +318,47 @@ class Regex private[matching](val pattern: Pattern, groupNames: String*) extends // @see UnanchoredRegex protected def runMatcher(m: Matcher) = m.matches() - /** Return all non-overlapping matches of this regexp in given character + /** Return all non-overlapping matches of this `Regex` in the given character * sequence as a [[scala.util.matching.Regex.MatchIterator]], * which is a special [[scala.collection.Iterator]] that returns the - * matched strings, but can also be converted into a normal iterator - * that returns objects of type [[scala.util.matching.Regex.Match]] - * that can be queried for data such as the text that precedes the - * match, subgroups, etc. + * matched strings but can also be queried for more data about the last match, + * such as capturing groups and start position. + * + * A `MatchIterator` can also be converted into an iterator + * that returns objects of type [[scala.util.matching.Regex.Match]], + * such as is normally returned by `findAllMatchIn`. * * Where potential matches overlap, the first possible match is returned, - * followed by the next match that is completely after the first. For - * instance, `"hat[^a]+".r` will match `hath` and `hattth` in the string - * `"hathatthattthatttt"`. + * followed by the next match that follows the input consumed by the + * first match: + * + * {{{ + * val hat = "hat[^a]+".r + * val hathaway = "hathatthattthatttt" + * val hats = (hat findAllIn hathaway).toList // List(hath, hattth) + * val pos = (hat findAllMatchIn hathaway map (_.start)).toList // List(0, 7) + * }}} + * + * To return overlapping matches, it is possible to formulate a regular expression + * with lookahead (`?=`) that does not consume the overlapping region. + * + * {{{ + * val madhatter = "(h)(?=(at[^a]+))".r + * val madhats = (madhatter findAllMatchIn hathaway map { + * case madhatter(x,y) => s"$x$y" + * }).toList // List(hath, hatth, hattth, hatttt) + * }}} * - * Attempting to retrieve information about a match before initializing - * the iterator can result in [[java.lang.IllegalStateException]]s. See - * [[scala.util.matching.Regex.MatchIterator]] for details. + * Attempting to retrieve match information before performing the first match + * or after exhausting the iterator results in [[java.lang.IllegalStateException]]. + * See [[scala.util.matching.Regex.MatchIterator]] for details. * * @param source The text to match against. - * @return A [[scala.util.matching.Regex.MatchIterator]] of all matches. + * @return A [[scala.util.matching.Regex.MatchIterator]] of matched substrings. * @example {{{for (words <- """\w+""".r findAllIn "A simple example.") yield words}}} */ def findAllIn(source: CharSequence) = new Regex.MatchIterator(source, this, groupNames) - /** Return all non-overlapping matches of this regexp in given character sequence as a * [[scala.collection.Iterator]] of [[scala.util.matching.Regex.Match]]. * @@ -316,8 +377,8 @@ class Regex private[matching](val pattern: Pattern, groupNames: String*) extends } } - /** Return optionally first matching string of this regexp in given character sequence, - * or None if it does not exist. + /** Return an optional first matching string of this `Regex` in the given character sequence, + * or None if there is no match. * * @param source The text to match against. * @return An [[scala.Option]] of the first matching string in the text. @@ -328,13 +389,11 @@ class Regex private[matching](val pattern: Pattern, groupNames: String*) extends if (m.find) Some(m.group) else None } - /** Return optionally first match of this regexp in given character sequence, + /** Return an optional first match of this `Regex` in the given character sequence, * or None if it does not exist. * - * The main difference between this method and `findFirstIn` is that the (optional) return - * type for this is [[scala.util.matching.Regex.Match]], through which more - * data can be obtained about the match, such as the strings that precede and follow it, - * or subgroups. + * If the match is successful, the [[scala.util.matching.Regex.Match]] can be queried for + * more data. * * @param source The text to match against. * @return A [[scala.Option]] of [[scala.util.matching.Regex.Match]] of the first matching string in the text. @@ -345,30 +404,28 @@ class Regex private[matching](val pattern: Pattern, groupNames: String*) extends if (m.find) Some(new Match(source, m, groupNames)) else None } - /** Return optionally match of this regexp at the beginning of the - * given character sequence, or None if regexp matches no prefix + /** Return an optional match of this `Regex` at the beginning of the + * given character sequence, or None if it matches no prefix * of the character sequence. * - * The main difference from this method to `findFirstIn` is that this - * method will not return any matches that do not begin at the start - * of the text being matched against. + * Unlike `findFirstIn`, this method will only return a match at + * the beginning of the input. * * @param source The text to match against. * @return A [[scala.Option]] of the matched prefix. - * @example {{{"""[a-z]""".r findPrefixOf "A simple example." // returns None, since the text does not begin with a lowercase letter}}} + * @example {{{"""\p{Lower}""".r findPrefixOf "A simple example." // returns None, since the text does not begin with a lowercase letter}}} */ def findPrefixOf(source: CharSequence): Option[String] = { val m = pattern.matcher(source) if (m.lookingAt) Some(m.group) else None } - /** Return optionally match of this regexp at the beginning of the - * given character sequence, or None if regexp matches no prefix + /** Return an optional match of this `Regex` at the beginning of the + * given character sequence, or None if it matches no prefix * of the character sequence. * - * The main difference from this method to `findFirstMatchIn` is that - * this method will not return any matches that do not begin at the - * start of the text being matched against. + * Unlike `findFirstMatchIn`, this method will only return a match at + * the beginning of the input. * * @param source The text to match against. * @return A [[scala.Option]] of the [[scala.util.matching.Regex.Match]] of the matched string. @@ -402,7 +459,7 @@ class Regex private[matching](val pattern: Pattern, groupNames: String*) extends * import scala.util.matching.Regex * val datePattern = new Regex("""(\d\d\d\d)-(\d\d)-(\d\d)""", "year", "month", "day") * val text = "From 2011-07-15 to 2011-07-17" - * val repl = datePattern replaceAllIn (text, m => m.group("month")+"/"+m.group("day")) + * val repl = datePattern replaceAllIn (text, m => s"${m group "month"}/${m group "day"}") * }}} * * $replacementString @@ -425,10 +482,10 @@ class Regex private[matching](val pattern: Pattern, groupNames: String*) extends * {{{ * import scala.util.matching.Regex._ * - * val map = Map("x" -> "a var", "y" -> """some $ and \ signs""") + * val vars = Map("x" -> "a var", "y" -> """some $ and \ signs""") * val text = "A text with variables %x, %y and %z." * val varPattern = """%(\w+)""".r - * val mapper = (m: Match) => map get (m group 1) map (quoteReplacement(_)) + * val mapper = (m: Match) => vars get (m group 1) map (quoteReplacement(_)) * val repl = varPattern replaceSomeIn (text, mapper) * }}} * @@ -469,17 +526,25 @@ class Regex private[matching](val pattern: Pattern, groupNames: String*) extends pattern.split(toSplit) /** Create a new Regex with the same pattern, but no requirement that - * the entire String matches in extractor patterns. For instance, the strings - * shown below lead to successful matches, where they would not otherwise. + * the entire String matches in extractor patterns. + * + * Normally, matching on `date` behaves as though the pattern were + * enclosed in anchors, `"^pattern$"`. + * + * The unanchored `Regex` behaves as though those anchors were removed. + * + * Note that this method does not actually strip any matchers from the pattern. + * + * Calling `anchored` returns the original `Regex`. * * {{{ - * val dateP1 = """(\d\d\d\d)-(\d\d)-(\d\d)""".r.unanchored + * val date = """(\d\d\d\d)-(\d\d)-(\d\d)""".r.unanchored * - * val dateP1(year, month, day) = "Date 2011-07-15" + * val date(year, month, day) = "Date 2011-07-15" // OK * * val copyright: String = "Date of this document: 2011-07-15" match { - * case dateP1(year, month, day) => "Copyright "+year - * case _ => "No copyright" + * case date(year, month, day) => s"Copyright $year" // OK + * case _ => "No copyright" * } * }}} * @@ -494,6 +559,10 @@ class Regex private[matching](val pattern: Pattern, groupNames: String*) extends override def toString = regex } +/** A [[Regex]] that finds the first match when used in a pattern match. + * + * @see [[Regex#unanchored]] + */ trait UnanchoredRegex extends Regex { override protected def runMatcher(m: Matcher) = m.find() override def unanchored = this @@ -509,70 +578,79 @@ object Regex { */ trait MatchData { - /** The source from where the match originated */ + /** The source from which the match originated */ val source: CharSequence - /** The names of the groups, or some empty sequence if one defined */ + /** The names of the groups, or an empty sequence if none defined */ val groupNames: Seq[String] - /** The number of subgroups in the pattern (not all of these need to match!) */ + /** The number of capturing groups in the pattern. + * (For a given successful match, some of those groups may not have matched any input.) + */ def groupCount: Int /** The index of the first matched character, or -1 if nothing was matched */ def start: Int /** The index of the first matched character in group `i`, - * or -1 if nothing was matched for that group */ + * or -1 if nothing was matched for that group. + */ def start(i: Int): Int - /** The index of the last matched character, or -1 if nothing was matched */ + /** The index following the last matched character, or -1 if nothing was matched. */ def end: Int /** The index following the last matched character in group `i`, - * or -1 if nothing was matched for that group */ + * or -1 if nothing was matched for that group. + */ def end(i: Int): Int - /** The matched string, or `null` if nothing was matched */ + /** The matched string, or `null` if nothing was matched. */ def matched: String = if (start >= 0) source.subSequence(start, end).toString else null /** The matched string in group `i`, - * or `null` if nothing was matched */ + * or `null` if nothing was matched. + */ def group(i: Int): String = if (start(i) >= 0) source.subSequence(start(i), end(i)).toString else null - /** All matched subgroups, i.e. not including group(0) */ + /** All capturing groups, i.e., not including group(0). */ def subgroups: List[String] = (1 to groupCount).toList map group /** The char sequence before first character of match, - * or `null` if nothing was matched */ + * or `null` if nothing was matched. + */ def before: CharSequence = if (start >= 0) source.subSequence(0, start) else null /** The char sequence before first character of match in group `i`, - * or `null` if nothing was matched for that group */ + * or `null` if nothing was matched for that group. + */ def before(i: Int): CharSequence = if (start(i) >= 0) source.subSequence(0, start(i)) else null /** Returns char sequence after last character of match, - * or `null` if nothing was matched */ + * or `null` if nothing was matched. + */ def after: CharSequence = if (end >= 0) source.subSequence(end, source.length) else null /** The char sequence after last character of match in group `i`, - * or `null` if nothing was matched for that group */ + * or `null` if nothing was matched for that group. + */ def after(i: Int): CharSequence = if (end(i) >= 0) source.subSequence(end(i), source.length) else null private lazy val nameToIndex: Map[String, Int] = Map[String, Int]() ++ ("" :: groupNames.toList).zipWithIndex - /** Returns the group with given name + /** Returns the group with given name. * * @param id The group name * @return The requested group @@ -583,24 +661,22 @@ object Regex { case Some(index) => group(index) } - /** The matched string; equivalent to `matched.toString` */ + /** The matched string; equivalent to `matched.toString`. */ override def toString = matched - } - /** Provides information about a succesful match. - */ + /** Provides information about a successful match. */ class Match(val source: CharSequence, private[matching] val matcher: Matcher, val groupNames: Seq[String]) extends MatchData { - /** The index of the first matched character */ + /** The index of the first matched character. */ val start = matcher.start - /** The index following the last matched character */ + /** The index following the last matched character. */ val end = matcher.end - /** The number of subgroups */ + /** The number of subgroups. */ def groupCount = matcher.groupCount private lazy val starts: Array[Int] = @@ -608,19 +684,19 @@ object Regex { private lazy val ends: Array[Int] = ((0 to groupCount) map matcher.end).toArray - /** The index of the first matched character in group `i` */ + /** The index of the first matched character in group `i`. */ def start(i: Int) = starts(i) - /** The index following the last matched character in group `i` */ + /** The index following the last matched character in group `i`. */ def end(i: Int) = ends(i) /** The match itself with matcher-dependent lazy vals forced, - * so that match is valid even once matcher is advanced + * so that match is valid even once matcher is advanced. */ def force: this.type = { starts; ends; this } } - /** An extractor object for Matches, yielding the matched string + /** An extractor object for Matches, yielding the matched string. * * This can be used to help writing replacer functions when you * are not interested in match data. For example: @@ -635,15 +711,15 @@ object Regex { def unapply(m: Match): Some[String] = Some(m.matched) } - /** An extractor object that yields the groups in the match. Using an extractor - * rather than the original regex avoids recomputing the match. + /** An extractor object that yields the groups in the match. Using this extractor + * rather than the original `Regex` ensures that the match is not recomputed. * * {{{ * import scala.util.matching.Regex.Groups * - * val datePattern = """(\d\d\d\d)-(\d\d)-(\d\d)""".r + * val date = """(\d\d\d\d)-(\d\d)-(\d\d)""".r * val text = "The doc spree happened on 2011-07-15." - * val day = datePattern replaceAllIn(text, _ match { case Groups(year, month, day) => month+"/"+day }) + * val day = date replaceAllIn(text, _ match { case Groups(_, month, day) => s"$month/$day" }) * }}} */ object Groups { @@ -672,7 +748,7 @@ object Regex { nextSeen } - /** The next matched substring of `source` */ + /** The next matched substring of `source`. */ def next(): String = { if (!hasNext) throw new NoSuchElementException nextSeen = false @@ -681,28 +757,28 @@ object Regex { override def toString = super[AbstractIterator].toString - /** The index of the first matched character */ + /** The index of the first matched character. */ def start: Int = matcher.start - /** The index of the first matched character in group `i` */ + /** The index of the first matched character in group `i`. */ def start(i: Int): Int = matcher.start(i) - /** The index of the last matched character */ + /** The index of the last matched character. */ def end: Int = matcher.end - /** The index following the last matched character in group `i` */ + /** The index following the last matched character in group `i`. */ def end(i: Int): Int = matcher.end(i) - /** The number of subgroups */ + /** The number of subgroups. */ def groupCount = matcher.groupCount - /** Convert to an iterator that yields MatchData elements instead of Strings */ + /** Convert to an iterator that yields MatchData elements instead of Strings. */ def matchData: Iterator[Match] = new AbstractIterator[Match] { def hasNext = self.hasNext def next = { self.next(); new Match(source, matcher, groupNames).force } } - /** Convert to an iterator that yields MatchData elements instead of Strings and has replacement support */ + /** Convert to an iterator that yields MatchData elements instead of Strings and has replacement support. */ private[matching] def replacementData = new AbstractIterator[Match] with Replacement { def matcher = self.matcher def hasNext = self.hasNext diff --git a/src/reflect/scala/reflect/api/Quasiquotes.scala b/src/reflect/scala/reflect/api/Quasiquotes.scala index e905aa4153..eaae05bed5 100644 --- a/src/reflect/scala/reflect/api/Quasiquotes.scala +++ b/src/reflect/scala/reflect/api/Quasiquotes.scala @@ -13,7 +13,7 @@ trait Quasiquotes { self: Universe => protected trait api { // implementation is hardwired to `dispatch` method of `scala.tools.reflect.quasiquotes.Quasiquotes` // using the mechanism implemented in `scala.tools.reflect.FastTrack` - def apply[T](args: T*): Tree = macro ??? + def apply[A >: Any](args: A*): Tree = macro ??? def unapply(scrutinee: Any): Any = macro ??? } object q extends api diff --git a/src/reflect/scala/reflect/internal/AnnotationInfos.scala b/src/reflect/scala/reflect/internal/AnnotationInfos.scala index f814a746f5..fcef4dd6be 100644 --- a/src/reflect/scala/reflect/internal/AnnotationInfos.scala +++ b/src/reflect/scala/reflect/internal/AnnotationInfos.scala @@ -388,11 +388,11 @@ trait AnnotationInfos extends api.Annotations { self: SymbolTable => case Literal(const) => LiteralAnnotArg(const) case Apply(ArrayModule, args) => ArrayAnnotArg(args map encodeJavaArg toArray) case Apply(Select(New(tpt), nme.CONSTRUCTOR), args) => NestedAnnotArg(treeToAnnotation(arg)) - case _ => throw new Exception("unexpected java argument shape $arg: literals, arrays and nested annotations are supported") + case _ => throw new Exception(s"unexpected java argument shape $arg: literals, arrays and nested annotations are supported") } def encodeJavaArgs(args: List[Tree]): List[(Name, ClassfileAnnotArg)] = args match { case AssignOrNamedArg(Ident(name), arg) :: rest => (name, encodeJavaArg(arg)) :: encodeJavaArgs(rest) - case arg :: rest => throw new Exception("unexpected java argument shape $arg: only AssignOrNamedArg trees are supported") + case arg :: rest => throw new Exception(s"unexpected java argument shape $arg: only AssignOrNamedArg trees are supported") case Nil => Nil } val atp = tpt.tpe diff --git a/src/reflect/scala/reflect/internal/Definitions.scala b/src/reflect/scala/reflect/internal/Definitions.scala index bf560a21e5..02578e2038 100644 --- a/src/reflect/scala/reflect/internal/Definitions.scala +++ b/src/reflect/scala/reflect/internal/Definitions.scala @@ -836,12 +836,18 @@ trait Definitions extends api.StandardDefinitions { def typeOfMemberNamedHead(tp: Type) = typeArgOfBaseTypeOr(tp, SeqClass)(resultOfMatchingMethod(tp, nme.head)()) def typeOfMemberNamedApply(tp: Type) = typeArgOfBaseTypeOr(tp, SeqClass)(resultOfMatchingMethod(tp, nme.apply)(IntTpe)) def typeOfMemberNamedDrop(tp: Type) = typeArgOfBaseTypeOr(tp, SeqClass)(resultOfMatchingMethod(tp, nme.drop)(IntTpe)) - def typesOfSelectors(tp: Type) = getterMemberTypes(tp, productSelectors(tp)) + def typesOfSelectors(tp: Type) = + if (isTupleType(tp)) tp.typeArgs + else getterMemberTypes(tp, productSelectors(tp)) + // SI-8128 Still using the type argument of the base type at Seq/Option if this is an old-style (2.10 compatible) // extractor to limit exposure to regressions like the reported problem with existentials. // TODO fix the existential problem in the general case, see test/pending/pos/t8128.scala private def typeArgOfBaseTypeOr(tp: Type, baseClass: Symbol)(or: => Type): Type = (tp baseType baseClass).typeArgs match { - case x :: Nil => x + case x :: Nil => + val x1 = x + val x2 = repackExistential(x1) + x2 case _ => or } diff --git a/src/reflect/scala/reflect/internal/ReificationSupport.scala b/src/reflect/scala/reflect/internal/ReificationSupport.scala index 2caa30d27e..759bd2e791 100644 --- a/src/reflect/scala/reflect/internal/ReificationSupport.scala +++ b/src/reflect/scala/reflect/internal/ReificationSupport.scala @@ -866,7 +866,7 @@ trait ReificationSupport { self: SymbolTable => protected def mkCases(cases: List[Tree]): List[CaseDef] = cases.map { case c: CaseDef => c - case tree => throw new IllegalArgumentException("$tree is not valid representation of pattern match case") + case tree => throw new IllegalArgumentException(s"$tree is not valid representation of pattern match case") } object SyntacticPartialFunction extends SyntacticPartialFunctionExtractor { diff --git a/src/reflect/scala/reflect/internal/Reporting.scala b/src/reflect/scala/reflect/internal/Reporting.scala index 423127803e..f2de83bc5d 100644 --- a/src/reflect/scala/reflect/internal/Reporting.scala +++ b/src/reflect/scala/reflect/internal/Reporting.scala @@ -89,6 +89,9 @@ abstract class Reporter { def count(severity: Severity): Int def resetCount(severity: Severity): Unit + def errorCount: Int = count(ERROR) + def warningCount: Int = count(WARNING) + def hasErrors: Boolean = count(ERROR) > 0 def hasWarnings: Boolean = count(WARNING) > 0 diff --git a/src/reflect/scala/reflect/internal/StdNames.scala b/src/reflect/scala/reflect/internal/StdNames.scala index 6848c357c5..d203218c09 100644 --- a/src/reflect/scala/reflect/internal/StdNames.scala +++ b/src/reflect/scala/reflect/internal/StdNames.scala @@ -473,7 +473,7 @@ trait StdNames { ) def localDummyName(clazz: Symbol): TermName = newTermName(LOCALDUMMY_PREFIX + clazz.name + ">") - def superName(name: Name): TermName = newTermName(SUPER_PREFIX_STRING + name) + def superName(name: Name, mix: Name = EMPTY): TermName = newTermName(SUPER_PREFIX_STRING + name + (if (mix.isEmpty) "" else "$" + mix)) /** The name of an accessor for protected symbols. */ def protName(name: Name): TermName = newTermName(PROTECTED_PREFIX + name) diff --git a/src/reflect/scala/reflect/internal/Symbols.scala b/src/reflect/scala/reflect/internal/Symbols.scala index b6cce4524b..2670faa22d 100644 --- a/src/reflect/scala/reflect/internal/Symbols.scala +++ b/src/reflect/scala/reflect/internal/Symbols.scala @@ -74,15 +74,6 @@ trait Symbols extends api.Symbols { self: SymbolTable => } } - protected def originalEnclosingMethod(sym: Symbol): Symbol = { - if (sym.isMethod || sym == NoSymbol) sym - else { - val owner = sym.originalOwner - if (sym.isLocalDummy) owner.enclClass.primaryConstructor - else originalEnclosingMethod(owner) - } - } - def symbolOf[T: WeakTypeTag]: TypeSymbol = weakTypeOf[T].typeSymbolDirect.asType abstract class SymbolContextApiImpl extends SymbolApi { @@ -795,7 +786,7 @@ trait Symbols extends api.Symbols { self: SymbolTable => info.firstParent.typeSymbol == AnyValClass && !isPrimitiveValueClass final def isMethodWithExtension = - isMethod && owner.isDerivedValueClass && !isParamAccessor && !isConstructor && !hasFlag(SUPERACCESSOR) && !isMacro + isMethod && owner.isDerivedValueClass && !isParamAccessor && !isConstructor && !hasFlag(SUPERACCESSOR) && !isMacro && !isSpecialized final def isAnonymousFunction = isSynthetic && (name containsName tpnme.ANON_FUN_NAME) final def isDefinedInPackage = effectiveOwner.isPackageClass @@ -2122,16 +2113,6 @@ trait Symbols extends api.Symbols { self: SymbolTable => * is not one. */ def enclosingPackage: Symbol = enclosingPackageClass.companionModule - /** Return the original enclosing method of this symbol. It should return - * the same thing as enclMethod when called before lambda lift, - * but it preserves the original nesting when called afterwards. - * - * @note This method is NOT available in the presentation compiler run. The - * originalOwner map is not populated for memory considerations (the symbol - * may hang on to lazy types and in turn to whole (outdated) compilation units. - */ - def originalEnclosingMethod: Symbol = Symbols.this.originalEnclosingMethod(this) - /** The method or class which logically encloses the current symbol. * If the symbol is defined in the initialization part of a template * this is the template's primary constructor, otherwise it is @@ -3532,7 +3513,6 @@ trait Symbols extends api.Symbols { self: SymbolTable => override def rawInfo: Type = NoType override def accessBoundary(base: Symbol): Symbol = enclosingRootClass def cloneSymbolImpl(owner: Symbol, newFlags: Long) = abort("NoSymbol.clone()") - override def originalEnclosingMethod = this } protected def makeNoSymbol: NoSymbol = new NoSymbol diff --git a/src/reflect/scala/reflect/runtime/JavaMirrors.scala b/src/reflect/scala/reflect/runtime/JavaMirrors.scala index 6584d80de3..b7f229b6e5 100644 --- a/src/reflect/scala/reflect/runtime/JavaMirrors.scala +++ b/src/reflect/scala/reflect/runtime/JavaMirrors.scala @@ -807,31 +807,26 @@ private[scala] trait JavaMirrors extends internal.SymbolTable with api.JavaUnive // field containing the cache for structural calls. if (mods.isStatic) module.moduleClass.orElse(clazz) else clazz - /** Methods which need to be treated with care - * because they either are getSimpleName or call getSimpleName: + /** + * Certain method of the Java reflection api cannot be used on classfiles created by Scala. + * See the comment in test/files/jvm/javaReflection/Test.scala. The methods are * * public String getSimpleName() * public boolean isAnonymousClass() * public boolean isLocalClass() * public String getCanonicalName() - * - * A typical manifestation: - * - * // java.lang.Error: sOwner(class Test$A$1) has failed - * // Caused by: java.lang.InternalError: Malformed class name - * // at java.lang.Class.getSimpleName(Class.java:1133) - * // at java.lang.Class.isAnonymousClass(Class.java:1188) - * // at java.lang.Class.isLocalClass(Class.java:1199) - * // (see t5256c.scala for more details) + * public boolean isSynthetic() * * TODO - find all such calls and wrap them. * TODO - create mechanism to avoid the recurrence of unwrapped calls. */ implicit class RichClass(jclazz: jClass[_]) { - // `jclazz.isLocalClass` doesn't work because of problems with `getSimpleName` - // hence we have to approximate by removing the `isAnonymousClass` check -// def isLocalClass0: Boolean = jclazz.isLocalClass - def isLocalClass0: Boolean = jclazz.getEnclosingMethod != null || jclazz.getEnclosingConstructor != null + // As explained in the javaReflection test, Class.isLocalClass is true for all non-member + // nested classes in Scala. This is fine per se, however the implementation may throw an + // InternalError. We therefore re-implement it here. + // TODO: this method should be renamed to `isLocalOrAnonymousClass`. + // due to bin compat that's only possible in 2.12, we cannot introduce a new alias in 2.11. + def isLocalClass0: Boolean = jclazz.getEnclosingClass != null && !jclazz.isMemberClass } /** diff --git a/src/repl/scala/tools/nsc/interpreter/IMain.scala b/src/repl/scala/tools/nsc/interpreter/IMain.scala index 8ea8759ee5..6e30b73e0e 100644 --- a/src/repl/scala/tools/nsc/interpreter/IMain.scala +++ b/src/repl/scala/tools/nsc/interpreter/IMain.scala @@ -1121,7 +1121,7 @@ class IMain(@BeanProperty val factory: ScriptEngineFactory, initialSettings: Set def apply(line: String): Result = debugging(s"""parse("$line")""") { var isIncomplete = false - currentRun.reporting.withIncompleteHandler((_, _) => isIncomplete = true) { + currentRun.parsing.withIncompleteHandler((_, _) => isIncomplete = true) { reporter.reset() val trees = newUnitParser(line).parseStats() if (reporter.hasErrors) Error diff --git a/src/repl/scala/tools/nsc/interpreter/MemberHandlers.scala b/src/repl/scala/tools/nsc/interpreter/MemberHandlers.scala index f4cbcb50fe..a37cdc2ec8 100644 --- a/src/repl/scala/tools/nsc/interpreter/MemberHandlers.scala +++ b/src/repl/scala/tools/nsc/interpreter/MemberHandlers.scala @@ -116,7 +116,7 @@ trait MemberHandlers { else any2stringOf(path, maxStringElements) val vidString = - if (replProps.vids) s"""" + " @ " + "%%8x".format(System.identityHashCode($path)) + " """.trim + if (replProps.vids) s"""" + f"@$${System.identityHashCode($path)}%8x" + """" else "" """ + "%s%s: %s = " + %s""".format(string2code(prettyName), vidString, string2code(req typeOf name), resultString) diff --git a/src/scaladoc/scala/tools/nsc/doc/ScaladocAnalyzer.scala b/src/scaladoc/scala/tools/nsc/doc/ScaladocAnalyzer.scala index ccf18b76de..cbf8ff22ba 100644 --- a/src/scaladoc/scala/tools/nsc/doc/ScaladocAnalyzer.scala +++ b/src/scaladoc/scala/tools/nsc/doc/ScaladocAnalyzer.scala @@ -208,7 +208,7 @@ abstract class ScaladocSyntaxAnalyzer[G <: Global](val global: G) extends Syntax super.skipDocComment() } override def skipBlockComment(): Unit = { - inDocComment = false + inDocComment = false // ??? this means docBuffer won't receive contents of this comment??? docBuffer = new StringBuilder("/*") super.skipBlockComment() } @@ -217,9 +217,10 @@ abstract class ScaladocSyntaxAnalyzer[G <: Global](val global: G) extends Syntax def foundStarComment(start: Int, end: Int) = try { val str = docBuffer.toString val pos = Position.range(unit.source, start, start, end) - unit.comment(pos, str) - if (inDocComment) + if (inDocComment) { + signalParsedDocComment(str, pos) lastDoc = DocComment(str, pos) + } true } finally { docBuffer = null diff --git a/src/scaladoc/scala/tools/nsc/doc/ScaladocGlobal.scala b/src/scaladoc/scala/tools/nsc/doc/ScaladocGlobal.scala index 2ea3a0eb7c..4b40d25c17 100644 --- a/src/scaladoc/scala/tools/nsc/doc/ScaladocGlobal.scala +++ b/src/scaladoc/scala/tools/nsc/doc/ScaladocGlobal.scala @@ -11,6 +11,7 @@ import reporters.Reporter import typechecker.Analyzer import scala.reflect.internal.util.{ BatchSourceFile, RangePosition } + trait ScaladocGlobalTrait extends Global { outer => diff --git a/src/scaladoc/scala/tools/nsc/doc/Settings.scala b/src/scaladoc/scala/tools/nsc/doc/Settings.scala index a8e1dee4a0..44683f1755 100644 --- a/src/scaladoc/scala/tools/nsc/doc/Settings.scala +++ b/src/scaladoc/scala/tools/nsc/doc/Settings.scala @@ -66,7 +66,7 @@ class Settings(error: String => Unit, val printMsg: String => Unit = println(_)) val docsourceurl = StringSetting ( "-doc-source-url", "url", - "A URL pattern used to build links to template sources; use variables, for example: ?{TPL_NAME} ('Seq'), ?{TPL_OWNER} ('scala.collection'), ?{FILE_PATH} ('scala/collection/Seq')", + s"A URL pattern used to link to the source file; the following variables are available: €{TPL_NAME}, €{TPL_OWNER} and respectively €{FILE_PATH}. For example, for `scala.collection.Seq`, the variables will be expanded to `Seq`, `scala.collection` and respectively `scala/collection/Seq` (without the backquotes). To obtain a relative path for €{FILE_PATH} instead of an absolute one, use the ${sourcepath.name} setting.", "" ) diff --git a/src/scaladoc/scala/tools/nsc/doc/base/CommentFactoryBase.scala b/src/scaladoc/scala/tools/nsc/doc/base/CommentFactoryBase.scala index 19cc27b40b..7cd8fa8e51 100755 --- a/src/scaladoc/scala/tools/nsc/doc/base/CommentFactoryBase.scala +++ b/src/scaladoc/scala/tools/nsc/doc/base/CommentFactoryBase.scala @@ -131,18 +131,19 @@ trait CommentFactoryBase { this: MemberLookupBase => /** Javadoc tags that should be replaced by something useful, such as wiki * syntax, or that should be dropped. */ private val JavadocTags = - new Regex("""\{\@(code|docRoot|inheritDoc|link|linkplain|literal|value)([^}]*)\}""") + new Regex("""\{\@(code|docRoot|linkplain|link|literal|value)\p{Zs}*([^}]*)\}""") /** Maps a javadoc tag to a useful wiki replacement, or an empty string if it cannot be salvaged. */ - private def javadocReplacement(mtch: Regex.Match): String = mtch.group(1) match { - case "code" => "`" + mtch.group(2) + "`" - case "docRoot" => "" - case "inheritDoc" => "" - case "link" => "`" + mtch.group(2) + "`" - case "linkplain" => "`" + mtch.group(2) + "`" - case "literal" => mtch.group(2) - case "value" => "`" + mtch.group(2) + "`" - case _ => "" + private def javadocReplacement(mtch: Regex.Match): String = { + mtch.group(1) match { + case "code" => "<code>" + mtch.group(2) + "</code>" + case "docRoot" => "" + case "link" => "`[[" + mtch.group(2) + "]]`" + case "linkplain" => "[[" + mtch.group(2) + "]]" + case "literal" => "`" + mtch.group(2) + "`" + case "value" => "`" + mtch.group(2) + "`" + case _ => "" + } } /** Safe HTML tags that can be kept. */ @@ -680,11 +681,10 @@ trait CommentFactoryBase { this: MemberLookupBase => jump("[[") val parens = 2 + repeatJump('[') val stop = "]" * parens - //println("link with " + parens + " matching parens") - val target = readUntil { check(stop) || check(" ") } + val target = readUntil { check(stop) || isWhitespaceOrNewLine(char) } val title = if (!check(stop)) Some({ - jump(" ") + jumpWhitespaceOrNewLine() inline(check(stop)) }) else None @@ -723,49 +723,15 @@ trait CommentFactoryBase { this: MemberLookupBase => */ def normalizeIndentation(_code: String): String = { - val code = _code.trim - var maxSkip = Integer.MAX_VALUE - var crtSkip = 0 - var wsArea = true - var index = 0 - var firstLine = true - var emptyLine = true - - while (index < code.length) { - code(index) match { - case ' ' => - if (wsArea) - crtSkip += 1 - case c => - wsArea = (c == '\n') - maxSkip = if (firstLine || emptyLine) maxSkip else if (maxSkip <= crtSkip) maxSkip else crtSkip - crtSkip = if (c == '\n') 0 else crtSkip - firstLine = if (c == '\n') false else firstLine - emptyLine = if (c == '\n') true else false - } - index += 1 - } + val code = _code.replaceAll("\\s+$", "").dropWhile(_ == '\n') // right-trim + remove all leading '\n' + val lines = code.split("\n") - if (maxSkip == 0) - code - else { - index = 0 - val builder = new StringBuilder - while (index < code.length) { - builder.append(code(index)) - if (code(index) == '\n') { - // we want to skip as many spaces are available, if there are less spaces (like on empty lines, do not - // over-consume them) - index += 1 - val limit = index + maxSkip - while ((index < code.length) && (code(index) == ' ') && index < limit) - index += 1 - } - else - index += 1 - } - builder.toString - } + // maxSkip - size of the longest common whitespace prefix of non-empty lines + val nonEmptyLines = lines.filter(_.trim.nonEmpty) + val maxSkip = if (nonEmptyLines.isEmpty) 0 else nonEmptyLines.map(line => line.prefixLength(_ == ' ')).min + + // remove common whitespace prefix + lines.map(line => if (line.trim.nonEmpty) line.substring(maxSkip) else line).mkString("\n") } def checkParaEnded(): Boolean = { @@ -899,6 +865,8 @@ trait CommentFactoryBase { this: MemberLookupBase => def jumpWhitespace() = jumpUntil(!isWhitespace(char)) + def jumpWhitespaceOrNewLine() = jumpUntil(!isWhitespaceOrNewLine(char)) + /* READERS */ final def readUntil(c: Char): String = { @@ -938,5 +906,7 @@ trait CommentFactoryBase { this: MemberLookupBase => /* CHARS CLASSES */ def isWhitespace(c: Char) = c == ' ' || c == '\t' + + def isWhitespaceOrNewLine(c: Char) = isWhitespace(c) || c == '\n' } } |