diff options
Diffstat (limited to 'src')
28 files changed, 403 insertions, 320 deletions
diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BCodeAsmCommon.scala b/src/compiler/scala/tools/nsc/backend/jvm/BCodeAsmCommon.scala index 0c0d726630..54d4a30553 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/BCodeAsmCommon.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/BCodeAsmCommon.scala @@ -14,6 +14,13 @@ import PartialFunction._ */ final class BCodeAsmCommon[G <: Global](val global: G) { import global._ + import definitions._ + + val ExcludedForwarderFlags = { + import scala.tools.nsc.symtab.Flags._ + // Should include DEFERRED but this breaks findMember. + SPECIALIZED | LIFTED | PROTECTED | STATIC | EXPANDEDNAME | BridgeAndPrivateFlags | MACRO + } /** * True if `classSym` is an anonymous class or a local class. I.e., false if `classSym` is a @@ -23,10 +30,10 @@ final class BCodeAsmCommon[G <: Global](val global: G) { */ def isAnonymousOrLocalClass(classSym: Symbol): Boolean = { assert(classSym.isClass, s"not a class: $classSym") - val res = (classSym.isAnonymousClass || !classSym.originalOwner.isClass) - // lambda classes are always top-level classes. - if (res) assert(!classSym.isDelambdafyFunction) - res + // Here used to be an `assert(!classSym.isDelambdafyFunction)`: delambdafy lambda classes are + // always top-level. However, SI-8900 shows an example where the weak name-based implementation + // of isDelambdafyFunction failed (for a function declared in a package named "lambda"). + classSym.isAnonymousClass || !classSym.originalOwner.isClass } /** @@ -124,4 +131,36 @@ final class BCodeAsmCommon[G <: Global](val global: G) { assert(r != NoSymbol, sym.fullLocationString) r })(collection.breakOut) + + lazy val AnnotationRetentionPolicyModule = AnnotationRetentionPolicyAttr.companionModule + lazy val AnnotationRetentionPolicySourceValue = AnnotationRetentionPolicyModule.tpe.member(TermName("SOURCE")) + lazy val AnnotationRetentionPolicyClassValue = AnnotationRetentionPolicyModule.tpe.member(TermName("CLASS")) + lazy val AnnotationRetentionPolicyRuntimeValue = AnnotationRetentionPolicyModule.tpe.member(TermName("RUNTIME")) + + /** Whether an annotation should be emitted as a Java annotation + * .initialize: if 'annot' is read from pickle, atp might be un-initialized + */ + def shouldEmitAnnotation(annot: AnnotationInfo) = { + annot.symbol.initialize.isJavaDefined && + annot.matches(ClassfileAnnotationClass) && + retentionPolicyOf(annot) != AnnotationRetentionPolicySourceValue && + annot.args.isEmpty + } + + def isRuntimeVisible(annot: AnnotationInfo): Boolean = { + annot.atp.typeSymbol.getAnnotation(AnnotationRetentionAttr) match { + case Some(retentionAnnot) => + retentionAnnot.assocs.contains(nme.value -> LiteralAnnotArg(Constant(AnnotationRetentionPolicyRuntimeValue))) + case _ => + // SI-8926: if the annotation class symbol doesn't have a @RetentionPolicy annotation, the + // annotation is emitted with visibility `RUNTIME` + true + } + } + + private def retentionPolicyOf(annot: AnnotationInfo): Symbol = + annot.atp.typeSymbol.getAnnotation(AnnotationRetentionAttr).map(_.assocs).map(assoc => + assoc.collectFirst { + case (`nme`.value, LiteralAnnotArg(Constant(value: Symbol))) => value + }).flatten.getOrElse(AnnotationRetentionPolicyClassValue) } diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BCodeHelpers.scala b/src/compiler/scala/tools/nsc/backend/jvm/BCodeHelpers.scala index 14bffd67ab..806d4b277c 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/BCodeHelpers.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/BCodeHelpers.scala @@ -469,6 +469,7 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters { trait BCAnnotGen extends BCInnerClassGen { import genASM.{ubytesToCharArray, arrEncode} + import bCodeAsmCommon.{shouldEmitAnnotation, isRuntimeVisible} /* * can-multi-thread @@ -533,17 +534,6 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters { } } - /* Whether an annotation should be emitted as a Java annotation - * .initialize: if 'annot' is read from pickle, atp might be un-initialized - * - * must-single-thread - */ - private def shouldEmitAnnotation(annot: AnnotationInfo) = - annot.symbol.initialize.isJavaDefined && - annot.matches(definitions.ClassfileAnnotationClass) && - annot.args.isEmpty && - !annot.matches(definitions.DeprecatedAttr) - /* * In general, * must-single-thread @@ -563,7 +553,7 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters { for(annot <- annotations; if shouldEmitAnnotation(annot)) { val AnnotationInfo(typ, args, assocs) = annot assert(args.isEmpty, args) - val av = cw.visitAnnotation(descriptor(typ), true) + val av = cw.visitAnnotation(descriptor(typ), isRuntimeVisible(annot)) emitAssocs(av, assocs) } } @@ -575,7 +565,7 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters { for(annot <- annotations; if shouldEmitAnnotation(annot)) { val AnnotationInfo(typ, args, assocs) = annot assert(args.isEmpty, args) - val av = mw.visitAnnotation(descriptor(typ), true) + val av = mw.visitAnnotation(descriptor(typ), isRuntimeVisible(annot)) emitAssocs(av, assocs) } } @@ -587,7 +577,7 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters { for(annot <- annotations; if shouldEmitAnnotation(annot)) { val AnnotationInfo(typ, args, assocs) = annot assert(args.isEmpty, args) - val av = fw.visitAnnotation(descriptor(typ), true) + val av = fw.visitAnnotation(descriptor(typ), isRuntimeVisible(annot)) emitAssocs(av, assocs) } } @@ -602,7 +592,7 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters { annot <- annots) { val AnnotationInfo(typ, args, assocs) = annot assert(args.isEmpty, args) - val pannVisitor: asm.AnnotationVisitor = jmethod.visitParameterAnnotation(idx, descriptor(typ), true) + val pannVisitor: asm.AnnotationVisitor = jmethod.visitParameterAnnotation(idx, descriptor(typ), isRuntimeVisible(annot)) emitAssocs(pannVisitor, assocs) } } @@ -625,13 +615,6 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters { trait BCForwardersGen extends BCAnnotGen with BCJGenSigGen { - // ----------------------------------------------------------------------------------------- - // Static forwarders (related to mirror classes but also present in - // a plain class lacking companion module, for details see `isCandidateForForwarders`). - // ----------------------------------------------------------------------------------------- - - val ExcludedForwarderFlags = genASM.ExcludedForwarderFlags - /* Adds a @remote annotation, actual use unknown. * * Invoked from genMethod() and addForwarder(). @@ -727,7 +710,7 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters { } debuglog(s"Potentially conflicting names for forwarders: $conflictingNames") - for (m <- moduleClass.info.membersBasedOnFlags(ExcludedForwarderFlags, symtab.Flags.METHOD)) { + for (m <- moduleClass.info.membersBasedOnFlags(bCodeAsmCommon.ExcludedForwarderFlags, symtab.Flags.METHOD)) { if (m.isType || m.isDeferred || (m.owner eq definitions.ObjectClass) || m.isConstructor) debuglog(s"No forwarder for '$m' from $jclassName to '$moduleClass'") else if (conflictingNames(m.name)) diff --git a/src/compiler/scala/tools/nsc/backend/jvm/GenASM.scala b/src/compiler/scala/tools/nsc/backend/jvm/GenASM.scala index 7c4c02c2d3..d0a12c32e5 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/GenASM.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/GenASM.scala @@ -20,7 +20,7 @@ import scala.annotation.tailrec * * Documentation at http://lamp.epfl.ch/~magarcia/ScalaCompilerCornerReloaded/2012Q2/GenASM.pdf */ -abstract class GenASM extends SubComponent with BytecodeWriters with GenJVMASM { self => +abstract class GenASM extends SubComponent with BytecodeWriters { self => import global._ import icodes._ import icodes.opcodes._ @@ -99,6 +99,63 @@ abstract class GenASM extends SubComponent with BytecodeWriters with GenJVMASM { } } + private def isJavaEntryPoint(icls: IClass) = { + val sym = icls.symbol + def fail(msg: String, pos: Position = sym.pos) = { + reporter.warning(sym.pos, + sym.name + " has a main method with parameter type Array[String], but " + sym.fullName('.') + " will not be a runnable program.\n" + + " Reason: " + msg + // TODO: make this next claim true, if possible + // by generating valid main methods as static in module classes + // not sure what the jvm allows here + // + " You can still run the program by calling it as " + sym.javaSimpleName + " instead." + ) + false + } + def failNoForwarder(msg: String) = { + fail(msg + ", which means no static forwarder can be generated.\n") + } + val possibles = if (sym.hasModuleFlag) (sym.tpe nonPrivateMember nme.main).alternatives else Nil + val hasApproximate = possibles exists { m => + m.info match { + case MethodType(p :: Nil, _) => p.tpe.typeSymbol == ArrayClass + case _ => false + } + } + // At this point it's a module with a main-looking method, so either succeed or warn that it isn't. + hasApproximate && { + // Before erasure so we can identify generic mains. + enteringErasure { + val companion = sym.linkedClassOfClass + + if (hasJavaMainMethod(companion)) + failNoForwarder("companion contains its own main method") + else if (companion.tpe.member(nme.main) != NoSymbol) + // this is only because forwarders aren't smart enough yet + failNoForwarder("companion contains its own main method (implementation restriction: no main is allowed, regardless of signature)") + else if (companion.isTrait) + failNoForwarder("companion is a trait") + // Now either succeeed, or issue some additional warnings for things which look like + // attempts to be java main methods. + else (possibles exists isJavaMainMethod) || { + possibles exists { m => + m.info match { + case PolyType(_, _) => + fail("main methods cannot be generic.") + case MethodType(params, res) => + if (res.typeSymbol :: params exists (_.isAbstractType)) + fail("main methods cannot refer to type parameters or abstract types.", m.pos) + else + isJavaMainMethod(m) || fail("main method must have exact signature (Array[String])Unit", m.pos) + case tp => + fail("don't know what this is: " + tp, m.pos) + } + } + } + } + } + } + override def run() { if (settings.debug) @@ -807,15 +864,6 @@ abstract class GenASM extends SubComponent with BytecodeWriters with GenJVMASM { for (ThrownException(exc) <- excs.distinct) yield javaName(exc) - /** Whether an annotation should be emitted as a Java annotation - * .initialize: if 'annot' is read from pickle, atp might be un-initialized - */ - private def shouldEmitAnnotation(annot: AnnotationInfo) = - annot.symbol.initialize.isJavaDefined && - annot.matches(ClassfileAnnotationClass) && - annot.args.isEmpty && - !annot.matches(DeprecatedAttr) - def getCurrentCUnit(): CompilationUnit def getGenericSignature(sym: Symbol, owner: Symbol) = self.getGenericSignature(sym, owner, getCurrentCUnit()) @@ -877,7 +925,7 @@ abstract class GenASM extends SubComponent with BytecodeWriters with GenJVMASM { for(annot <- annotations; if shouldEmitAnnotation(annot)) { val AnnotationInfo(typ, args, assocs) = annot assert(args.isEmpty, args) - val av = cw.visitAnnotation(descriptor(typ), true) + val av = cw.visitAnnotation(descriptor(typ), isRuntimeVisible(annot)) emitAssocs(av, assocs) } } @@ -886,7 +934,7 @@ abstract class GenASM extends SubComponent with BytecodeWriters with GenJVMASM { for(annot <- annotations; if shouldEmitAnnotation(annot)) { val AnnotationInfo(typ, args, assocs) = annot assert(args.isEmpty, args) - val av = mw.visitAnnotation(descriptor(typ), true) + val av = mw.visitAnnotation(descriptor(typ), isRuntimeVisible(annot)) emitAssocs(av, assocs) } } @@ -895,7 +943,7 @@ abstract class GenASM extends SubComponent with BytecodeWriters with GenJVMASM { for(annot <- annotations; if shouldEmitAnnotation(annot)) { val AnnotationInfo(typ, args, assocs) = annot assert(args.isEmpty, args) - val av = fw.visitAnnotation(descriptor(typ), true) + val av = fw.visitAnnotation(descriptor(typ), isRuntimeVisible(annot)) emitAssocs(av, assocs) } } @@ -907,7 +955,7 @@ abstract class GenASM extends SubComponent with BytecodeWriters with GenJVMASM { annot <- annots) { val AnnotationInfo(typ, args, assocs) = annot assert(args.isEmpty, args) - val pannVisitor: asm.AnnotationVisitor = jmethod.visitParameterAnnotation(idx, descriptor(typ), true) + val pannVisitor: asm.AnnotationVisitor = jmethod.visitParameterAnnotation(idx, descriptor(typ), isRuntimeVisible(annot)) emitAssocs(pannVisitor, assocs) } } diff --git a/src/compiler/scala/tools/nsc/backend/jvm/GenJVMASM.scala b/src/compiler/scala/tools/nsc/backend/jvm/GenJVMASM.scala deleted file mode 100644 index 2bcde7f7b9..0000000000 --- a/src/compiler/scala/tools/nsc/backend/jvm/GenJVMASM.scala +++ /dev/null @@ -1,83 +0,0 @@ -/* NSC -- new Scala compiler - * Copyright 2005-2013 LAMP/EPFL - * @author Jason Zaugg - */ - -package scala.tools.nsc -package backend.jvm -import scala.tools.nsc.symtab._ - -/** Code shared between the erstwhile legacy backend (aka GenJVM) - * and the new backend [[scala.tools.nsc.backend.jvm.GenASM]]. There should be - * more here, but for now I'm starting with the refactorings that are either - * straightforward to review or necessary for maintenance. - */ -trait GenJVMASM { - val global: Global - import global._ - import icodes._ - import definitions._ - - val ExcludedForwarderFlags = { - import Flags._ - // Should include DEFERRED but this breaks findMember. - ( SPECIALIZED | LIFTED | PROTECTED | STATIC | EXPANDEDNAME | BridgeAndPrivateFlags | MACRO ) - } - - protected def isJavaEntryPoint(icls: IClass) = { - val sym = icls.symbol - def fail(msg: String, pos: Position = sym.pos) = { - reporter.warning(sym.pos, - sym.name + " has a main method with parameter type Array[String], but " + sym.fullName('.') + " will not be a runnable program.\n" + - " Reason: " + msg - // TODO: make this next claim true, if possible - // by generating valid main methods as static in module classes - // not sure what the jvm allows here - // + " You can still run the program by calling it as " + sym.javaSimpleName + " instead." - ) - false - } - def failNoForwarder(msg: String) = { - fail(msg + ", which means no static forwarder can be generated.\n") - } - val possibles = if (sym.hasModuleFlag) (sym.tpe nonPrivateMember nme.main).alternatives else Nil - val hasApproximate = possibles exists { m => - m.info match { - case MethodType(p :: Nil, _) => p.tpe.typeSymbol == ArrayClass - case _ => false - } - } - // At this point it's a module with a main-looking method, so either succeed or warn that it isn't. - hasApproximate && { - // Before erasure so we can identify generic mains. - enteringErasure { - val companion = sym.linkedClassOfClass - - if (hasJavaMainMethod(companion)) - failNoForwarder("companion contains its own main method") - else if (companion.tpe.member(nme.main) != NoSymbol) - // this is only because forwarders aren't smart enough yet - failNoForwarder("companion contains its own main method (implementation restriction: no main is allowed, regardless of signature)") - else if (companion.isTrait) - failNoForwarder("companion is a trait") - // Now either succeeed, or issue some additional warnings for things which look like - // attempts to be java main methods. - else (possibles exists isJavaMainMethod) || { - possibles exists { m => - m.info match { - case PolyType(_, _) => - fail("main methods cannot be generic.") - case MethodType(params, res) => - if (res.typeSymbol :: params exists (_.isAbstractType)) - fail("main methods cannot refer to type parameters or abstract types.", m.pos) - else - isJavaMainMethod(m) || fail("main method must have exact signature (Array[String])Unit", m.pos) - case tp => - fail("don't know what this is: " + tp, m.pos) - } - } - } - } - } - } -} diff --git a/src/compiler/scala/tools/nsc/reporters/ConsoleReporter.scala b/src/compiler/scala/tools/nsc/reporters/ConsoleReporter.scala index 0b218b711c..5bf611a7b0 100644 --- a/src/compiler/scala/tools/nsc/reporters/ConsoleReporter.scala +++ b/src/compiler/scala/tools/nsc/reporters/ConsoleReporter.scala @@ -29,7 +29,7 @@ class ConsoleReporter(val settings: Settings, reader: BufferedReader, writer: Pr case INFO => null } - private def clabel(severity: Severity): String = { + protected def clabel(severity: Severity): String = { val label0 = label(severity) if (label0 eq null) "" else label0 + ": " } diff --git a/src/compiler/scala/tools/nsc/settings/AbsSettings.scala b/src/compiler/scala/tools/nsc/settings/AbsSettings.scala index 4727e6d867..060a24d8d4 100644 --- a/src/compiler/scala/tools/nsc/settings/AbsSettings.scala +++ b/src/compiler/scala/tools/nsc/settings/AbsSettings.scala @@ -35,7 +35,11 @@ trait AbsSettings extends scala.reflect.internal.settings.AbsSettings { case s: AbsSettings => this.userSetSettings == s.userSetSettings case _ => false } - override def toString() = "Settings {\n%s}\n" format (userSetSettings map (" " + _ + "\n")).mkString + override def toString() = { + val uss = userSetSettings + val indent = if (uss.nonEmpty) " " * 2 else "" + uss.mkString(f"Settings {%n$indent", f"%n$indent", f"%n}%n") + } def toConciseString = userSetSettings.mkString("(", " ", ")") def checkDependencies = diff --git a/src/compiler/scala/tools/nsc/settings/MutableSettings.scala b/src/compiler/scala/tools/nsc/settings/MutableSettings.scala index bbe21477cb..23611bb629 100644 --- a/src/compiler/scala/tools/nsc/settings/MutableSettings.scala +++ b/src/compiler/scala/tools/nsc/settings/MutableSettings.scala @@ -125,14 +125,26 @@ class MutableSettings(val errorFn: String => Unit) case Some(cmd) => setter(cmd)(args) } - // if arg is of form -Xfoo:bar,baz,quux - def parseColonArg(s: String): Option[List[String]] = { - val (p, args) = StringOps.splitWhere(s, _ == ':', doDropIndex = true) getOrElse (return None) - - // any non-Nil return value means failure and we return s unmodified - tryToSetIfExists(p, (args split ",").toList, (s: Setting) => s.tryToSetColon _) + // -Xfoo: clears Clearables + def clearIfExists(cmd: String): Option[List[String]] = lookupSetting(cmd) match { + case Some(c: Clearable) => c.clear() ; Some(Nil) + case Some(s) => s.errorAndValue(s"Missing argument to $cmd", None) + case None => None } + // if arg is of form -Xfoo:bar,baz,quux + // the entire arg is consumed, so return None for failure + // any non-Nil return value means failure and we return s unmodified + def parseColonArg(s: String): Option[List[String]] = + if (s endsWith ":") { + clearIfExists(s.init) + } else { + for { + (p, args) <- StringOps.splitWhere(s, _ == ':', doDropIndex = true) + rest <- tryToSetIfExists(p, (args split ",").toList, (s: Setting) => s.tryToSetColon _) + } yield rest + } + // if arg is of form -Xfoo or -Xfoo bar (name = "-Xfoo") def parseNormalArg(p: String, args: List[String]): Option[List[String]] = tryToSetIfExists(p, args, (s: Setting) => s.tryToSet _) @@ -532,6 +544,7 @@ class MutableSettings(val errorFn: String => Unit) def prepend(s: String) = prependPath.value = join(s, prependPath.value) def append(s: String) = appendPath.value = join(appendPath.value, s) + override def isDefault = super.isDefault && prependPath.isDefault && appendPath.isDefault override def value = join( prependPath.value, super.value, diff --git a/src/compiler/scala/tools/nsc/transform/Delambdafy.scala b/src/compiler/scala/tools/nsc/transform/Delambdafy.scala index 12e7b23f48..835d338ab3 100644 --- a/src/compiler/scala/tools/nsc/transform/Delambdafy.scala +++ b/src/compiler/scala/tools/nsc/transform/Delambdafy.scala @@ -260,7 +260,7 @@ abstract class Delambdafy extends Transform with TypingTransformers with ast.Tre val captureProxies2 = new LinkedHashMap[Symbol, TermSymbol] captures foreach {capture => - val sym = lambdaClass.newVariable(capture.name.toTermName, capture.pos, SYNTHETIC) + val sym = lambdaClass.newVariable(unit.freshTermName(capture.name.toString + "$"), capture.pos, SYNTHETIC) sym setInfo capture.info captureProxies2 += ((capture, sym)) } diff --git a/src/compiler/scala/tools/nsc/transform/Flatten.scala b/src/compiler/scala/tools/nsc/transform/Flatten.scala index fa53ef48b5..4662ef6224 100644 --- a/src/compiler/scala/tools/nsc/transform/Flatten.scala +++ b/src/compiler/scala/tools/nsc/transform/Flatten.scala @@ -77,8 +77,11 @@ abstract class Flatten extends InfoTransform { if (sym.isTerm && !sym.isStaticModule) { decls1 enter sym 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") + // In theory, we could assert(sym.isMethod), because nested, non-static moduls are + // transformed to methods (lateMETHOD flag added in RefChecks). But this requires + // forcing sym.info (see comment on isModuleNotMethod), which forces stub symbols + // too eagerly (SI-8907). + // 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 diff --git a/src/compiler/scala/tools/nsc/transform/patmat/MatchAnalysis.scala b/src/compiler/scala/tools/nsc/transform/patmat/MatchAnalysis.scala index a8852a3ff3..21e90b1d78 100644 --- a/src/compiler/scala/tools/nsc/transform/patmat/MatchAnalysis.scala +++ b/src/compiler/scala/tools/nsc/transform/patmat/MatchAnalysis.scala @@ -172,7 +172,6 @@ trait TreeAndTypeAnalysis extends Debugging { // a type is "uncheckable" (for exhaustivity) if we don't statically know its subtypes (i.e., it's unsealed) // we consider tuple types with at least one component of a checkable type as a checkable type def uncheckableType(tp: Type): Boolean = { - def tupleComponents(tp: Type) = tp.normalize.typeArgs val checkable = ( (isTupleType(tp) && tupleComponents(tp).exists(tp => !uncheckableType(tp))) || enumerateSubtypes(tp).nonEmpty) diff --git a/src/compiler/scala/tools/nsc/transform/patmat/MatchTreeMaking.scala b/src/compiler/scala/tools/nsc/transform/patmat/MatchTreeMaking.scala index 1974befb45..3abec521df 100644 --- a/src/compiler/scala/tools/nsc/transform/patmat/MatchTreeMaking.scala +++ b/src/compiler/scala/tools/nsc/transform/patmat/MatchTreeMaking.scala @@ -550,8 +550,24 @@ trait MatchTreeMaking extends MatchCodeGen with Debugging { case _ => false } val suppression = Suppression(suppressExhaustive, supressUnreachable) + val hasSwitchAnnotation = treeInfo.isSwitchAnnotation(tpt.tpe) // matches with two or fewer cases need not apply for switchiness (if-then-else will do) - val requireSwitch = treeInfo.isSwitchAnnotation(tpt.tpe) && casesNoSubstOnly.lengthCompare(2) > 0 + // `case 1 | 2` is considered as two cases. + def exceedsTwoCasesOrAlts = { + // avoids traversing the entire list if there are more than 3 elements + def lengthMax3[T](l: List[T]): Int = l match { + case a :: b :: c :: _ => 3 + case cases => + cases.map({ + case AlternativesTreeMaker(_, alts, _) :: _ => lengthMax3(alts) + case c => 1 + }).sum + } + lengthMax3(casesNoSubstOnly) > 2 + } + val requireSwitch = hasSwitchAnnotation && exceedsTwoCasesOrAlts + if (hasSwitchAnnotation && !requireSwitch) + reporter.warning(scrut.pos, "matches with two cases or fewer are emitted using if-then-else instead of switch") (suppression, requireSwitch) case _ => (Suppression.NoSuppression, false) diff --git a/src/compiler/scala/tools/nsc/typechecker/ContextErrors.scala b/src/compiler/scala/tools/nsc/typechecker/ContextErrors.scala index 20e462bbce..866ca37303 100644 --- a/src/compiler/scala/tools/nsc/typechecker/ContextErrors.scala +++ b/src/compiler/scala/tools/nsc/typechecker/ContextErrors.scala @@ -885,22 +885,31 @@ trait ContextErrors { val WrongNumber, NoParams, ArgsDoNotConform = Value } - private def issueAmbiguousTypeErrorUnlessErroneous(pos: Position, pre: Type, sym1: Symbol, sym2: Symbol, rest: String): Unit = - if (!(pre.isErroneous || sym1.isErroneous || sym2.isErroneous)) { - if (sym1.hasDefault && sym2.hasDefault && sym1.enclClass == sym2.enclClass) { - val methodName = nme.defaultGetterToMethod(sym1.name) - context.issueAmbiguousError(AmbiguousTypeError(sym1.enclClass.pos, - "in "+ sym1.enclClass +", multiple overloaded alternatives of " + methodName + - " define default arguments")) - } else { - context.issueAmbiguousError(AmbiguousTypeError(pos, - ("ambiguous reference to overloaded definition,\n" + - "both " + sym1 + sym1.locationString + " of type " + pre.memberType(sym1) + - "\nand " + sym2 + sym2.locationString + " of type " + pre.memberType(sym2) + - "\nmatch " + rest) - )) - } - } + private def issueAmbiguousTypeErrorUnlessErroneous(pos: Position, pre: Type, sym1: Symbol, sym2: Symbol, rest: String): Unit = { + // To avoid stack overflows (SI-8890), we MUST (at least) report when either `validTargets` OR `ambiguousSuppressed` + // More details: + // If `!context.ambiguousErrors`, `reporter.issueAmbiguousError` (which `context.issueAmbiguousError` forwards to) + // buffers ambiguous errors. In this case, to avoid looping, we must issue even if `!validTargets`. (TODO: why?) + // When not buffering (and thus reporting to the user), we shouldn't issue unless `validTargets`, + // otherwise we report two different errors that trace back to the same root cause, + // and unless `validTargets`, we don't know for sure the ambiguity is real anyway. + val validTargets = !(pre.isErroneous || sym1.isErroneous || sym2.isErroneous) + val ambiguousBuffered = !context.ambiguousErrors + if (validTargets || ambiguousBuffered) + context.issueAmbiguousError( + if (sym1.hasDefault && sym2.hasDefault && sym1.enclClass == sym2.enclClass) { + val methodName = nme.defaultGetterToMethod(sym1.name) + AmbiguousTypeError(sym1.enclClass.pos, + s"in ${sym1.enclClass}, multiple overloaded alternatives of $methodName define default arguments") + + } else { + AmbiguousTypeError(pos, + "ambiguous reference to overloaded definition,\n" + + s"both ${sym1.fullLocationString} of type ${pre.memberType(sym1)}\n" + + s"and ${sym2.fullLocationString} of type ${pre.memberType(sym2)}\n" + + s"match $rest") + }) + } def AccessError(tree: Tree, sym: Symbol, ctx: Context, explanation: String): AbsTypeError = AccessError(tree, sym, ctx.enclClass.owner.thisType, ctx.enclClass.owner, explanation) diff --git a/src/compiler/scala/tools/nsc/typechecker/Typers.scala b/src/compiler/scala/tools/nsc/typechecker/Typers.scala index b498d9e667..aae2d24b32 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Typers.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Typers.scala @@ -1726,7 +1726,10 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper if ((clazz isNonBottomSubClass ClassfileAnnotationClass) && (clazz != ClassfileAnnotationClass)) { if (!clazz.owner.isPackageClass) context.error(clazz.pos, "inner classes cannot be classfile annotations") - else restrictionWarning(cdef.pos, unit, + // Ignore @SerialVersionUID, because it is special-cased and handled completely differently. + // It only extends ClassfileAnnotationClass instead of StaticAnnotation to get the enforcement + // of constant argument values "for free". Related to SI-7041. + else if (clazz != SerialVersionUIDAttr) restrictionWarning(cdef.pos, unit, """|subclassing Classfile does not |make your annotation visible at runtime. If that is what |you want, you must write the annotation class in Java.""".stripMargin) diff --git a/src/library/scala/collection/TraversableLike.scala b/src/library/scala/collection/TraversableLike.scala index a8731a51b1..d3a7db6968 100644 --- a/src/library/scala/collection/TraversableLike.scala +++ b/src/library/scala/collection/TraversableLike.scala @@ -253,7 +253,7 @@ trait TraversableLike[+A, +Repr] extends Any b.result } - private[scala] def filterImpl(p: A => Boolean, isFlipped: Boolean): Repr = { + private def filterImpl(p: A => Boolean, isFlipped: Boolean): Repr = { val b = newBuilder for (x <- this) if (p(x) != isFlipped) b += x diff --git a/src/library/scala/collection/immutable/Stream.scala b/src/library/scala/collection/immutable/Stream.scala index 91a4e1c43d..714d5117d3 100644 --- a/src/library/scala/collection/immutable/Stream.scala +++ b/src/library/scala/collection/immutable/Stream.scala @@ -499,16 +499,6 @@ self => ) else super.flatMap(f)(bf) - override private[scala] def filterImpl(p: A => Boolean, isFlipped: Boolean): Stream[A] = { - // optimization: drop leading prefix of elems for which f returns false - // var rest = this dropWhile (!p(_)) - forget DRY principle - GC can't collect otherwise - var rest = this - while (!rest.isEmpty && p(rest.head) == isFlipped) rest = rest.tail - // private utility func to avoid `this` on stack (would be needed for the lazy arg) - if (rest.nonEmpty) Stream.filteredTail(rest, p, isFlipped) - else Stream.Empty - } - /** Returns all the elements of this `Stream` that satisfy the predicate `p` * in a new `Stream` - i.e., it is still a lazy data structure. The order of * the elements is preserved @@ -522,7 +512,15 @@ self => * // produces * }}} */ - override def filter(p: A => Boolean): Stream[A] = filterImpl(p, isFlipped = false) // This override is only left in 2.11 because of binary compatibility, see PR #3925 + override def filter(p: A => Boolean): Stream[A] = { + // optimization: drop leading prefix of elems for which f returns false + // var rest = this dropWhile (!p(_)) - forget DRY principle - GC can't collect otherwise + var rest = this + while (!rest.isEmpty && !p(rest.head)) rest = rest.tail + // private utility func to avoid `this` on stack (would be needed for the lazy arg) + if (rest.nonEmpty) Stream.filteredTail(rest, p) + else Stream.Empty + } override final def withFilter(p: A => Boolean): StreamWithFilter = new StreamWithFilter(p) @@ -1286,8 +1284,8 @@ object Stream extends SeqFactory[Stream] { else cons(start, range(start + step, end, step)) } - private[immutable] def filteredTail[A](stream: Stream[A], p: A => Boolean, isFlipped: Boolean) = { - cons(stream.head, stream.tail.filterImpl(p, isFlipped)) + private[immutable] def filteredTail[A](stream: Stream[A], p: A => Boolean) = { + cons(stream.head, stream.tail filter p) } private[immutable] def collectedTail[A, B, That](head: B, stream: Stream[A], pf: PartialFunction[A, B], bf: CanBuildFrom[Stream[A], B, That]) = { diff --git a/src/library/scala/collection/immutable/StringLike.scala b/src/library/scala/collection/immutable/StringLike.scala index 738b294ce6..bf93a38f55 100644 --- a/src/library/scala/collection/immutable/StringLike.scala +++ b/src/library/scala/collection/immutable/StringLike.scala @@ -135,23 +135,29 @@ self => def linesIterator: Iterator[String] = linesWithSeparators map (line => new WrappedString(line).stripLineEnd) - /** Returns this string with first character converted to upper case */ + /** Returns this string with first character converted to upper case. + * If the first character of the string is capitalized, it is returned unchanged. + */ def capitalize: String = if (toString == null) null else if (toString.length == 0) "" + else if (toString.charAt(0).isUpper) toString else { val chars = toString.toCharArray chars(0) = chars(0).toUpper new String(chars) } - /** Returns this string with the given `prefix` stripped. */ + /** Returns this string with the given `prefix` stripped. If this string does not + * start with `prefix`, it is returned unchanged. + */ def stripPrefix(prefix: String) = if (toString.startsWith(prefix)) toString.substring(prefix.length) else toString /** Returns this string with the given `suffix` stripped. If this string does not - * end with `suffix`, it is returned unchanged. */ + * end with `suffix`, it is returned unchanged. + */ def stripSuffix(suffix: String) = if (toString.endsWith(suffix)) toString.substring(0, toString.length() - suffix.length) else toString diff --git a/src/reflect/scala/reflect/internal/Definitions.scala b/src/reflect/scala/reflect/internal/Definitions.scala index f5662b47c2..666a3a5e64 100644 --- a/src/reflect/scala/reflect/internal/Definitions.scala +++ b/src/reflect/scala/reflect/internal/Definitions.scala @@ -653,6 +653,7 @@ trait Definitions extends api.StandardDefinitions { // tends to change the course of events by forcing types. def isFunctionType(tp: Type) = isFunctionTypeDirect(tp.dealiasWiden) def isTupleType(tp: Type) = isTupleTypeDirect(tp.dealiasWiden) + def tupleComponents(tp: Type) = tp.dealiasWiden.typeArgs lazy val ProductRootClass: ClassSymbol = requiredClass[scala.Product] def Product_productArity = getMemberMethod(ProductRootClass, nme.productArity) @@ -837,7 +838,7 @@ trait Definitions extends api.StandardDefinitions { def typeOfMemberNamedApply(tp: Type) = typeArgOfBaseTypeOr(tp, SeqClass)(resultOfMatchingMethod(tp, nme.apply)(IntTpe)) def typeOfMemberNamedDrop(tp: Type) = typeArgOfBaseTypeOr(tp, SeqClass)(resultOfMatchingMethod(tp, nme.drop)(IntTpe)) def typesOfSelectors(tp: Type) = - if (isTupleType(tp)) tp.typeArgs + if (isTupleType(tp)) tupleComponents(tp) else getterMemberTypes(tp, productSelectors(tp)) // SI-8128 Still using the type argument of the base type at Seq/Option if this is an old-style (2.10 compatible) @@ -1089,6 +1090,10 @@ trait Definitions extends api.StandardDefinitions { lazy val ClassfileAnnotationClass = requiredClass[scala.annotation.ClassfileAnnotation] lazy val StaticAnnotationClass = requiredClass[scala.annotation.StaticAnnotation] + // Java retention annotations + lazy val AnnotationRetentionAttr = requiredClass[java.lang.annotation.Retention] + lazy val AnnotationRetentionPolicyAttr = requiredClass[java.lang.annotation.RetentionPolicy] + // Annotations lazy val BridgeClass = requiredClass[scala.annotation.bridge] lazy val ElidableMethodClass = requiredClass[scala.annotation.elidable] diff --git a/src/reflect/scala/reflect/internal/Symbols.scala b/src/reflect/scala/reflect/internal/Symbols.scala index 3296b2322f..70fb9dfa8e 100644 --- a/src/reflect/scala/reflect/internal/Symbols.scala +++ b/src/reflect/scala/reflect/internal/Symbols.scala @@ -734,31 +734,31 @@ trait Symbols extends api.Symbols { self: SymbolTable => final def hasGetter = isTerm && nme.isLocalName(name) - /** A little explanation for this confusing situation. - * Nested modules which have no static owner when ModuleDefs - * are eliminated (refchecks) are given the lateMETHOD flag, - * which makes them appear as methods after refchecks. - * Here's an example where one can see all four of FF FT TF TT - * for (isStatic, isMethod) at various phases. + /** + * Nested modules which have no static owner when ModuleDefs are eliminated (refchecks) are + * given the lateMETHOD flag, which makes them appear as methods after refchecks. + * + * Note: the lateMETHOD flag is added lazily in the info transformer of the RefChecks phase. + * This means that forcing the `sym.info` may change the value of `sym.isMethod`. Forcing the + * info is in the responsability of the caller. Doing it eagerly here was tried (0ccdb151f) but + * has proven to lead to bugs (SI-8907). * - * trait A1 { case class Quux() } - * object A2 extends A1 { object Flax } - * // -- namer object Quux in trait A1 - * // -M flatten object Quux in trait A1 - * // S- flatten object Flax in object A2 - * // -M posterasure object Quux in trait A1 - * // -M jvm object Quux in trait A1 - * // SM jvm object Quux in object A2 + * Here's an example where one can see all four of FF FT TF TT for (isStatic, isMethod) at + * various phases. * - * So "isModuleNotMethod" exists not for its achievement in - * brevity, but to encapsulate the relevant condition. + * trait A1 { case class Quux() } + * object A2 extends A1 { object Flax } + * // -- namer object Quux in trait A1 + * // -M flatten object Quux in trait A1 + * // S- flatten object Flax in object A2 + * // -M posterasure object Quux in trait A1 + * // -M jvm object Quux in trait A1 + * // SM jvm object Quux in object A2 + * + * So "isModuleNotMethod" exists not for its achievement in brevity, but to encapsulate the + * relevant condition. */ - def isModuleNotMethod = { - if (isModule) { - if (phase.refChecked) this.info // force completion to make sure lateMETHOD is there. - !isMethod - } else false - } + def isModuleNotMethod = isModule && !isMethod // 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 diff --git a/src/reflect/scala/reflect/internal/util/AbstractFileClassLoader.scala b/src/reflect/scala/reflect/internal/util/AbstractFileClassLoader.scala index 10a8b4c812..30dcbc21ca 100644 --- a/src/reflect/scala/reflect/internal/util/AbstractFileClassLoader.scala +++ b/src/reflect/scala/reflect/internal/util/AbstractFileClassLoader.scala @@ -5,16 +5,16 @@ package scala package reflect.internal.util -import scala.reflect.io.AbstractFile +import scala.collection.{ mutable, immutable } +import scala.reflect.io.{ AbstractFile, Streamable } +import java.net.{ URL, URLConnection, URLStreamHandler } import java.security.cert.Certificate import java.security.{ ProtectionDomain, CodeSource } -import java.net.{ URL, URLConnection, URLStreamHandler } -import scala.collection.{ mutable, immutable } +import java.util.{ Collections => JCollections, Enumeration => JEnumeration } -/** - * A class loader that loads files from a {@link scala.tools.nsc.io.AbstractFile}. +/** A class loader that loads files from a {@link scala.tools.nsc.io.AbstractFile}. * - * @author Lex Spoon + * @author Lex Spoon */ class AbstractFileClassLoader(val root: AbstractFile, parent: ClassLoader) extends ClassLoader(parent) @@ -22,7 +22,7 @@ class AbstractFileClassLoader(val root: AbstractFile, parent: ClassLoader) { protected def classNameToPath(name: String): String = if (name endsWith ".class") name - else name.replace('.', '/') + ".class" + else s"${name.replace('.', '/')}.class" protected def findAbstractFile(name: String): AbstractFile = { var file: AbstractFile = root @@ -56,35 +56,25 @@ class AbstractFileClassLoader(val root: AbstractFile, parent: ClassLoader) file } - // parent delegation in JCL uses getResource; so either add parent.getResAsStream - // or implement findResource, which we do here as a study in scarlet (my complexion - // after looking at CLs and URLs) - override def findResource(name: String): URL = findAbstractFile(name) match { + override protected def findClass(name: String): Class[_] = { + val bytes = classBytes(name) + if (bytes.length == 0) + throw new ClassNotFoundException(name) + else + defineClass(name, bytes, 0, bytes.length, protectionDomain) + } + override protected def findResource(name: String): URL = findAbstractFile(name) match { case null => null - case file => new URL(null, "repldir:" + file.path, new URLStreamHandler { + case file => new URL(null, s"memory:${file.path}", new URLStreamHandler { override def openConnection(url: URL): URLConnection = new URLConnection(url) { - override def connect() { } + override def connect() = () override def getInputStream = file.input } }) } - - // this inverts delegation order: super.getResAsStr calls parent.getRes if we fail - override def getResourceAsStream(name: String) = findAbstractFile(name) match { - case null => super.getResourceAsStream(name) - case file => file.input - } - // ScalaClassLoader.classBytes uses getResAsStream, so we'll try again before delegating - override def classBytes(name: String): Array[Byte] = findAbstractFile(classNameToPath(name)) match { - case null => super.classBytes(name) - case file => file.toByteArray - } - override def findClass(name: String): Class[_] = { - val bytes = classBytes(name) - if (bytes.length == 0) - throw new ClassNotFoundException(name) - else - defineClass(name, bytes, 0, bytes.length, protectionDomain) + override protected def findResources(name: String): JEnumeration[URL] = findResource(name) match { + case null => JCollections.enumeration(JCollections.emptyList[URL]) //JCollections.emptyEnumeration[URL] + case url => JCollections.enumeration(JCollections.singleton(url)) } lazy val protectionDomain = { @@ -106,15 +96,13 @@ class AbstractFileClassLoader(val root: AbstractFile, parent: ClassLoader) throw new UnsupportedOperationException() } - override def getPackage(name: String): Package = { - findAbstractDir(name) match { - case null => super.getPackage(name) - case file => packages.getOrElseUpdate(name, { - val ctor = classOf[Package].getDeclaredConstructor(classOf[String], classOf[String], classOf[String], classOf[String], classOf[String], classOf[String], classOf[String], classOf[URL], classOf[ClassLoader]) - ctor.setAccessible(true) - ctor.newInstance(name, null, null, null, null, null, null, null, this) - }) - } + override def getPackage(name: String): Package = findAbstractDir(name) match { + case null => super.getPackage(name) + case file => packages.getOrElseUpdate(name, { + val ctor = classOf[Package].getDeclaredConstructor(classOf[String], classOf[String], classOf[String], classOf[String], classOf[String], classOf[String], classOf[String], classOf[URL], classOf[ClassLoader]) + ctor.setAccessible(true) + ctor.newInstance(name, null, null, null, null, null, null, null, this) + }) } override def getPackages(): Array[Package] = diff --git a/src/reflect/scala/reflect/internal/util/ScalaClassLoader.scala b/src/reflect/scala/reflect/internal/util/ScalaClassLoader.scala index 63ea6e2c49..41011f6c6b 100644 --- a/src/reflect/scala/reflect/internal/util/ScalaClassLoader.scala +++ b/src/reflect/scala/reflect/internal/util/ScalaClassLoader.scala @@ -53,8 +53,10 @@ trait ScalaClassLoader extends JClassLoader { } /** An InputStream representing the given class name, or null if not found. */ - def classAsStream(className: String) = - getResourceAsStream(className.replaceAll("""\.""", "/") + ".class") + def classAsStream(className: String) = getResourceAsStream { + if (className endsWith ".class") className + else s"${className.replace('.', '/')}.class" // classNameToPath + } /** Run the main method of a class to be loaded by this classloader */ def run(objectName: String, arguments: Seq[String]) { diff --git a/src/reflect/scala/reflect/runtime/JavaUniverseForce.scala b/src/reflect/scala/reflect/runtime/JavaUniverseForce.scala index dcd262c288..18a3c5d63f 100644 --- a/src/reflect/scala/reflect/runtime/JavaUniverseForce.scala +++ b/src/reflect/scala/reflect/runtime/JavaUniverseForce.scala @@ -360,6 +360,8 @@ trait JavaUniverseForce { self: runtime.JavaUniverse => definitions.AnnotationClass definitions.ClassfileAnnotationClass definitions.StaticAnnotationClass + definitions.AnnotationRetentionAttr + definitions.AnnotationRetentionPolicyAttr definitions.BridgeClass definitions.ElidableMethodClass definitions.ImplicitNotFoundClass diff --git a/src/repl/scala/tools/nsc/interpreter/ILoop.scala b/src/repl/scala/tools/nsc/interpreter/ILoop.scala index 6197aea93d..6e18682494 100644 --- a/src/repl/scala/tools/nsc/interpreter/ILoop.scala +++ b/src/repl/scala/tools/nsc/interpreter/ILoop.scala @@ -75,6 +75,7 @@ class ILoop(in0: Option[BufferedReader], protected val out: JPrintWriter) def history = in.history // classpath entries added via :cp + @deprecated("Use reset, replay or require to update class path", since = "2.11") var addedClasspath: String = "" /** A reverse list of commands to replay if the user requests a :replay */ @@ -207,7 +208,6 @@ class ILoop(in0: Option[BufferedReader], protected val out: JPrintWriter) /** Standard commands **/ lazy val standardCommands = List( - cmd("cp", "<path>", "add a jar or directory to the classpath", addClasspath), cmd("edit", "<id>|<line>", "edit history", editCommand), cmd("help", "[command]", "print this summary or command-specific help", helpCommand), historyCommand, @@ -220,11 +220,12 @@ class ILoop(in0: Option[BufferedReader], protected val out: JPrintWriter) cmd("paste", "[-raw] [path]", "enter paste mode or paste a file", pasteCommand), nullary("power", "enable power user mode", powerCmd), nullary("quit", "exit the interpreter", () => Result(keepRunning = false, None)), - nullary("replay", "reset execution and replay all previous commands", replay), - nullary("reset", "reset the repl to its initial state, forgetting all session entries", resetCommand), + cmd("replay", "[options]", "reset the repl and replay all previous commands", replayCommand), + //cmd("require", "<path>", "add a jar or directory to the classpath", require), // TODO + cmd("reset", "[options]", "reset the repl to its initial state, forgetting all session entries", resetCommand), cmd("save", "<path>", "save replayable session to a file", saveCommand), shCommand, - cmd("settings", "[+|-]<options>", "+enable/-disable flags, set compiler options", changeSettings), + cmd("settings", "<options>", "update compiler options, if possible; see reset", changeSettings), nullary("silent", "disable/enable automatic printing of results", verbosity), cmd("type", "[-v] <expr>", "display the type of an expression without evaluating it", typeCommand), cmd("kind", "[-v] <expr>", "display the kind of expression's type", kindCommand), @@ -304,57 +305,23 @@ class ILoop(in0: Option[BufferedReader], protected val out: JPrintWriter) intp.lastWarnings foreach { case (pos, msg) => intp.reporter.warning(pos, msg) } } - private def changeSettings(args: String): Result = { - def showSettings() = { - for (s <- settings.userSetSettings.toSeq.sorted) echo(s.toString) - } - def updateSettings() = { - // put aside +flag options - val (pluses, rest) = (args split "\\s+").toList partition (_.startsWith("+")) - val tmps = new Settings - val (ok, leftover) = tmps.processArguments(rest, processAll = true) - if (!ok) echo("Bad settings request.") - else if (leftover.nonEmpty) echo("Unprocessed settings.") - else { - // boolean flags set-by-user on tmp copy should be off, not on - val offs = tmps.userSetSettings filter (_.isInstanceOf[Settings#BooleanSetting]) - val (minuses, nonbools) = rest partition (arg => offs exists (_ respondsTo arg)) - // update non-flags - settings.processArguments(nonbools, processAll = true) - // also snag multi-value options for clearing, e.g. -Ylog: and -language: - for { - s <- settings.userSetSettings - if s.isInstanceOf[Settings#MultiStringSetting] || s.isInstanceOf[Settings#PhasesSetting] - if nonbools exists (arg => arg.head == '-' && arg.last == ':' && (s respondsTo arg.init)) - } s match { - case c: Clearable => c.clear() - case _ => - } - def update(bs: Seq[String], name: String=>String, setter: Settings#Setting=>Unit) = { - for (b <- bs) - settings.lookupSetting(name(b)) match { - case Some(s) => - if (s.isInstanceOf[Settings#BooleanSetting]) setter(s) - else echo(s"Not a boolean flag: $b") - case _ => - echo(s"Not an option: $b") - } - } - update(minuses, identity, _.tryToSetFromPropertyValue("false")) // turn off - update(pluses, "-" + _.drop(1), _.tryToSet(Nil)) // turn on - } - } - if (args.isEmpty) showSettings() else updateSettings() + private def changeSettings(line: String): Result = { + def showSettings() = for (s <- settings.userSetSettings.toSeq.sorted) echo(s.toString) + if (line.isEmpty) showSettings() else { updateSettings(line) ; () } + } + private def updateSettings(line: String) = { + val (ok, rest) = settings.processArguments(words(line), processAll = false) + ok && rest.isEmpty } private def javapCommand(line: String): Result = { if (javap == null) - ":javap unavailable, no tools.jar at %s. Set JDK_HOME.".format(jdkHome) + s":javap unavailable, no tools.jar at $jdkHome. Set JDK_HOME." else if (line == "") ":javap [-lcsvp] [path1 path2 ...]" else javap(words(line)) foreach { res => - if (res.isError) return "Failed: " + res.value + if (res.isError) return s"Failed: ${res.value}" else res.show() } } @@ -445,8 +412,14 @@ class ILoop(in0: Option[BufferedReader], protected val out: JPrintWriter) } private def readOneLine() = { + import scala.io.AnsiColor.{ MAGENTA, RESET } out.flush() - in readLine prompt + in readLine ( + if (replProps.colorOk) + MAGENTA + prompt + RESET + else + prompt + ) } /** The main read-eval-print loop for the repl. It calls @@ -472,8 +445,16 @@ class ILoop(in0: Option[BufferedReader], protected val out: JPrintWriter) } /** create a new interpreter and replay the given commands */ - def replay() { - reset() + def replayCommand(line: String): Unit = { + def run(destructive: Boolean): Unit = { + if (destructive) createInterpreter() else reset() + replay() + } + if (line.isEmpty) run(destructive = false) + else if (updateSettings(line)) run(destructive = true) + } + /** Announces as it replays. */ + def replay(): Unit = { if (replayCommandStack.isEmpty) echo("Nothing to replay.") else for (cmd <- replayCommands) { @@ -482,21 +463,28 @@ class ILoop(in0: Option[BufferedReader], protected val out: JPrintWriter) echo("") } } - def resetCommand() { - echo("Resetting interpreter state.") - if (replayCommandStack.nonEmpty) { - echo("Forgetting this session history:\n") - replayCommands foreach echo - echo("") - replayCommandStack = Nil + /** `reset` the interpreter in an attempt to start fresh. + * Supplying settings creates a new compiler. + */ + def resetCommand(line: String): Unit = { + def run(destructive: Boolean): Unit = { + echo("Resetting interpreter state.") + if (replayCommandStack.nonEmpty) { + echo("Forgetting this session history:\n") + replayCommands foreach echo + echo("") + replayCommandStack = Nil + } + if (intp.namedDefinedTerms.nonEmpty) + echo("Forgetting all expression results and named terms: " + intp.namedDefinedTerms.mkString(", ")) + if (intp.definedTypes.nonEmpty) + echo("Forgetting defined types: " + intp.definedTypes.mkString(", ")) + if (destructive) createInterpreter() else reset() } - if (intp.namedDefinedTerms.nonEmpty) - echo("Forgetting all expression results and named terms: " + intp.namedDefinedTerms.mkString(", ")) - if (intp.definedTypes.nonEmpty) - echo("Forgetting defined types: " + intp.definedTypes.mkString(", ")) - - reset() + if (line.isEmpty) run(destructive = false) + else if (updateSettings(line)) run(destructive = true) } + /** Resets without announcements. */ def reset() { intp.reset() unleashAndSetPhase() @@ -619,6 +607,7 @@ class ILoop(in0: Option[BufferedReader], protected val out: JPrintWriter) else File(filename).printlnAll(replayCommands: _*) ) + @deprecated("Use reset, replay or require to update class path", since = "2.11") def addClasspath(arg: String): Unit = { val f = File(arg).normalize if (f.exists) { diff --git a/src/repl/scala/tools/nsc/interpreter/IMain.scala b/src/repl/scala/tools/nsc/interpreter/IMain.scala index 6e30b73e0e..20b5a79aaa 100644 --- a/src/repl/scala/tools/nsc/interpreter/IMain.scala +++ b/src/repl/scala/tools/nsc/interpreter/IMain.scala @@ -295,22 +295,38 @@ class IMain(@BeanProperty val factory: ScriptEngineFactory, initialSettings: Set def originalPath(name: Name): String = typerOp path name def originalPath(sym: Symbol): String = typerOp path sym def flatPath(sym: Symbol): String = flatOp shift sym.javaClassName + def translatePath(path: String) = { val sym = if (path endsWith "$") symbolOfTerm(path.init) else symbolOfIdent(path) sym.toOption map flatPath } + + /** If path represents a class resource in the default package, + * see if the corresponding symbol has a class file that is a REPL artifact + * residing at a different resource path. Translate X.class to $line3/$read$$iw$$iw$X.class. + */ + def translateSimpleResource(path: String): Option[String] = { + if (!(path contains '/') && (path endsWith ".class")) { + val name = path stripSuffix ".class" + val sym = if (name endsWith "$") symbolOfTerm(name.init) else symbolOfIdent(name) + def pathOf(s: String) = s"${s.replace('.', '/')}.class" + sym.toOption map (s => pathOf(flatPath(s))) + } else { + None + } + } def translateEnclosingClass(n: String) = symbolOfTerm(n).enclClass.toOption map flatPath + /** If unable to find a resource foo.class, try taking foo as a symbol in scope + * and use its java class name as a resource to load. + * + * $intp.classLoader classBytes "Bippy" or $intp.classLoader getResource "Bippy.class" just work. + */ private class TranslatingClassLoader(parent: ClassLoader) extends util.AbstractFileClassLoader(replOutput.dir, parent) { - /** Overridden here to try translating a simple name to the generated - * class name if the original attempt fails. This method is used by - * getResourceAsStream as well as findClass. - */ - override protected def findAbstractFile(name: String): AbstractFile = - super.findAbstractFile(name) match { - case null if _initializeComplete => translatePath(name) map (super.findAbstractFile(_)) orNull - case file => file - } + override protected def findAbstractFile(name: String): AbstractFile = super.findAbstractFile(name) match { + case null if _initializeComplete => translateSimpleResource(name) map super.findAbstractFile orNull + case file => file + } } private def makeClassLoader(): util.AbstractFileClassLoader = new TranslatingClassLoader(parentClassLoader match { diff --git a/src/repl/scala/tools/nsc/interpreter/MemberHandlers.scala b/src/repl/scala/tools/nsc/interpreter/MemberHandlers.scala index a37cdc2ec8..bcba7b6dfd 100644 --- a/src/repl/scala/tools/nsc/interpreter/MemberHandlers.scala +++ b/src/repl/scala/tools/nsc/interpreter/MemberHandlers.scala @@ -102,6 +102,18 @@ trait MemberHandlers { class GenericHandler(member: Tree) extends MemberHandler(member) + import scala.io.AnsiColor.{ BOLD, BLUE, GREEN, RESET } + + def color(c: String, s: String) = + if (replProps.colorOk) string2code(BOLD) + string2code(c) + s + string2code(RESET) + else s + + def colorName(s: String) = + color(BLUE, string2code(s)) + + def colorType(s: String) = + color(GREEN, string2code(s)) + class ValHandler(member: ValDef) extends MemberDefHandler(member) { val maxStringElements = 1000 // no need to mkString billions of elements override def definesValue = true @@ -119,15 +131,20 @@ trait MemberHandlers { if (replProps.vids) s"""" + f"@$${System.identityHashCode($path)}%8x" + """" else "" - """ + "%s%s: %s = " + %s""".format(string2code(prettyName), vidString, string2code(req typeOf name), resultString) + val nameString = colorName(prettyName) + vidString + val typeString = colorType(req typeOf name) + s""" + "$nameString: $typeString = " + $resultString""" } } } class DefHandler(member: DefDef) extends MemberDefHandler(member) { override def definesValue = flattensToEmpty(member.vparamss) // true if 0-arity - override def resultExtractionCode(req: Request) = - if (mods.isPublic) codegenln(name, ": ", req.typeOf(name)) else "" + override def resultExtractionCode(req: Request) = { + val nameString = colorName(name) + val typeString = colorType(req typeOf name) + if (mods.isPublic) s""" + "$nameString: $typeString\\n"""" else "" + } } abstract class MacroHandler(member: DefDef) extends MemberDefHandler(member) { diff --git a/src/repl/scala/tools/nsc/interpreter/ReplProps.scala b/src/repl/scala/tools/nsc/interpreter/ReplProps.scala index 36e6dbbccc..8c4faf7278 100644 --- a/src/repl/scala/tools/nsc/interpreter/ReplProps.scala +++ b/src/repl/scala/tools/nsc/interpreter/ReplProps.scala @@ -13,6 +13,9 @@ class ReplProps { private def bool(name: String) = BooleanProp.keyExists(name) private def int(name: String) = IntProp(name) + // This property is used in TypeDebugging. Let's recycle it. + val colorOk = bool("scala.color") + val info = bool("scala.repl.info") val debug = bool("scala.repl.debug") val trace = bool("scala.repl.trace") diff --git a/src/repl/scala/tools/nsc/interpreter/ReplReporter.scala b/src/repl/scala/tools/nsc/interpreter/ReplReporter.scala index 88372334d6..e6f5a4089e 100644 --- a/src/repl/scala/tools/nsc/interpreter/ReplReporter.scala +++ b/src/repl/scala/tools/nsc/interpreter/ReplReporter.scala @@ -32,6 +32,24 @@ class ReplReporter(intp: IMain) extends ConsoleReporter(intp.settings, Console.i override def warning(pos: Position, msg: String): Unit = withoutTruncating(super.warning(pos, msg)) override def error(pos: Position, msg: String): Unit = withoutTruncating(super.error(pos, msg)) + import scala.io.AnsiColor.{ RED, YELLOW, RESET } + + def severityColor(severity: Severity): String = severity match { + case ERROR => RED + case WARNING => YELLOW + case INFO => RESET + } + + override def print(pos: Position, msg: String, severity: Severity) { + val prefix = ( + if (replProps.colorOk) + severityColor(severity) + clabel(severity) + RESET + else + clabel(severity) + ) + printMessage(pos, prefix + msg) + } + override def printMessage(msg: String) { // Avoiding deadlock if the compiler starts logging before // the lazy val is complete. diff --git a/src/repl/scala/tools/nsc/interpreter/ReplStrings.scala b/src/repl/scala/tools/nsc/interpreter/ReplStrings.scala index 43da5c6f12..1664546cab 100644 --- a/src/repl/scala/tools/nsc/interpreter/ReplStrings.scala +++ b/src/repl/scala/tools/nsc/interpreter/ReplStrings.scala @@ -28,5 +28,8 @@ trait ReplStrings { def any2stringOf(x: Any, maxlen: Int) = "scala.runtime.ScalaRunTime.replStringOf(%s, %s)".format(x, maxlen) - def words(s: String) = (s.trim split "\\s+" filterNot (_ == "")).toList + // no escaped or nested quotes + private[this] val inquotes = """(['"])(.*?)\1""".r + def unquoted(s: String) = s match { case inquotes(_, w) => w ; case _ => s } + def words(s: String) = (s.trim split "\\s+" filterNot (_ == "") map unquoted).toList } diff --git a/src/scaladoc/scala/tools/nsc/ScalaDoc.scala b/src/scaladoc/scala/tools/nsc/ScalaDoc.scala index 52a0c20a11..32a6ba0ce3 100644 --- a/src/scaladoc/scala/tools/nsc/ScalaDoc.scala +++ b/src/scaladoc/scala/tools/nsc/ScalaDoc.scala @@ -18,14 +18,10 @@ class ScalaDoc { val versionMsg = "Scaladoc %s -- %s".format(Properties.versionString, Properties.copyrightString) def process(args: Array[String]): Boolean = { - var reporter: ConsoleReporter = null + var reporter: ScalaDocReporter = null val docSettings = new doc.Settings(msg => reporter.error(FakePos("scaladoc"), msg + "\n scaladoc -help gives more information"), msg => reporter.printMessage(msg)) - reporter = new ConsoleReporter(docSettings) { - // need to do this so that the Global instance doesn't trash all the - // symbols just because there was an error - override def hasErrors = false - } + reporter = new ScalaDocReporter(docSettings) val command = new ScalaDoc.Command(args.toList, docSettings) def hasFiles = command.files.nonEmpty || docSettings.uncompilableFiles.nonEmpty @@ -50,12 +46,18 @@ class ScalaDoc { } finally reporter.printSummary() - // not much point in returning !reporter.hasErrors when it has - // been overridden with constant false. - true + !reporter.reallyHasErrors } } +class ScalaDocReporter(settings: Settings) extends ConsoleReporter(settings) { + + // need to do sometimes lie so that the Global instance doesn't + // trash all the symbols just because there was an error + override def hasErrors = false + def reallyHasErrors = super.hasErrors +} + object ScalaDoc extends ScalaDoc { class Command(arguments: List[String], settings: doc.Settings) extends CompilerCommand(arguments, settings) { override def cmdName = "scaladoc" |