diff options
author | Lukas Rytz <lukas.rytz@typesafe.com> | 2016-09-02 12:51:08 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2016-09-02 12:51:08 +0200 |
commit | 50462a45dcbe91ef43652c235721b7a7ec0c8bb2 (patch) | |
tree | 1fdcdd15dfee8a49f38de80b5a265fb50d196723 | |
parent | f90bf565a529d0e95155992a3c243952183f25a5 (diff) | |
parent | 2277d37982bffb666b5c4bdb655d44234885e0bb (diff) | |
download | scala-50462a45dcbe91ef43652c235721b7a7ec0c8bb2.tar.gz scala-50462a45dcbe91ef43652c235721b7a7ec0c8bb2.tar.bz2 scala-50462a45dcbe91ef43652c235721b7a7ec0c8bb2.zip |
Merge pull request #5369 from lrytz/sd210
Fixes to mixin forwarders
21 files changed, 245 insertions, 93 deletions
diff --git a/src/compiler/scala/tools/nsc/CompilerCommand.scala b/src/compiler/scala/tools/nsc/CompilerCommand.scala index 9b8e9fa330..3879d7b425 100644 --- a/src/compiler/scala/tools/nsc/CompilerCommand.scala +++ b/src/compiler/scala/tools/nsc/CompilerCommand.scala @@ -107,7 +107,7 @@ class CompilerCommand(arguments: List[String], val settings: Settings) { else { val sb = new StringBuilder allSettings foreach { - case s: MultiChoiceSetting[_] if s.isHelping => sb append s.help + case s if s.isHelping => sb append s.help case _ => } sb.toString diff --git a/src/compiler/scala/tools/nsc/settings/AbsScalaSettings.scala b/src/compiler/scala/tools/nsc/settings/AbsScalaSettings.scala index 8386722b63..9d643825f6 100644 --- a/src/compiler/scala/tools/nsc/settings/AbsScalaSettings.scala +++ b/src/compiler/scala/tools/nsc/settings/AbsScalaSettings.scala @@ -30,8 +30,8 @@ trait AbsScalaSettings { type OutputSetting <: Setting def BooleanSetting(name: String, descr: String): BooleanSetting - def ChoiceSetting(name: String, helpArg: String, descr: String, choices: List[String], default: String): ChoiceSetting - def ChoiceSettingForcedDefault(name: String, helpArg: String, descr: String, choices: List[String], default: String): ChoiceSetting + def ChoiceSetting(name: String, helpArg: String, descr: String, choices: List[String], default: String, choicesHelp: List[String] = Nil): ChoiceSetting + def ChoiceSettingForcedDefault(name: String, helpArg: String, descr: String, choices: List[String], default: String, choicesHelp: List[String] = Nil): 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[E <: MultiChoiceEnumeration](name: String, helpArg: String, descr: String, domain: E, default: Option[List[String]]): MultiChoiceSetting[E] diff --git a/src/compiler/scala/tools/nsc/settings/AbsSettings.scala b/src/compiler/scala/tools/nsc/settings/AbsSettings.scala index 060a24d8d4..08fa56d8e9 100644 --- a/src/compiler/scala/tools/nsc/settings/AbsSettings.scala +++ b/src/compiler/scala/tools/nsc/settings/AbsSettings.scala @@ -88,6 +88,12 @@ trait AbsSettings extends scala.reflect.internal.settings.AbsSettings { /** Issue error and return */ def errorAndValue[T](msg: String, x: T): T = { errorFn(msg) ; x } + /** If this method returns true, print the [[help]] message and exit. */ + def isHelping: Boolean = false + + /** The help message to be printed if [[isHelping]]. */ + def help: String = "" + /** After correct Setting has been selected, tryToSet is called with the * remainder of the command line. It consumes any applicable arguments and * returns the unconsumed ones. diff --git a/src/compiler/scala/tools/nsc/settings/MutableSettings.scala b/src/compiler/scala/tools/nsc/settings/MutableSettings.scala index 9cc8faf8c2..7b4c55c2af 100644 --- a/src/compiler/scala/tools/nsc/settings/MutableSettings.scala +++ b/src/compiler/scala/tools/nsc/settings/MutableSettings.scala @@ -219,10 +219,10 @@ class MutableSettings(val errorFn: String => Unit) } def BooleanSetting(name: String, descr: String) = add(new BooleanSetting(name, descr)) - def ChoiceSetting(name: String, helpArg: String, descr: String, choices: List[String], default: String) = - add(new ChoiceSetting(name, helpArg, descr, choices, default)) - def ChoiceSettingForcedDefault(name: String, helpArg: String, descr: String, choices: List[String], default: String) = - ChoiceSetting(name, helpArg, descr, choices, default).withPostSetHook(sett => + def ChoiceSetting(name: String, helpArg: String, descr: String, choices: List[String], default: String, choicesHelp: List[String]) = + add(new ChoiceSetting(name, helpArg, descr, choices, default, choicesHelp)) + def ChoiceSettingForcedDefault(name: String, helpArg: String, descr: String, choices: List[String], default: String, choicesHelp: List[String]) = + ChoiceSetting(name, helpArg, descr, choices, default, choicesHelp).withPostSetHook(sett => if (sett.value != default) { sett.withDeprecationMessage(s"${name}:${sett.value} is deprecated, forcing use of $default") sett.value = default @@ -627,7 +627,7 @@ class MutableSettings(val errorFn: String => Unit) descr: String, val domain: E, val default: Option[List[String]] - ) extends Setting(name, s"$descr: `_' for all, `$name:help' to list") with Clearable { + ) extends Setting(name, s"$descr: `_' for all, `$name:help' to list choices.") with Clearable { withHelpSyntax(s"$name:<_,$helpArg,-$helpArg>") @@ -748,9 +748,9 @@ class MutableSettings(val errorFn: String => Unit) def contains(choice: domain.Value): Boolean = value contains choice - def isHelping: Boolean = sawHelp + override def isHelping: Boolean = sawHelp - def help: String = { + override def help: String = { val choiceLength = choices.map(_.length).max + 1 val formatStr = s" %-${choiceLength}s %s" choices.zipAll(descriptions, "", "").map { @@ -808,18 +808,33 @@ class MutableSettings(val errorFn: String => Unit) helpArg: String, descr: String, override val choices: List[String], - val default: String) - extends Setting(name, descr + choices.mkString(" (", ",", ") default:" + default)) { + val default: String, + val choicesHelp: List[String]) + extends Setting(name, + if (choicesHelp.isEmpty) s"$descr Choices: ${choices.mkString("(", ",", ")")}, default: $default." + else s"$descr Default: `$default', `help' to list choices.") { type T = String protected var v: T = default def indexOfChoice: Int = choices indexOf value - private def usageErrorMessage = f"Usage: $name:<$helpArg>%n where <$helpArg> choices are ${choices mkString ", "} (default: $default)%n" + private def choicesHelpMessage = if (choicesHelp.isEmpty) "" else { + val choiceLength = choices.map(_.length).max + 1 + val formatStr = s" %-${choiceLength}s %s%n" + choices.zipAll(choicesHelp, "", "").map({ + case (choice, desc) => formatStr.format(choice, desc) + }).mkString("") + } + private def usageErrorMessage = f"Usage: $name:<$helpArg> where <$helpArg> choices are ${choices mkString ", "} (default: $default).%n$choicesHelpMessage" + + private var sawHelp = false + override def isHelping = sawHelp + override def help = usageErrorMessage def tryToSet(args: List[String]) = errorAndValue(usageErrorMessage, None) override def tryToSetColon(args: List[String]) = args match { case Nil => errorAndValue(usageErrorMessage, None) + case List("help") => sawHelp = true; Some(Nil) case List(x) if choices contains x => value = x ; Some(Nil) case List(x) => errorAndValue("'" + x + "' is not a valid choice for '" + name + "'", None) case xs => errorAndValue("'" + name + "' does not accept multiple arguments.", None) diff --git a/src/compiler/scala/tools/nsc/settings/ScalaSettings.scala b/src/compiler/scala/tools/nsc/settings/ScalaSettings.scala index dae8539c66..e10fa3a114 100644 --- a/src/compiler/scala/tools/nsc/settings/ScalaSettings.scala +++ b/src/compiler/scala/tools/nsc/settings/ScalaSettings.scala @@ -38,8 +38,8 @@ 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 } + /** Any -option:help? */ + private def multihelp = allSettings exists { case s => s.isHelping case _ => false } /** Is an info setting set? */ def isInfo = (infoSettings exists (_.isSetByUser)) || multihelp @@ -133,7 +133,22 @@ trait ScalaSettings extends AbsScalaSettings val XnoPatmatAnalysis = BooleanSetting ("-Xno-patmat-analysis", "Don't perform exhaustivity/unreachability analysis. Also, ignore @switch annotation.") val XfullLubs = BooleanSetting ("-Xfull-lubs", "Retains pre 2.10 behavior of less aggressive truncation of least upper bounds.") - val XgenMixinForwarders = BooleanSetting("-Xgen-mixin-forwarders", "Generate forwarder methods in classes inhering concrete methods from traits.") + + val XmixinForceForwarders = ChoiceSetting( + name = "-Xmixin-force-forwarders", + helpArg = "mode", + descr = "Generate forwarder methods in classes inhering concrete methods from traits.", + choices = List("true", "junit", "false"), + default = "junit", + choicesHelp = List( + "Always generate mixin forwarders.", + "Generate mixin forwarders for JUnit-annotated methods (JUnit 4 does not support default methods).", + "Only generate mixin forwarders required for program correctness.")) + + object mixinForwarderChoices { + def isTruthy = XmixinForceForwarders.value == "true" + def isJunit = isTruthy || XmixinForceForwarders.value == "junit" + } // XML parsing options object XxmlSettings extends MultiChoiceEnumeration { @@ -143,7 +158,7 @@ trait ScalaSettings extends AbsScalaSettings val Xxml = MultiChoiceSetting( name = "-Xxml", helpArg = "property", - descr = "Configure XML parsing", + descr = "Configure XML parsing.", domain = XxmlSettings ) @@ -169,7 +184,7 @@ trait ScalaSettings extends AbsScalaSettings val Ycompacttrees = BooleanSetting ("-Ycompact-trees", "Use compact tree printer when displaying trees.") val noCompletion = BooleanSetting ("-Yno-completion", "Disable tab-completion in the REPL.") val debug = BooleanSetting ("-Ydebug", "Increase the quantity of debugging output.") - val termConflict = ChoiceSetting ("-Yresolve-term-conflict", "strategy", "Resolve term conflicts", List("package", "object", "error"), "error") + val termConflict = ChoiceSetting ("-Yresolve-term-conflict", "strategy", "Resolve term conflicts.", List("package", "object", "error"), "error") val log = PhasesSetting ("-Ylog", "Log operations during") val Ylogcp = BooleanSetting ("-Ylog-classpath", "Output information about what classpath is being applied.") val Ynogenericsig = BooleanSetting ("-Yno-generic-signatures", "Suppress generation of generic signatures for Java.") @@ -193,7 +208,7 @@ trait ScalaSettings extends AbsScalaSettings val Yrangepos = BooleanSetting ("-Yrangepos", "Use range positions for syntax trees.") val Ymemberpos = StringSetting ("-Yshow-member-pos", "output style", "Show start and end positions of members", "") withPostSetHook (_ => Yrangepos.value = true) val Yreifycopypaste = BooleanSetting ("-Yreify-copypaste", "Dump the reified trees in copypasteable representation.") - val Ymacroexpand = ChoiceSetting ("-Ymacro-expand", "policy", "Control expansion of macros, useful for scaladoc and presentation compiler", List(MacroExpand.Normal, MacroExpand.None, MacroExpand.Discard), MacroExpand.Normal) + val Ymacroexpand = ChoiceSetting ("-Ymacro-expand", "policy", "Control expansion of macros, useful for scaladoc and presentation compiler.", List(MacroExpand.Normal, MacroExpand.None, MacroExpand.Discard), MacroExpand.Normal) val Ymacronoexpand = BooleanSetting ("-Ymacro-no-expand", "Don't expand macros. Might be useful for scaladoc and presentation compiler, but will crash anything which uses macros and gets past typer.") withDeprecationMessage(s"Use ${Ymacroexpand.name}:${MacroExpand.None}") withPostSetHook(_ => Ymacroexpand.value = MacroExpand.None) val Yreplsync = BooleanSetting ("-Yrepl-sync", "Do not use asynchronous code for repl startup") val Yreplclassbased = BooleanSetting ("-Yrepl-class-based", "Use classes to wrap REPL snippets instead of objects") diff --git a/src/compiler/scala/tools/nsc/settings/Warnings.scala b/src/compiler/scala/tools/nsc/settings/Warnings.scala index 7ef606b6ef..839e734abc 100644 --- a/src/compiler/scala/tools/nsc/settings/Warnings.scala +++ b/src/compiler/scala/tools/nsc/settings/Warnings.scala @@ -25,8 +25,6 @@ trait Warnings { // currently considered too noisy for general use val warnUnusedImport = BooleanSetting("-Ywarn-unused-import", "Warn when imports are unused.") - val nowarnDefaultJunitMethods = BooleanSetting("-Ynowarn-default-junit-methods", "Don't warn when a JUnit @Test method is generated as a default method (not supported in JUnit 4).") - // Experimental lint warnings that are turned off, but which could be turned on programmatically. // They are not activated by -Xlint and can't be enabled on the command line because they are not // created using the standard factory methods. diff --git a/src/compiler/scala/tools/nsc/transform/Mixin.scala b/src/compiler/scala/tools/nsc/transform/Mixin.scala index d462b00261..582c51b90d 100644 --- a/src/compiler/scala/tools/nsc/transform/Mixin.scala +++ b/src/compiler/scala/tools/nsc/transform/Mixin.scala @@ -215,51 +215,59 @@ abstract class Mixin extends InfoTransform with ast.TreeDSL with AccessorSynthes case NoSymbol => val isMemberOfClazz = clazz.info.findMember(member.name, 0, 0L, stableOnly = false).alternatives.contains(member) if (isMemberOfClazz) { - def genForwarder(): Unit = { - cloneAndAddMixinMember(mixinClass, member).asInstanceOf[TermSymbol] setAlias member + def genForwarder(required: Boolean): Unit = { + val owner = member.owner + if (owner.isJavaDefined && owner.isInterface && !clazz.parentSymbols.contains(owner)) { + val text = s"Unable to implement a mixin forwarder for $member in $clazz unless interface ${owner.name} is directly extended by $clazz." + if (required) reporter.error(clazz.pos, text) + else warning(clazz.pos, text) + } else + cloneAndAddMixinMember(mixinClass, member).asInstanceOf[TermSymbol] setAlias member } - if (settings.XgenMixinForwarders) genForwarder() - else { - - // `member` is a concrete method defined in `mixinClass`, which is a base class of - // `clazz`, and the method is not overridden in `clazz`. A forwarder is needed if: - // - // - A non-trait base class of `clazz` defines a matching method. Example: - // class C {def f: Int}; trait T extends C {def f = 1}; class D extends T - // Even if C.f is abstract, the forwarder in D is needed, otherwise the JVM would - // resolve `D.f` to `C.f`, see jvms-6.5.invokevirtual. - // - // - There exists another concrete, matching method in a parent interface `p` of - // `clazz`, and the `mixinClass` does not itself extend `p`. In this case the - // forwarder is needed to disambiguate. Example: - // trait T1 {def f = 1}; trait T2 extends T1 {override def f = 2}; class C extends T2 - // In C we don't need a forwarder for f because T2 extends T1, so the JVM resolves - // C.f to T2.f non-ambiguously. See jvms-5.4.3.3, "maximally-specific method". - // trait U1 {def f = 1}; trait U2 {self:U1 => override def f = 2}; class D extends U2 - // In D the forwarder is needed, the interfaces U1 and U2 are unrelated at the JVM - // level. - - @tailrec - def existsCompetingMethod(baseClasses: List[Symbol]): Boolean = baseClasses match { - case baseClass :: rest => - if (baseClass ne mixinClass) { - val m = member.overriddenSymbol(baseClass) - val isCompeting = m.exists && { - !m.owner.isTraitOrInterface || - (!m.isDeferred && !mixinClass.isNonBottomSubClass(m.owner)) - } - isCompeting || existsCompetingMethod(rest) - } else existsCompetingMethod(rest) - - case _ => false - } - - if (existsCompetingMethod(clazz.baseClasses)) - genForwarder() - else if (!settings.nowarnDefaultJunitMethods && JUnitTestClass.exists && member.hasAnnotation(JUnitTestClass)) - warning(member.pos, "JUnit tests in traits that are compiled as default methods are not executed by JUnit 4. JUnit 5 will fix this issue.") + // `member` is a concrete method defined in `mixinClass`, which is a base class of + // `clazz`, and the method is not overridden in `clazz`. A forwarder is needed if: + // + // - A non-trait base class of `clazz` defines a matching method. Example: + // class C {def f: Int}; trait T extends C {def f = 1}; class D extends T + // Even if C.f is abstract, the forwarder in D is needed, otherwise the JVM would + // resolve `D.f` to `C.f`, see jvms-6.5.invokevirtual. + // + // - There exists another concrete, matching method in a parent interface `p` of + // `clazz`, and the `mixinClass` does not itself extend `p`. In this case the + // forwarder is needed to disambiguate. Example: + // trait T1 {def f = 1}; trait T2 extends T1 {override def f = 2}; class C extends T2 + // In C we don't need a forwarder for f because T2 extends T1, so the JVM resolves + // C.f to T2.f non-ambiguously. See jvms-5.4.3.3, "maximally-specific method". + // trait U1 {def f = 1}; trait U2 {self:U1 => override def f = 2}; class D extends U2 + // In D the forwarder is needed, the interfaces U1 and U2 are unrelated at the JVM + // level. + + @tailrec + def existsCompetingMethod(baseClasses: List[Symbol]): Boolean = baseClasses match { + case baseClass :: rest => + if (baseClass ne mixinClass) { + val m = member.overriddenSymbol(baseClass) + val isCompeting = m.exists && { + !m.owner.isTraitOrInterface || + (!m.isDeferred && !mixinClass.isNonBottomSubClass(m.owner)) + } + isCompeting || existsCompetingMethod(rest) + } else existsCompetingMethod(rest) + + case _ => false } + + def generateJUnitForwarder: Boolean = { + settings.mixinForwarderChoices.isJunit && + member.annotations.nonEmpty && + JUnitAnnotations.exists(annot => annot.exists && member.hasAnnotation(annot)) + } + + if (existsCompetingMethod(clazz.baseClasses) || generateJUnitForwarder) + genForwarder(required = true) + else if (settings.mixinForwarderChoices.isTruthy) + genForwarder(required = false) } case _ => diff --git a/src/compiler/scala/tools/nsc/typechecker/SuperAccessors.scala b/src/compiler/scala/tools/nsc/typechecker/SuperAccessors.scala index 49d892e04f..a38d21fe10 100644 --- a/src/compiler/scala/tools/nsc/typechecker/SuperAccessors.scala +++ b/src/compiler/scala/tools/nsc/typechecker/SuperAccessors.scala @@ -146,7 +146,22 @@ abstract class SuperAccessors extends transform.Transform with transform.TypingT val intermediateClasses = clazz.info.baseClasses.tail.takeWhile(_ != sym.owner) intermediateClasses.map(sym.overridingSymbol).find(s => s.isDeferred && !s.isAbstractOverride && !s.owner.isTrait).foreach { absSym => - reporter.error(sel.pos, s"${sym.fullLocationString} cannot be directly accessed from ${clazz} because ${absSym.owner} redeclares it as abstract") + reporter.error(sel.pos, s"${sym.fullLocationString} cannot be directly accessed from $clazz because ${absSym.owner} redeclares it as abstract") + } + } else if (mix != tpnme.EMPTY) { + // SD-143: a call super[T].m that resolves to A.m cannot be translated to correct bytecode if + // - A is a class (not a trait / interface), but not the direct superclass. Invokespecial + // would select an overriding method in the direct superclass, rather than A.m. + // https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-6.html#jvms-6.5.invokespecial + // - A is a java-defined interface and not listed as direct parent of the class. In this + // case, `invokespecial A.m` would be invalid. + val owner = sym.owner + if (!owner.isTrait && owner != clazz.superClass) { + reporter.error(sel.pos, + s"cannot emit super call: the selected $sym is declared in $owner, which is not the direct superclass of $clazz.\n" + + s"An unqualified super call (super.${sym.name}) would be allowed.") + } else if (owner.isInterface && owner.isJavaDefined && !clazz.parentSymbols.contains(owner)) { + reporter.error(sel.pos, s"unable to emit super call unless interface ${owner.name} (which declares $sym) is directly extended by $clazz.") } } diff --git a/src/compiler/scala/tools/nsc/typechecker/Typers.scala b/src/compiler/scala/tools/nsc/typechecker/Typers.scala index 3360599c1b..d8183ea8df 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Typers.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Typers.scala @@ -2040,7 +2040,7 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper override def matches(sym: Symbol, sym1: Symbol) = if (sym.isSkolem) matches(sym.deSkolemize, sym1) else if (sym1.isSkolem) matches(sym, sym1.deSkolemize) - else super[SubstTypeMap].matches(sym, sym1) + else super.matches(sym, sym1) } // allow defaults on by-name parameters if (sym hasFlag BYNAMEPARAM) diff --git a/src/reflect/scala/reflect/internal/Definitions.scala b/src/reflect/scala/reflect/internal/Definitions.scala index 89d1b0637a..0f7cf07f08 100644 --- a/src/reflect/scala/reflect/internal/Definitions.scala +++ b/src/reflect/scala/reflect/internal/Definitions.scala @@ -1154,7 +1154,6 @@ trait Definitions extends api.StandardDefinitions { lazy val ElidableMethodClass = requiredClass[scala.annotation.elidable] lazy val ImplicitNotFoundClass = requiredClass[scala.annotation.implicitNotFound] lazy val ImplicitAmbiguousClass = getClassIfDefined("scala.annotation.implicitAmbiguous") - lazy val JUnitTestClass = getClassIfDefined("org.junit.Test") lazy val MigrationAnnotationClass = requiredClass[scala.annotation.migration] lazy val ScalaStrictFPAttr = requiredClass[scala.annotation.strictfp] lazy val SwitchClass = requiredClass[scala.annotation.switch] @@ -1196,6 +1195,8 @@ trait Definitions extends api.StandardDefinitions { lazy val MethodTargetClass = requiredClass[meta.companionMethod] // TODO: module, moduleClass? package, packageObject? lazy val LanguageFeatureAnnot = requiredClass[meta.languageFeature] + lazy val JUnitAnnotations = List("Test", "Ignore", "Before", "After", "BeforeClass", "AfterClass").map(n => getClassIfDefined("org.junit." + n)) + // Language features lazy val languageFeatureModule = getRequiredModule("scala.languageFeature") diff --git a/src/reflect/scala/reflect/runtime/JavaUniverseForce.scala b/src/reflect/scala/reflect/runtime/JavaUniverseForce.scala index d56cecc965..b74ccb9177 100644 --- a/src/reflect/scala/reflect/runtime/JavaUniverseForce.scala +++ b/src/reflect/scala/reflect/runtime/JavaUniverseForce.scala @@ -379,7 +379,6 @@ trait JavaUniverseForce { self: runtime.JavaUniverse => definitions.ElidableMethodClass definitions.ImplicitNotFoundClass definitions.ImplicitAmbiguousClass - definitions.JUnitTestClass definitions.MigrationAnnotationClass definitions.ScalaStrictFPAttr definitions.SwitchClass @@ -417,6 +416,7 @@ trait JavaUniverseForce { self: runtime.JavaUniverse => definitions.ClassTargetClass definitions.MethodTargetClass definitions.LanguageFeatureAnnot + definitions.JUnitAnnotations definitions.languageFeatureModule definitions.metaAnnotations definitions.AnnotationDefaultAttr diff --git a/src/scaladoc/scala/tools/nsc/doc/Settings.scala b/src/scaladoc/scala/tools/nsc/doc/Settings.scala index 063a949323..fbb2dd9f87 100644 --- a/src/scaladoc/scala/tools/nsc/doc/Settings.scala +++ b/src/scaladoc/scala/tools/nsc/doc/Settings.scala @@ -22,7 +22,7 @@ class Settings(error: String => Unit, val printMsg: String => Unit = println(_)) val docformat = ChoiceSetting ( "-doc-format", "format", - "Selects in which format documentation is rendered", + "Selects in which format documentation is rendered.", List("html"), "html" ) diff --git a/test/files/neg/choices.check b/test/files/neg/choices.check index df4f23461f..2449cadcd6 100644 --- a/test/files/neg/choices.check +++ b/test/files/neg/choices.check @@ -1,5 +1,4 @@ -error: Usage: -Yresolve-term-conflict:<strategy> - where <strategy> choices are package, object, error (default: error) +error: Usage: -Yresolve-term-conflict:<strategy> where <strategy> choices are package, object, error (default: error). error: bad option: '-Yresolve-term-conflict' error: bad options: -Yresolve-term-conflict error: flags file may only contain compiler options, found: -Yresolve-term-conflict diff --git a/test/files/neg/nowarnDefaultJunitMethods.check b/test/files/neg/nowarnDefaultJunitMethods.check deleted file mode 100644 index 7efdcc299a..0000000000 --- a/test/files/neg/nowarnDefaultJunitMethods.check +++ /dev/null @@ -1,6 +0,0 @@ -C_1.scala:2: warning: JUnit tests in traits that are compiled as default methods are not executed by JUnit 4. JUnit 5 will fix this issue. - @org.junit.Test def foo = 0 - ^ -error: No warnings can be incurred under -Xfatal-warnings. -one warning found -one error found diff --git a/test/files/neg/nowarnDefaultJunitMethods.flags b/test/files/neg/nowarnDefaultJunitMethods.flags deleted file mode 100644 index 85d8eb2ba2..0000000000 --- a/test/files/neg/nowarnDefaultJunitMethods.flags +++ /dev/null @@ -1 +0,0 @@ --Xfatal-warnings diff --git a/test/files/neg/nowarnDefaultJunitMethods/C_1.scala b/test/files/neg/nowarnDefaultJunitMethods/C_1.scala deleted file mode 100644 index e2565a48bc..0000000000 --- a/test/files/neg/nowarnDefaultJunitMethods/C_1.scala +++ /dev/null @@ -1,5 +0,0 @@ -trait T { - @org.junit.Test def foo = 0 -} - -class C extends T diff --git a/test/files/neg/nowarnDefaultJunitMethods/Test.java b/test/files/neg/nowarnDefaultJunitMethods/Test.java deleted file mode 100644 index e8d64c2cc8..0000000000 --- a/test/files/neg/nowarnDefaultJunitMethods/Test.java +++ /dev/null @@ -1,3 +0,0 @@ -package org.junit; - -public @interface Test { } diff --git a/test/files/run/junitForwarders/C_1.scala b/test/files/run/junitForwarders/C_1.scala new file mode 100644 index 0000000000..2af2026a61 --- /dev/null +++ b/test/files/run/junitForwarders/C_1.scala @@ -0,0 +1,15 @@ +trait T { + @org.junit.Test def foo = 0 +} + +class C extends T + +object Test extends App { + def check(c: Class[_], e: String) = { + val s = c.getDeclaredMethods.sortBy(_.getName).map(m => s"${m.getName} - ${m.getDeclaredAnnotations.mkString(", ")}").mkString(";") + assert(s == e, s"found: $s\nexpected: $e") + } + check(classOf[C], "foo - @org.junit.Test()") + // TODO scala-dev#213: should `foo$` really carry the @Test annotation? + check(classOf[T], "$init$ - ;foo - @org.junit.Test();foo$ - @org.junit.Test()") +} diff --git a/test/files/run/junitForwarders/Test.java b/test/files/run/junitForwarders/Test.java new file mode 100644 index 0000000000..57c4d5b544 --- /dev/null +++ b/test/files/run/junitForwarders/Test.java @@ -0,0 +1,10 @@ +package org.junit; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.METHOD}) +public @interface Test { } diff --git a/test/junit/scala/collection/immutable/PagedSeqTest.scala b/test/junit/scala/collection/immutable/PagedSeqTest.scala index 74f8825307..6c974db884 100644 --- a/test/junit/scala/collection/immutable/PagedSeqTest.scala +++ b/test/junit/scala/collection/immutable/PagedSeqTest.scala @@ -2,13 +2,14 @@ package scala.collection.immutable import org.junit.runner.RunWith import org.junit.runners.JUnit4 -import org.junit.Test +import org.junit.{Ignore, Test} import org.junit.Assert._ @RunWith(classOf[JUnit4]) class PagedSeqTest { // should not NPE, and should equal the given Seq @Test + @Ignore("This tests a non-stack safe method in a deprecated class that requires ~1.5M stack, disabling") def test_SI6615(): Unit = { assertEquals(Seq('a'), PagedSeq.fromStrings(List.fill(5000)("a")).slice(4096, 4097)) } diff --git a/test/junit/scala/lang/traits/BytecodeTest.scala b/test/junit/scala/lang/traits/BytecodeTest.scala index e6c74b86ab..7bfa3362d4 100644 --- a/test/junit/scala/lang/traits/BytecodeTest.scala +++ b/test/junit/scala/lang/traits/BytecodeTest.scala @@ -9,7 +9,6 @@ import scala.collection.JavaConverters._ import scala.tools.asm.Opcodes import scala.tools.asm.Opcodes._ import scala.tools.asm.tree.ClassNode -import scala.tools.nsc.backend.jvm.opt.BytecodeUtils import scala.tools.partest.ASMConverters._ import scala.tools.testing.BytecodeTesting import scala.tools.testing.BytecodeTesting._ @@ -237,7 +236,7 @@ class BytecodeTest extends BytecodeTesting { |class C extends T """.stripMargin val List(c1, _) = compileClasses(code) - val List(c2, _) = newCompiler(extraArgs = "-Xgen-mixin-forwarders").compileClasses(code) + val List(c2, _) = newCompiler(extraArgs = "-Xmixin-force-forwarders:true").compileClasses(code) assert(getMethods(c1, "f").isEmpty) assertSameCode(getMethod(c2, "f"), List(VarOp(ALOAD, 0), Invoke(INVOKESTATIC, "T", "f$", "(LT;)I", true), Op(IRETURN))) @@ -245,7 +244,6 @@ class BytecodeTest extends BytecodeTesting { @Test def sd143(): Unit = { - // this tests the status quo, which is wrong. val code = """class A { def m = 1 } |class B extends A { override def m = 2 } @@ -254,10 +252,96 @@ class BytecodeTest extends BytecodeTesting { | override def m = super[T].m // should invoke A.m |} """.stripMargin - val List(_, _, c, _) = compileClasses(code) - // even though the bytecode refers to A.m, invokespecial will resolve to B.m - assertSameCode(getMethod(c, "m"), - List(VarOp(ALOAD, 0), Invoke(INVOKESPECIAL, "A", "m", "()I", false), Op(IRETURN))) + + val err = + """cannot emit super call: the selected method m is declared in class A, which is not the direct superclass of class C. + |An unqualified super call (super.m) would be allowed.""".stripMargin + val cls = compileClasses(code, allowMessage = _.msg contains err) + assert(cls.isEmpty, cls.map(_.name)) + } + + @Test + def sd143b(): Unit = { + val jCode = List("interface A { default int m() { return 1; } }" -> "A.java") + val code = + """class B extends A { override def m = 2 } + |trait T extends A + |class C extends B with T { + | override def m = super[T].m + |} + """.stripMargin + + val err = "unable to emit super call unless interface A (which declares method m) is directly extended by class C" + val cls = compileClasses(code, jCode, allowMessage = _.msg contains err) + assert(cls.isEmpty, cls.map(_.name)) + } + + @Test + def sd210(): Unit = { + val forwardersCompiler = newCompiler(extraArgs = "-Xmixin-force-forwarders:true") + val jCode = List("interface A { default int m() { return 1; } }" -> "A.java") + + + // used to crash in the backend (SD-210) under `-Xmixin-force-forwarders:true` + val code1 = + """trait B1 extends A // called "B1" not "B" due to scala-dev#214 + |class C extends B1 + """.stripMargin + + val List(_, c1a) = compileClasses(code1, jCode) + assert(getAsmMethods(c1a, "m").isEmpty) // ok, no forwarder + + // here we test a warning. without `-Xmixin-force-forwarders:true`, the forwarder would not be + // generated, it is not necessary for correctness. + val warn = "Unable to implement a mixin forwarder for method m in class C unless interface A is directly extended by class C" + val List(_, c1b) = forwardersCompiler.compileClasses(code1, jCode, allowMessage = _.msg.contains(warn)) + assert(getAsmMethods(c1a, "m").isEmpty) // no forwarder + + + val code2 = + """abstract class B { def m(): Int } + |trait T extends B with A + |class C extends T + """.stripMargin + + // here we test a compilation error. the forwarder is required for correctness, but it cannot be generated. + val err = "Unable to implement a mixin forwarder for method m in class C unless interface A is directly extended by class C" + val cs = compileClasses(code2, jCode, allowMessage = _.msg contains err) + assert(cs.isEmpty, cs.map(_.name)) + + + val code3 = + """abstract class B { def m: Int } + |class C extends B with A + """.stripMargin + + val List(_, c3) = compileClasses(code3, jCode) + // invokespecial to A.m is correct here: A is an interface, so resolution starts at A. + // https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-6.html#jvms-6.5.invokespecial + val ins3 = getMethod(c3, "m").instructions + assert(ins3 contains Invoke(INVOKESPECIAL, "A", "m", "()I", true), ins3.stringLines) + + + val code4 = + """trait B { self: A => override def m = 2 } + |class C extends A with B // forwarder, invokestatic B.m$ + """.stripMargin + + val List(_, c4) = compileClasses(code4, jCode) + val ins4 = getMethod(c4, "m").instructions + assert(ins4 contains Invoke(INVOKESTATIC, "B", "m$", "(LB;)I", true), ins4.stringLines) + + + // scala-only example + val code5 = + """trait AS { def m = 1 } + |abstract class B { def m: Int } + |class C extends B with AS // forwarder, invokestatic AS.m$ + """.stripMargin + + val List(_, _, c5) = compileClasses(code5) + val ins5 = getMethod(c5, "m").instructions + assert(ins5 contains Invoke(INVOKESTATIC, "AS", "m$", "(LAS;)I", true), ins5.stringLines) } } |