diff options
author | Lukas Rytz <lukas.rytz@gmail.com> | 2014-09-02 16:33:48 +0200 |
---|---|---|
committer | Lukas Rytz <lukas.rytz@gmail.com> | 2014-09-02 16:33:48 +0200 |
commit | f196ee2ee7807f007d2397a4d6168800900ae7fa (patch) | |
tree | 475de2822a89d25217acc0b7ff0fbc7f3d798519 /src | |
parent | 61dbf4ac711094db38a4b2dfb853d88499c2b1a5 (diff) | |
parent | 01f2d279e84fda300176e83ee94c17c08ad1dc3f (diff) | |
download | scala-f196ee2ee7807f007d2397a4d6168800900ae7fa.tar.gz scala-f196ee2ee7807f007d2397a4d6168800900ae7fa.tar.bz2 scala-f196ee2ee7807f007d2397a4d6168800900ae7fa.zip |
Merge commit '01f2d27' into merge/2.11-to-2.12-is-it-really-sept-2-already-where-was-summer
Diffstat (limited to 'src')
47 files changed, 928 insertions, 517 deletions
diff --git a/src/compiler/scala/tools/nsc/CompilerCommand.scala b/src/compiler/scala/tools/nsc/CompilerCommand.scala index a1d0d52dcf..3ded456378 100644 --- a/src/compiler/scala/tools/nsc/CompilerCommand.scala +++ b/src/compiler/scala/tools/nsc/CompilerCommand.scala @@ -103,7 +103,15 @@ class CompilerCommand(arguments: List[String], val settings: Settings) { val components = global.phaseNames // global.phaseDescriptors // one initializes s"Phase graph of ${components.size} components output to ${genPhaseGraph.value}*.dot." } - else "" + // would be nicer if we could ask all the options for their helpful messages + else { + val sb = new StringBuilder + allSettings foreach { + case s: MultiChoiceSetting if s.isHelping => sb append s.help + case _ => + } + sb.toString + } } /** diff --git a/src/compiler/scala/tools/nsc/Global.scala b/src/compiler/scala/tools/nsc/Global.scala index 572e579aca..5bf0d8d9f7 100644 --- a/src/compiler/scala/tools/nsc/Global.scala +++ b/src/compiler/scala/tools/nsc/Global.scala @@ -831,198 +831,11 @@ class Global(var currentSettings: Settings, var reporter: Reporter) } reverse } - // ------------ Invalidations --------------------------------- - - /** Is given package class a system package class that cannot be invalidated? - */ - private def isSystemPackageClass(pkg: Symbol) = - pkg == RootClass || - pkg == definitions.ScalaPackageClass || { - val pkgname = pkg.fullName - (pkgname startsWith "scala.") && !(pkgname startsWith "scala.tools") - } - - /** Invalidates packages that contain classes defined in a classpath entry, and - * rescans that entry. - * @param paths Fully qualified names that refer to directories or jar files that are - * a entries on the classpath. - * First, causes the classpath entry referred to by `path` to be rescanned, so that - * any new files or deleted files or changes in subpackages are picked up. - * Second, invalidates any packages for which one of the following considitions is met: - - * - the classpath entry contained during the last compilation run classfiles - * that represent a member in the package - * - the classpath entry now contains classfiles - * that represent a member in the package - * - the set of subpackages has changed. - * - * The invalidated packages are reset in their entirety; all member classes and member packages - * are re-accessed using the new classpath. - * Not invalidated are system packages that the compiler needs to access as parts - * of standard definitions. The criterion what is a system package is currently: - * any package rooted in "scala", with the exception of packages rooted in "scala.tools". - * This can be refined later. - * @return A pair consisting of - * - a list of invalidated packages - * - a list of of packages that should have been invalidated but were not because - * they are system packages. - */ - def invalidateClassPathEntries(paths: String*): (List[ClassSymbol], List[ClassSymbol]) = { - val invalidated, failed = new mutable.ListBuffer[ClassSymbol] - classPath match { - case cp: MergedClassPath[_] => - def assoc(path: String): List[(PlatformClassPath, PlatformClassPath)] = { - val dir = AbstractFile getDirectory path - val canonical = dir.canonicalPath - def matchesCanonical(e: ClassPath[_]) = e.origin match { - case Some(opath) => - (AbstractFile getDirectory opath).canonicalPath == canonical - case None => - false - } - cp.entries find matchesCanonical match { - case Some(oldEntry) => - List(oldEntry -> cp.context.newClassPath(dir)) - case None => - println(s"canonical = $canonical, origins = ${cp.entries map (_.origin)}") - error(s"cannot invalidate: no entry named $path in classpath $classPath") - List() - } - } - val subst = Map(paths flatMap assoc: _*) - if (subst.nonEmpty) { - platform updateClassPath subst - informProgress(s"classpath updated on entries [${subst.keys mkString ","}]") - def mkClassPath(elems: Iterable[PlatformClassPath]): PlatformClassPath = - if (elems.size == 1) elems.head - else new MergedClassPath(elems, classPath.context) - val oldEntries = mkClassPath(subst.keys) - val newEntries = mkClassPath(subst.values) - reSync(RootClass, Some(classPath), Some(oldEntries), Some(newEntries), invalidated, failed) - } - } - def show(msg: String, syms: scala.collection.Traversable[Symbol]) = - if (syms.nonEmpty) - informProgress(s"$msg: ${syms map (_.fullName) mkString ","}") - show("invalidated packages", invalidated) - show("could not invalidate system packages", failed) - (invalidated.toList, failed.toList) - } - - /** Re-syncs symbol table with classpath - * @param root The root symbol to be resynced (a package class) - * @param allEntries Optionally, the corresponding package in the complete current classPath - * @param oldEntries Optionally, the corresponding package in the old classPath entries - * @param newEntries Optionally, the corresponding package in the new classPath entries - * @param invalidated A listbuffer collecting the invalidated package classes - * @param failed A listbuffer collecting system package classes which could not be invalidated - * The resyncing strategy is determined by the absence or presence of classes and packages. - * If either oldEntries or newEntries contains classes, root is invalidated, provided a corresponding package - * exists in allEntries, or otherwise is removed. - * Otherwise, the action is determined by the following matrix, with columns: - * - * old new all sym action - * + + + + recurse into all child packages of old ++ new - * + - + + invalidate root - * + - - + remove root from its scope - * - + + + invalidate root - * - + + - create and enter root - * - - * * no action - * - * Here, old, new, all mean classpaths and sym means symboltable. + is presence of an - * entry in its column, - is absence, * is don't care. - * - * Note that new <= all and old <= sym, so the matrix above covers all possibilities. - */ - private def reSync(root: ClassSymbol, - allEntries: OptClassPath, oldEntries: OptClassPath, newEntries: OptClassPath, - invalidated: mutable.ListBuffer[ClassSymbol], failed: mutable.ListBuffer[ClassSymbol]) { - ifDebug(informProgress(s"syncing $root, $oldEntries -> $newEntries")) - - val getName: ClassPath[AbstractFile] => String = (_.name) - def hasClasses(cp: OptClassPath) = cp.isDefined && cp.get.classes.nonEmpty - def invalidateOrRemove(root: ClassSymbol) = { - allEntries match { - case Some(cp) => root setInfo new loaders.PackageLoader(cp) - case None => root.owner.info.decls unlink root.sourceModule - } - invalidated += root - } - def packageNames(cp: PlatformClassPath): Set[String] = cp.packages.toSet map getName - def subPackage(cp: PlatformClassPath, name: String): OptClassPath = - cp.packages find (cp1 => getName(cp1) == name) - - val classesFound = hasClasses(oldEntries) || hasClasses(newEntries) - if (classesFound && !isSystemPackageClass(root)) { - invalidateOrRemove(root) - } else { - if (classesFound) { - if (root.isRoot) invalidateOrRemove(EmptyPackageClass) - else failed += root - } - (oldEntries, newEntries) match { - case (Some(oldcp) , Some(newcp)) => - for (pstr <- packageNames(oldcp) ++ packageNames(newcp)) { - val pname = newTermName(pstr) - val pkg = (root.info decl pname) orElse { - // package was created by external agent, create symbol to track it - assert(!subPackage(oldcp, pstr).isDefined) - loaders.enterPackage(root, pstr, new loaders.PackageLoader(allEntries.get)) - } - reSync( - pkg.moduleClass.asInstanceOf[ClassSymbol], - subPackage(allEntries.get, pstr), subPackage(oldcp, pstr), subPackage(newcp, pstr), - invalidated, failed) - } - case (Some(oldcp), None) => - invalidateOrRemove(root) - case (None, Some(newcp)) => - invalidateOrRemove(root) - case (None, None) => - } - } - } - - /** Invalidate contents of setting -Yinvalidate */ - def doInvalidation() = settings.Yinvalidate.value match { - case "" => - case entry => invalidateClassPathEntries(entry) - } - // ----------- Runs --------------------------------------- private var curRun: Run = null private var curRunId = 0 - /** A hook that lets subclasses of `Global` define whether a package or class should be kept loaded for the - * next compiler run. If the parameter `sym` is a class or object, and `clearOnNextRun(sym)` returns `true`, - * then the symbol is unloaded and reset to its state before the last compiler run. If the parameter `sym` is - * a package, and clearOnNextRun(sym)` returns `true`, the package is recursively searched for - * classes to drop. - * - * Example: Let's say I want a compiler that drops all classes corresponding to the current project - * between runs. Then `keepForNextRun` of a toplevel class or object should return `true` if the - * class or object does not form part of the current project, `false` otherwise. For a package, - * clearOnNextRun should return `true` if no class in that package forms part of the current project, - * `false` otherwise. - * - * @param sym A class symbol, object symbol, package, or package class. - */ - @deprecated("use invalidateClassPathEntries instead", "2.10.0") - def clearOnNextRun(sym: Symbol) = false - /* To try out clearOnNext run on the scala.tools.nsc project itself - * replace `false` above with the following code - - settings.Xexperimental.value && { sym.isRoot || { - sym.fullName match { - case "scala" | "scala.tools" | "scala.tools.nsc" => true - case _ => sym.owner.fullName.startsWith("scala.tools.nsc") - } - }} - - * Then, fsc -Xexperimental clears the nsc project between successive runs of `fsc`. - */ - object typeDeconstruct extends { val global: Global.this.type = Global.this } with typechecker.StructuredTypeStrings @@ -1292,47 +1105,6 @@ class Global(var currentSettings: Settings, var reporter: Reporter) first } - /** Reset all classes contained in current project, as determined by - * the clearOnNextRun hook - */ - @deprecated("use invalidateClassPathEntries instead", "2.10.0") - def resetProjectClasses(root: Symbol): Unit = try { - def unlink(sym: Symbol) = - if (sym != NoSymbol) root.info.decls.unlink(sym) - if (settings.verbose) inform("[reset] recursing in "+root) - val toReload = mutable.Set[String]() - for (sym <- root.info.decls) { - if (sym.isInitialized && clearOnNextRun(sym)) - if (sym.hasPackageFlag) { - resetProjectClasses(sym.moduleClass) - openPackageModule(sym.moduleClass) - } else { - unlink(sym) - unlink(root.info.decls.lookup( - if (sym.isTerm) sym.name.toTypeName else sym.name.toTermName)) - toReload += sym.fullName - // note: toReload could be set twice with the same name - // but reinit must happen only once per name. That's why - // the following classPath.findClass { ... } code cannot be moved here. - } - } - for (fullname <- toReload) - classPath.findClass(fullname) match { - case Some(classRep) => - if (settings.verbose) inform("[reset] reinit "+fullname) - loaders.initializeFromClassPath(root, classRep) - case _ => - } - } catch { - case ex: Throwable => - // this handler should not be nessasary, but it seems that `fsc` - // eats exceptions if they appear here. Need to find out the cause for - // this and fix it. - inform("[reset] exception happened: "+ex) - ex.printStackTrace() - throw ex - } - // --------------- Miscellania ------------------------------- /** Progress tracking. Measured in "progress units" which are 1 per @@ -1542,8 +1314,6 @@ class Global(var currentSettings: Settings, var reporter: Reporter) compileUnitsInternal(units, fromPhase) private def compileUnitsInternal(units: List[CompilationUnit], fromPhase: Phase) { - doInvalidation() - units foreach addUnit val startTime = currentTime @@ -1619,13 +1389,6 @@ class Global(var currentSettings: Settings, var reporter: Reporter) // Clear any sets or maps created via perRunCaches. perRunCaches.clearAll() - - // Reset project - if (!stopPhase("namer")) { - enteringPhase(namerPhase) { - resetProjectClasses(RootClass) - } - } } /** Compile list of abstract files. */ diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BCodeBodyBuilder.scala b/src/compiler/scala/tools/nsc/backend/jvm/BCodeBodyBuilder.scala index 4583462b71..2d1030121e 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/BCodeBodyBuilder.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/BCodeBodyBuilder.scala @@ -977,7 +977,7 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder { if (!isModuleInitialized && jMethodName == INSTANCE_CONSTRUCTOR_NAME && jname == INSTANCE_CONSTRUCTOR_NAME && - isStaticModule(siteSymbol)) { + isStaticModuleClass(siteSymbol)) { isModuleInitialized = true mnode.visitVarInsn(asm.Opcodes.ALOAD, 0) mnode.visitFieldInsn( diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BCodeHelpers.scala b/src/compiler/scala/tools/nsc/backend/jvm/BCodeHelpers.scala index 31a392ed55..de1587c7c3 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/BCodeHelpers.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/BCodeHelpers.scala @@ -460,6 +460,7 @@ abstract class BCodeHelpers extends BCodeTypes with BytecodeWriters { // 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)) @@ -469,6 +470,8 @@ abstract class BCodeHelpers extends BCodeTypes with BytecodeWriters { } } + // 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.") diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BCodeIdiomatic.scala b/src/compiler/scala/tools/nsc/backend/jvm/BCodeIdiomatic.scala index 9b7c975960..2343d378db 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/BCodeIdiomatic.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/BCodeIdiomatic.scala @@ -34,6 +34,7 @@ abstract class BCodeIdiomatic extends SubComponent { case "jvm-1.5" => asm.Opcodes.V1_5 case "jvm-1.6" => asm.Opcodes.V1_6 case "jvm-1.7" => asm.Opcodes.V1_7 + case "jvm-1.8" => asm.Opcodes.V1_8 } val majorVersion: Int = (classfileVersion & 0xFF) diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BCodeSkelBuilder.scala b/src/compiler/scala/tools/nsc/backend/jvm/BCodeSkelBuilder.scala index 8845ffa0cd..0d67a07e0f 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/BCodeSkelBuilder.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/BCodeSkelBuilder.scala @@ -95,7 +95,7 @@ abstract class BCodeSkelBuilder extends BCodeHelpers { claszSymbol = cd.symbol isCZParcelable = isAndroidParcelableClass(claszSymbol) - isCZStaticModule = isStaticModule(claszSymbol) + isCZStaticModule = isStaticModuleClass(claszSymbol) isCZRemote = isRemote(claszSymbol) thisName = internalName(claszSymbol) diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BCodeTypes.scala b/src/compiler/scala/tools/nsc/backend/jvm/BCodeTypes.scala index 62dfb4917d..b373f8d74d 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/BCodeTypes.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/BCodeTypes.scala @@ -21,8 +21,9 @@ abstract class BCodeTypes extends BCodeIdiomatic { import global._ import bTypes._ - // when compiling the Scala library, some assertions don't hold (e.g., scala.Boolean has null superClass although it's not an interface) - val isCompilingStdLib = !(settings.sourcepath.isDefault) + // 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 @@ -175,12 +176,21 @@ abstract class BCodeTypes extends BCodeIdiomatic { // ------------------------------------------------ /** - * TODO @lry should probably be a map form ClassBType to Tracked + * Type information for classBTypes. + * + * TODO rename Tracked */ - val exemplars = new java.util.concurrent.ConcurrentHashMap[BType, 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] @@ -313,7 +323,7 @@ abstract class BCodeTypes extends BCodeIdiomatic { final def isDeprecated(sym: Symbol): Boolean = { sym.annotations exists (_ matches definitions.DeprecatedAttr) } /* must-single-thread */ - final def hasInternalName(sym: Symbol) = { sym.isClass || (sym.isModule && !sym.isMethod) } + final def hasInternalName(sym: Symbol) = sym.isClass || sym.isModuleNotMethod /* must-single-thread */ def getSuperInterfaces(csym: Symbol): List[Symbol] = { @@ -617,20 +627,52 @@ abstract class BCodeTypes extends BCodeIdiomatic { false } - /* + /** * must-single-thread + * + * True for module classes of package level objects. The backend will generate a mirror class for + * such objects. */ - def isTopLevelModule(sym: Symbol): Boolean = { - exitingPickler { sym.isModuleClass && !sym.isImplClass && !sym.isNestedClass } + 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 isStaticModule(sym: Symbol): Boolean = { - sym.isModuleClass && !sym.isImplClass && !sym.isLifted + 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) ---------------- // --------------------------------------------------------------------- @@ -702,6 +744,10 @@ abstract class BCodeTypes extends BCodeIdiomatic { 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 @@ -729,7 +775,7 @@ abstract class BCodeTypes extends BCodeIdiomatic { null else { val outerName = innerSym.rawowner.javaBinaryName - if (isTopLevelModule(innerSym.rawowner)) nme.stripModuleSuffix(outerName) + if (isTopLevelModuleClass(innerSym.rawowner)) nme.stripModuleSuffix(outerName) else outerName } } @@ -741,12 +787,23 @@ abstract class BCodeTypes extends BCodeIdiomatic { innerSym.rawname + innerSym.moduleSuffix } - val flagsWithFinal: Int = mkFlags( + // 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 flags = if (innerSym.isModuleClass) flagsWithFinal & ~asm.Opcodes.ACC_FINAL else flagsWithFinal // For SI-5676, object overriding. val jname = innerSym.javaBinaryName.toString // never null val oname = { // null when method-enclosed @@ -794,14 +851,23 @@ abstract class BCodeTypes extends BCodeIdiomatic { * must-single-thread */ def javaFlags(sym: Symbol): Int = { - // constructors of module classes should be private - // PP: why are they only being marked private at this stage and not earlier? + // 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 && isTopLevelModule(sym.owner)) + sym.isPrivate || (sym.isPrimaryConstructor && isTopLevelModuleClass(sym.owner)) - // Final: the only fields which can receive ACC_FINAL are eager vals. - // Neither vars nor lazy vals can, because: + // 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 @@ -818,10 +884,9 @@ abstract class BCodeTypes extends BCodeIdiomatic { // we can exclude lateFINAL. Such symbols are eligible for inlining, but to // avoid breaking proxy software which depends on subclassing, we do not // emit ACC_FINAL. - // Nested objects won't receive ACC_FINAL in order to allow for their overriding. val finalFlag = ( - (((sym.rawflags & symtab.Flags.FINAL) != 0) || isTopLevelModule(sym)) + (((sym.rawflags & symtab.Flags.FINAL) != 0) || isTopLevelModuleClass(sym)) && !sym.enclClass.isInterface && !sym.isClassConstructor && !sym.isMutable // lazy vals and vars both @@ -845,6 +910,10 @@ abstract class BCodeTypes extends BCodeIdiomatic { 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. } /* diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BTypes.scala b/src/compiler/scala/tools/nsc/backend/jvm/BTypes.scala index 5b0fa6f7a8..15bc068533 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/BTypes.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/BTypes.scala @@ -43,9 +43,9 @@ abstract class BTypes[G <: Global](val __global_dont_use: G) { case FLOAT => "F" case LONG => "J" case DOUBLE => "D" - case c @ ClassBType(_, _) => "L" + c.internalName + ";" - case ArrayBType(component) => "[" + component - case MethodBType(args, res) => "(" + args.mkString + ")" + res + case ClassBType(internalName) => "L" + internalName + ";" + case ArrayBType(component) => "[" + component + case MethodBType(args, res) => "(" + args.mkString + ")" + res } /** @@ -160,9 +160,9 @@ abstract class BTypes[G <: Global](val __global_dont_use: G) { case FLOAT => asm.Type.FLOAT_TYPE case LONG => asm.Type.LONG_TYPE case DOUBLE => asm.Type.DOUBLE_TYPE - case c @ ClassBType(_, _) => asm.Type.getObjectType(c.internalName) // (*) - case a @ ArrayBType(_) => asm.Type.getObjectType(a.descriptor) - case m @ MethodBType(_, _) => asm.Type.getMethodType(m.descriptor) + case ClassBType(internalName) => asm.Type.getObjectType(internalName) // see (*) above + case a: ArrayBType => asm.Type.getObjectType(a.descriptor) + case m: MethodBType => asm.Type.getMethodType(m.descriptor) } def asRefBType : RefBType = this.asInstanceOf[RefBType] @@ -227,39 +227,254 @@ abstract class BTypes[G <: Global](val __global_dont_use: G) { * This can be verified for example using javap or ASMifier. */ def classOrArrayType: String = this match { - case c: ClassBType => c.internalName - case a: ArrayBType => a.descriptor + case ClassBType(internalName) => internalName + case a: ArrayBType => a.descriptor } } /** + * InnerClass and EnclosingMethod attributes (EnclosingMethod is displayed as OUTERCLASS in asm). + * + * In this summary, "class" means "class or interface". + * + * JLS: http://docs.oracle.com/javase/specs/jls/se8/html/index.html + * JVMS: http://docs.oracle.com/javase/specs/jvms/se8/html/index.html + * + * Terminology + * ----------- + * + * - Nested class (JLS 8): class whose declaration occurs within the body of another class + * + * - Top-level class (JLS 8): non-nested class + * + * - Inner class (JLS 8.1.3): nested class that is not (explicitly or implicitly) static + * + * - Member class (JLS 8.5): class directly enclosed in the body of a class (and not, for + * example, defined in a method). Member classes cannot be anonymous. May be static. + * + * - Local class (JLS 14.3): nested, non-anonymous class that is not a member of a class + * - cannot be static (therefore they are "inner" classes) + * - can be defined in a method, a constructor or in an initializer block + * + * - Initializer block (JLS 8.6 / 8.7): block of statements in a java class + * - static initializer: executed before constructor body + * - instance initializer: exectued when class is initialized (instance creation, static + * field access, ...) + * + * + * InnerClass + * ---------- + * + * The JVMS 4.7.6 requires an entry for every class mentioned in a CONSTANT_Class_info in the + * constant pool (CP) that is not a member of a package (JLS 7.1). + * + * The JLS 13.1, points 9. / 10. requires: a class must reference (in the CP) + * - its immediately enclosing class + * - all of its member classes + * - all local and anonymous classes that appear elsewhere (method, constructor, initializer + * block, field initializer) + * + * In a comment, the 4.7.6 spec says: this implies an entry in the InnerClass attribute for + * - All enclosing classes (except the outermost, which is top-level) + * - My comment: not sure how this is implied, below (*) a Java counter-example. + * In any case, the Java compiler seems to add all enclosing classes, even if they are not + * otherwise mentioned in the CP. So we should do the same. + * - All nested classes (including anonymous and local, but not transitively) + * + * Fields in the InnerClass entries: + * - inner class: the (nested) class C we are talking about + * - outer class: the class of which C is a member. Has to be null for non-members, i.e. for + * local and anonymous classes. + * - 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. + * + * + * Note 1: when a nested class is present in the InnerClass attribute, all of its enclosing + * classes have to be present as well (by the rules above). Example: + * + * class Outer { class I1 { class I2 { } } } + * class User { Outer.I1.I2 foo() { } } + * + * The return type "Outer.I1.I2" puts "Outer$I1$I2" in the CP, therefore the class is added to the + * InnerClass attribute. For this entry, the "outer class" field will be "Outer$I1". This in turn + * adds "Outer$I1" to the CP, which requires adding that class to the InnerClass attribute. + * (For local / anonymous classes this would not be the case, since the "outer class" attribute + * would be empty. However, no class (other than the enclosing class) can refer to them, as they + * have no name.) + * + * In the current implementation of the Scala compiler, when adding a class to the InnerClass + * attribute, all of its enclosing classes will be added as well. Javac seems to do the same, + * see (*). + * + * + * Note 2: If a class name is mentioned only in a CONSTANT_Utf8_info, but not in a + * CONSTANT_Class_info, the JVMS does not require an entry in the InnerClass attribute. However, + * the Java compiler seems to add such classes anyway. For example, when using an annotation, the + * annotation class is stored as a CONSTANT_Utf8_info in the CP: + * + * @O.Ann void foo() { } + * + * adds "const #13 = Asciz LO$Ann;;" in the constant pool. The "RuntimeInvisibleAnnotations" + * attribute refers to that constant pool entry. Even though there is no other reference to + * `O.Ann`, the java compiler adds an entry for that class to the InnerClass attribute (which + * entails adding a CONSTANT_Class_info for the class). + * + * + * + * EnclosingMethod + * --------------- + * + * JVMS 4.7.7: the attribute must be present "if and only if it represents a local class + * or an anonymous class" (i.e. not for member classes). + * + * Fields: + * - class: the enclosing class + * - method: the enclosing method (or constructor). Null if the class is not enclosed by a + * method, i.e. for + * - local or anonymous classes defined in (static or non-static) initializer blocks + * - anonymous classes defined in initializer blocks or field initializers + * + * Note: the field is required for anonymous classes defined within local variable + * initializers (within a method), Java example below (**). + * + * 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. + * + * + * (*) + * public class Test { + * void foo() { + * class Foo1 { + * // constructor statement block + * { + * class Foo2 { + * class Foo3 { } + * } + * } + * } + * } + * } + * + * The class file Test$1Foo1$1Foo2$Foo3 has no reference to the class Test$1Foo1, however it + * still contains an InnerClass attribute for Test$1Foo1. + * Maybe this is just because the Java compiler follows the JVMS comment ("InnerClasses + * information for each enclosing class"). + * + * + * (**) + * void foo() { + * // anonymous class defined in local variable initializer expression. + * Runnable x = true ? (new Runnable() { + * public void run() { return; } + * }) : null; + * } + * + * The EnclosingMethod attribute of the anonymous class mentions "foo" in the "method" field. + * + * + * Java Compatibility + * ------------------ + * + * In the InnerClass entry for classes in top-level modules, the "outer class" is emitted as the + * mirror class (or the existing companion class), i.e. C1 is nested in T (not T$). + * For classes nested in a nested object, the "outer class" is the module class: C2 is nested in T$N$ + * object T { + * class C1 + * object N { class C2 } + * } + * + * Reason: java compat. It's a "best effort" "solution". If you want to use "C1" from Java, you + * can write "T.C1", and the Java compiler will translate that to the classfile T$C1. + * + * If we would emit the "outer class" of C1 as "T$", then in Java you'd need to write "T$.C1" + * because the java compiler looks at the InnerClass attribute to find if an inner class exists. + * However, the Java compiler would then translate the '.' to '$' and you'd get the class name + * "T$$C1". This class file obviously does not exist. + * + * Directly using the encoded class name "T$C1" in Java does not work: since the classfile + * describes a nested class, the Java compiler hides it from the classpath and will report + * "cannot find symbol T$C1". This means that the class T.N.C2 cannot be referenced from a + * Java source file in any way. + * + * + * STATIC flag + * ----------- + * + * Java: static 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. + * + * 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 { + * def f = { + * class C2 { // static + * class C3 // non-static, needs outer + * } + * } + * } + * } + * + * 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 + * -------------- + * + * TODO: innerclass attributes on mirror class, bean info class + */ + + /** * Class or Interface type. * - * Classes are represented using their name as a slice of the `chrs` array. This representation is - * efficient because the JVM class name is initially created using `classSymbol.javaBinaryName`. - * This already adds the necessary string to the `chrs` array, so it makes sense to reuse the same - * name table in the backend. + * 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). */ class ClassBType private(val offset: Int, val length: Int) extends RefBType { /** - * Construct a ClassBType for a given (intenred) class name. + * Construct a ClassBType from the (intenred) internal name of a class. * - * @param n The class name as a slice of the `chrs` array, without the surrounding 'L' and ';'. - * Note that `classSymbol.javaBinaryName` returns exactly such a name. + * @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. */ - def this(n: BTypeName) = this(n.start, n.length) + def this(internalName: BTypeName) = this(internalName.start, internalName.length) /** - * Construct a ClassBType for a given java class name. + * Construct a ClassBType from the internal name of a class. * - * @param s A class name of the form "java/lang/String", without the surrounding 'L' and ';'. + * @param internalName The internal name of a class has the form "java/lang/String", without the + * surrounding 'L' and ';'. */ - def this(s: String) = this({ - assert(!(s.head == 'L' && s.last == ';'), s"Descriptor instead of internal name: $s") - createNewName(s) + def this(internalName: String) = this({ + assert(!(internalName.head == 'L' && internalName.last == ';'), s"Descriptor instead of internal name: $internalName") + createNewName(internalName) }) /** @@ -277,7 +492,7 @@ abstract class BTypes[G <: Global](val __global_dont_use: G) { * Custom equals / hashCode are needed because this is not a case class. */ override def equals(o: Any): Boolean = (this eq o.asInstanceOf[Object]) || (o match { - case ClassBType(`offset`, `length`) => true + case c: ClassBType => c.offset == this.offset && c.length == this.length case _ => false }) @@ -291,12 +506,15 @@ abstract class BTypes[G <: Global](val __global_dont_use: G) { } object ClassBType { - def apply(n: BTypeName): ClassBType = new ClassBType(n) - def apply(s: String): ClassBType = new ClassBType(s) + def apply(internalName: BTypeName): ClassBType = new ClassBType(internalName) + def apply(internalName: String): ClassBType = new ClassBType(internalName) - def unapply(c: ClassBType): Option[(Int, Int)] = + /** + * Pattern matching on a ClassBType extracts the `internalName` of the class. + */ + def unapply(c: ClassBType): Option[String] = if (c == null) None - else Some((c.offset, c.length)) + else Some(c.internalName) } case class ArrayBType(componentType: BType) extends RefBType { diff --git a/src/compiler/scala/tools/nsc/backend/jvm/GenASM.scala b/src/compiler/scala/tools/nsc/backend/jvm/GenASM.scala index b0fb3069c1..2392033760 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/GenASM.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/GenASM.scala @@ -381,6 +381,7 @@ abstract class GenASM extends SubComponent with BytecodeWriters with GenJVMASM { case "jvm-1.5" => asm.Opcodes.V1_5 case "jvm-1.6" => asm.Opcodes.V1_6 case "jvm-1.7" => asm.Opcodes.V1_7 + case "jvm-1.8" => asm.Opcodes.V1_8 } private val majorVersion: Int = (classfileVersion & 0xFF) @@ -636,6 +637,9 @@ abstract class GenASM extends SubComponent with BytecodeWriters with GenJVMASM { 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 diff --git a/src/compiler/scala/tools/nsc/backend/jvm/GenBCode.scala b/src/compiler/scala/tools/nsc/backend/jvm/GenBCode.scala index a401de05e5..89866b7ce9 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/GenBCode.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/GenBCode.scala @@ -165,7 +165,7 @@ abstract class GenBCode extends BCodeSyncAndTry { // -------------- mirror class, if needed -------------- val mirrorC = - if (isStaticModule(claszSymbol) && isTopLevelModule(claszSymbol)) { + if (isTopLevelModuleClass(claszSymbol)) { if (claszSymbol.companionClass == NoSymbol) { mirrorCodeGen.genMirrorClass(claszSymbol, cunit) } else { @@ -271,7 +271,7 @@ abstract class GenBCode extends BCodeSyncAndTry { override def run() { arrivalPos = 0 // just in case - scalaPrimitives.init + scalaPrimitives.init() initBCodeTypes() // initBytecodeWriter invokes fullName, thus we have to run it before the typer-dependent thread is activated. diff --git a/src/compiler/scala/tools/nsc/plugins/Plugins.scala b/src/compiler/scala/tools/nsc/plugins/Plugins.scala index 12f9aeba27..6e3d013e52 100644 --- a/src/compiler/scala/tools/nsc/plugins/Plugins.scala +++ b/src/compiler/scala/tools/nsc/plugins/Plugins.scala @@ -33,7 +33,7 @@ trait Plugins { global: Global => } val maybes = Plugin.loadAllFrom(paths, dirs, settings.disable.value) val (goods, errors) = maybes partition (_.isSuccess) - // Explicit parameterization of recover to suppress -Xlint warning about inferred Any + // Explicit parameterization of recover to avoid -Xlint warning about inferred Any errors foreach (_.recover[Any] { // legacy behavior ignores altogether, so at least warn devs case e: MissingPluginException => if (global.isDeveloper) warning(e.getMessage) diff --git a/src/compiler/scala/tools/nsc/settings/AbsScalaSettings.scala b/src/compiler/scala/tools/nsc/settings/AbsScalaSettings.scala index 38a7525862..d0b8fd70ed 100644 --- a/src/compiler/scala/tools/nsc/settings/AbsScalaSettings.scala +++ b/src/compiler/scala/tools/nsc/settings/AbsScalaSettings.scala @@ -29,7 +29,7 @@ trait AbsScalaSettings { def ChoiceSetting(name: String, helpArg: String, descr: String, choices: List[String], default: String): ChoiceSetting def IntSetting(name: String, descr: String, default: Int, range: Option[(Int, Int)], parser: String => Option[Int]): IntSetting def MultiStringSetting(name: String, helpArg: String, descr: String): MultiStringSetting - def MultiChoiceSetting(name: String, helpArg: String, descr: String, choices: List[String]): MultiChoiceSetting + def MultiChoiceSetting(name: String, helpArg: String, descr: String, choices: List[String], default: Option[() => Unit])(helper: MultiChoiceSetting => String): MultiChoiceSetting def OutputSetting(outputDirs: OutputDirs, default: String): OutputSetting def PathSetting(name: String, descr: String, default: String): PathSetting def PhasesSetting(name: String, descr: String, default: String): PhasesSetting diff --git a/src/compiler/scala/tools/nsc/settings/MutableSettings.scala b/src/compiler/scala/tools/nsc/settings/MutableSettings.scala index 54e444decf..f26192f88a 100644 --- a/src/compiler/scala/tools/nsc/settings/MutableSettings.scala +++ b/src/compiler/scala/tools/nsc/settings/MutableSettings.scala @@ -211,11 +211,10 @@ class MutableSettings(val errorFn: String => Unit) add(new ChoiceSetting(name, helpArg, descr, choices, default)) def IntSetting(name: String, descr: String, default: Int, range: Option[(Int, Int)], parser: String => Option[Int]) = add(new IntSetting(name, descr, default, range, parser)) def MultiStringSetting(name: String, arg: String, descr: String) = add(new MultiStringSetting(name, arg, descr)) - def MultiChoiceSetting(name: String, helpArg: String, descr: String, choices: List[String]): MultiChoiceSetting = { - val fullChoix = choices.mkString(": ", ",", ".") - val fullDescr = s"$descr$fullChoix" - add(new MultiChoiceSetting(name, helpArg, fullDescr, choices)) - } + def MultiChoiceSetting(name: String, helpArg: String, descr: String, choices: List[String], default: Option[() => Unit] = None)( + helper: MultiChoiceSetting => String = _ => choices.mkString(f"$descr:%n", f"%n ", f"%n") + ) = + add(new MultiChoiceSetting(name, helpArg, descr, choices, default, helper)) def OutputSetting(outputDirs: OutputDirs, default: String) = add(new OutputSetting(outputDirs, default)) def PhasesSetting(name: String, descr: String, default: String = "") = add(new PhasesSetting(name, descr, default)) def StringSetting(name: String, arg: String, descr: String, default: String) = add(new StringSetting(name, arg, descr, default)) @@ -444,7 +443,7 @@ class MutableSettings(val errorFn: String => Unit) value = s.equalsIgnoreCase("true") } override def tryToSetColon(args: List[String]) = args match { - case Nil => tryToSet(Nil) + case Nil => tryToSet(Nil) case List(x) => if (x.equalsIgnoreCase("true")) { value = true @@ -452,7 +451,8 @@ class MutableSettings(val errorFn: String => Unit) } else if (x.equalsIgnoreCase("false")) { value = false Some(Nil) - } else errorAndValue("'" + x + "' is not a valid choice for '" + name + "'", None) + } else errorAndValue(s"'$x' is not a valid choice for '$name'", None) + case _ => errorAndValue(s"'$name' accepts only one boolean value", None) } } @@ -553,12 +553,58 @@ class MutableSettings(val errorFn: String => Unit) } } + /** A setting that receives any combination of enumerated values, + * including "_" to mean all values and "help" for verbose info. + * In non-colonated mode, stops consuming args at the first + * non-value, instead of at the next option, as for a multi-string. + */ class MultiChoiceSetting private[nsc]( name: String, arg: String, descr: String, - override val choices: List[String]) - extends MultiStringSetting(name, arg, descr) + override val choices: List[String], + val default: Option[() => Unit], + helper: MultiChoiceSetting => String + ) extends MultiStringSetting(name, s"_,$arg,-$arg", s"$descr: `_' for all, `$name:help' to list") { + + private def badChoice(s: String, n: String) = errorFn(s"'$s' is not a valid choice for '$name'") + private def choosing = choices.nonEmpty + private def isChoice(s: String) = (s == "_") || (choices contains (s stripPrefix "-")) + + private var sawHelp = false + private var sawAll = false + private val adderAll = () => sawAll = true + private val noargs = () => errorFn(s"'$name' requires an option. See '$name:help'.") + + override protected def tts(args: List[String], halting: Boolean) = { + val added = collection.mutable.ListBuffer.empty[String] + def tryArg(arg: String) = arg match { + case "_" if choosing => addAll() + case "help" if choosing => sawHelp = true + case s if !choosing || isChoice(s) => added += s + case s => badChoice(s, name) + } + def stoppingAt(arg: String) = (arg startsWith "-") || (choosing && !isChoice(arg)) + def loop(args: List[String]): List[String] = args match { + case arg :: _ if halting && stoppingAt(arg) => args + case arg :: rest => tryArg(arg) ; loop(rest) + case Nil => Nil + } + val rest = loop(args) + if (rest.size == args.size) + (default getOrElse noargs)() // if no arg consumed, trigger default action or error + else + value ++= added.toList // update all new settings at once + Some(rest) + } + + def isHelping: Boolean = sawHelp + def help: String = helper(this) + def addAll(): Unit = (default getOrElse adderAll)() + + // the semantics is: s is enabled, i.e., either s or (_ but not -s) + override def contains(s: String) = isChoice(s) && (value contains s) || (sawAll && !(value contains s"-$s")) + } /** A setting that accumulates all strings supplied to it, * until it encounters one starting with a '-'. @@ -570,24 +616,23 @@ class MutableSettings(val errorFn: String => Unit) extends Setting(name, descr) with Clearable { type T = List[String] protected var v: T = Nil - def appendToValue(str: String) { value ++= List(str) } - def badChoice(s: String, n: String) = errorFn(s"'$s' is not a valid choice for '$name'") - - def tryToSet(args: List[String]) = { - val (strings, rest) = args span (x => !x.startsWith("-")) - strings foreach { - case "_" if choices.nonEmpty => choices foreach appendToValue - case s if choices.isEmpty || (choices contains s) => appendToValue(s) - case s => badChoice(s, name) + def appendToValue(str: String) = value ++= List(str) + + // try to set. halting means halt at first non-arg + protected def tts(args: List[String], halting: Boolean) = { + def loop(args: List[String]): List[String] = args match { + case arg :: rest => if (halting && (arg startsWith "-")) args else { appendToValue(arg) ; loop(rest) } + case Nil => Nil } - Some(rest) + Some(loop(args)) } - override def tryToSetColon(args: List[String]) = tryToSet(args) + def tryToSet(args: List[String]) = tts(args, halting = true) + override def tryToSetColon(args: List[String]) = tts(args, halting = false) override def tryToSetFromPropertyValue(s: String) = tryToSet(s.trim.split(',').toList) // used from ide - def clear(): Unit = (v = Nil) - def unparse: List[String] = value map (name + ":" + _) - def contains(s: String) = value contains s + def clear(): Unit = (v = Nil) + def unparse: List[String] = value map (name + ":" + _) + def contains(s: String) = value contains s withHelpSyntax(name + ":<" + arg + ">") } @@ -606,10 +651,8 @@ class MutableSettings(val errorFn: String => Unit) protected var v: T = default def indexOfChoice: Int = choices indexOf value - private def usageErrorMessage = { - "Usage: %s:<%s>\n where <%s> choices are %s (default: %s)\n".format( - name, helpArg, helpArg, choices mkString ", ", default) - } + private def usageErrorMessage = f"Usage: $name:<$helpArg>%n where <$helpArg> choices are ${choices mkString ", "} (default: $default)%n" + def tryToSet(args: List[String]) = errorAndValue(usageErrorMessage, None) override def tryToSetColon(args: List[String]) = args match { diff --git a/src/compiler/scala/tools/nsc/settings/ScalaSettings.scala b/src/compiler/scala/tools/nsc/settings/ScalaSettings.scala index d22dcacad6..8e69598614 100644 --- a/src/compiler/scala/tools/nsc/settings/ScalaSettings.scala +++ b/src/compiler/scala/tools/nsc/settings/ScalaSettings.scala @@ -44,8 +44,11 @@ trait ScalaSettings extends AbsScalaSettings /** If any of these settings is enabled, the compiler should print a message and exit. */ def infoSettings = List[Setting](version, help, Xhelp, Yhelp, showPlugins, showPhases, genPhaseGraph) + /** Any -multichoice:help? Nicer if any option could report that it had help to offer. */ + private def multihelp = allSettings exists { case s: MultiChoiceSetting => s.isHelping case _ => false } + /** Is an info setting set? */ - def isInfo = infoSettings exists (_.isSetByUser) + def isInfo = (infoSettings exists (_.isSetByUser)) || multihelp /** Disable a setting */ def disable(s: Setting) = allSettings -= s @@ -67,8 +70,27 @@ trait ScalaSettings extends AbsScalaSettings // Would be nice to build this dynamically from scala.languageFeature. // The two requirements: delay error checking until you have symbols, and let compiler command build option-specific help. val language = { - val features = List("dynamics", "postfixOps", "reflectiveCalls", "implicitConversions", "higherKinds", "existentials", "experimental.macros") - MultiChoiceSetting("-language", "feature", "Enable one or more language features", features) + val features = List( + "dynamics" -> "Allow direct or indirect subclasses of scala.Dynamic", + "postfixOps" -> "Allow postfix operator notation, such as `1 to 10 toList'", + "reflectiveCalls" -> "Allow reflective access to members of structural types", + "implicitConversions" -> "Allow definition of implicit functions called views", + "higherKinds" -> "Allow higher-kinded types", // "Ask Adriaan, but if you have to ask..." + "existentials" -> "Existential types (besides wildcard types) can be written and inferred", + "experimental.macros" -> "Allow macro defintion (besides implementation and application)" + ) + val description = "Enable or disable language features" + MultiChoiceSetting( + name = "-language", + helpArg = "feature", + descr = description, + choices = features map (_._1) + ) { s => + val helpline: ((String, String)) => String = { + case (name, text) => f" $name%-25s $text%n" + } + features map helpline mkString (f"$description:%n", "", f"%n") + } } /* @@ -185,7 +207,6 @@ trait ScalaSettings extends AbsScalaSettings val YmethodInfer = BooleanSetting ("-Yinfer-argument-types", "Infer types for arguments of overriden methods.") val etaExpandKeepsStar = BooleanSetting ("-Yeta-expand-keeps-star", "Eta-expand varargs methods to T* rather than Seq[T]. This is a temporary option to ease transition.").withDeprecationMessage(removalIn212) val inferByName = BooleanSetting ("-Yinfer-by-name", "Allow inference of by-name types. This is a temporary option to ease transition. See SI-7899.").withDeprecationMessage(removalIn212) - val Yinvalidate = StringSetting ("-Yinvalidate", "classpath-entry", "Invalidate classpath entry before run", "") val YvirtClasses = false // too embryonic to even expose as a -Y //BooleanSetting ("-Yvirtual-classes", "Support virtual classes") val YdisableUnreachablePrevention = BooleanSetting("-Ydisable-unreachable-prevention", "Disable the prevention of unreachable blocks in code generation.") val YnoLoadImplClass = BooleanSetting ("-Yno-load-impl-class", "Do not load $class.class files.") diff --git a/src/compiler/scala/tools/nsc/settings/StandardScalaSettings.scala b/src/compiler/scala/tools/nsc/settings/StandardScalaSettings.scala index 37dfafb01c..d42c0dd730 100644 --- a/src/compiler/scala/tools/nsc/settings/StandardScalaSettings.scala +++ b/src/compiler/scala/tools/nsc/settings/StandardScalaSettings.scala @@ -39,7 +39,7 @@ trait StandardScalaSettings { val optimise: BooleanSetting // depends on post hook which mutates other settings val print = BooleanSetting ("-print", "Print program with Scala-specific features removed.") val target = ChoiceSetting ("-target", "target", "Target platform for object files. All JVM 1.5 targets are deprecated.", - List("jvm-1.5", "jvm-1.6", "jvm-1.7"), "jvm-1.6") + List("jvm-1.5", "jvm-1.6", "jvm-1.7", "jvm-1.8"), "jvm-1.6") val unchecked = BooleanSetting ("-unchecked", "Enable additional warnings where generated code depends on assumptions.") val uniqid = BooleanSetting ("-uniqid", "Uniquely tag all identifiers in debugging output.") val usejavacp = BooleanSetting ("-usejavacp", "Utilize the java.class.path in classpath resolution.") diff --git a/src/compiler/scala/tools/nsc/settings/Warnings.scala b/src/compiler/scala/tools/nsc/settings/Warnings.scala index 1509ad13b8..bec068b56a 100644 --- a/src/compiler/scala/tools/nsc/settings/Warnings.scala +++ b/src/compiler/scala/tools/nsc/settings/Warnings.scala @@ -17,12 +17,15 @@ trait Warnings { // Warning semantics. val fatalWarnings = BooleanSetting("-Xfatal-warnings", "Fail the compilation if there are any warnings.") - // These warnings are all so noisy as to be useless in their + // These additional warnings are all so noisy as to be useless in their // present form, but have the potential to offer useful info. protected def allWarnings = lintWarnings ++ List( warnDeadCode, warnValueDiscard, - warnNumericWiden + warnNumericWiden, + warnUnused, // SI-7712, SI-7707 warnUnused not quite ready for prime-time + warnUnusedImport, // currently considered too noisy for general use + warnValueOverrides // currently turned off as experimental ) // These warnings should be pretty quiet unless you're doing // something inadvisable. @@ -31,27 +34,127 @@ trait Warnings { warnNullaryOverride, warnNullaryUnit, warnAdaptedArgs, - warnInferAny - // warnUnused SI-7712, SI-7707 warnUnused not quite ready for prime-time - // warnUnusedImport currently considered too noisy for general use + warnInferAny, + warnMissingInterpolator, + warnDocDetached, + warnPrivateShadow, + warnPolyImplicitOverload, + warnOptionImplicit, + warnDelayedInit, + warnByNameRightAssociative, + warnPackageObjectClasses, + warnUnsoundMatch ) - private lazy val warnSelectNullable = BooleanSetting("-Xcheck-null", "This option is obsolete and does nothing.") + // Individual warnings. They can be set with -Ywarn. + private def nonlintflag(name: String, text: String): BooleanSetting = BooleanSetting(name, text) + + val warnDeadCode = nonlintflag("-Ywarn-dead-code", + "Warn when dead code is identified.") + val warnValueDiscard = nonlintflag("-Ywarn-value-discard", + "Warn when non-Unit expression results are unused.") + val warnNumericWiden = nonlintflag("-Ywarn-numeric-widen", + "Warn when numerics are widened.") + val warnUnused = nonlintflag("-Ywarn-unused", + "Warn when local and private vals, vars, defs, and types are are unused") + val warnUnusedImport = nonlintflag("-Ywarn-unused-import", + "Warn when imports are unused") + + // Lint warnings that have no -Y avatar, created with new instead of the autoregistering factory method. + // They evaluate true if set to true or else are unset but -Xlint is true + private def lintflag(name: String, text: String): BooleanSetting = + new BooleanSetting(name, text) { + override def value = if (isSetByUser) super.value else xlint + } + + val warnAdaptedArgs = lintflag("adapted-args", + "Warn if an argument list is modified to match the receiver.") + val warnNullaryUnit = lintflag("nullary-unit", + "Warn when nullary methods return Unit.") + val warnInaccessible = lintflag("inaccessible", + "Warn about inaccessible types in method signatures.") + val warnNullaryOverride = lintflag("nullary-override", + "Warn when non-nullary `def f()' overrides nullary `def f'.") + val warnInferAny = lintflag("infer-any", + "Warn when a type argument is inferred to be `Any`.") + val warnMissingInterpolator = lintflag("missing-interpolator", + "A string literal appears to be missing an interpolator id.") + val warnDocDetached = lintflag("doc-detached", + "A ScalaDoc comment appears to be detached from its element.") + val warnPrivateShadow = lintflag("private-shadow", + "A private field (or class parameter) shadows a superclass field.") + val warnPolyImplicitOverload = lintflag("poly-implicit-overload", + "Parameterized overloaded implicit methods are not visible as view bounds") + val warnOptionImplicit = lintflag("option-implicit", + "Option.apply used implicit view.") + val warnDelayedInit = lintflag("delayedinit-select", + "Selecting member of DelayedInit") + val warnByNameRightAssociative = lintflag("by-name-right-associative", + "By-name parameter of right associative operator") + val warnPackageObjectClasses = lintflag("package-object-classes", + "Class or object defined in package object") + val warnUnsoundMatch = lintflag("unsound-match", + "Pattern match may not be typesafe") - // Individual warnings. - val warnAdaptedArgs = BooleanSetting ("-Ywarn-adapted-args", "Warn if an argument list is modified to match the receiver.") - val warnDeadCode = BooleanSetting ("-Ywarn-dead-code", "Warn when dead code is identified.") - val warnValueDiscard = BooleanSetting ("-Ywarn-value-discard", "Warn when non-Unit expression results are unused.") - val warnNumericWiden = BooleanSetting ("-Ywarn-numeric-widen", "Warn when numerics are widened.") - val warnNullaryUnit = BooleanSetting ("-Ywarn-nullary-unit", "Warn when nullary methods return Unit.") - val warnInaccessible = BooleanSetting ("-Ywarn-inaccessible", "Warn about inaccessible types in method signatures.") - val warnNullaryOverride = BooleanSetting ("-Ywarn-nullary-override", "Warn when non-nullary overrides nullary, e.g. `def foo()` over `def foo`.") - val warnInferAny = BooleanSetting ("-Ywarn-infer-any", "Warn when a type argument is inferred to be `Any`.") - val warnUnused = BooleanSetting ("-Ywarn-unused", "Warn when local and private vals, vars, defs, and types are are unused") - val warnUnusedImport = BooleanSetting ("-Ywarn-unused-import", "Warn when imports are unused") - - // Warning groups. - val lint = BooleanSetting("-Xlint", "Enable recommended additional warnings.") enablingIfNotSetByUser lintWarnings + // Experimental lint warnings that are turned off, but which could be turned on programmatically. + // These warnings are said to blind those who dare enable them. + // They are not activated by -Xlint and can't be enabled on the command line. + val warnValueOverrides = { + val flag = lintflag("value-overrides", "Generated value class method overrides an implementation") + flag.value = false + flag + } + + // The Xlint warning group. + private val xlint = new BooleanSetting("-Zunused", "True if -Xlint or -Xlint:_") + // On -Xlint or -Xlint:_, set xlint, otherwise set the lint warning unless already set true + val lint = { + val description = "Enable or disable specific warnings" + val choices = (lintWarnings map (_.name)).sorted + MultiChoiceSetting( + name = "-Xlint", + helpArg = "warning", + descr = description, + choices = choices, + default = Some(() => xlint.value = true) + ) { s => + def helpline(n: String) = lintWarnings.find(_.name == n).map(w => f" ${w.name}%-25s ${w.helpDescription}%n") + choices flatMap (helpline(_)) mkString (f"$description:%n", "", f"%n") + } withPostSetHook { x => + val Neg = "-" + def setPolitely(b: BooleanSetting, v: Boolean) = if (!b.isSetByUser || !b) b.value = v + def set(w: String, v: Boolean) = lintWarnings find (_.name == w) foreach (setPolitely(_, v)) + def propagate(ss: List[String]): Unit = ss match { + case w :: rest => if (w startsWith Neg) set(w stripPrefix Neg, false) else set(w, true) ; propagate(rest) + case Nil => () + } + propagate(x.value) + } + } + + // Lint warnings that are currently -Y, but deprecated in that usage + @deprecated("Use warnAdaptedArgs", since="2.11.2") + val YwarnAdaptedArgs = BooleanSetting("-Ywarn-adapted-args", + "Warn if an argument list is modified to match the receiver.") enabling List(warnAdaptedArgs) + //withDeprecationMessage "Enable -Xlint:adapted-args" + @deprecated("Use warnNullaryUnit", since="2.11.2") + val YwarnNullaryUnit = BooleanSetting("-Ywarn-nullary-unit", + "Warn when nullary methods return Unit.") enabling List(warnNullaryUnit) + //withDeprecationMessage "Enable -Xlint:nullary-unit" + @deprecated("Use warnInaccessible", since="2.11.2") + val YwarnInaccessible = BooleanSetting("-Ywarn-inaccessible", + "Warn about inaccessible types in method signatures.") enabling List(warnInaccessible) + //withDeprecationMessage "Enable -Xlint:inaccessible" + @deprecated("Use warnNullaryOverride", since="2.11.2") + val YwarnNullaryOverride = BooleanSetting("-Ywarn-nullary-override", + "Warn when non-nullary `def f()' overrides nullary `def f'.") enabling List(warnNullaryOverride) + //withDeprecationMessage "Enable -Xlint:nullary-override" + @deprecated("Use warnInferAny", since="2.11.2") + val YwarnInferAny = BooleanSetting("-Ywarn-infer-any", + "Warn when a type argument is inferred to be `Any`.") enabling List(warnInferAny) + //withDeprecationMessage "Enable -Xlint:infer-any" + + private lazy val warnSelectNullable = BooleanSetting("-Xcheck-null", "This option is obsolete and does nothing.") // Backward compatibility. @deprecated("Use fatalWarnings", "2.11.0") def Xwarnfatal = fatalWarnings // used by sbt diff --git a/src/compiler/scala/tools/nsc/symtab/SymbolLoaders.scala b/src/compiler/scala/tools/nsc/symtab/SymbolLoaders.scala index 447fa66ae4..82c2a4d6ed 100644 --- a/src/compiler/scala/tools/nsc/symtab/SymbolLoaders.scala +++ b/src/compiler/scala/tools/nsc/symtab/SymbolLoaders.scala @@ -240,6 +240,12 @@ abstract class SymbolLoaders { } } + private def phaseBeforeRefchecks: Phase = { + var resPhase = phase + while (resPhase.refChecked) resPhase = resPhase.prev + resPhase + } + /** * Load contents of a package */ @@ -248,19 +254,24 @@ abstract class SymbolLoaders { protected def doComplete(root: Symbol) { assert(root.isPackageClass, root) - root.setInfo(new PackageClassInfoType(newScope, root)) - - if (!root.isRoot) { - for (classRep <- classpath.classes) { - initializeFromClassPath(root, classRep) - } - } - if (!root.isEmptyPackageClass) { - for (pkg <- classpath.packages) { - enterPackage(root, pkg.name, new PackageLoader(pkg)) + // Time travel to a phase before refchecks avoids an initialization issue. `openPackageModule` + // creates a module symbol and invokes invokes `companionModule` while the `infos` field is + // still null. This calls `isModuleNotMethod`, which forces the `info` if run after refchecks. + enteringPhase(phaseBeforeRefchecks) { + root.setInfo(new PackageClassInfoType(newScope, root)) + + if (!root.isRoot) { + for (classRep <- classpath.classes) { + initializeFromClassPath(root, classRep) + } } + if (!root.isEmptyPackageClass) { + for (pkg <- classpath.packages) { + enterPackage(root, pkg.name, new PackageLoader(pkg)) + } - openPackageModule(root) + openPackageModule(root) + } } } } @@ -290,7 +301,13 @@ abstract class SymbolLoaders { protected def doComplete(root: Symbol) { val start = if (Statistics.canEnable) Statistics.startTimer(classReadNanos) else null - classfileParser.parse(classfile, root) + + // Running the classfile parser after refchecks can lead to "illegal class file dependency" + // errors. More concretely, the classfile parser calls "sym.companionModule", which calls + // "isModuleNotMethod" on the companion. After refchecks, this method forces the info, which + // may run the classfile parser. This produces the error. + enteringPhase(phaseBeforeRefchecks)(classfileParser.parse(classfile, root)) + if (root.associatedFile eq NoAbstractFile) { root match { // In fact, the ModuleSymbol forwards its setter to the module class diff --git a/src/compiler/scala/tools/nsc/transform/Flatten.scala b/src/compiler/scala/tools/nsc/transform/Flatten.scala index c3fbfae322..fa53ef48b5 100644 --- a/src/compiler/scala/tools/nsc/transform/Flatten.scala +++ b/src/compiler/scala/tools/nsc/transform/Flatten.scala @@ -76,8 +76,17 @@ abstract class Flatten extends InfoTransform { for (sym <- decls) { if (sym.isTerm && !sym.isStaticModule) { decls1 enter sym - if (sym.isModule) + if (sym.isModule) { + // Nested, non-static moduls are transformed to methods. + assert(sym.isMethod, s"Non-static $sym should have the lateMETHOD flag from RefChecks") + // Note that module classes are not entered into the 'decls' of the ClassInfoType + // of the outer class, only the module symbols are. So the current loop does + // not visit module classes. Therefore we set the LIFTED flag here for module + // classes. + // TODO: should we also set the LIFTED flag for static, nested module classes? + // currently they don't get the flag, even though they are lifted to the package sym.moduleClass setFlag LIFTED + } } else if (sym.isClass) liftSymbol(sym) } diff --git a/src/compiler/scala/tools/nsc/transform/LazyVals.scala b/src/compiler/scala/tools/nsc/transform/LazyVals.scala index b71d14a04f..38671ebaae 100644 --- a/src/compiler/scala/tools/nsc/transform/LazyVals.scala +++ b/src/compiler/scala/tools/nsc/transform/LazyVals.scala @@ -192,13 +192,15 @@ abstract class LazyVals extends Transform with TypingTransformers with ast.TreeD def mkSlowPathDef(clazz: Symbol, lzyVal: Symbol, cond: Tree, syncBody: List[Tree], stats: List[Tree], retVal: Tree): Tree = { + // Q: is there a reason to first set owner to `clazz` (by using clazz.newMethod), and then + // changing it to lzyVal.owner very soon after? Could we just do lzyVal.owner.newMethod? val defSym = clazz.newMethod(nme.newLazyValSlowComputeName(lzyVal.name.toTermName), lzyVal.pos, STABLE | PRIVATE) defSym setInfo MethodType(List(), lzyVal.tpe.resultType) defSym.owner = lzyVal.owner debuglog(s"crete slow compute path $defSym with owner ${defSym.owner} for lazy val $lzyVal") if (bitmaps.contains(lzyVal)) bitmaps(lzyVal).map(_.owner = defSym) - val rhs: Tree = (gen.mkSynchronizedCheck(clazz, cond, syncBody, stats)).changeOwner(currentOwner -> defSym) + val rhs: Tree = gen.mkSynchronizedCheck(clazz, cond, syncBody, stats).changeOwner(currentOwner -> defSym) DefDef(defSym, addBitmapDefs(lzyVal, BLOCK(rhs, retVal))) } diff --git a/src/compiler/scala/tools/nsc/transform/UnCurry.scala b/src/compiler/scala/tools/nsc/transform/UnCurry.scala index 2209aac00f..9b85f1b36a 100644 --- a/src/compiler/scala/tools/nsc/transform/UnCurry.scala +++ b/src/compiler/scala/tools/nsc/transform/UnCurry.scala @@ -69,7 +69,6 @@ abstract class UnCurry extends InfoTransform private val byNameArgs = mutable.HashSet[Tree]() private val noApply = mutable.HashSet[Tree]() private val newMembers = mutable.Map[Symbol, mutable.Buffer[Tree]]() - private val repeatedParams = mutable.Map[Symbol, List[ValDef]]() /** Add a new synthetic member for `currentOwner` */ private def addNewMember(t: Tree): Unit = @@ -428,7 +427,7 @@ abstract class UnCurry extends InfoTransform treeCopy.ValDef(p, p.mods, p.name, p.tpt, EmptyTree) }) - if (dd.symbol hasAnnotation VarargsClass) saveRepeatedParams(dd) + if (dd.symbol hasAnnotation VarargsClass) validateVarargs(dd) withNeedLift(needLift = false) { if (dd.symbol.isClassConstructor) { @@ -699,19 +698,12 @@ abstract class UnCurry extends InfoTransform } } - - /* Analyzes repeated params if method is annotated as `varargs`. - * If the repeated params exist, it saves them into the `repeatedParams` map, - * which is used later. - */ - private def saveRepeatedParams(dd: DefDef): Unit = + private def validateVarargs(dd: DefDef): Unit = if (dd.symbol.isConstructor) reporter.error(dd.symbol.pos, "A constructor cannot be annotated with a `varargs` annotation.") - else treeInfo.repeatedParams(dd) match { - case Nil => - reporter.error(dd.symbol.pos, "A method without repeated parameters cannot be annotated with the `varargs` annotation.") - case reps => - repeatedParams(dd.symbol) = reps + else { + val hasRepeated = mexists(dd.symbol.paramss)(sym => definitions.isRepeatedParamType(sym.tpe)) + if (!hasRepeated) reporter.error(dd.symbol.pos, "A method without repeated parameters cannot be annotated with the `varargs` annotation.") } /* Called during post transform, after the method argument lists have been flattened. @@ -719,7 +711,7 @@ abstract class UnCurry extends InfoTransform * varargs forwarder. */ private def addJavaVarargsForwarders(dd: DefDef, flatdd: DefDef): DefDef = { - if (!dd.symbol.hasAnnotation(VarargsClass) || !repeatedParams.contains(dd.symbol)) + if (!dd.symbol.hasAnnotation(VarargsClass) || !enteringUncurry(mexists(dd.symbol.paramss)(sym => definitions.isRepeatedParamType(sym.tpe)))) return flatdd def toArrayType(tp: Type): Type = { @@ -735,19 +727,18 @@ abstract class UnCurry extends InfoTransform ) } - val reps = repeatedParams(dd.symbol) - val rpsymbols = reps.map(_.symbol).toSet val theTyper = typer.atOwner(dd, currentClass) - val flatparams = flatdd.vparamss.head + val flatparams = flatdd.symbol.paramss.head + val isRepeated = enteringUncurry(dd.symbol.info.paramss.flatten.map(sym => definitions.isRepeatedParamType(sym.tpe))) // create the type - val forwformals = flatparams map { - case p if rpsymbols(p.symbol) => toArrayType(p.symbol.tpe) - case p => p.symbol.tpe + val forwformals = map2(flatparams, isRepeated) { + case (p, true) => toArrayType(p.tpe) + case (p, false)=> p.tpe } val forwresult = dd.symbol.tpe_*.finalResultType val forwformsyms = map2(forwformals, flatparams)((tp, oldparam) => - currentClass.newValueParameter(oldparam.name, oldparam.symbol.pos).setInfo(tp) + currentClass.newValueParameter(oldparam.name.toTermName, oldparam.pos).setInfo(tp) ) def mono = MethodType(forwformsyms, forwresult) val forwtype = dd.symbol.tpe match { @@ -761,13 +752,13 @@ abstract class UnCurry extends InfoTransform // create the tree val forwtree = theTyper.typedPos(dd.pos) { - val locals = map2(forwParams, flatparams) { - case (_, fp) if !rpsymbols(fp.symbol) => null - case (argsym, fp) => + val locals = map3(forwParams, flatparams, isRepeated) { + case (_, fp, false) => null + case (argsym, fp, true) => Block(Nil, gen.mkCast( gen.mkWrapArray(Ident(argsym), elementType(ArrayClass, argsym.tpe)), - seqType(elementType(SeqClass, fp.symbol.tpe)) + seqType(elementType(SeqClass, fp.tpe)) ) ) } diff --git a/src/compiler/scala/tools/nsc/transform/patmat/MatchAnalysis.scala b/src/compiler/scala/tools/nsc/transform/patmat/MatchAnalysis.scala index 6f81cbe152..b2dc6e4e52 100644 --- a/src/compiler/scala/tools/nsc/transform/patmat/MatchAnalysis.scala +++ b/src/compiler/scala/tools/nsc/transform/patmat/MatchAnalysis.scala @@ -50,7 +50,7 @@ trait TreeAndTypeAnalysis extends Debugging { // but the annotation didn't bubble up... // This is a pretty poor approximation. def unsoundAssumptionUsed = binder.name != nme.WILDCARD && !(pt <:< pat.tpe) - if (settings.lint && unsoundAssumptionUsed) + if (settings.warnUnsoundMatch && unsoundAssumptionUsed) reporter.warning(pat.pos, sm"""The value matched by $pat is bound to ${binder.name}, which may be used under the |unsound assumption that it has type ${pat.tpe}, whereas we can only safely diff --git a/src/compiler/scala/tools/nsc/typechecker/Adaptations.scala b/src/compiler/scala/tools/nsc/typechecker/Adaptations.scala index 37c39c07be..2f4d228347 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Adaptations.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Adaptations.scala @@ -82,7 +82,8 @@ trait Adaptations { } else if (settings.warnAdaptedArgs) context.warning(t.pos, adaptWarningMessage(s"Adapting argument list by creating a ${args.size}-tuple: this may not be what you want.")) - !settings.noAdaptedArgs || !(args.isEmpty && settings.future) + // return `true` if the adaptation should be kept + !(settings.noAdaptedArgs || (args.isEmpty && settings.future)) } } } diff --git a/src/compiler/scala/tools/nsc/typechecker/Namers.scala b/src/compiler/scala/tools/nsc/typechecker/Namers.scala index 1328119aac..7bbd81118a 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Namers.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Namers.scala @@ -717,7 +717,7 @@ trait Namers extends MethodSynthesis { m.updateAttachment(new ConstructorDefaultsAttachment(tree, null)) } val owner = tree.symbol.owner - if (settings.lint && owner.isPackageObjectClass && !mods.isImplicit) { + if (settings.warnPackageObjectClasses && owner.isPackageObjectClass && !mods.isImplicit) { reporter.warning(tree.pos, "it is not recommended to define classes/objects inside of package objects.\n" + "If possible, define " + tree.symbol + " in " + owner.skipPackageObject + " instead." diff --git a/src/compiler/scala/tools/nsc/typechecker/NamesDefaults.scala b/src/compiler/scala/tools/nsc/typechecker/NamesDefaults.scala index 43902d1c65..b6387fd56b 100644 --- a/src/compiler/scala/tools/nsc/typechecker/NamesDefaults.scala +++ b/src/compiler/scala/tools/nsc/typechecker/NamesDefaults.scala @@ -379,18 +379,37 @@ trait NamesDefaults { self: Analyzer => def makeNamedTypes(syms: List[Symbol]) = syms map (sym => NamedType(sym.name, sym.tpe)) - def missingParams[T](args: List[T], params: List[Symbol], argName: T => Option[Name] = nameOfNamedArg _): (List[Symbol], Boolean) = { - val namedArgs = args.dropWhile(arg => { - val n = argName(arg) - n.isEmpty || params.forall(p => p.name != n.get) - }) - val namedParams = params.drop(args.length - namedArgs.length) - // missing: keep those with a name which doesn't exist in namedArgs - val missingParams = namedParams.filter(p => namedArgs.forall(arg => { + /** + * Returns the parameter symbols of an invocation expression that are not defined by the list + * of arguments. + * + * @param args The list of arguments + * @param params The list of parameter sybols of the invoked method + * @param argName A function that extracts the name of an argument expression, if it is a named argument. + */ + def missingParams[T](args: List[T], params: List[Symbol], argName: T => Option[Name]): (List[Symbol], Boolean) = { + // The argument list contains first a mix of positional args and named args that are on the + // right parameter position, and then a number or named args on different positions. + + // collect all named arguments whose position does not match the parameter they define + val namedArgsOnChangedPosition = args.zip(params) dropWhile { + case (arg, param) => + val n = argName(arg) + // drop the argument if + // - it's not named, or + // - it's named, but defines the parameter on its current position, or + // - it's named, but none of the parameter names matches (treated as a positional argument, an assignment expression) + n.isEmpty || n.get == param.name || params.forall(_.name != n.get) + } map (_._1) + + val paramsWithoutPositionalArg = params.drop(args.length - namedArgsOnChangedPosition.length) + + // missing parameters: those with a name which is not specified in one of the namedArgsOnChangedPosition + val missingParams = paramsWithoutPositionalArg.filter(p => namedArgsOnChangedPosition.forall { arg => val n = argName(arg) n.isEmpty || n.get != p.name - })) - val allPositional = missingParams.length == namedParams.length + }) + val allPositional = missingParams.length == paramsWithoutPositionalArg.length (missingParams, allPositional) } @@ -407,7 +426,7 @@ trait NamesDefaults { self: Analyzer => previousArgss: List[List[Tree]], params: List[Symbol], pos: scala.reflect.internal.util.Position, context: Context): (List[Tree], List[Symbol]) = { if (givenArgs.length < params.length) { - val (missing, positional) = missingParams(givenArgs, params) + val (missing, positional) = missingParams(givenArgs, params, nameOfNamedArg) if (missing forall (_.hasDefault)) { val defaultArgs = missing flatMap (p => { val defGetter = defaultGetter(p, context) diff --git a/src/compiler/scala/tools/nsc/typechecker/RefChecks.scala b/src/compiler/scala/tools/nsc/typechecker/RefChecks.scala index 1b3da26bf2..47465875e9 100644 --- a/src/compiler/scala/tools/nsc/typechecker/RefChecks.scala +++ b/src/compiler/scala/tools/nsc/typechecker/RefChecks.scala @@ -167,7 +167,7 @@ abstract class RefChecks extends InfoTransform with scala.reflect.internal.trans } // This has become noisy with implicit classes. - if (settings.lint && settings.developer) { + if (settings.warnPolyImplicitOverload && settings.developer) { clazz.info.decls filter (x => x.isImplicit && x.typeParams.nonEmpty) foreach { sym => // implicit classes leave both a module symbol and a method symbol as residue val alts = clazz.info.decl(sym.name).alternatives filterNot (_.isModule) @@ -954,7 +954,7 @@ abstract class RefChecks extends InfoTransform with scala.reflect.internal.trans def apply(tp: Type) = mapOver(tp).normalize } - def checkImplicitViewOptionApply(pos: Position, fn: Tree, args: List[Tree]): Unit = if (settings.lint) (fn, args) match { + def checkImplicitViewOptionApply(pos: Position, fn: Tree, args: List[Tree]): Unit = if (settings.warnOptionImplicit) (fn, args) match { case (tap@TypeApply(fun, targs), List(view: ApplyImplicitView)) if fun.symbol == currentRun.runDefinitions.Option_apply => reporter.warning(pos, s"Suspicious application of an implicit view (${view.fun}) in the argument to Option.apply.") // SI-6567 case _ => @@ -1320,7 +1320,7 @@ abstract class RefChecks extends InfoTransform with scala.reflect.internal.trans && !qual.tpe.isInstanceOf[ThisType] && sym.accessedOrSelf.isVal ) - if (settings.lint.value && isLikelyUninitialized) + if (settings.warnDelayedInit && isLikelyUninitialized) reporter.warning(pos, s"Selecting ${sym} from ${sym.owner}, which extends scala.DelayedInit, is likely to yield an uninitialized value") } @@ -1387,7 +1387,7 @@ abstract class RefChecks extends InfoTransform with scala.reflect.internal.trans private def checkByNameRightAssociativeDef(tree: DefDef) { tree match { case DefDef(_, name, _, params :: _, _, _) => - if (settings.lint && !treeInfo.isLeftAssoc(name.decodedName) && params.exists(p => isByName(p.symbol))) + if (settings.warnByNameRightAssociative && !treeInfo.isLeftAssoc(name.decodedName) && params.exists(p => isByName(p.symbol))) reporter.warning(tree.pos, "by-name parameters will be evaluated eagerly when called as a right-associative infix operator. For more details, see SI-1980.") case _ => diff --git a/src/compiler/scala/tools/nsc/typechecker/SuperAccessors.scala b/src/compiler/scala/tools/nsc/typechecker/SuperAccessors.scala index d3a41b9570..38b00a015b 100644 --- a/src/compiler/scala/tools/nsc/typechecker/SuperAccessors.scala +++ b/src/compiler/scala/tools/nsc/typechecker/SuperAccessors.scala @@ -247,7 +247,7 @@ abstract class SuperAccessors extends transform.Transform with transform.TypingT // also exists in a superclass, because they may be surprised // to find out that a constructor parameter will shadow a // field. See SI-4762. - if (settings.lint) { + if (settings.warnPrivateShadow) { if (sym.isPrivateLocal && sym.paramss.isEmpty) { qual.symbol.ancestors foreach { parent => parent.info.decls filterNot (x => x.isPrivate || x.isLocalToThis) foreach { m2 => diff --git a/src/compiler/scala/tools/nsc/typechecker/SyntheticMethods.scala b/src/compiler/scala/tools/nsc/typechecker/SyntheticMethods.scala index 9516f94135..d0237fb468 100644 --- a/src/compiler/scala/tools/nsc/typechecker/SyntheticMethods.scala +++ b/src/compiler/scala/tools/nsc/typechecker/SyntheticMethods.scala @@ -339,12 +339,11 @@ trait SyntheticMethods extends ast.TreeDSL { !hasOverridingImplementation(m) || { clazz.isDerivedValueClass && (m == Any_hashCode || m == Any_equals) && { // Without a means to suppress this warning, I've thought better of it. - // - // if (settings.lint) { - // (clazz.info nonPrivateMember m.name) filter (m => (m.owner != AnyClass) && (m.owner != clazz) && !m.isDeferred) andAlso { m => - // currentUnit.warning(clazz.pos, s"Implementation of ${m.name} inherited from ${m.owner} overridden in $clazz to enforce value class semantics") - // } - // } + if (settings.warnValueOverrides) { + (clazz.info nonPrivateMember m.name) filter (m => (m.owner != AnyClass) && (m.owner != clazz) && !m.isDeferred) andAlso { m => + currentUnit.warning(clazz.pos, s"Implementation of ${m.name} inherited from ${m.owner} overridden in $clazz to enforce value class semantics") + } + } true } } diff --git a/src/compiler/scala/tools/nsc/typechecker/Typers.scala b/src/compiler/scala/tools/nsc/typechecker/Typers.scala index 2c31ef2e8e..70f44c4fc6 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Typers.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Typers.scala @@ -3248,25 +3248,25 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper * to that. This is the last thing which is tried (after * default arguments) */ - def tryTupleApply: Tree = ( + def tryTupleApply: Tree = { if (eligibleForTupleConversion(paramTypes, argslen) && !phase.erasedTypes) { val tupleArgs = List(atPos(tree.pos.makeTransparent)(gen.mkTuple(args))) // expected one argument, but got 0 or >1 ==> try applying to tuple // the inner "doTypedApply" does "extractUndetparams" => restore when it fails val savedUndetparams = context.undetparams silent(_.doTypedApply(tree, fun, tupleArgs, mode, pt)) map { t => - // Depending on user options, may warn or error here if - // a Unit or tuple was inserted. - val keepTree = ( - !mode.typingExprNotFun - || t.symbol == null - || checkValidAdaptation(t, args) - ) - if (keepTree) t else EmptyTree + // Depending on user options, may warn or error here if + // a Unit or tuple was inserted. + val keepTree = ( + !mode.typingExprNotFun // why? introduced in 4e488a60, doc welcome + || t.symbol == null // ditto + || checkValidAdaptation(t, args) + ) + if (keepTree) t else EmptyTree } orElse { _ => context.undetparams = savedUndetparams ; EmptyTree } } else EmptyTree - ) + } /* Treats an application which uses named or default arguments. * Also works if names + a vararg used: when names are used, the vararg @@ -5099,7 +5099,7 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper def isPlausible(m: Symbol) = m.alternatives exists (m => requiresNoArgs(m.info)) def maybeWarn(s: String): Unit = { - def warn(message: String) = context.warning(lit.pos, s"$message Did you forget the interpolator?") + def warn(message: String) = context.warning(lit.pos, s"possible missing interpolator: $message") def suspiciousSym(name: TermName) = context.lookupSymbol(name, _ => true).symbol def suspiciousExpr = InterpolatorCodeRegex findFirstIn s def suspiciousIdents = InterpolatorIdentRegex findAllIn s map (s => suspiciousSym(s drop 1)) @@ -5107,9 +5107,9 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper // heuristics - no warning on e.g. a string with only "$asInstanceOf" if (s contains ' ') ( if (suspiciousExpr.nonEmpty) - warn("That looks like an interpolated expression!") // "${...}" + warn("detected an interpolated expression") // "${...}" else - suspiciousIdents find isPlausible foreach (sym => warn(s"`$$${sym.name}` looks like an interpolated identifier!")) // "$id" + suspiciousIdents find isPlausible foreach (sym => warn(s"detected interpolated identifier `$$${sym.name}`")) // "$id" ) } lit match { @@ -5119,7 +5119,7 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper } def typedLiteral(tree: Literal) = { - if (settings.lint) warnMissingInterpolator(tree) + if (settings.warnMissingInterpolator) warnMissingInterpolator(tree) tree setType (if (tree.value.tag == UnitTag) UnitTpe else ConstantType(tree.value)) } diff --git a/src/compiler/scala/tools/reflect/FormatInterpolator.scala b/src/compiler/scala/tools/reflect/FormatInterpolator.scala index 57be1afdfe..b445f1e2bb 100644 --- a/src/compiler/scala/tools/reflect/FormatInterpolator.scala +++ b/src/compiler/scala/tools/reflect/FormatInterpolator.scala @@ -182,13 +182,23 @@ abstract class FormatInterpolator { case (part, n) => copyPart(part, n) } - //q"{..$evals; ${fstring.toString}.format(..$ids)}" - locally { + //q"{..$evals; new StringOps(${fstring.toString}).format(..$ids)}" + val format = fstring.toString + if (ids.isEmpty && !format.contains("%")) Literal(Constant(format)) + else { + val scalaPackage = Select(Ident(nme.ROOTPKG), TermName("scala")) + val newStringOps = Select( + New(Select(Select(Select(scalaPackage, + TermName("collection")), TermName("immutable")), TypeName("StringOps"))), + termNames.CONSTRUCTOR + ) val expr = Apply( Select( - Literal(Constant(fstring.toString)), - newTermName("format")), + Apply( + newStringOps, + List(Literal(Constant(format)))), + TermName("format")), ids.toList ) val p = c.macroApplication.pos diff --git a/src/compiler/scala/tools/reflect/quasiquotes/Parsers.scala b/src/compiler/scala/tools/reflect/quasiquotes/Parsers.scala index b68022afd9..392b7fc881 100644 --- a/src/compiler/scala/tools/reflect/quasiquotes/Parsers.scala +++ b/src/compiler/scala/tools/reflect/quasiquotes/Parsers.scala @@ -69,9 +69,16 @@ trait Parsers { self: Quasiquotes => override def makeTupleType(trees: List[Tree]): Tree = TupleTypePlaceholder(trees) // q"{ $x }" - override def makeBlock(stats: List[Tree]): Tree = stats match { - case (head @ Ident(name)) :: Nil if isHole(name) => Block(Nil, head) - case _ => super.makeBlock(stats) + override def makeBlock(stats: List[Tree]): Tree = method match { + case nme.apply => + stats match { + // we don't want to eagerly flatten trees with placeholders as they + // might have to be wrapped into a block depending on their value + case (head @ Ident(name)) :: Nil if isHole(name) => Block(Nil, head) + case _ => gen.mkBlock(stats, doFlatten = true) + } + case nme.unapply => gen.mkBlock(stats, doFlatten = false) + case other => global.abort("unreachable") } // tq"$a => $b" diff --git a/src/library/scala/StringContext.scala b/src/library/scala/StringContext.scala index c9e632c70e..ed3b305d59 100644 --- a/src/library/scala/StringContext.scala +++ b/src/library/scala/StringContext.scala @@ -104,7 +104,7 @@ case class StringContext(parts: String*) { * ''Note:'' Even when using the raw interpolator, Scala will preprocess unicode escapes. * For example: * {{{ - * scala> raw"\u005cu0025" + * scala> raw"\u005cu0023" * res0: String = # * }}} * @@ -112,8 +112,6 @@ case class StringContext(parts: String*) { * @throws An `IllegalArgumentException` * if the number of `parts` in the enclosing `StringContext` does not exceed * the number of arguments `arg` by exactly 1. - * @throws A `StringContext.InvalidEscapeException` if a `parts` string contains a backslash (`\`) character - * that does not start a valid escape sequence. */ def raw(args: Any*): String = standardInterpolator(identity, args) diff --git a/src/library/scala/collection/immutable/Range.scala b/src/library/scala/collection/immutable/Range.scala index 26ccd09803..720dfeed59 100644 --- a/src/library/scala/collection/immutable/Range.scala +++ b/src/library/scala/collection/immutable/Range.scala @@ -364,15 +364,16 @@ extends scala.collection.AbstractSeq[Int] override def equals(other: Any) = other match { case x: Range => // Note: this must succeed for overfull ranges (length > Int.MaxValue) - (x canEqual this) && ( - isEmpty || // all empty sequences are equal - (start == x.start && { // Otherwise, must have same start - val l0 = last - (l0 == x.last && ( // And same end - start == l0 || step == x.step // And either the same step, or not take any steps - )) - }) - ) + (x canEqual this) && { + if (isEmpty) x.isEmpty // empty sequences are equal + else // this is non-empty... + x.nonEmpty && start == x.start && { // ...so other must contain something and have same start + val l0 = last + (l0 == x.last && ( // And same end + start == l0 || step == x.step // And either the same step, or not take any steps + )) + } + } case _ => super.equals(other) } diff --git a/src/library/scala/concurrent/ExecutionContext.scala b/src/library/scala/concurrent/ExecutionContext.scala index 4674c9174b..11d3bb8b02 100644 --- a/src/library/scala/concurrent/ExecutionContext.scala +++ b/src/library/scala/concurrent/ExecutionContext.scala @@ -61,28 +61,44 @@ or import scala.concurrent.ExecutionContext.Implicits.global.""") trait ExecutionContext { /** Runs a block of code on this execution context. + * + * @param runnable the task to execute */ def execute(runnable: Runnable): Unit /** Reports that an asynchronous computation failed. + * + * @param cause the cause of the failure */ def reportFailure(@deprecatedName('t) cause: Throwable): Unit - /** Prepares for the execution of a task. Returns the prepared - * execution context. A valid implementation of `prepare` is one - * that simply returns `this`. + /** Prepares for the execution of a task. Returns the prepared execution context. + * + * `prepare` should be called at the site where an `ExecutionContext` is received (for + * example, through an implicit method parameter). The returned execution context may + * then be used to execute tasks. The role of `prepare` is to save any context relevant + * to an execution's ''call site'', so that this context may be restored at the + * ''execution site''. (These are often different: for example, execution may be + * suspended through a `Promise`'s future until the `Promise` is completed, which may + * be done in another thread, on another stack.) + * + * Note: a valid implementation of `prepare` is one that simply returns `this`. + * + * @return the prepared execution context */ def prepare(): ExecutionContext = this } /** - * An [[ExecutionContext]] that is also a Java [[Executor]]. + * An [[ExecutionContext]] that is also a + * Java [[http://docs.oracle.com/javase/8/docs/api/java/util/concurrent/Executor.html Executor]]. */ trait ExecutionContextExecutor extends ExecutionContext with Executor /** - * An [[ExecutionContext]] that is also a Java [[ExecutorService]]. + * An [[ExecutionContext]] that is also a + * Java [[http://docs.oracle.com/javase/8/docs/api/java/util/concurrent/ExecutorService.html ExecutorService]]. */ trait ExecutionContextExecutorService extends ExecutionContextExecutor with ExecutorService @@ -91,38 +107,70 @@ trait ExecutionContextExecutorService extends ExecutionContextExecutor with Exec */ object ExecutionContext { /** - * This is the explicit global ExecutionContext, - * call this when you want to provide the global ExecutionContext explicitly + * The explicit global `ExecutionContext`. Invoke `global` when you want to provide the global + * `ExecutionContext` explicitly. + * + * The default `ExecutionContext` implementation is backed by a port of + * [[http://gee.cs.oswego.edu/dl/jsr166/dist/jsr166-4jdk7docs/java/util/concurrent/ForkJoinPool.html java.util.concurrent.ForkJoinPool]]. + * + * @return the global `ExecutionContext` */ def global: ExecutionContextExecutor = Implicits.global object Implicits { /** - * This is the implicit global ExecutionContext, - * import this when you want to provide the global ExecutionContext implicitly + * The implicit global `ExecutionContext`. Import `global` when you want to provide the global + * `ExecutionContext` implicitly. + * + * The default `ExecutionContext` implementation is backed by a port of + * [[http://gee.cs.oswego.edu/dl/jsr166/dist/jsr166-4jdk7docs/java/util/concurrent/ForkJoinPool.html java.util.concurrent.ForkJoinPool]]. */ implicit lazy val global: ExecutionContextExecutor = impl.ExecutionContextImpl.fromExecutor(null: Executor) } /** Creates an `ExecutionContext` from the given `ExecutorService`. + * + * @param e the `ExecutorService` to use + * @param reporter a function for error reporting + * @return the `ExecutionContext` using the given `ExecutorService` */ def fromExecutorService(e: ExecutorService, reporter: Throwable => Unit): ExecutionContextExecutorService = impl.ExecutionContextImpl.fromExecutorService(e, reporter) - /** Creates an `ExecutionContext` from the given `ExecutorService` with the default Reporter. + /** Creates an `ExecutionContext` from the given `ExecutorService` with the [[scala.concurrent.ExecutionContext$.defaultReporter default reporter]]. + * + * If it is guaranteed that none of the executed tasks are blocking, a single-threaded `ExecutorService` + * can be used to create an `ExecutionContext` as follows: + * + * {{{ + * import java.util.concurrent.Executors + * val ec = ExecutionContext.fromExecutorService(Executors.newSingleThreadExecutor()) + * }}} + * + * @param e the `ExecutorService` to use + * @return the `ExecutionContext` using the given `ExecutorService` */ def fromExecutorService(e: ExecutorService): ExecutionContextExecutorService = fromExecutorService(e, defaultReporter) /** Creates an `ExecutionContext` from the given `Executor`. + * + * @param e the `Executor` to use + * @param reporter a function for error reporting + * @return the `ExecutionContext` using the given `Executor` */ def fromExecutor(e: Executor, reporter: Throwable => Unit): ExecutionContextExecutor = impl.ExecutionContextImpl.fromExecutor(e, reporter) - /** Creates an `ExecutionContext` from the given `Executor` with the default Reporter. + /** Creates an `ExecutionContext` from the given `Executor` with the [[scala.concurrent.ExecutionContext$.defaultReporter default reporter]]. + * + * @param e the `Executor` to use + * @return the `ExecutionContext` using the given `Executor` */ def fromExecutor(e: Executor): ExecutionContextExecutor = fromExecutor(e, defaultReporter) - /** The default reporter simply prints the stack trace of the `Throwable` to System.err. + /** The default reporter simply prints the stack trace of the `Throwable` to [[http://docs.oracle.com/javase/8/docs/api/java/lang/System.html#err System.err]]. + * + * @return the function for error reporting */ def defaultReporter: Throwable => Unit = _.printStackTrace() } diff --git a/src/reflect/scala/reflect/api/Exprs.scala b/src/reflect/scala/reflect/api/Exprs.scala index 5b6ff2325c..3230fdbc67 100644 --- a/src/reflect/scala/reflect/api/Exprs.scala +++ b/src/reflect/scala/reflect/api/Exprs.scala @@ -9,6 +9,7 @@ package api import scala.reflect.runtime.{universe => ru} import scala.annotation.compileTimeOnly +import java.io.ObjectStreamException /** * <span class="badge badge-red" style="float: right;">EXPERIMENTAL</span> @@ -157,23 +158,23 @@ trait Exprs { self: Universe => |if you want to get a value of the underlying expression, add scala-compiler.jar to the classpath, |import `scala.tools.reflect.Eval` and call `<your expr>.eval` instead.""".trim.stripMargin) + @throws(classOf[ObjectStreamException]) private def writeReplace(): AnyRef = new SerializedExpr(treec, implicitly[WeakTypeTag[T]].in(ru.rootMirror)) } } +@SerialVersionUID(1L) private[scala] class SerializedExpr(var treec: TreeCreator, var tag: ru.WeakTypeTag[_]) extends Serializable { - private def writeObject(out: java.io.ObjectOutputStream): Unit = { - out.writeObject(treec) - out.writeObject(tag) - } - - private def readObject(in: java.io.ObjectInputStream): Unit = { - treec = in.readObject().asInstanceOf[TreeCreator] - tag = in.readObject().asInstanceOf[ru.WeakTypeTag[_]] - } + import scala.reflect.runtime.universe.{Expr, runtimeMirror} + @throws(classOf[ObjectStreamException]) private def readResolve(): AnyRef = { - import ru._ - Expr(rootMirror, treec)(tag) + val loader: ClassLoader = try { + Thread.currentThread().getContextClassLoader() + } catch { + case se: SecurityException => null + } + val m = runtimeMirror(loader) + Expr(m, treec)(tag.in(m)) } } diff --git a/src/reflect/scala/reflect/api/Symbols.scala b/src/reflect/scala/reflect/api/Symbols.scala index dddd3c0e61..42cf600c85 100644 --- a/src/reflect/scala/reflect/api/Symbols.scala +++ b/src/reflect/scala/reflect/api/Symbols.scala @@ -260,6 +260,9 @@ trait Symbols { self: Universe => * with an object definition (module class in scala compiler parlance). * If yes, `isType` is also guaranteed to be true. * + * Note to compiler developers: During the "mixin" phase, trait implementation class symbols + * receive the `lateMODULE` flag, hence `isImplClass && isModuleClass` becomes true. + * * @group Tests */ def isModuleClass: Boolean = false diff --git a/src/reflect/scala/reflect/api/TreeCreator.scala b/src/reflect/scala/reflect/api/TreeCreator.scala index 027c840955..000eaa1aa6 100644 --- a/src/reflect/scala/reflect/api/TreeCreator.scala +++ b/src/reflect/scala/reflect/api/TreeCreator.scala @@ -2,12 +2,12 @@ package scala package reflect package api -/** This is an internal implementation class. +/** A mirror-aware factory for trees. * * This class is used internally by Scala Reflection, and is not recommended for use in client code. * - * @group ReflectionAPI + * @group ReflectionAPI */ -abstract class TreeCreator { +abstract class TreeCreator extends Serializable { def apply[U <: Universe with Singleton](m: scala.reflect.api.Mirror[U]): U # Tree } diff --git a/src/reflect/scala/reflect/api/TypeCreator.scala b/src/reflect/scala/reflect/api/TypeCreator.scala index 37fff90b43..cbd55b9428 100644 --- a/src/reflect/scala/reflect/api/TypeCreator.scala +++ b/src/reflect/scala/reflect/api/TypeCreator.scala @@ -8,6 +8,6 @@ package api * * @group ReflectionAPI */ -abstract class TypeCreator { +abstract class TypeCreator extends Serializable { def apply[U <: Universe with Singleton](m: scala.reflect.api.Mirror[U]): U # Type } diff --git a/src/reflect/scala/reflect/api/TypeTags.scala b/src/reflect/scala/reflect/api/TypeTags.scala index 1dfc84be69..7db375ca61 100644 --- a/src/reflect/scala/reflect/api/TypeTags.scala +++ b/src/reflect/scala/reflect/api/TypeTags.scala @@ -9,6 +9,7 @@ package api import java.lang.{ Class => jClass } import scala.language.implicitConversions +import java.io.ObjectStreamException /** * A `TypeTag[T]` encapsulates the runtime type representation of some type `T`. @@ -233,6 +234,7 @@ trait TypeTags { self: Universe => val otherMirror1 = otherMirror.asInstanceOf[scala.reflect.api.Mirror[otherMirror.universe.type]] otherMirror.universe.WeakTypeTag[T](otherMirror1, tpec) } + @throws(classOf[ObjectStreamException]) private def writeReplace(): AnyRef = new SerializedTypeTag(tpec, concrete = false) } @@ -293,10 +295,13 @@ trait TypeTags { self: Universe => val otherMirror1 = otherMirror.asInstanceOf[scala.reflect.api.Mirror[otherMirror.universe.type]] otherMirror.universe.TypeTag[T](otherMirror1, tpec) } + @throws(classOf[ObjectStreamException]) private def writeReplace(): AnyRef = new SerializedTypeTag(tpec, concrete = true) } /* @group TypeTags */ + // This class only exists to silence MIMA complaining about a binary incompatibility. + // Only the top-level class (api.PredefTypeCreator) should be used. private class PredefTypeCreator[T](copyIn: Universe => Universe#TypeTag[T]) extends TypeCreator { def apply[U <: Universe with Singleton](m: scala.reflect.api.Mirror[U]): U # Type = { copyIn(m.universe).asInstanceOf[U # TypeTag[T]].tpe @@ -304,8 +309,9 @@ trait TypeTags { self: Universe => } /* @group TypeTags */ - private class PredefTypeTag[T](_tpe: Type, copyIn: Universe => Universe#TypeTag[T]) extends TypeTagImpl[T](rootMirror, new PredefTypeCreator(copyIn)) { + private class PredefTypeTag[T](_tpe: Type, copyIn: Universe => Universe#TypeTag[T]) extends TypeTagImpl[T](rootMirror, new api.PredefTypeCreator(copyIn)) { override lazy val tpe: Type = _tpe + @throws(classOf[ObjectStreamException]) private def writeReplace(): AnyRef = new SerializedTypeTag(tpec, concrete = true) } @@ -341,20 +347,27 @@ trait TypeTags { self: Universe => def symbolOf[T: WeakTypeTag]: TypeSymbol } +// This class should be final, but we can't do that in Scala 2.11.x without breaking +// binary incompatibility. +@SerialVersionUID(1L) private[scala] class SerializedTypeTag(var tpec: TypeCreator, var concrete: Boolean) extends Serializable { - private def writeObject(out: java.io.ObjectOutputStream): Unit = { - out.writeObject(tpec) - out.writeBoolean(concrete) - } - - private def readObject(in: java.io.ObjectInputStream): Unit = { - tpec = in.readObject().asInstanceOf[TypeCreator] - concrete = in.readBoolean() + import scala.reflect.runtime.universe.{TypeTag, WeakTypeTag, runtimeMirror} + @throws(classOf[ObjectStreamException]) + private def readResolve(): AnyRef = { + val loader: ClassLoader = try { + Thread.currentThread().getContextClassLoader() + } catch { + case se: SecurityException => null + } + val m = runtimeMirror(loader) + if (concrete) TypeTag(m, tpec) + else WeakTypeTag(m, tpec) } +} - private def readResolve(): AnyRef = { - import scala.reflect.runtime.universe._ - if (concrete) TypeTag(rootMirror, tpec) - else WeakTypeTag(rootMirror, tpec) +/* @group TypeTags */ +private class PredefTypeCreator[T](copyIn: Universe => Universe#TypeTag[T]) extends TypeCreator { + def apply[U <: Universe with Singleton](m: scala.reflect.api.Mirror[U]): U # Type = { + copyIn(m.universe).asInstanceOf[U # TypeTag[T]].tpe } } diff --git a/src/reflect/scala/reflect/internal/Internals.scala b/src/reflect/scala/reflect/internal/Internals.scala index e9916cf7d1..26f3bfd9d0 100644 --- a/src/reflect/scala/reflect/internal/Internals.scala +++ b/src/reflect/scala/reflect/internal/Internals.scala @@ -129,7 +129,7 @@ trait Internals extends api.Internals { def typeBounds(lo: Type, hi: Type): TypeBounds = self.TypeBounds(lo, hi) def boundedWildcardType(bounds: TypeBounds): BoundedWildcardType = self.BoundedWildcardType(bounds) - def subpatterns(tree: Tree): Option[List[Tree]] = tree.attachments.get[SubpatternsAttachment].map(_.patterns.map(_.duplicate)) + def subpatterns(tree: Tree): Option[List[Tree]] = tree.attachments.get[SubpatternsAttachment].map(_.patterns.map(duplicateAndKeepPositions)) type Decorators = MacroDecoratorApi lazy val decorators: Decorators = new MacroDecoratorApi { diff --git a/src/reflect/scala/reflect/internal/Symbols.scala b/src/reflect/scala/reflect/internal/Symbols.scala index 2ce54d2259..b6cce4524b 100644 --- a/src/reflect/scala/reflect/internal/Symbols.scala +++ b/src/reflect/scala/reflect/internal/Symbols.scala @@ -55,23 +55,29 @@ trait Symbols extends api.Symbols { self: SymbolTable => def newFreeTypeSymbol(name: TypeName, flags: Long = 0L, origin: String): FreeTypeSymbol = new FreeTypeSymbol(name, origin) initFlags flags - /** The original owner of a class. Used by the backend to generate - * EnclosingMethod attributes. + /** + * This map stores the original owner the the first time the owner of a symbol is re-assigned. + * The original owner of a symbol is needed in some places in the backend. Ideally, owners should + * be versioned like the type history. */ - val originalOwner = perRunCaches.newMap[Symbol, Symbol]() + private val originalOwnerMap = perRunCaches.newMap[Symbol, Symbol]() // TODO - don't allow the owner to be changed without checking invariants, at least // when under some flag. Define per-phase invariants for owner/owned relationships, // e.g. after flatten all classes are owned by package classes, there are lots and // lots of these to be declared (or more realistically, discovered.) - protected def saveOriginalOwner(sym: Symbol) { - if (originalOwner contains sym) () - else originalOwner(sym) = sym.rawowner + protected def saveOriginalOwner(sym: Symbol): Unit = { + // some synthetic symbols have NoSymbol as owner initially + if (sym.owner != NoSymbol) { + if (originalOwnerMap contains sym) () + else originalOwnerMap(sym) = sym.rawowner + } } + protected def originalEnclosingMethod(sym: Symbol): Symbol = { if (sym.isMethod || sym == NoSymbol) sym else { - val owner = originalOwner.getOrElse(sym, sym.rawowner) + val owner = sym.originalOwner if (sym.isLocalDummy) owner.enclClass.primaryConstructor else originalEnclosingMethod(owner) } @@ -757,8 +763,22 @@ trait Symbols extends api.Symbols { self: SymbolTable => * So "isModuleNotMethod" exists not for its achievement in * brevity, but to encapsulate the relevant condition. */ - def isModuleNotMethod = isModule && !isMethod - def isStaticModule = isModuleNotMethod && isStatic + def isModuleNotMethod = { + if (isModule) { + if (phase.refChecked) this.info // force completion to make sure lateMETHOD is there. + !isMethod + } else false + } + + // After RefChecks, the `isStatic` check is mostly redundant: all non-static modules should + // be methods (and vice versa). There's a corner case on the vice-versa with mixed-in module + // symbols: + // trait T { object A } + // object O extends T + // The module symbol A is cloned into T$impl (addInterfaces), and then cloned into O (mixin). + // Since the original A is not static, it's turned into a method. The clone in O however is + // static (owned by a module), but it's also a method. + def isStaticModule = isModuleNotMethod && isStatic final def isInitializedToDefault = !isType && hasAllFlags(DEFAULTINIT | ACCESSOR) final def isThisSym = isTerm && owner.thisSym == this @@ -909,10 +929,31 @@ trait Symbols extends api.Symbols { self: SymbolTable => ) final def isModuleVar = hasFlag(MODULEVAR) - /** Is this symbol static (i.e. with no outer instance)? - * Q: When exactly is a sym marked as STATIC? - * A: If it's a member of a toplevel object, or of an object contained in a toplevel object, or any number of levels deep. - * http://groups.google.com/group/scala-internals/browse_thread/thread/d385bcd60b08faf6 + /** + * Is this symbol static (i.e. with no outer instance)? + * Q: When exactly is a sym marked as STATIC? + * A: If it's a member of a toplevel object, or of an object contained in a toplevel object, or + * any number of levels deep. + * http://groups.google.com/group/scala-internals/browse_thread/thread/d385bcd60b08faf6 + * + * TODO: should this only be invoked on class / module symbols? because there's also `isStaticMember`. + * + * Note: the result of `isStatic` changes over time. + * - Lambdalift local definitions to the class level, the `owner` field is modified. + * object T { def foo { object O } } + * After lambdalift, the OModule.isStatic is true. + * + * - After flatten, nested classes are moved to the package level. Invoking `owner` on a + * class returns a package class, for which `isStaticOwner` is true. For example, + * class C { object O } + * OModuleClass.isStatic is true after flatten. Using phase travel to get before flatten, + * method `owner` returns the class C. + * + * Why not make a stable version of `isStatic`? Maybe some parts of the compiler depend on the + * current implementation. For example + * trait T { def foo = 1 } + * The method `foo` in the implementation class T$impl will be `isStatic`, because trait + * impl classes get the `lateMODULE` flag (T$impl.isStaticOwner is true). */ def isStatic = (this hasFlag STATIC) || owner.isStaticOwner @@ -1099,13 +1140,28 @@ trait Symbols extends api.Symbols { self: SymbolTable => // ------ owner attribute -------------------------------------------------------------- - /** In general when seeking the owner of a symbol, one should call `owner`. - * The other possibilities include: - * - call `safeOwner` if it is expected that the target may be NoSymbol - * - call `assertOwner` if it is an unrecoverable error if the target is NoSymbol + /** + * The owner of a symbol. Changes over time to adapt to the structure of the trees: + * - Up to lambdalift, the owner is the lexically enclosing definition. For definitions + * in a local block, the owner is also the next enclosing definition. + * - After lambdalift, all local method and class definitions (those not owned by a class + * or package class) change their owner to the enclosing class. This is done through + * a destructive "sym.owner = sym.owner.enclClass". The old owner is saved by + * saveOriginalOwner. + * - After flatten, all classes are owned by a PackageClass. This is done through a + * phase check (if after flatten) in the (overridden) method "def owner" in + * ModuleSymbol / ClassSymbol. The `rawowner` field is not modified. + * - Owners are also changed in other situations, for example when moving trees into a new + * lexical context, e.g. in the named/default arguments tranformation, or when translating + * extension method definitions. + * + * In general when seeking the owner of a symbol, one should call `owner`. + * The other possibilities include: + * - call `safeOwner` if it is expected that the target may be NoSymbol + * - call `assertOwner` if it is an unrecoverable error if the target is NoSymbol * - * `owner` behaves like `safeOwner`, but logs NoSymbol.owner calls under -Xdev. - * `assertOwner` aborts compilation immediately if called on NoSymbol. + * `owner` behaves like `safeOwner`, but logs NoSymbol.owner calls under -Xdev. + * `assertOwner` aborts compilation immediately if called on NoSymbol. */ def owner: Symbol = { if (Statistics.hotEnabled) Statistics.incCounter(ownerCount) @@ -1114,6 +1170,11 @@ trait Symbols extends api.Symbols { self: SymbolTable => final def safeOwner: Symbol = if (this eq NoSymbol) NoSymbol else owner final def assertOwner: Symbol = if (this eq NoSymbol) abort("no-symbol does not have an owner") else owner + /** + * The initial owner of this symbol. + */ + def originalOwner: Symbol = originalOwnerMap.getOrElse(this, rawowner) + // TODO - don't allow the owner to be changed without checking invariants, at least // when under some flag. Define per-phase invariants for owner/owned relationships, // e.g. after flatten all classes are owned by package classes, there are lots and @@ -1127,7 +1188,6 @@ trait Symbols extends api.Symbols { self: SymbolTable => } def ownerChain: List[Symbol] = this :: owner.ownerChain - def originalOwnerChain: List[Symbol] = this :: originalOwner.getOrElse(this, rawowner).originalOwnerChain // Non-classes skip self and return rest of owner chain; overridden in ClassSymbol. def enclClassChain: List[Symbol] = owner.enclClassChain @@ -2811,6 +2871,7 @@ trait Symbols extends api.Symbols { self: SymbolTable => override def owner = { if (Statistics.hotEnabled) Statistics.incCounter(ownerCount) + // a module symbol may have the lateMETHOD flag after refchecks, see isModuleNotMethod if (!isMethod && needsFlatClasses) rawowner.owner else rawowner } diff --git a/src/reflect/scala/reflect/internal/TreeGen.scala b/src/reflect/scala/reflect/internal/TreeGen.scala index 9066c73393..6e8e992d16 100644 --- a/src/reflect/scala/reflect/internal/TreeGen.scala +++ b/src/reflect/scala/reflect/internal/TreeGen.scala @@ -451,10 +451,10 @@ abstract class TreeGen { def mkSyntheticUnit() = Literal(Constant(())).updateAttachment(SyntheticUnitAttachment) /** Create block of statements `stats` */ - def mkBlock(stats: List[Tree]): Tree = + def mkBlock(stats: List[Tree], doFlatten: Boolean = true): Tree = if (stats.isEmpty) mkSyntheticUnit() else if (!stats.last.isTerm) Block(stats, mkSyntheticUnit()) - else if (stats.length == 1) stats.head + else if (stats.length == 1 && doFlatten) stats.head else Block(stats.init, stats.last) /** Create a block that wraps multiple statements but don't diff --git a/src/reflect/scala/reflect/internal/TreeInfo.scala b/src/reflect/scala/reflect/internal/TreeInfo.scala index b7d7d4df88..c521277f69 100644 --- a/src/reflect/scala/reflect/internal/TreeInfo.scala +++ b/src/reflect/scala/reflect/internal/TreeInfo.scala @@ -509,13 +509,6 @@ abstract class TreeInfo { case _ => false } - /** The parameter ValDefs of a method definition that have vararg types of the form T* - */ - def repeatedParams(tree: Tree): List[ValDef] = tree match { - case DefDef(_, _, _, vparamss, _, _) => vparamss.flatten filter (vd => isRepeatedParamType(vd.tpt)) - case _ => Nil - } - /** Is tpt a by-name parameter type of the form => T? */ def isByNameParamType(tpt: Tree) = tpt match { case TypeTree() => definitions.isByNameParamType(tpt.tpe) diff --git a/src/reflect/scala/reflect/macros/Attachments.scala b/src/reflect/scala/reflect/macros/Attachments.scala index 5ccdc15a03..b5c340645a 100644 --- a/src/reflect/scala/reflect/macros/Attachments.scala +++ b/src/reflect/scala/reflect/macros/Attachments.scala @@ -35,7 +35,7 @@ abstract class Attachments { self => def all: Set[Any] = Set.empty private def matchesTag[T: ClassTag](datum: Any) = - classTag[T].runtimeClass == datum.getClass + classTag[T].runtimeClass.isInstance(datum) /** An underlying payload of the given class type `T`. */ def get[T: ClassTag]: Option[T] = diff --git a/src/reflect/scala/reflect/runtime/SymbolLoaders.scala b/src/reflect/scala/reflect/runtime/SymbolLoaders.scala index 7ba68b8733..50ea8d9868 100644 --- a/src/reflect/scala/reflect/runtime/SymbolLoaders.scala +++ b/src/reflect/scala/reflect/runtime/SymbolLoaders.scala @@ -65,10 +65,15 @@ private[reflect] trait SymbolLoaders { self: SymbolTable => class LazyPackageType extends LazyType with FlagAgnosticCompleter { override def complete(sym: Symbol) { assert(sym.isPackageClass) - sym setInfo new ClassInfoType(List(), new PackageScope(sym), sym) + // Time travel to a phase before refchecks avoids an initialization issue. `openPackageModule` + // creates a module symbol and invokes invokes `companionModule` while the `infos` field is + // still null. This calls `isModuleNotMethod`, which forces the `info` if run after refchecks. + slowButSafeEnteringPhaseNotLaterThan(picklerPhase) { + sym setInfo new ClassInfoType(List(), new PackageScope(sym), sym) // override def safeToString = pkgClass.toString - openPackageModule(sym) - markAllCompleted(sym) + openPackageModule(sym) + markAllCompleted(sym) + } } } diff --git a/src/scaladoc/scala/tools/nsc/doc/ScaladocAnalyzer.scala b/src/scaladoc/scala/tools/nsc/doc/ScaladocAnalyzer.scala index 10c382e169..ccf18b76de 100644 --- a/src/scaladoc/scala/tools/nsc/doc/ScaladocAnalyzer.scala +++ b/src/scaladoc/scala/tools/nsc/doc/ScaladocAnalyzer.scala @@ -190,7 +190,7 @@ abstract class ScaladocSyntaxAnalyzer[G <: Global](val global: G) extends Syntax typeParams.nonEmpty || version.nonEmpty || since.nonEmpty } def isDirty = unclean(unmooredParser parseComment doc) - if ((doc ne null) && (settings.lint || isDirty)) + if ((doc ne null) && (settings.warnDocDetached || isDirty)) reporter.warning(doc.pos, "discarding unmoored doc comment") } diff --git a/src/scaladoc/scala/tools/nsc/doc/Settings.scala b/src/scaladoc/scala/tools/nsc/doc/Settings.scala index 02929c5ecb..44683f1755 100644 --- a/src/scaladoc/scala/tools/nsc/doc/Settings.scala +++ b/src/scaladoc/scala/tools/nsc/doc/Settings.scala @@ -249,7 +249,7 @@ class Settings(error: String => Unit, val printMsg: String => Unit = println(_)) val idx = s.indexOf("#") if (idx > 0) { val (first, last) = s.splitAt(idx) - Some(new File(first).getAbsolutePath -> appendIndex(last.substring(1))) + Some(new File(first).getCanonicalPath -> appendIndex(last.substring(1))) } else { error(s"Illegal -doc-external-doc option; expected a pair with '#' separator, found: '$s'") None diff --git a/src/scaladoc/scala/tools/nsc/doc/model/MemberLookup.scala b/src/scaladoc/scala/tools/nsc/doc/model/MemberLookup.scala index 339129bdbc..64eb1adbea 100644 --- a/src/scaladoc/scala/tools/nsc/doc/model/MemberLookup.scala +++ b/src/scaladoc/scala/tools/nsc/doc/model/MemberLookup.scala @@ -45,7 +45,7 @@ trait MemberLookup extends base.MemberLookupBase { sym.info.member(newTermName("package")) else sym Option(sym1.associatedFile) flatMap (_.underlyingSource) flatMap { src => - val path = src.path + val path = src.canonicalPath settings.extUrlMapping get path map { url => LinkToExternal(name, url + "#" + name) } |