diff --git a/compiler/src/dotty/tools/dotc/Run.scala b/compiler/src/dotty/tools/dotc/Run.scala index d75042d5a238..166bf5ad9342 100644 --- a/compiler/src/dotty/tools/dotc/Run.scala +++ b/compiler/src/dotty/tools/dotc/Run.scala @@ -16,7 +16,9 @@ import io.{AbstractFile, PlainFile, VirtualFile} import Phases.unfusedPhases import util._ -import reporting.Reporter +import reporting.{Reporter, Suppression, Action} +import reporting.Diagnostic +import reporting.Diagnostic.Warning import rewrites.Rewrites import profile.Profiler @@ -96,6 +98,60 @@ class Run(comp: Compiler, ictx: Context) extends ImplicitRunInfo with Constraint private var myUnitsCached: List[CompilationUnit] = _ private var myFiles: Set[AbstractFile] = _ + // `@nowarn` annotations by source file, populated during typer + private val mySuppressions: mutable.LinkedHashMap[SourceFile, mutable.ListBuffer[Suppression]] = mutable.LinkedHashMap.empty + // source files whose `@nowarn` annotations are processed + private val mySuppressionsComplete: mutable.Set[SourceFile] = mutable.Set.empty + // warnings issued before a source file's `@nowarn` annotations are processed, suspended so that `@nowarn` can filter them + private val mySuspendedMessages: mutable.LinkedHashMap[SourceFile, mutable.LinkedHashSet[Warning]] = mutable.LinkedHashMap.empty + + object suppressions: + // When the REPL creates a new run (ReplDriver.compile), parsing is already done in the old context, with the + // previous Run. Parser warnings were suspended in the old run and need to be copied over so they are not lost. + // Same as scala/scala/commit/79ca1408c7. + def initSuspendedMessages(oldRun: Run) = if oldRun != null then + mySuspendedMessages.clear() + mySuspendedMessages ++= oldRun.mySuspendedMessages + + def suppressionsComplete(source: SourceFile) = source == NoSource || mySuppressionsComplete(source) + + def addSuspendedMessage(warning: Warning) = + mySuspendedMessages.getOrElseUpdate(warning.pos.source, mutable.LinkedHashSet.empty) += warning + + def nowarnAction(dia: Diagnostic): Action.Warning.type | Action.Verbose.type | Action.Silent.type = + mySuppressions.getOrElse(dia.pos.source, Nil).find(_.matches(dia)) match { + case Some(s) => + s.markUsed() + if (s.verbose) Action.Verbose + else Action.Silent + case _ => + Action.Warning + } + + def addSuppression(sup: Suppression): Unit = + val source = sup.annotPos.source + mySuppressions.getOrElseUpdate(source, mutable.ListBuffer.empty) += sup + + def reportSuspendedMessages(source: SourceFile)(using Context): Unit = { + // sort suppressions. they are not added in any particular order because of lazy type completion + for (sups <- mySuppressions.get(source)) + mySuppressions(source) = sups.sortBy(sup => 0 - sup.start) + mySuppressionsComplete += source + mySuspendedMessages.remove(source).foreach(_.foreach(ctx.reporter.issueIfNotSuppressed)) + } + + def runFinished(hasErrors: Boolean): Unit = + // report suspended messages (in case the run finished before typer) + mySuspendedMessages.keysIterator.toList.foreach(reportSuspendedMessages) + // report unused nowarns only if all all phases are done + if !hasErrors && ctx.settings.WunusedHas.nowarn then + for { + source <- mySuppressions.keysIterator.toList + sups <- mySuppressions.remove(source) + sup <- sups.reverse + } if (!sup.used) + report.warning("@nowarn annotation does not suppress any warnings", sup.annotPos) + /** The compilation units currently being compiled, this may return different * results over time. */ @@ -222,7 +278,9 @@ class Run(comp: Compiler, ictx: Context) extends ImplicitRunInfo with Constraint runCtx.setProfiler(Profiler()) unfusedPhases.foreach(_.initContext(runCtx)) runPhases(using runCtx) - if (!ctx.reporter.hasErrors) Rewrites.writeBack() + if (!ctx.reporter.hasErrors) + Rewrites.writeBack() + suppressions.runFinished(hasErrors = ctx.reporter.hasErrors) while (finalizeActions.nonEmpty) { val action = finalizeActions.remove(0) action() diff --git a/compiler/src/dotty/tools/dotc/config/CompilerCommand.scala b/compiler/src/dotty/tools/dotc/config/CompilerCommand.scala index 7743dcd3cba7..29a02b7f7a29 100644 --- a/compiler/src/dotty/tools/dotc/config/CompilerCommand.scala +++ b/compiler/src/dotty/tools/dotc/config/CompilerCommand.scala @@ -13,14 +13,17 @@ abstract class CompilerCommand extends CliCommand: type ConcreteSettings = ScalaSettings final def helpMsg(using settings: ScalaSettings)(using SettingsState, Context): String = - if (settings.help.value) usageMessage - else if (settings.Vhelp.value) vusageMessage - else if (settings.Whelp.value) wusageMessage - else if (settings.Xhelp.value) xusageMessage - else if (settings.Yhelp.value) yusageMessage - else if (settings.showPlugins.value) ctx.base.pluginDescriptions - else if (settings.XshowPhases.value) phasesMessage - else "" + settings.allSettings.find(isHelping) match + case Some(s) => s.description + case _ => + if (settings.help.value) usageMessage + else if (settings.Vhelp.value) vusageMessage + else if (settings.Whelp.value) wusageMessage + else if (settings.Xhelp.value) xusageMessage + else if (settings.Yhelp.value) yusageMessage + else if (settings.showPlugins.value) ctx.base.pluginDescriptions + else if (settings.XshowPhases.value) phasesMessage + else "" final def isHelpFlag(using settings: ScalaSettings)(using SettingsState): Boolean = import settings._ diff --git a/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala b/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala index e89c8b97aea8..ce6bc0257f2b 100644 --- a/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala +++ b/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala @@ -1,11 +1,13 @@ package dotty.tools.dotc package config +import dotty.tools.dotc.config.PathResolver.Defaults +import dotty.tools.dotc.config.Settings.{Setting, SettingGroup} import dotty.tools.dotc.core.Contexts._ -import dotty.tools.io.{ Directory, PlainDirectory, AbstractFile, JDK9Reflectors } -import PathResolver.Defaults -import rewrites.Rewrites -import Settings.{ Setting, SettingGroup } +import dotty.tools.dotc.rewrites.Rewrites +import dotty.tools.io.{AbstractFile, Directory, JDK9Reflectors, PlainDirectory} + +import scala.util.chaining._ class ScalaSettings extends SettingGroup with AllScalaSettings @@ -51,7 +53,7 @@ trait AllScalaSettings extends CommonScalaSettings, VerboseSettings, WarningSett val explain: Setting[Boolean] = BooleanSetting("-explain", "Explain errors in more detail.", aliases = List("--explain")) val feature: Setting[Boolean] = BooleanSetting("-feature", "Emit warning and location for usages of features that should be imported explicitly.", aliases = List("--feature")) val source: Setting[String] = ChoiceSetting("-source", "source version", "source version", List("3.0", "future", "3.0-migration", "future-migration"), "3.0", aliases = List("--source")) - val unchecked: Setting[Boolean] = BooleanSetting("-unchecked", "Enable additional warnings where generated code depends on assumptions.", aliases = List("--unchecked")) + val unchecked: Setting[Boolean] = BooleanSetting("-unchecked", "Enable additional warnings where generated code depends on assumptions.", initialValue = true, aliases = List("--unchecked")) val uniqid: Setting[Boolean] = BooleanSetting("-uniqid", "Uniquely tag all identifiers in debugging output.", aliases = List("--unique-id")) val language: Setting[List[String]] = MultiStringSetting("-language", "feature", "Enable one or more language features.", aliases = List("--language")) val rewrite: Setting[Option[Rewrites]] = OptionSetting[Rewrites]("-rewrite", "When used in conjunction with a `...-migration` source version, rewrites sources to migrate to new version.", aliases = List("--rewrite")) @@ -133,6 +135,64 @@ private sealed trait WarningSettings: val Whelp: Setting[Boolean] = BooleanSetting("-W", "Print a synopsis of warning options.") val XfatalWarnings: Setting[Boolean] = BooleanSetting("-Werror", "Fail the compilation if there are any warnings.", aliases = List("-Xfatal-warnings")) + val Wunused: Setting[List[String]] = MultiChoiceSetting( + name = "-Wunused", + helpArg = "warning", + descr = "Enable or disable specific `unused` warnings", + choices = List("nowarn", "all"), + default = Nil + ) + object WunusedHas: + def allOr(s: String)(using Context) = Wunused.value.pipe(us => us.contains("all") || us.contains(s)) + def nowarn(using Context) = allOr("nowarn") + + val Wconf: Setting[List[String]] = MultiStringSetting( + "-Wconf", + "patterns", + default = List(), + descr = + s"""Configure compiler warnings. + |Syntax: -Wconf::,:,... + |multiple are combined with &, i.e., &...& + | + | + | - Any message: any + | + | - Message categories: cat=deprecation, cat=feature, cat=unchecked + | + | - Message content: msg=regex + | The regex need only match some part of the message, not all of it. + | + | - Message id: id=E129 + | The message id is printed with the warning. + | + | - Message name: name=PureExpressionInStatementPosition + | The message name is printed with the warning in verbose warning mode. + | + |In verbose warning mode the compiler prints matching filters for warnings. + |Verbose mode can be enabled globally using `-Wconf:any:verbose`, or locally + |using the @nowarn annotation (example: `@nowarn("v") def test = try 1`). + | + | + | - error / e + | - warning / w + | - verbose / v (emit warning, show additional help for writing `-Wconf` filters) + | - info / i (infos are not counted as warnings and not affected by `-Werror`) + | - silent / s + | + |The default configuration is empty. + | + |User-defined configurations are added to the left. The leftmost rule matching + |a warning message defines the action. + | + |Examples: + | - change every warning into an error: -Wconf:any:error + | - silence deprecations: -Wconf:cat=deprecation:s + | + |Note: on the command-line you might need to quote configurations containing `*` or `&` + |to prevent the shell from expanding patterns.""".stripMargin, + ) + /** -X "Extended" or "Advanced" settings */ private sealed trait XSettings: self: SettingGroup => diff --git a/compiler/src/dotty/tools/dotc/config/Settings.scala b/compiler/src/dotty/tools/dotc/config/Settings.scala index 59d6e638f98d..1922007adc11 100644 --- a/compiler/src/dotty/tools/dotc/config/Settings.scala +++ b/compiler/src/dotty/tools/dotc/config/Settings.scala @@ -56,7 +56,7 @@ object Settings: description: String, default: T, helpArg: String = "", - choices: Option[Seq[T]] = None, + choices: Option[Seq[?]] = None, prefix: String = "", aliases: List[String] = Nil, depends: List[(Setting[?], Any)] = Nil, @@ -115,7 +115,13 @@ object Settings: update(Some(propertyClass.get.getConstructor().newInstance()), args) case (ListTag, _) => if (argRest.isEmpty) missingArg - else update((argRest split ",").toList, args) + else + val strings = argRest.split(",").toList + choices match + case Some(valid) => strings.filterNot(valid.contains) match + case Nil => update(strings, args) + case invalid => fail(s"invalid choice(s) for $name: ${invalid.mkString(",")}", args) + case _ => update(strings, args) case (StringTag, _) if argRest.nonEmpty || choices.exists(_.contains("")) => setString(argRest, args) case (StringTag, arg2 :: args2) => @@ -251,14 +257,17 @@ object Settings: def ChoiceSetting(name: String, helpArg: String, descr: String, choices: List[String], default: String, aliases: List[String] = Nil): Setting[String] = publish(Setting(name, descr, default, helpArg, Some(choices), aliases = aliases)) + def MultiChoiceSetting(name: String, helpArg: String, descr: String, choices: List[String], default: List[String], aliases: List[String] = Nil): Setting[List[String]] = + publish(Setting(name, descr, default, helpArg, Some(choices), aliases = aliases)) + def IntSetting(name: String, descr: String, default: Int, aliases: List[String] = Nil): Setting[Int] = publish(Setting(name, descr, default, aliases = aliases)) def IntChoiceSetting(name: String, descr: String, choices: Seq[Int], default: Int): Setting[Int] = publish(Setting(name, descr, default, choices = Some(choices))) - def MultiStringSetting(name: String, helpArg: String, descr: String, aliases: List[String] = Nil): Setting[List[String]] = - publish(Setting(name, descr, Nil, helpArg, aliases = aliases)) + def MultiStringSetting(name: String, helpArg: String, descr: String, default: List[String] = Nil, aliases: List[String] = Nil): Setting[List[String]] = + publish(Setting(name, descr, default, helpArg, aliases = aliases)) def OutputSetting(name: String, helpArg: String, descr: String, default: AbstractFile): Setting[AbstractFile] = publish(Setting(name, descr, default, helpArg)) diff --git a/compiler/src/dotty/tools/dotc/core/Contexts.scala b/compiler/src/dotty/tools/dotc/core/Contexts.scala index ced6502833e3..3c5adcba3aec 100644 --- a/compiler/src/dotty/tools/dotc/core/Contexts.scala +++ b/compiler/src/dotty/tools/dotc/core/Contexts.scala @@ -959,6 +959,8 @@ object Contexts { private[core] val reusableDataReader = ReusableInstance(new ReusableDataReader()) + private[dotc] var wConfCache: (List[String], WConf) = _ + def sharedCharArray(len: Int): Array[Char] = while len > charArray.length do charArray = new Array[Char](charArray.length * 2) diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index e4797d2fa4c1..e5701db53d56 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -903,6 +903,7 @@ class Definitions { @tu lazy val InvariantBetweenAnnot: ClassSymbol = requiredClass("scala.annotation.internal.InvariantBetween") @tu lazy val MainAnnot: ClassSymbol = requiredClass("scala.main") @tu lazy val MigrationAnnot: ClassSymbol = requiredClass("scala.annotation.migration") + @tu lazy val NowarnAnnot: ClassSymbol = requiredClass("scala.annotation.nowarn") @tu lazy val TransparentTraitAnnot: ClassSymbol = requiredClass("scala.annotation.transparentTrait") @tu lazy val NativeAnnot: ClassSymbol = requiredClass("scala.native") @tu lazy val RepeatedAnnot: ClassSymbol = requiredClass("scala.annotation.internal.Repeated") diff --git a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala index 399eabfff0f1..dd00c763b909 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala @@ -1158,10 +1158,11 @@ object Parsers { } else if !in.featureEnabled(Feature.symbolLiterals) then + val name = in.name // capture name (not `in`) in the warning message closure report.errorOrMigrationWarning( - em"""symbol literal '${in.name} is no longer supported, - |use a string literal "${in.name}" or an application Symbol("${in.name}") instead, - |or enclose in braces '{${in.name}} if you want a quoted expression. + em"""symbol literal '$name is no longer supported, + |use a string literal "$name" or an application Symbol("$name") instead, + |or enclose in braces '{$name} if you want a quoted expression. |For now, you can also `import language.deprecated.symbolLiterals` to accept |the idiom, but this possibility might no longer be available in the future.""", in.sourcePos()) diff --git a/compiler/src/dotty/tools/dotc/report.scala b/compiler/src/dotty/tools/dotc/report.scala index 912ae9af52aa..36c857b94e37 100644 --- a/compiler/src/dotty/tools/dotc/report.scala +++ b/compiler/src/dotty/tools/dotc/report.scala @@ -21,15 +21,7 @@ object report: ctx.reporter.report(new Info(msg, pos.sourcePos)) private def issueWarning(warning: Warning)(using Context): Unit = - if (!ctx.settings.silentWarnings.value) - if (ctx.settings.XfatalWarnings.value) - warning match { - case warning: ConditionalWarning if !warning.enablingOption.value => - ctx.reporter.report(warning) // conditional warnings that are not enabled are not fatal - case _ => - ctx.reporter.report(warning.toError) - } - else ctx.reporter.report(warning) + ctx.reporter.report(warning) def deprecationWarning(msg: Message, pos: SrcPos = NoSourcePosition)(using Context): Unit = issueWarning(new DeprecationWarning(msg, pos.sourcePos)) diff --git a/compiler/src/dotty/tools/dotc/reporting/ConsoleReporter.scala b/compiler/src/dotty/tools/dotc/reporting/ConsoleReporter.scala index c3d6d042e379..0609dd8c643c 100644 --- a/compiler/src/dotty/tools/dotc/reporting/ConsoleReporter.scala +++ b/compiler/src/dotty/tools/dotc/reporting/ConsoleReporter.scala @@ -21,21 +21,16 @@ class ConsoleReporter( /** Prints the message with the given position indication. */ def doReport(dia: Diagnostic)(using Context): Unit = { - val didPrint = dia match { + dia match case dia: Error => - printMessage(messageAndPos(dia.msg, dia.pos, diagnosticLevel(dia))) + printMessage(messageAndPos(dia)) if (ctx.settings.Xprompt.value) Reporter.displayPrompt(reader, writer) - true - case dia: ConditionalWarning if !dia.enablingOption.value => - false case dia => - printMessage(messageAndPos(dia.msg, dia.pos, diagnosticLevel(dia))) - true - } + printMessage(messageAndPos(dia)) - if (didPrint && shouldExplain(dia)) + if shouldExplain(dia) then printMessage(explanation(dia.msg)) - else if (didPrint && dia.msg.canExplain) + else if dia.msg.canExplain then printMessage("\nlonger explanation available when compiling with `-explain`") } diff --git a/compiler/src/dotty/tools/dotc/reporting/Diagnostic.scala b/compiler/src/dotty/tools/dotc/reporting/Diagnostic.scala index fbb7145152d6..3f498b432f98 100644 --- a/compiler/src/dotty/tools/dotc/reporting/Diagnostic.scala +++ b/compiler/src/dotty/tools/dotc/reporting/Diagnostic.scala @@ -2,12 +2,13 @@ package dotty.tools package dotc package reporting -import util.SourcePosition -import core.Contexts._ -import config.Settings.Setting -import interfaces.Diagnostic.{ERROR, INFO, WARNING} +import dotty.tools.dotc.config.Settings.Setting +import dotty.tools.dotc.core.Contexts._ +import dotty.tools.dotc.interfaces.Diagnostic.{ERROR, INFO, WARNING} +import dotty.tools.dotc.util.SourcePosition import java.util.Optional +import scala.util.chaining._ object Diagnostic: @@ -35,7 +36,9 @@ object Diagnostic: msg: Message, pos: SourcePosition ) extends Diagnostic(msg, pos, WARNING) { - def toError: Error = new Error(msg, pos) + def toError: Error = new Error(msg, pos).tap(e => if isVerbose then e.setVerbose()) + def toInfo: Info = new Info(msg, pos).tap(e => if isVerbose then e.setVerbose()) + def isSummarizedConditional(using Context): Boolean = false } class Info( @@ -48,6 +51,7 @@ object Diagnostic: pos: SourcePosition ) extends Warning(msg, pos) { def enablingOption(using Context): Setting[Boolean] + override def isSummarizedConditional(using Context): Boolean = !enablingOption.value } class FeatureWarning( @@ -81,6 +85,12 @@ class Diagnostic( val pos: SourcePosition, val level: Int ) extends Exception with interfaces.Diagnostic: + private var verbose: Boolean = false + def isVerbose: Boolean = verbose + def setVerbose(): this.type = + verbose = true + this + override def position: Optional[interfaces.SourcePosition] = if (pos.exists && pos.source.exists) Optional.of(pos) else Optional.empty() override def message: String = diff --git a/compiler/src/dotty/tools/dotc/reporting/ErrorMessageID.scala b/compiler/src/dotty/tools/dotc/reporting/ErrorMessageID.scala index ccdce7cfb1a4..d1b95d31dd62 100644 --- a/compiler/src/dotty/tools/dotc/reporting/ErrorMessageID.scala +++ b/compiler/src/dotty/tools/dotc/reporting/ErrorMessageID.scala @@ -1,7 +1,7 @@ package dotty.tools.dotc.reporting /** Unique IDs identifying the messages */ -enum ErrorMessageID extends java.lang.Enum[ErrorMessageID] { +enum ErrorMessageID extends java.lang.Enum[ErrorMessageID]: // IMPORTANT: Add new IDs only at the end and never remove IDs case @@ -177,4 +177,9 @@ enum ErrorMessageID extends java.lang.Enum[ErrorMessageID] { CannotExtendFunctionID def errorNumber = ordinal - 2 -} + +object ErrorMessageID: + def fromErrorNumber(n: Int): Option[ErrorMessageID] = + val enumId = n + 2 + if enumId >= 2 && enumId < ErrorMessageID.values.length then Some(fromOrdinal(enumId)) + else None diff --git a/compiler/src/dotty/tools/dotc/reporting/MessageRendering.scala b/compiler/src/dotty/tools/dotc/reporting/MessageRendering.scala index 4e7c7ce5bb7e..d16d15f42070 100644 --- a/compiler/src/dotty/tools/dotc/reporting/MessageRendering.scala +++ b/compiler/src/dotty/tools/dotc/reporting/MessageRendering.scala @@ -142,22 +142,43 @@ trait MessageRendering { sb.toString } + def appendFilterHelp(dia: Diagnostic, sb: mutable.StringBuilder): Unit = + import dia._ + val hasId = msg.errorId.errorNumber >= 0 + val category = dia match { + case _: UncheckedWarning => "unchecked" + case _: DeprecationWarning => "deprecation" + case _: FeatureWarning => "feature" + case _ => "" + } + if (hasId || category.nonEmpty) + sb.append(EOL).append("Matching filters for @nowarn or -Wconf:") + if (hasId) + sb.append(EOL).append(" - id=E").append(msg.errorId.errorNumber) + sb.append(EOL).append(" - name=").append(msg.errorId.productPrefix.stripSuffix("ID")) + if (category.nonEmpty) + sb.append(EOL).append(" - cat=").append(category) + /** The whole message rendered from `msg` */ - def messageAndPos(msg: Message, pos: SourcePosition, diagnosticLevel: String)(using Context): String = { + def messageAndPos(dia: Diagnostic)(using Context): String = { + import dia._ + val levelString = diagnosticLevel(dia) val sb = mutable.StringBuilder() - val posString = posStr(pos, diagnosticLevel, msg) + val posString = posStr(pos, levelString, msg) if (posString.nonEmpty) sb.append(posString).append(EOL) if (pos.exists) { val pos1 = pos.nonInlined if (pos1.exists && pos1.source.file.exists) { - val (srcBefore, srcAfter, offset) = sourceLines(pos1, diagnosticLevel) - val marker = columnMarker(pos1, offset, diagnosticLevel) + val (srcBefore, srcAfter, offset) = sourceLines(pos1, levelString) + val marker = columnMarker(pos1, offset, levelString) val err = errorMsg(pos1, msg.message, offset) sb.append((srcBefore ::: marker :: err :: outer(pos, " " * (offset - 1)) ::: srcAfter).mkString(EOL)) } else sb.append(msg.message) } else sb.append(msg.message) + if (dia.isVerbose) + appendFilterHelp(dia, sb) sb.toString } diff --git a/compiler/src/dotty/tools/dotc/reporting/Reporter.scala b/compiler/src/dotty/tools/dotc/reporting/Reporter.scala index 64a2d826075e..8c736993de8e 100644 --- a/compiler/src/dotty/tools/dotc/reporting/Reporter.scala +++ b/compiler/src/dotty/tools/dotc/reporting/Reporter.scala @@ -2,30 +2,25 @@ package dotty.tools package dotc package reporting +import dotty.tools.dotc.ast.{Trees, tpd} +import dotty.tools.dotc.core.Contexts._ +import dotty.tools.dotc.core.Decorators._ +import dotty.tools.dotc.core.Mode +import dotty.tools.dotc.core.Symbols.{NoSymbol, Symbol} +import dotty.tools.dotc.reporting.Diagnostic._ +import dotty.tools.dotc.reporting.Message._ +import dotty.tools.dotc.util.NoSourcePosition + +import java.io.{BufferedReader, PrintWriter} import scala.annotation.internal.sharable - -import core.Contexts._ -import core.Decorators._ -import collection.mutable -import core.Mode -import dotty.tools.dotc.core.Symbols.{Symbol, NoSymbol} -import Diagnostic._ -import ast.{tpd, Trees} -import Message._ -import core.Decorators._ -import util.NoSourcePosition - -import java.io.{ BufferedReader, PrintWriter } +import scala.collection.mutable +import scala.util.chaining._ object Reporter { /** Convert a SimpleReporter into a real Reporter */ def fromSimpleReporter(simple: interfaces.SimpleReporter): Reporter = new Reporter with UniqueMessagePositions with HideNonSensicalMessages { - override def doReport(dia: Diagnostic)(using Context): Unit = dia match { - case dia: ConditionalWarning if !dia.enablingOption.value => - case _ => - simple.report(dia) - } + override def doReport(dia: Diagnostic)(using Context): Unit = simple.report(dia) } /** A reporter that ignores reports, and doesn't record errors */ @@ -95,6 +90,8 @@ abstract class Reporter extends interfaces.ReporterResult { finally incompleteHandler = saved } + private def isIncompleteChecking = incompleteHandler ne defaultIncompleteHandler + private var _errorCount = 0 private var _warningCount = 0 @@ -140,27 +137,63 @@ abstract class Reporter extends interfaces.ReporterResult { var unreportedWarnings: Map[String, Int] = Map.empty - def report(dia: Diagnostic)(using Context): Unit = - val isSummarized = dia match - case dia: ConditionalWarning => !dia.enablingOption.value - case _ => false - if isSummarized // avoid isHidden test for summarized warnings so that message is not forced - || !isHidden(dia) - then - withMode(Mode.Printing)(doReport(dia)) - dia match - case dia: ConditionalWarning if !dia.enablingOption.value => - val key = dia.enablingOption.name + def issueIfNotSuppressed(dia: Diagnostic)(using Context): Unit = + def go() = + import Action._ + + val toReport = dia match + case w: Warning => + def fatal(w: Warning) = if ctx.settings.XfatalWarnings.value && !w.isSummarizedConditional then Some(w.toError) else Some(w) + if ctx.settings.silentWarnings.value then None + else WConf.parsed.action(dia) match + case Silent => None + case Info => Some(w.toInfo) + case Warning => fatal(w) + case Verbose => fatal(w).tap(_.foreach(_.setVerbose())) + case Error => Some(w.toError) + case _ => Some(dia) + + toReport foreach { + case cw: ConditionalWarning if cw.isSummarizedConditional => + val key = cw.enablingOption.name unreportedWarnings = unreportedWarnings.updated(key, unreportedWarnings.getOrElse(key, 0) + 1) - case dia: Warning => _warningCount += 1 - case dia: Error => - errors = dia :: errors - _errorCount += 1 - if ctx.typerState.isGlobalCommittable then - ctx.base.errorsToBeReported = true - case dia: Info => // nothing to do here - // match error if d is something else + case d if !isHidden(d) => + withMode(Mode.Printing)(doReport(d)) + d match { + case _: Warning => _warningCount += 1 + case e: Error => + errors = e :: errors + _errorCount += 1 + if ctx.typerState.isGlobalCommittable then + ctx.base.errorsToBeReported = true + case _: Info => // nothing to do here + // match error if d is something else + } + case _ => // hidden + } + end go + + // `ctx.run` can be null in test, also in the repl when parsing the first line. The parser runs early, the Run is + // only created in ReplDriver.compile when a line is submitted. This means that `@nowarn` doesnt work on parser + // warnings in the first line. + dia match + case w: Warning if ctx.run != null => + val sup = ctx.run.suppressions + if sup.suppressionsComplete(w.pos.source) then sup.nowarnAction(w) match + case Action.Warning => go() + case Action.Verbose => w.setVerbose(); go() + case Action.Silent => + else + // ParseResult.isIncomplete creates a new source file and reporter to check if the input is complete. + // The reporter's warnings are discarded, and we should not add them to the run's suspended messages, + // otherwise they are later reported. + if !isIncompleteChecking then + sup.addSuspendedMessage(w) + case _ => go() + end issueIfNotSuppressed + + def report(dia: Diagnostic)(using Context): Unit = issueIfNotSuppressed(dia) def incomplete(dia: Diagnostic)(using Context): Unit = incompleteHandler(dia, ctx) diff --git a/compiler/src/dotty/tools/dotc/reporting/WConf.scala b/compiler/src/dotty/tools/dotc/reporting/WConf.scala new file mode 100644 index 000000000000..34a47fa3db9d --- /dev/null +++ b/compiler/src/dotty/tools/dotc/reporting/WConf.scala @@ -0,0 +1,124 @@ +package dotty.tools +package dotc +package reporting + +import dotty.tools.dotc.core.Contexts._ +import dotty.tools.dotc.util.SourcePosition + +import java.util.regex.PatternSyntaxException +import scala.annotation.internal.sharable +import scala.collection.mutable.ListBuffer +import scala.util.matching.Regex + +enum MessageFilter: + def matches(message: Diagnostic): Boolean = this match + case Any => true + case Deprecated => message.isInstanceOf[Diagnostic.DeprecationWarning] + case Feature => message.isInstanceOf[Diagnostic.FeatureWarning] + case Unchecked => message.isInstanceOf[Diagnostic.UncheckedWarning] + case MessagePattern(pattern) => + val noHighlight = message.msg.rawMessage.replaceAll("\\e\\[[\\d;]*[^\\d;]","") + pattern.findFirstIn(noHighlight).nonEmpty + case MessageID(errorId) => message.msg.errorId == errorId + case None => false + + case Any, Deprecated, Feature, Unchecked, None + case MessagePattern(pattern: Regex) + case MessageID(errorId: ErrorMessageID) + +enum Action: + case Error, Warning, Verbose, Info, Silent + +final case class WConf(confs: List[(List[MessageFilter], Action)]): + def action(message: Diagnostic): Action = confs.collectFirst { + case (filters, action) if filters.forall(_.matches(message)) => action + }.getOrElse(Action.Warning) + +object WConf: + import Action._ + import MessageFilter._ + + private type Conf = (List[MessageFilter], Action) + + def parseAction(s: String): Either[List[String], Action] = s match + case "error" | "e" => Right(Error) + case "warning" | "w" => Right(Warning) + case "verbose" | "v" => Right(Verbose) + case "info" | "i" => Right(Info) + case "silent" | "s" => Right(Silent) + case _ => Left(List(s"unknown action: `$s`")) + + private def regex(s: String) = + try Right(s.r) + catch case e: PatternSyntaxException => Left(s"invalid pattern `$s`: ${e.getMessage}") + + @sharable val Splitter = raw"([^=]+)=(.+)".r + @sharable val ErrorId = raw"E?(\d+)".r + + def parseFilters(s: String): Either[List[String], List[MessageFilter]] = + // TODO: don't split on escaped \& + val (parseErrors, filters) = s.split('&').toList.partitionMap(parseFilter) + if parseErrors.nonEmpty then Left(parseErrors) + else if filters.isEmpty then Left(List("no filters or no action defined")) + else Right(filters) + + def parseFilter(s: String): Either[String, MessageFilter] = s match + case "any" => Right(Any) + case Splitter(filter, conf) => filter match + case "msg" => regex(conf).map(MessagePattern.apply) + case "id" => conf match + case ErrorId(num) => + ErrorMessageID.fromErrorNumber(num.toInt) match + case Some(errId) => Right(MessageID(errId)) + case _ => Left(s"unknonw error message number: E$num") + case _ => + Left(s"invalid error message id: $conf") + case "name" => + try Right(MessageID(ErrorMessageID.valueOf(conf + "ID"))) + catch case _: IllegalArgumentException => Left(s"unknown error message name: $conf") + + case "cat" => conf match + case "deprecation" => Right(Deprecated) + case "feature" => Right(Feature) + case "unchecked" => Right(Unchecked) + case _ => Left(s"unknown category: $conf") + case _ => Left(s"unknown filter: $filter") + case _ => Left(s"unknown filter: $s") + + def parsed(using Context): WConf = + val setting = ctx.settings.Wconf.value + def cached = ctx.base.wConfCache + if cached == null || cached._1 != setting then + val conf = fromSettings(setting) + ctx.base.wConfCache = (setting, conf.getOrElse(WConf(Nil))) + conf.swap.foreach(msgs => + val multiHelp = + if setting.sizeIs > 1 then + """ + |Note: for multiple filters, use `-Wconf:filter1:action1,filter2:action2` + | or alternatively `-Wconf:filter1:action1 -Wconf:filter2:action2`""".stripMargin + else "" + report.warning(s"Failed to parse `-Wconf` configuration: ${ctx.settings.Wconf.value.mkString(",")}\n${msgs.mkString("\n")}$multiHelp")) + cached._2 + + def fromSettings(settings: List[String]): Either[List[String], WConf] = + if (settings.isEmpty) Right(WConf(Nil)) + else + val parsedConfs: List[Either[List[String], (List[MessageFilter], Action)]] = settings.map(conf => + val filtersAndAction = conf.split(':') + if filtersAndAction.length != 2 then Left(List("exactly one `:` expected (&...&:)")) + else + parseFilters(filtersAndAction(0)).flatMap(filters => + parseAction(filtersAndAction(1)).map((filters, _)))) + val (parseErrorss, configs) = parsedConfs.partitionMap(identity) + if (parseErrorss.nonEmpty) Left(parseErrorss.flatten) + else Right(WConf(configs)) + +class Suppression(val annotPos: SourcePosition, filters: List[MessageFilter], val start: Int, end: Int, val verbose: Boolean): + private[this] var _used = false + def used: Boolean = _used + def markUsed(): Unit = { _used = true } + + def matches(dia: Diagnostic): Boolean = + val pos = dia.pos + pos.exists && start <= pos.start && pos.end <= end && filters.forall(_.matches(dia)) diff --git a/compiler/src/dotty/tools/dotc/transform/TypeTestsCasts.scala b/compiler/src/dotty/tools/dotc/transform/TypeTestsCasts.scala index a4951b99f599..8ffe2198c4d9 100644 --- a/compiler/src/dotty/tools/dotc/transform/TypeTestsCasts.scala +++ b/compiler/src/dotty/tools/dotc/transform/TypeTestsCasts.scala @@ -353,7 +353,7 @@ object TypeTestsCasts { val argType = tree.args.head.tpe val isTrusted = tree.hasAttachment(PatternMatcher.TrustedTypeTestKey) if (!isTrusted && !checkable(expr.tpe, argType, tree.span)) - report.warning(i"the type test for $argType cannot be checked at runtime", expr.srcPos) + report.uncheckedWarning(i"the type test for $argType cannot be checked at runtime", expr.srcPos) transformTypeTest(expr, tree.args.head.tpe, flagUnrelated = true) } else if (sym.isTypeCast) diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 6c13afa219b8..866bac6b47ae 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -2077,12 +2077,43 @@ class Typer extends Namer lazy val annotCtx = annotContext(mdef, sym) // necessary in order to mark the typed ahead annotations as definitely typed: for (annot <- mdef.mods.annotations) - checkAnnotApplicable(typedAnnotation(annot)(using annotCtx), sym) + val annot1 = typedAnnotation(annot)(using annotCtx) + checkAnnotApplicable(annot1, sym) + if Annotations.annotClass(annot1) == defn.NowarnAnnot then + registerNowarn(annot1, mdef) } def typedAnnotation(annot: untpd.Tree)(using Context): Tree = checkAnnotArgs(typed(annot, defn.AnnotationClass.typeRef)) + def registerNowarn(tree: Tree, mdef: untpd.Tree)(using Context): Unit = + val annot = Annotations.Annotation(tree) + def argPos = annot.argument(0).getOrElse(tree).sourcePos + var verbose = false + val filters = annot.argumentConstantString(0) match + case None => annot.argument(0) match + case Some(t: Select) if t.name.is(DefaultGetterName) => + // default argument used for `@nowarn` and `@nowarn()` + List(MessageFilter.Any) + case _ => + report.warning(s"filter needs to be a compile-time constant string", argPos) + List(MessageFilter.None) + case Some("") => + List(MessageFilter.Any) + case Some("verbose") | Some("v") => + verbose = true + List(MessageFilter.Any) + case Some(s) => + WConf.parseFilters(s).left.map(parseErrors => + report.warning (s"Invalid message filter\n${parseErrors.mkString ("\n")}", argPos) + List(MessageFilter.None) + ).merge + val range = mdef.sourcePos + val sup = Suppression(tree.sourcePos, filters, range.start, range.end, verbose) + // invalid suppressions, don't report as unused + if filters == List(MessageFilter.None) then sup.markUsed() + ctx.run.suppressions.addSuppression(sup) + def typedValDef(vdef: untpd.ValDef, sym: Symbol)(using Context): Tree = { val ValDef(name, tpt, _) = vdef completeAnnotations(vdef, sym) @@ -2488,6 +2519,8 @@ class Typer extends Namer def typedAnnotated(tree: untpd.Annotated, pt: Type)(using Context): Tree = { val annot1 = typedExpr(tree.annot, defn.AnnotationClass.typeRef) + if Annotations.annotClass(annot1) == defn.NowarnAnnot then + registerNowarn(annot1, tree) val arg1 = typed(tree.arg, pt) if (ctx.mode is Mode.Type) { if arg1.isType then diff --git a/compiler/src/dotty/tools/dotc/typer/TyperPhase.scala b/compiler/src/dotty/tools/dotc/typer/TyperPhase.scala index 01082ba7dcbf..8141bfabc3fb 100644 --- a/compiler/src/dotty/tools/dotc/typer/TyperPhase.scala +++ b/compiler/src/dotty/tools/dotc/typer/TyperPhase.scala @@ -57,6 +57,7 @@ class TyperPhase(addRootImports: Boolean = true) extends Phase { typr.println("typed: " + unit.source) record("retained untyped trees", unit.untpdTree.treeSize) record("retained typed trees after typer", unit.tpdTree.treeSize) + ctx.run.suppressions.reportSuspendedMessages(unit.source) catch case ex: CompilationUnit.SuspendException => } diff --git a/compiler/src/dotty/tools/repl/Rendering.scala b/compiler/src/dotty/tools/repl/Rendering.scala index 5cadbb622015..81f4f584c3c7 100644 --- a/compiler/src/dotty/tools/repl/Rendering.scala +++ b/compiler/src/dotty/tools/repl/Rendering.scala @@ -129,7 +129,7 @@ private[repl] class Rendering(parentClassLoader: Option[ClassLoader] = None) { /** Formats errors using the `messageRenderer` */ def formatError(dia: Diagnostic)(implicit state: State): Diagnostic = new Diagnostic( - messageRenderer.messageAndPos(dia.msg, dia.pos, messageRenderer.diagnosticLevel(dia))(using state.context), + messageRenderer.messageAndPos(dia)(using state.context), dia.pos, dia.level ) diff --git a/compiler/src/dotty/tools/repl/ReplCompiler.scala b/compiler/src/dotty/tools/repl/ReplCompiler.scala index b98e511d3864..5d0d5ae99d95 100644 --- a/compiler/src/dotty/tools/repl/ReplCompiler.scala +++ b/compiler/src/dotty/tools/repl/ReplCompiler.scala @@ -38,34 +38,36 @@ class ReplCompiler extends Compiler { List(new PostTyper), ) - def newRun(initCtx: Context, state: State): Run = new Run(this, initCtx) { - - /** Import previous runs and user defined imports */ - override protected def rootContext(using Context): Context = { - def importContext(imp: tpd.Import)(using Context) = - ctx.importContext(imp, imp.symbol) - - def importPreviousRun(id: Int)(using Context) = { - // we first import the wrapper object id - val path = nme.REPL_PACKAGE ++ "." ++ objectNames(id) - val ctx0 = ctx.fresh - .setNewScope - .withRootImports(RootRef(() => requiredModuleRef(path)) :: Nil) - - // then its user defined imports - val imports = state.imports.getOrElse(id, Nil) - if imports.isEmpty then ctx0 - else imports.foldLeft(ctx0.fresh.setNewScope)((ctx, imp) => - importContext(imp)(using ctx)) - } + def newRun(initCtx: Context, state: State): Run = + val run = new Run(this, initCtx) { + /** Import previous runs and user defined imports */ + override protected def rootContext(using Context): Context = { + def importContext(imp: tpd.Import)(using Context) = + ctx.importContext(imp, imp.symbol) + + def importPreviousRun(id: Int)(using Context) = { + // we first import the wrapper object id + val path = nme.REPL_PACKAGE ++ "." ++ objectNames(id) + val ctx0 = ctx.fresh + .setNewScope + .withRootImports(RootRef(() => requiredModuleRef(path)) :: Nil) + + // then its user defined imports + val imports = state.imports.getOrElse(id, Nil) + if imports.isEmpty then ctx0 + else imports.foldLeft(ctx0.fresh.setNewScope)((ctx, imp) => + importContext(imp)(using ctx)) + } - val rootCtx = super.rootContext - .withRootImports // default root imports - .withRootImports(RootRef(() => defn.EmptyPackageVal.termRef) :: Nil) - (1 to state.objectIndex).foldLeft(rootCtx)((ctx, id) => - importPreviousRun(id)(using ctx)) + val rootCtx = super.rootContext + .withRootImports // default root imports + .withRootImports(RootRef(() => defn.EmptyPackageVal.termRef) :: Nil) + (1 to state.objectIndex).foldLeft(rootCtx)((ctx, id) => + importPreviousRun(id)(using ctx)) + } } - } + run.suppressions.initSuspendedMessages(state.context.run) + run private val objectNames = mutable.Map.empty[Int, TermName] diff --git a/compiler/test-resources/repl/nowarn.scala b/compiler/test-resources/repl/nowarn.scala new file mode 100644 index 000000000000..63b9c4f3aa63 --- /dev/null +++ b/compiler/test-resources/repl/nowarn.scala @@ -0,0 +1,24 @@ +scala> @annotation.nowarn def f = try 1 // @nowarn doesn't work on first line, ctx.run is null in issueIfNotSuppressed +-- Warning: +1 | @annotation.nowarn def f = try 1 // @nowarn doesn't work on first line, ctx.run is null in issueIfNotSuppressed + | ^^^^^ + | A try without catch or finally is equivalent to putting + | its body in a block; no exceptions are handled. +def f: Int +scala> @annotation.nowarn def f = try 1 +def f: Int +scala> def f = try 1 +-- Warning: +1 | def f = try 1 + | ^^^^^ + | A try without catch or finally is equivalent to putting + | its body in a block; no exceptions are handled. +def f: Int +scala> @annotation.nowarn def f = { 1; 2 } +def f: Int +scala> def f = { 1; 2 } +-- Warning: +1 | def f = { 1; 2 } + | ^ + |A pure expression does nothing in statement position; you may be omitting necessary parentheses +def f: Int diff --git a/compiler/test/dotty/tools/dotc/CompilationTests.scala b/compiler/test/dotty/tools/dotc/CompilationTests.scala index a88f94565e32..ada3c899fad7 100644 --- a/compiler/test/dotty/tools/dotc/CompilationTests.scala +++ b/compiler/test/dotty/tools/dotc/CompilationTests.scala @@ -129,6 +129,7 @@ class CompilationTests { compileFilesInDir("tests/neg-no-kind-polymorphism", defaultOptions and "-Yno-kind-polymorphism"), compileFilesInDir("tests/neg-custom-args/deprecation", defaultOptions.and("-Xfatal-warnings", "-deprecation")), compileFilesInDir("tests/neg-custom-args/fatal-warnings", defaultOptions.and("-Xfatal-warnings")), + compileFilesInDir("tests/neg-custom-args/nowarn", defaultOptions.and("-deprecation", "-Wunused:nowarn", "-Wconf:msg=@nowarn annotation does not suppress any warnings:e")), compileFilesInDir("tests/neg-custom-args/erased", defaultOptions.and("-language:experimental.erasedDefinitions")), compileFilesInDir("tests/neg-custom-args/allow-double-bindings", allowDoubleBindings), compileFilesInDir("tests/neg-custom-args/allow-deep-subtypes", allowDeepSubtypes), diff --git a/compiler/test/dotty/tools/dotc/config/ScalaSettingsTests.scala b/compiler/test/dotty/tools/dotc/config/ScalaSettingsTests.scala index a18f96dfd35b..05f218059f02 100644 --- a/compiler/test/dotty/tools/dotc/config/ScalaSettingsTests.scala +++ b/compiler/test/dotty/tools/dotc/config/ScalaSettingsTests.scala @@ -64,4 +64,22 @@ class ScalaSettingsTests: assertTrue("Has the feature", set.contains("implicitConversions")) assertTrue("Has the feature", set.contains("dynamics")) + @Test def `WConf setting is parsed`: Unit = + import reporting.{Action, Diagnostic, NoExplanation} + val sets = new ScalaSettings + val args = List("-Wconf:cat=deprecation:s,cat=feature:e", "-Wconf:msg=a problem\\.:s") + val sumy = ArgsSummary(sets.defaultState, args, errors = Nil, warnings = Nil) + val proc = sets.processArguments(sumy, processAll = true, skipped = Nil) + val conf = sets.Wconf.valueIn(proc.sstate) + val sut = reporting.WConf.fromSettings(conf).getOrElse(???) + val msg = NoExplanation("There was a problem!") + val depr = new Diagnostic.DeprecationWarning(msg, util.NoSourcePosition) + assertEquals(Action.Silent, sut.action(depr)) + val feat = new Diagnostic.FeatureWarning(msg, util.NoSourcePosition) + assertEquals(Action.Error, sut.action(feat)) + val warn = new Diagnostic.Warning(msg, util.NoSourcePosition) + assertEquals(Action.Warning, sut.action(warn)) + val nowr = new Diagnostic.Warning(NoExplanation("This is a problem."), util.NoSourcePosition) + assertEquals(Action.Silent, sut.action(nowr)) + end ScalaSettingsTests diff --git a/compiler/test/dotty/tools/dotc/reporting/TestReporter.scala b/compiler/test/dotty/tools/dotc/reporting/TestReporter.scala index 24890279accd..da9b2498d528 100644 --- a/compiler/test/dotty/tools/dotc/reporting/TestReporter.scala +++ b/compiler/test/dotty/tools/dotc/reporting/TestReporter.scala @@ -53,7 +53,7 @@ extends Reporter with UniqueMessagePositions with HideNonSensicalMessages with M /** Prints the message with the given position indication. */ def printMessageAndPos(dia: Diagnostic, extra: String)(using Context): Unit = { - val msg = messageAndPos(dia.msg, dia.pos, diagnosticLevel(dia)) + val msg = messageAndPos(dia) val extraInfo = inlineInfo(dia.pos) if (dia.level >= logLevel) { @@ -73,15 +73,9 @@ extends Reporter with UniqueMessagePositions with HideNonSensicalMessages with M case _ => "" } - dia match { - case dia: Error => { - _errorBuf.append(dia) - _consoleReporter.doReport(dia) - printMessageAndPos(dia, extra) - } - case dia => - printMessageAndPos(dia, extra) - } + if dia.level >= ERROR then _errorBuf.append(dia) + if dia.level >= WARNING then _consoleReporter.doReport(dia) + printMessageAndPos(dia, extra) } } diff --git a/compiler/test/dotty/tools/vulpix/ParallelTesting.scala b/compiler/test/dotty/tools/vulpix/ParallelTesting.scala index f6c894f367e2..4ba1229feecb 100644 --- a/compiler/test/dotty/tools/vulpix/ParallelTesting.scala +++ b/compiler/test/dotty/tools/vulpix/ParallelTesting.scala @@ -665,7 +665,7 @@ trait ParallelTesting extends RunnerOrchestration { self => lazy val actualErrors = reporters.foldLeft(0)(_ + _.errorCount) def hasMissingAnnotations = getMissingExpectedErrors(errorMap, reporters.iterator.flatMap(_.errors)) def showErrors = "-> following the errors:\n" + - reporters.flatMap(_.allErrors.map(e => e.pos.line.toString + ": " + e.message)).mkString(start = "at ", sep = "\n at ", end = "") + reporters.flatMap(_.allErrors.map(e => (e.pos.line + 1).toString + ": " + e.message)).mkString(start = "at ", sep = "\n at ", end = "") if (compilerCrashed) Some(s"Compiler crashed when compiling: ${testSource.title}") else if (actualErrors == 0) Some(s"\nNo errors found when compiling neg test $testSource") diff --git a/sbt-bridge/src/dotty/tools/xsbt/DelegatingReporter.java b/sbt-bridge/src/dotty/tools/xsbt/DelegatingReporter.java index 65988443d318..4a170af85c88 100644 --- a/sbt-bridge/src/dotty/tools/xsbt/DelegatingReporter.java +++ b/sbt-bridge/src/dotty/tools/xsbt/DelegatingReporter.java @@ -32,9 +32,9 @@ public void doReport(Diagnostic dia, Context ctx) { Severity severity = severityOf(dia.level()); Position position = positionOf(dia.pos().nonInlined()); - Message message = dia.msg(); StringBuilder rendered = new StringBuilder(); - rendered.append(messageAndPos(message, dia.pos(), diagnosticLevel(dia), ctx)); + rendered.append(messageAndPos(dia, ctx)); + Message message = dia.msg(); boolean shouldExplain = Diagnostic.shouldExplain(dia, ctx); if (shouldExplain && !message.explanation().isEmpty()) { rendered.append(explanation(message, ctx)); diff --git a/tests/neg-custom-args/nowarn/nowarn-parser-error.check b/tests/neg-custom-args/nowarn/nowarn-parser-error.check new file mode 100644 index 000000000000..049d3b5b1a18 --- /dev/null +++ b/tests/neg-custom-args/nowarn/nowarn-parser-error.check @@ -0,0 +1,13 @@ +-- [E040] Syntax Error: tests/neg-custom-args/nowarn/nowarn-parser-error.scala:3:6 ------------------------------------- +3 | def def // error + | ^^^ + | an identifier expected, but 'def' found + +longer explanation available when compiling with `-explain` +-- [E000] Syntax Warning: tests/neg-custom-args/nowarn/nowarn-parser-error.scala:2:10 ---------------------------------- +2 | def a = try 1 // warn + | ^^^^^ + | A try without catch or finally is equivalent to putting + | its body in a block; no exceptions are handled. + +longer explanation available when compiling with `-explain` diff --git a/tests/neg-custom-args/nowarn/nowarn-parser-error.scala b/tests/neg-custom-args/nowarn/nowarn-parser-error.scala new file mode 100644 index 000000000000..2c65d3cf1838 --- /dev/null +++ b/tests/neg-custom-args/nowarn/nowarn-parser-error.scala @@ -0,0 +1,4 @@ +class C { + def a = try 1 // warn + def def // error +} diff --git a/tests/neg-custom-args/nowarn/nowarn-typer-error.check b/tests/neg-custom-args/nowarn/nowarn-typer-error.check new file mode 100644 index 000000000000..da143965006b --- /dev/null +++ b/tests/neg-custom-args/nowarn/nowarn-typer-error.check @@ -0,0 +1,6 @@ +-- [E006] Not Found Error: tests/neg-custom-args/nowarn/nowarn-typer-error.scala:4:11 ---------------------------------- +4 | def t1 = / // error + | ^ + | Not found: / + +longer explanation available when compiling with `-explain` diff --git a/tests/neg-custom-args/nowarn/nowarn-typer-error.scala b/tests/neg-custom-args/nowarn/nowarn-typer-error.scala new file mode 100644 index 000000000000..8ab871b108f6 --- /dev/null +++ b/tests/neg-custom-args/nowarn/nowarn-typer-error.scala @@ -0,0 +1,7 @@ +import annotation.nowarn +object T { + @deprecated def f = 1 + def t1 = / // error + @nowarn // unused-nowarn is not issued if earlier phase has an error. + def t2 = f // no warning, refchecks doesn't run if typer has an error +} diff --git a/tests/neg-custom-args/nowarn/nowarn.check b/tests/neg-custom-args/nowarn/nowarn.check new file mode 100644 index 000000000000..736d9f82e910 --- /dev/null +++ b/tests/neg-custom-args/nowarn/nowarn.check @@ -0,0 +1,108 @@ +-- [E000] Syntax Warning: tests/neg-custom-args/nowarn/nowarn.scala:9:10 ----------------------------------------------- +9 |def t1a = try 1 // warning (parser) + | ^^^^^ + | A try without catch or finally is equivalent to putting + | its body in a block; no exceptions are handled. + +longer explanation available when compiling with `-explain` +-- [E000] Syntax Warning: tests/neg-custom-args/nowarn/nowarn.scala:23:25 ---------------------------------------------- +23 |@nowarn(o.inl) def t2d = try 1 // two warnings (`inl` is not a compile-time constant) + | ^^^^^ + | A try without catch or finally is equivalent to putting + | its body in a block; no exceptions are handled. + +longer explanation available when compiling with `-explain` +-- [E000] Syntax Warning: tests/neg-custom-args/nowarn/nowarn.scala:31:26 ---------------------------------------------- +31 |@nowarn("id=1") def t4d = try 1 // error and warning (unused nowarn, wrong id) + | ^^^^^ + | A try without catch or finally is equivalent to putting + | its body in a block; no exceptions are handled. + +longer explanation available when compiling with `-explain` +-- [E000] Syntax Warning: tests/neg-custom-args/nowarn/nowarn.scala:33:28 ---------------------------------------------- +33 |@nowarn("verbose") def t5 = try 1 // warning with details + | ^^^^^ + | A try without catch or finally is equivalent to putting + | its body in a block; no exceptions are handled. +Matching filters for @nowarn or -Wconf: + - id=E0 + - name=EmptyCatchOrFinallyBlock + +longer explanation available when compiling with `-explain` +-- [E129] Potential Issue Warning: tests/neg-custom-args/nowarn/nowarn.scala:13:11 ------------------------------------- +13 |def t2 = { 1; 2 } // warning (the invalid nowarn doesn't silence anything) + | ^ + | A pure expression does nothing in statement position; you may be omitting necessary parentheses + +longer explanation available when compiling with `-explain` +-- Warning: tests/neg-custom-args/nowarn/nowarn.scala:12:8 ------------------------------------------------------------- +12 |@nowarn("wat?") // warning (typer, invalid filter) + | ^^^^^^ + | Invalid message filter + | unknown filter: wat? +-- [E129] Potential Issue Warning: tests/neg-custom-args/nowarn/nowarn.scala:16:12 ------------------------------------- +16 |def t2a = { 1; 2 } // warning (invalid nowarn doesn't silence) + | ^ + | A pure expression does nothing in statement position; you may be omitting necessary parentheses + +longer explanation available when compiling with `-explain` +-- Warning: tests/neg-custom-args/nowarn/nowarn.scala:15:8 ------------------------------------------------------------- +15 |@nowarn(t1a.toString) // warning (typer, argument not a compile-time constant) + | ^^^^^^^^^^^^ + | filter needs to be a compile-time constant string +-- Warning: tests/neg-custom-args/nowarn/nowarn.scala:23:10 ------------------------------------------------------------ +23 |@nowarn(o.inl) def t2d = try 1 // two warnings (`inl` is not a compile-time constant) + | ^^^^^ + | filter needs to be a compile-time constant string +-- Deprecation Warning: tests/neg-custom-args/nowarn/nowarn.scala:37:10 ------------------------------------------------ +37 |def t6a = f // warning (refchecks, deprecation) + | ^ + | method f is deprecated +-- Deprecation Warning: tests/neg-custom-args/nowarn/nowarn.scala:40:30 ------------------------------------------------ +40 |@nowarn("msg=fish") def t6d = f // error (unused nowarn), warning (deprecation) + | ^ + | method f is deprecated +-- Deprecation Warning: tests/neg-custom-args/nowarn/nowarn.scala:47:10 ------------------------------------------------ +47 |def t7c = f: // warning (deprecation) + | ^ + | method f is deprecated +-- Unchecked Warning: tests/neg-custom-args/nowarn/nowarn.scala:53:7 --------------------------------------------------- +53 | case _: List[Int] => 0 // warning (patmat, unchecked) + | ^ + | the type test for List[Int] cannot be checked at runtime +-- Error: tests/neg-custom-args/nowarn/nowarn.scala:31:1 --------------------------------------------------------------- +31 |@nowarn("id=1") def t4d = try 1 // error and warning (unused nowarn, wrong id) + |^^^^^^^^^^^^^^^ + |@nowarn annotation does not suppress any warnings +-- Error: tests/neg-custom-args/nowarn/nowarn.scala:40:1 --------------------------------------------------------------- +40 |@nowarn("msg=fish") def t6d = f // error (unused nowarn), warning (deprecation) + |^^^^^^^^^^^^^^^^^^^ + |@nowarn annotation does not suppress any warnings +-- Error: tests/neg-custom-args/nowarn/nowarn.scala:48:3 --------------------------------------------------------------- +48 | @nowarn("msg=fish") // error (unused nowarn) + | ^^^^^^^^^^^^^^^^^^^ + | @nowarn annotation does not suppress any warnings +-- Error: tests/neg-custom-args/nowarn/nowarn.scala:60:0 --------------------------------------------------------------- +60 |@nowarn def t9a = { 1: @nowarn; 2 } // error (outer @nowarn is unused) + |^^^^^^^ + |@nowarn annotation does not suppress any warnings +-- Error: tests/neg-custom-args/nowarn/nowarn.scala:61:27 -------------------------------------------------------------- +61 |@nowarn def t9b = { 1: Int @nowarn; 2 } // error (inner @nowarn is unused, it covers the type, not the expression) + | ^^^^^^^ + | @nowarn annotation does not suppress any warnings +-- Error: tests/neg-custom-args/nowarn/nowarn.scala:66:0 --------------------------------------------------------------- +66 |@nowarn @ann(f) def t10b = 0 // error (unused nowarn) + |^^^^^^^ + |@nowarn annotation does not suppress any warnings +-- Error: tests/neg-custom-args/nowarn/nowarn.scala:67:8 --------------------------------------------------------------- +67 |@ann(f: @nowarn) def t10c = 0 // error (unused nowarn), should be silent + | ^^^^^^^ + | @nowarn annotation does not suppress any warnings +-- Error: tests/neg-custom-args/nowarn/nowarn.scala:70:0 --------------------------------------------------------------- +70 |@nowarn class I1a { // error (unused nowarn) + |^^^^^^^ + |@nowarn annotation does not suppress any warnings +-- Error: tests/neg-custom-args/nowarn/nowarn.scala:75:0 --------------------------------------------------------------- +75 |@nowarn class I1b { // error (unused nowarn) + |^^^^^^^ + |@nowarn annotation does not suppress any warnings diff --git a/tests/neg-custom-args/nowarn/nowarn.scala b/tests/neg-custom-args/nowarn/nowarn.scala new file mode 100644 index 000000000000..39ecde91517f --- /dev/null +++ b/tests/neg-custom-args/nowarn/nowarn.scala @@ -0,0 +1,87 @@ +import scala.annotation.{ nowarn, Annotation } + +// This test doesn't run with `-Werror`, because once there's an error, later phases are skipped and we would not see +// their warnings. +// Instead, this test runs with `-Wunused:nowarn -Wconf:msg=@nowarn annotation does not suppress any warnings:e`. +// Only "unused nowarn" warnings are reported as errors. Since these warnings are reported at the very end, all other +// phases of the compiler run normally. + +def t1a = try 1 // warning (parser) +@nowarn("msg=try without catch") def t1b = try 1 + +@nowarn("wat?") // warning (typer, invalid filter) +def t2 = { 1; 2 } // warning (the invalid nowarn doesn't silence anything) + +@nowarn(t1a.toString) // warning (typer, argument not a compile-time constant) +def t2a = { 1; 2 } // warning (invalid nowarn doesn't silence) + +object o: + final val const = "msg=try" + inline def inl = "msg=try" + +@nowarn(o.const) def t2c = try 1 // no warning +@nowarn(o.inl) def t2d = try 1 // two warnings (`inl` is not a compile-time constant) + +@nowarn("id=E129") def t3a = { 1; 2 } +@nowarn("name=PureExpressionInStatementPosition") def t3b = { 1; 2 } + +@nowarn("id=E000") def t4a = try 1 +@nowarn("id=E0") def t4b = try 1 +@nowarn("id=0") def t4c = try 1 +@nowarn("id=1") def t4d = try 1 // error and warning (unused nowarn, wrong id) + +@nowarn("verbose") def t5 = try 1 // warning with details + +@deprecated def f = 0 + +def t6a = f // warning (refchecks, deprecation) +@nowarn("cat=deprecation") def t6b = f +@nowarn("msg=deprecated") def t6c = f +@nowarn("msg=fish") def t6d = f // error (unused nowarn), warning (deprecation) +@nowarn("") def t6e = f +@nowarn def t6f = f + +def t7a = f: @nowarn("cat=deprecation") +def t7b = f: + @nowarn("msg=deprecated") +def t7c = f: // warning (deprecation) + @nowarn("msg=fish") // error (unused nowarn) +def t7d = f: @nowarn("") +def t7e = f: @nowarn + +def t8a(x: Any) = x match + case _: List[Int] => 0 // warning (patmat, unchecked) + case _ => 1 + +@nowarn("cat=unchecked") def t8(x: Any) = x match + case _: List[Int] => 0 + case _ => 1 + +@nowarn def t9a = { 1: @nowarn; 2 } // error (outer @nowarn is unused) +@nowarn def t9b = { 1: Int @nowarn; 2 } // error (inner @nowarn is unused, it covers the type, not the expression) + +class ann(a: Any) extends Annotation + +@ann(f) def t10a = 0 // should be a deprecation warning, but currently isn't +@nowarn @ann(f) def t10b = 0 // error (unused nowarn) +@ann(f: @nowarn) def t10c = 0 // error (unused nowarn), should be silent + +def forceCompletionOfI1a = (new I1a).m +@nowarn class I1a { // error (unused nowarn) + @nowarn def m = { 1; 2 } +} + +// completion during type checking +@nowarn class I1b { // error (unused nowarn) + @nowarn def m = { 1; 2 } +} + +@nowarn class I1c { + def m = { 1; 2 } +} + +trait T { + @nowarn val t1 = { 0; 1 } +} + +class K extends T diff --git a/tests/neg-scalajs/jsname-argument.check b/tests/neg-scalajs/jsname-argument.check index c7640631e730..bccdee995b81 100644 --- a/tests/neg-scalajs/jsname-argument.check +++ b/tests/neg-scalajs/jsname-argument.check @@ -22,6 +22,10 @@ 42 | @JSName(new NamesClass().a) // error | ^^^^^^^^^^^^^^^^^^ | A js.Symbol argument to JSName must be a static, stable identifier +-- Warning: tests/neg-scalajs/jsname-argument.scala:49:10 -------------------------------------------------------------- +49 | @JSName(a) // warning, untested + | ^ + |This symbol is defined in the same object as the annotation's target. This will cause a stackoverflow at runtime -- Error: tests/neg-scalajs/jsname-argument.scala:57:3 ----------------------------------------------------------------- 57 | @JSName(Names.sym) // error | ^^^^^^^^^^^^^^^^^^ diff --git a/tests/neg/i12150.check b/tests/neg/i12150.check index 884498ae2d1e..44f09d772b43 100644 --- a/tests/neg/i12150.check +++ b/tests/neg/i12150.check @@ -4,3 +4,9 @@ | expression expected but end found longer explanation available when compiling with `-explain` +-- [E129] Potential Issue Warning: tests/neg/i12150.scala:1:11 --------------------------------------------------------- +1 |def f: Unit = // error + | ^ + | A pure expression does nothing in statement position; you may be omitting necessary parentheses + +longer explanation available when compiling with `-explain` diff --git a/tests/neg/i2033.check b/tests/neg/i2033.check index 4878ad153a47..0c5a9da975bd 100644 --- a/tests/neg/i2033.check +++ b/tests/neg/i2033.check @@ -9,3 +9,7 @@ | Required: String longer explanation available when compiling with `-explain` +-- Warning: tests/neg/i2033.scala:6:37 --------------------------------------------------------------------------------- +6 | val out = new ObjectOutputStream(println) + | ^^^^^^^ + |method println is eta-expanded even though java.io.OutputStream does not have the @FunctionalInterface annotation. diff --git a/tests/neg/spaces-vs-tabs.check b/tests/neg/spaces-vs-tabs.check index 883e62b6aa00..862465a18dbe 100644 --- a/tests/neg/spaces-vs-tabs.check +++ b/tests/neg/spaces-vs-tabs.check @@ -28,3 +28,9 @@ | The start of this line does not match any of the previous indentation widths. | Indentation width of current line : 1 tab, 2 spaces | This falls between previous widths: 1 tab and 1 tab, 4 spaces +-- [E129] Potential Issue Warning: tests/neg/spaces-vs-tabs.scala:13:6 ------------------------------------------------- +13 | 1 + | ^ + | A pure expression does nothing in statement position; you may be omitting necessary parentheses + +longer explanation available when compiling with `-explain` diff --git a/tests/neg/t6810.check b/tests/neg/t6810.check index 55f9f7ca2443..147081e0daf5 100644 --- a/tests/neg/t6810.check +++ b/tests/neg/t6810.check @@ -22,3 +22,11 @@ 30 | val b = ' | ^ | unclosed character literal +-- Warning: tests/neg/t6810.scala:6:0 ---------------------------------------------------------------------------------- +6 |' // but not embedded EOL sequences not represented as escapes + |^ + |Line is indented too far to the left, or a `}` is missing +-- Warning: tests/neg/t6810.scala:31:0 --------------------------------------------------------------------------------- +31 |' // anypos-error CR seen as EOL by scanner; FSR, error only on open quote, unlike `y` + |^ + |Line is indented too far to the left, or a `}` is missing