From 9634b34039428d4a4505d0b541513eedd296c1c6 Mon Sep 17 00:00:00 2001 From: Lukas Rytz Date: Tue, 8 Jun 2021 17:38:19 +0200 Subject: [PATCH 01/21] Wconf filtered messages --- .../tools/dotc/config/ScalaSettings.scala | 35 +++++++ .../dotty/tools/dotc/config/Settings.scala | 4 +- .../dotty/tools/dotc/parsing/Parsers.scala | 7 +- .../tools/dotc/reporting/Diagnostic.scala | 1 + .../dotty/tools/dotc/reporting/Reporter.scala | 51 ++++++---- .../dotty/tools/dotc/reporting/WConf.scala | 94 +++++++++++++++++++ .../dotc/config/ScalaSettingsTests.scala | 14 +++ .../tools/dotc/reporting/WConfTest.scala | 16 ++++ 8 files changed, 197 insertions(+), 25 deletions(-) create mode 100644 compiler/src/dotty/tools/dotc/reporting/WConf.scala create mode 100644 compiler/test/dotty/tools/dotc/reporting/WConfTest.scala diff --git a/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala b/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala index e89c8b97aea8..e8218bf3e73c 100644 --- a/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala +++ b/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala @@ -132,6 +132,41 @@ private sealed trait WarningSettings: self: SettingGroup => 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 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 + | + | - Message content: msg=regex + | The regex need only match some part of the message, not all of it. + | + | + | - error / e + | - warning / w + | - info / i (infos are not counted as warnings and don't affect `-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: diff --git a/compiler/src/dotty/tools/dotc/config/Settings.scala b/compiler/src/dotty/tools/dotc/config/Settings.scala index 59d6e638f98d..5b794255df48 100644 --- a/compiler/src/dotty/tools/dotc/config/Settings.scala +++ b/compiler/src/dotty/tools/dotc/config/Settings.scala @@ -257,8 +257,8 @@ object Settings: 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/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/reporting/Diagnostic.scala b/compiler/src/dotty/tools/dotc/reporting/Diagnostic.scala index fbb7145152d6..e917f4809cbd 100644 --- a/compiler/src/dotty/tools/dotc/reporting/Diagnostic.scala +++ b/compiler/src/dotty/tools/dotc/reporting/Diagnostic.scala @@ -36,6 +36,7 @@ object Diagnostic: pos: SourcePosition ) extends Diagnostic(msg, pos, WARNING) { def toError: Error = new Error(msg, pos) + def toInfo: Info = new Info(msg, pos) } class Info( diff --git a/compiler/src/dotty/tools/dotc/reporting/Reporter.scala b/compiler/src/dotty/tools/dotc/reporting/Reporter.scala index 64a2d826075e..e621ec7f23eb 100644 --- a/compiler/src/dotty/tools/dotc/reporting/Reporter.scala +++ b/compiler/src/dotty/tools/dotc/reporting/Reporter.scala @@ -141,26 +141,37 @@ 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 - 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 + import Action._ + val toReport = dia match { + case w: Warning => WConf.parsed.action(dia) match { + case Silent => None + case Info => Some(w.toInfo) + case Warning => Some(w) + case Error => Some(w.toError) + } + case _ => Some(dia) + } + for (d <- toReport) { + val isSummarized = d match + case cw: ConditionalWarning => !cw.enablingOption.value + case _ => false + // avoid isHidden test for summarized warnings so that message is not forced + if isSummarized || !isHidden(d) then + withMode(Mode.Printing)(doReport(d)) + d match + case cw: ConditionalWarning if !cw.enablingOption.value => + val key = cw.enablingOption.name + unreportedWarnings = + unreportedWarnings.updated(key, unreportedWarnings.getOrElse(key, 0) + 1) + case _: Warning => _warningCount += 1 + case e: Error => + errors = e :: 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 + } 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..cd9a43465609 --- /dev/null +++ b/compiler/src/dotty/tools/dotc/reporting/WConf.scala @@ -0,0 +1,94 @@ +package dotty.tools +package dotc +package reporting + +import dotty.tools.dotc.core.Contexts._ + +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 MessagePattern(pattern) => pattern.findFirstIn(message.msg.rawMessage).nonEmpty + } + case Any, Deprecated, Feature + case MessagePattern(pattern: Regex) + +enum Action: + case Error, Warning, 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) + +@sharable 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 "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}") } + + val splitter = raw"([^=]+)=(.+)".r + + 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 "cat" => + conf match { + case "deprecation" => Right(Deprecated) + case "feature" => Right(Feature) + case _ => Left(s"unknown category: $conf") + } + case _ => Left(s"unknown filter: $filter") + } + case _ => Left(s"unknown filter: $s") + } + + private var parsedCache: (List[String], WConf) = null + def parsed(using Context): WConf = + val setting = ctx.settings.Wconf.value + if parsedCache == null || parsedCache._1 != setting then + val conf = fromSettings(setting) + parsedCache = (setting, conf.getOrElse(WConf(Nil))) + conf.swap.foreach(msgs => + val multiHelp = + if (setting.sizeIs > 1) + """ + |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")) + parsedCache._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 parts = conf.split("[&:]") // TODO: don't split on escaped \& + val (ms, fs) = parts.view.init.map(parseFilter).toList.partitionMap(identity) + if (ms.nonEmpty) Left(ms) + else if (fs.isEmpty) Left(List("no filters or no action defined")) + else parseAction(parts.last).map((fs, _)) + }) + val (ms, fs) = parsedConfs.partitionMap(identity) + if (ms.nonEmpty) Left(ms.flatten) + else Right(WConf(fs)) + } diff --git a/compiler/test/dotty/tools/dotc/config/ScalaSettingsTests.scala b/compiler/test/dotty/tools/dotc/config/ScalaSettingsTests.scala index a18f96dfd35b..380afc5ffeb9 100644 --- a/compiler/test/dotty/tools/dotc/config/ScalaSettingsTests.scala +++ b/compiler/test/dotty/tools/dotc/config/ScalaSettingsTests.scala @@ -64,4 +64,18 @@ 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 = tokenize("-Wconf:cat=deprecation:w,cat=feature:w -Wconf:msg=message\\.pattern: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 diag = new Diagnostic.DeprecationWarning(msg, util.NoSourcePosition) + assertEquals("Warns deprecation", Action.Warning, sut.action(diag)) + val feat = new Diagnostic.FeatureWarning(msg, util.NoSourcePosition) + assertEquals("Warns feature", Action.Warning, sut.action(feat)) + end ScalaSettingsTests diff --git a/compiler/test/dotty/tools/dotc/reporting/WConfTest.scala b/compiler/test/dotty/tools/dotc/reporting/WConfTest.scala new file mode 100644 index 000000000000..bb99557eaf74 --- /dev/null +++ b/compiler/test/dotty/tools/dotc/reporting/WConfTest.scala @@ -0,0 +1,16 @@ +package dotty.tools.dotc +package reporting + +import config.CommandLineParser.tokenize +import config.Settings._ + +import org.junit.Test +import org.junit.Assert._ + +class WConfTest: + + @Test def `WConf setting is parsed`: Unit = + assertEquals(1, 1) + assertTrue("No warnings!", true) + +end WConfTest From ae5547b695a773d4c0aa7dbdb69e4df2bed119a6 Mon Sep 17 00:00:00 2001 From: Lukas Rytz Date: Thu, 17 Jun 2021 14:20:17 +0200 Subject: [PATCH 02/21] support @nowarn annotation --- compiler/src/dotty/tools/dotc/Run.scala | 34 ++++++++++++- .../dotty/tools/dotc/core/Definitions.scala | 1 + .../dotty/tools/dotc/reporting/Reporter.scala | 50 ++++++++++++------- .../dotty/tools/dotc/reporting/WConf.scala | 11 ++++ .../src/dotty/tools/dotc/typer/Typer.scala | 30 ++++++++++- .../dotty/tools/dotc/typer/TyperPhase.scala | 1 + tests/neg/nowarn.scala | 22 ++++++++ 7 files changed, 129 insertions(+), 20 deletions(-) create mode 100644 tests/neg/nowarn.scala diff --git a/compiler/src/dotty/tools/dotc/Run.scala b/compiler/src/dotty/tools/dotc/Run.scala index d75042d5a238..131631107509 100644 --- a/compiler/src/dotty/tools/dotc/Run.scala +++ b/compiler/src/dotty/tools/dotc/Run.scala @@ -16,7 +16,8 @@ import io.{AbstractFile, PlainFile, VirtualFile} import Phases.unfusedPhases import util._ -import reporting.Reporter +import reporting.{Reporter, Suppression} +import reporting.Diagnostic.Warning import rewrites.Rewrites import profile.Profiler @@ -96,6 +97,37 @@ 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: + def suppressionsComplete(source: SourceFile) = source == NoSource || mySuppressionsComplete(source) + + def addSuspendedMessage(warning: Warning) = + mySuspendedMessages.getOrElseUpdate(warning.pos.source, mutable.LinkedHashSet.empty) += warning + + def isSuppressed(warning: Warning): Boolean = + mySuppressions.getOrElse(warning.pos.source, Nil).find(_.matches(warning)) match { + case Some(s) => s.markUsed(); true + case _ => false + } + + def addSuppression(sup: Suppression): Unit = + val source = sup.annotPos.source + mySuppressions.getOrElseUpdate(source, mutable.ListBuffer.empty) += sup + + def reportSuspendedMessages(unit: CompilationUnit)(using Context): Unit = { + // sort suppressions. they are not added in any particular order because of lazy type completion + for (sups <- mySuppressions.get(unit.source)) + mySuppressions(unit.source) = sups.sortBy(sup => 0 - sup.start) + mySuppressionsComplete += unit.source + mySuspendedMessages.remove(unit.source).foreach(_.foreach(ctx.reporter.issueIfNotSuppressed)) + } + /** The compilation units currently being compiled, this may return different * results over time. */ 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/reporting/Reporter.scala b/compiler/src/dotty/tools/dotc/reporting/Reporter.scala index e621ec7f23eb..3bf78a774105 100644 --- a/compiler/src/dotty/tools/dotc/reporting/Reporter.scala +++ b/compiler/src/dotty/tools/dotc/reporting/Reporter.scala @@ -140,23 +140,19 @@ abstract class Reporter extends interfaces.ReporterResult { var unreportedWarnings: Map[String, Int] = Map.empty - def report(dia: Diagnostic)(using Context): Unit = - import Action._ - val toReport = dia match { - case w: Warning => WConf.parsed.action(dia) match { - case Silent => None - case Info => Some(w.toInfo) - case Warning => Some(w) - case Error => Some(w.toError) + def issueIfNotSuppressed(dia: Diagnostic)(using Context): Unit = + def go() = + import Action._ + val toReport = dia match { + case w: Warning => WConf.parsed.action(dia) match { + case Silent => None + case Info => Some(w.toInfo) + case Warning => Some(w) + case Error => Some(w.toError) + } + case _ => Some(dia) } - case _ => Some(dia) - } - for (d <- toReport) { - val isSummarized = d match - case cw: ConditionalWarning => !cw.enablingOption.value - case _ => false - // avoid isHidden test for summarized warnings so that message is not forced - if isSummarized || !isHidden(d) then + for (d <- toReport) { withMode(Mode.Printing)(doReport(d)) d match case cw: ConditionalWarning if !cw.enablingOption.value => @@ -169,10 +165,28 @@ abstract class Reporter extends interfaces.ReporterResult { _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 _: Info => // nothing to do here + // match error if d is something else + } + + dia match { + case w: Warning if ctx.run != null => + val sup = ctx.run.suppressions + if sup.suppressionsComplete(w.pos.source) then + if !sup.isSuppressed(w) then go() + else + sup.addSuspendedMessage(w) + case _ => go() } + def report(dia: Diagnostic)(using Context): Unit = + val isSummarized = dia match + case cw: ConditionalWarning => !cw.enablingOption.value + case _ => false + // avoid isHidden test for summarized warnings so that message is not forced + if isSummarized || !isHidden(dia) then + 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 index cd9a43465609..9ed53256d16d 100644 --- a/compiler/src/dotty/tools/dotc/reporting/WConf.scala +++ b/compiler/src/dotty/tools/dotc/reporting/WConf.scala @@ -3,6 +3,7 @@ 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 @@ -92,3 +93,13 @@ final case class WConf(confs: List[(List[MessageFilter], Action)]): if (ms.nonEmpty) Left(ms.flatten) else Right(WConf(fs)) } + +case class Suppression(annotPos: SourcePosition, filters: List[MessageFilter], start: Int, end: Int): + 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/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 6c13afa219b8..9f7a058c4335 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -2077,12 +2077,38 @@ 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) + 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 + val filters = + if annot.arguments.isEmpty then List(MessageFilter.Any) + else annot.argumentConstantString(0) match { + case None => annot.argument(0) match { + case Some(t: Select) if t.name.toString == "$lessinit$greater$default$1" => List(MessageFilter.Any) + case _ => + report.warning(s"filter needs to be a compile-time constant string", argPos) + Nil + } + case Some(s) => + if s.isEmpty then Nil + else + val (ms, fs) = s.split('&').map(WConf.parseFilter).toList.partitionMap(identity) + if (ms.nonEmpty) + report.warning(s"Invalid message filter\n${ms.mkString("\n")}", argPos) + fs + } + val range = mdef.sourcePos + ctx.run.suppressions.addSuppression(Suppression(tree.sourcePos, filters, range.start, range.end)) + def typedValDef(vdef: untpd.ValDef, sym: Symbol)(using Context): Tree = { val ValDef(name, tpt, _) = vdef completeAnnotations(vdef, sym) @@ -2488,6 +2514,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..c6bcf14ddbe5 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) catch case ex: CompilationUnit.SuspendException => } diff --git a/tests/neg/nowarn.scala b/tests/neg/nowarn.scala new file mode 100644 index 000000000000..410e9fe7dba6 --- /dev/null +++ b/tests/neg/nowarn.scala @@ -0,0 +1,22 @@ +import annotation.nowarn + +@deprecated def f = 1 + +def t1 = f // warn + +@nowarn("cat=deprecation") def t2 = f +@nowarn("msg=deprecated") def t3 = f +@nowarn("msg=fish") def t4 = f // warn +@nowarn("") def t5 = f +@nowarn def t6 = f + +def t7 = f: @nowarn("cat=deprecation") +def t8 = f: @nowarn("msg=deprecated") +def t9 = f: @nowarn("msg=fish") // warn +def t10 = f: @nowarn("") +def t11 = f: @nowarn + +def t12 = List(List(1): _*) // warn -source:future-migration +@nowarn("msg=vararg splices") def t13 = List(List(1): _*) + +class K { override def blup = 1 } // error From 364e07a63b2913c1bd185fd8504dbc4461db50d9 Mon Sep 17 00:00:00 2001 From: Lukas Rytz Date: Mon, 12 Jul 2021 19:37:04 +0200 Subject: [PATCH 03/21] nowarn cleanups, fixes, tests - undo ansi escapes before matching the nowarn msg=regex - first tests for nowarn in parser, typer, refchecks warnings - handle summarized conditional warnings in one place --- compiler/src/dotty/tools/dotc/report.scala | 10 +-- .../dotc/reporting/ConsoleReporter.scala | 11 +-- .../tools/dotc/reporting/Diagnostic.scala | 2 + .../dotty/tools/dotc/reporting/Reporter.scala | 71 +++++++++---------- .../dotty/tools/dotc/reporting/WConf.scala | 7 +- .../src/dotty/tools/dotc/typer/Typer.scala | 16 ++--- .../dotty/tools/dotc/CompilationTests.scala | 1 + .../nowarn/nowarn-parser-typer.check | 18 +++++ .../nowarn/nowarn-parser-typer.scala | 16 +++++ .../nowarn/nowarn-refchecks.check | 12 ++++ .../nowarn/nowarn-refchecks.scala} | 11 +-- 11 files changed, 103 insertions(+), 72 deletions(-) create mode 100644 tests/neg-custom-args/nowarn/nowarn-parser-typer.check create mode 100644 tests/neg-custom-args/nowarn/nowarn-parser-typer.scala create mode 100644 tests/neg-custom-args/nowarn/nowarn-refchecks.check rename tests/{neg/nowarn.scala => neg-custom-args/nowarn/nowarn-refchecks.scala} (52%) 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..984a2104a697 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))) 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 - } - 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 e917f4809cbd..5afe6f4bd992 100644 --- a/compiler/src/dotty/tools/dotc/reporting/Diagnostic.scala +++ b/compiler/src/dotty/tools/dotc/reporting/Diagnostic.scala @@ -37,6 +37,7 @@ object Diagnostic: ) extends Diagnostic(msg, pos, WARNING) { def toError: Error = new Error(msg, pos) def toInfo: Info = new Info(msg, pos) + def isSummarizedConditional(using Context): Boolean = false } class Info( @@ -49,6 +50,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( diff --git a/compiler/src/dotty/tools/dotc/reporting/Reporter.scala b/compiler/src/dotty/tools/dotc/reporting/Reporter.scala index 3bf78a774105..d3571af5454f 100644 --- a/compiler/src/dotty/tools/dotc/reporting/Reporter.scala +++ b/compiler/src/dotty/tools/dotc/reporting/Reporter.scala @@ -21,11 +21,7 @@ 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 */ @@ -143,33 +139,40 @@ abstract class Reporter extends interfaces.ReporterResult { def issueIfNotSuppressed(dia: Diagnostic)(using Context): Unit = def go() = import Action._ - val toReport = dia match { - case w: Warning => WConf.parsed.action(dia) match { - case Silent => None - case Info => Some(w.toInfo) - case Warning => Some(w) - case Error => Some(w.toError) - } + + val toReport = dia match + case w: Warning => + if ctx.settings.silentWarnings.value then None + else if ctx.settings.XfatalWarnings.value && !w.isSummarizedConditional then Some(w.toError) + else WConf.parsed.action(dia) match + case Silent => None + case Info => Some(w.toInfo) + case Warning => Some(w) + 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 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 } - for (d <- toReport) { - withMode(Mode.Printing)(doReport(d)) - d match - case cw: ConditionalWarning if !cw.enablingOption.value => - val key = cw.enablingOption.name - unreportedWarnings = - unreportedWarnings.updated(key, unreportedWarnings.getOrElse(key, 0) + 1) - 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 - } + end go - dia match { + dia match case w: Warning if ctx.run != null => val sup = ctx.run.suppressions if sup.suppressionsComplete(w.pos.source) then @@ -177,15 +180,9 @@ abstract class Reporter extends interfaces.ReporterResult { else sup.addSuspendedMessage(w) case _ => go() - } + end issueIfNotSuppressed - def report(dia: Diagnostic)(using Context): Unit = - val isSummarized = dia match - case cw: ConditionalWarning => !cw.enablingOption.value - case _ => false - // avoid isHidden test for summarized warnings so that message is not forced - if isSummarized || !isHidden(dia) then - issueIfNotSuppressed(dia) + 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 index 9ed53256d16d..f2cc95e65254 100644 --- a/compiler/src/dotty/tools/dotc/reporting/WConf.scala +++ b/compiler/src/dotty/tools/dotc/reporting/WConf.scala @@ -15,9 +15,12 @@ enum MessageFilter: case Any => true case Deprecated => message.isInstanceOf[Diagnostic.DeprecationWarning] case Feature => message.isInstanceOf[Diagnostic.FeatureWarning] - case MessagePattern(pattern) => pattern.findFirstIn(message.msg.rawMessage).nonEmpty + case MessagePattern(pattern) => + val noHighlight = message.msg.rawMessage.replaceAll("\\e\\[[\\d;]*[^\\d;]","") + pattern.findFirstIn(noHighlight).nonEmpty + case None => false } - case Any, Deprecated, Feature + case Any, Deprecated, Feature, None case MessagePattern(pattern: Regex) enum Action: diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 9f7a058c4335..736bd031d955 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -2079,7 +2079,7 @@ class Typer extends Namer for (annot <- mdef.mods.annotations) val annot1 = typedAnnotation(annot)(using annotCtx) checkAnnotApplicable(annot1, sym) - if (Annotations.annotClass(annot1) == defn.NowarnAnnot) + if Annotations.annotClass(annot1) == defn.NowarnAnnot then registerNowarn(annot1, mdef) } @@ -2091,21 +2091,21 @@ class Typer extends Namer def argPos = annot.argument(0).getOrElse(tree).sourcePos val filters = if annot.arguments.isEmpty then List(MessageFilter.Any) - else annot.argumentConstantString(0) match { - case None => annot.argument(0) match { - case Some(t: Select) if t.name.toString == "$lessinit$greater$default$1" => List(MessageFilter.Any) + else annot.argumentConstantString(0) match + case None => annot.argument(0) match + case Some(t: Select) if t.name.is(DefaultGetterName) => List(MessageFilter.Any) case _ => report.warning(s"filter needs to be a compile-time constant string", argPos) Nil - } case Some(s) => if s.isEmpty then Nil else val (ms, fs) = s.split('&').map(WConf.parseFilter).toList.partitionMap(identity) - if (ms.nonEmpty) + if ms.nonEmpty then report.warning(s"Invalid message filter\n${ms.mkString("\n")}", argPos) - fs - } + List(MessageFilter.None) + else + fs val range = mdef.sourcePos ctx.run.suppressions.addSuppression(Suppression(tree.sourcePos, filters, range.start, range.end)) diff --git a/compiler/test/dotty/tools/dotc/CompilationTests.scala b/compiler/test/dotty/tools/dotc/CompilationTests.scala index a88f94565e32..50303d1c1159 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", "-Werror")), 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/tests/neg-custom-args/nowarn/nowarn-parser-typer.check b/tests/neg-custom-args/nowarn/nowarn-parser-typer.check new file mode 100644 index 000000000000..aa48042997f1 --- /dev/null +++ b/tests/neg-custom-args/nowarn/nowarn-parser-typer.check @@ -0,0 +1,18 @@ +-- [E000] Syntax Error: tests/neg-custom-args/nowarn/nowarn-parser-typer.scala:8:10 ------------------------------------ +8 |def t1a = try 1 // error, parser warning + | ^^^^^ + | 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` +-- [E129] Potential Issue Error: tests/neg-custom-args/nowarn/nowarn-parser-typer.scala:16:11 -------------------------- +16 |def t3 = { 1; 2 } // error, the invalid nowarn doesn't silence this warning + | ^ + | A pure expression does nothing in statement position; you may be omitting necessary parentheses + +longer explanation available when compiling with `-explain` +-- Error: tests/neg-custom-args/nowarn/nowarn-parser-typer.scala:15:8 -------------------------------------------------- +15 |@nowarn("wat?") // error, invalid filter + | ^^^^^^ + | Invalid message filter + | unknown filter: wat? diff --git a/tests/neg-custom-args/nowarn/nowarn-parser-typer.scala b/tests/neg-custom-args/nowarn/nowarn-parser-typer.scala new file mode 100644 index 000000000000..c70e5e5bdfe8 --- /dev/null +++ b/tests/neg-custom-args/nowarn/nowarn-parser-typer.scala @@ -0,0 +1,16 @@ +import annotation.nowarn + +// parser warnigns are buffered until the end of typer (where `@nowarn` annotations are handled). + +// separate test file, because with `-Werror` this test only runs to the end of type checking. +// later warnings (eg deprecations in refchecks) are not issued. + +def t1a = try 1 // error, parser warning +@nowarn("msg=try without catch") def t1b = try 1 + +@deprecated def f = 0 + +def t2 = f // not reported because refchecks doesn't run + +@nowarn("wat?") // error, invalid filter +def t3 = { 1; 2 } // error, the invalid nowarn doesn't silence this warning diff --git a/tests/neg-custom-args/nowarn/nowarn-refchecks.check b/tests/neg-custom-args/nowarn/nowarn-refchecks.check new file mode 100644 index 000000000000..425730c0f5a5 --- /dev/null +++ b/tests/neg-custom-args/nowarn/nowarn-refchecks.check @@ -0,0 +1,12 @@ +-- Error: tests/neg-custom-args/nowarn/nowarn-refchecks.scala:5:9 ------------------------------------------------------ +5 |def t1 = f // error + | ^ + | method f is deprecated +-- Error: tests/neg-custom-args/nowarn/nowarn-refchecks.scala:9:29 ----------------------------------------------------- +9 |@nowarn("msg=fish") def t4 = f // error + | ^ + | method f is deprecated +-- Error: tests/neg-custom-args/nowarn/nowarn-refchecks.scala:15:9 ----------------------------------------------------- +15 |def t9 = f: @nowarn("msg=fish") // error + | ^ + | method f is deprecated diff --git a/tests/neg/nowarn.scala b/tests/neg-custom-args/nowarn/nowarn-refchecks.scala similarity index 52% rename from tests/neg/nowarn.scala rename to tests/neg-custom-args/nowarn/nowarn-refchecks.scala index 410e9fe7dba6..89de259b4249 100644 --- a/tests/neg/nowarn.scala +++ b/tests/neg-custom-args/nowarn/nowarn-refchecks.scala @@ -2,21 +2,16 @@ import annotation.nowarn @deprecated def f = 1 -def t1 = f // warn +def t1 = f // error @nowarn("cat=deprecation") def t2 = f @nowarn("msg=deprecated") def t3 = f -@nowarn("msg=fish") def t4 = f // warn +@nowarn("msg=fish") def t4 = f // error @nowarn("") def t5 = f @nowarn def t6 = f def t7 = f: @nowarn("cat=deprecation") def t8 = f: @nowarn("msg=deprecated") -def t9 = f: @nowarn("msg=fish") // warn +def t9 = f: @nowarn("msg=fish") // error def t10 = f: @nowarn("") def t11 = f: @nowarn - -def t12 = List(List(1): _*) // warn -source:future-migration -@nowarn("msg=vararg splices") def t13 = List(List(1): _*) - -class K { override def blup = 1 } // error From a5345dd268c80262e3ea91cc944d74fea4a3bf46 Mon Sep 17 00:00:00 2001 From: Lukas Rytz Date: Tue, 13 Jul 2021 11:11:01 +0200 Subject: [PATCH 04/21] Move WConf cache to ContextBase --- .../src/dotty/tools/dotc/core/Contexts.scala | 2 ++ .../dotty/tools/dotc/reporting/WConf.scala | 35 ++++++++----------- .../dotc/config/ScalaSettingsTests.scala | 12 ++++--- .../tools/dotc/reporting/WConfTest.scala | 16 --------- 4 files changed, 25 insertions(+), 40 deletions(-) delete mode 100644 compiler/test/dotty/tools/dotc/reporting/WConfTest.scala 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/reporting/WConf.scala b/compiler/src/dotty/tools/dotc/reporting/WConf.scala index f2cc95e65254..b68aea5e9ffa 100644 --- a/compiler/src/dotty/tools/dotc/reporting/WConf.scala +++ b/compiler/src/dotty/tools/dotc/reporting/WConf.scala @@ -6,7 +6,6 @@ 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 @@ -31,7 +30,7 @@ final case class WConf(confs: List[(List[MessageFilter], Action)]): case (filters, action) if filters.forall(_.matches(message)) => action }.getOrElse(Action.Warning) -@sharable object WConf: +object WConf: import Action._ import MessageFilter._ @@ -49,38 +48,34 @@ final case class WConf(confs: List[(List[MessageFilter], Action)]): try Right(s.r) catch { case e: PatternSyntaxException => Left(s"invalid pattern `$s`: ${e.getMessage}") } - val splitter = raw"([^=]+)=(.+)".r - - 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 "cat" => - conf match { + def parseFilter(s: String): Either[String, MessageFilter] = + val splitter = raw"([^=]+)=(.+)".r + s match + case "any" => Right(Any) + case splitter(filter, conf) => filter match + case "msg" => regex(conf).map(MessagePattern.apply) + case "cat" => conf match case "deprecation" => Right(Deprecated) case "feature" => Right(Feature) case _ => Left(s"unknown category: $conf") - } - case _ => Left(s"unknown filter: $filter") - } - case _ => Left(s"unknown filter: $s") - } + case _ => Left(s"unknown filter: $filter") + case _ => Left(s"unknown filter: $s") - private var parsedCache: (List[String], WConf) = null def parsed(using Context): WConf = val setting = ctx.settings.Wconf.value - if parsedCache == null || parsedCache._1 != setting then + def cached = ctx.base.wConfCache + if cached == null || cached._1 != setting then val conf = fromSettings(setting) - parsedCache = (setting, conf.getOrElse(WConf(Nil))) + ctx.base.wConfCache = (setting, conf.getOrElse(WConf(Nil))) conf.swap.foreach(msgs => val multiHelp = - if (setting.sizeIs > 1) + 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")) - parsedCache._2 + cached._2 def fromSettings(settings: List[String]): Either[List[String], WConf] = if (settings.isEmpty) Right(WConf(Nil)) diff --git a/compiler/test/dotty/tools/dotc/config/ScalaSettingsTests.scala b/compiler/test/dotty/tools/dotc/config/ScalaSettingsTests.scala index 380afc5ffeb9..05f218059f02 100644 --- a/compiler/test/dotty/tools/dotc/config/ScalaSettingsTests.scala +++ b/compiler/test/dotty/tools/dotc/config/ScalaSettingsTests.scala @@ -67,15 +67,19 @@ class ScalaSettingsTests: @Test def `WConf setting is parsed`: Unit = import reporting.{Action, Diagnostic, NoExplanation} val sets = new ScalaSettings - val args = tokenize("-Wconf:cat=deprecation:w,cat=feature:w -Wconf:msg=message\\.pattern:s") + 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 diag = new Diagnostic.DeprecationWarning(msg, util.NoSourcePosition) - assertEquals("Warns deprecation", Action.Warning, sut.action(diag)) + val depr = new Diagnostic.DeprecationWarning(msg, util.NoSourcePosition) + assertEquals(Action.Silent, sut.action(depr)) val feat = new Diagnostic.FeatureWarning(msg, util.NoSourcePosition) - assertEquals("Warns feature", Action.Warning, sut.action(feat)) + 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/WConfTest.scala b/compiler/test/dotty/tools/dotc/reporting/WConfTest.scala deleted file mode 100644 index bb99557eaf74..000000000000 --- a/compiler/test/dotty/tools/dotc/reporting/WConfTest.scala +++ /dev/null @@ -1,16 +0,0 @@ -package dotty.tools.dotc -package reporting - -import config.CommandLineParser.tokenize -import config.Settings._ - -import org.junit.Test -import org.junit.Assert._ - -class WConfTest: - - @Test def `WConf setting is parsed`: Unit = - assertEquals(1, 1) - assertTrue("No warnings!", true) - -end WConfTest From 1164e85b42a440f78ae16693975e5bba57fa2aba Mon Sep 17 00:00:00 2001 From: Lukas Rytz Date: Tue, 13 Jul 2021 16:57:19 +0200 Subject: [PATCH 05/21] allow filtering messages by error id --- .../tools/dotc/config/ScalaSettings.scala | 5 ++- .../dotty/tools/dotc/reporting/WConf.scala | 37 +++++++++++++------ .../nowarn/nowarn-parser-typer.check | 7 ++++ .../nowarn/nowarn-parser-typer.scala | 6 +++ 4 files changed, 42 insertions(+), 13 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala b/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala index e8218bf3e73c..992328938f44 100644 --- a/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala +++ b/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala @@ -149,10 +149,13 @@ private sealed trait WarningSettings: | - 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. + | | | - error / e | - warning / w - | - info / i (infos are not counted as warnings and don't affect `-Werror`) + | - info / i (infos are not counted as warnings and not affected by `-Werror`) | - silent / s | |The default configuration is empty. diff --git a/compiler/src/dotty/tools/dotc/reporting/WConf.scala b/compiler/src/dotty/tools/dotc/reporting/WConf.scala index b68aea5e9ffa..781637cda365 100644 --- a/compiler/src/dotty/tools/dotc/reporting/WConf.scala +++ b/compiler/src/dotty/tools/dotc/reporting/WConf.scala @@ -6,6 +6,7 @@ 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 @@ -17,10 +18,12 @@ enum MessageFilter: 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, None case MessagePattern(pattern: Regex) + case MessageID(errorId: ErrorMessageID) enum Action: case Error, Warning, Info, Silent @@ -48,18 +51,28 @@ object WConf: try Right(s.r) catch { case e: PatternSyntaxException => Left(s"invalid pattern `$s`: ${e.getMessage}") } - def parseFilter(s: String): Either[String, MessageFilter] = - val splitter = raw"([^=]+)=(.+)".r - s match - case "any" => Right(Any) - case splitter(filter, conf) => filter match - case "msg" => regex(conf).map(MessagePattern.apply) - case "cat" => conf match - case "deprecation" => Right(Deprecated) - case "feature" => Right(Feature) - case _ => Left(s"unknown category: $conf") - case _ => Left(s"unknown filter: $filter") - case _ => Left(s"unknown filter: $s") + @sharable val Splitter = raw"([^=]+)=(.+)".r + @sharable val ErrorId = raw"E?(\d+)".r + + 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) => + val n = num.toInt + 2 + if n < ErrorMessageID.values.length then + Right(MessageID(ErrorMessageID.fromOrdinal(n))) + else + Left(s"unknonw error message id: E$n") + case _ => + Left(s"invalid error message id: $conf") + case "cat" => conf match + case "deprecation" => Right(Deprecated) + case "feature" => Right(Feature) + 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 diff --git a/tests/neg-custom-args/nowarn/nowarn-parser-typer.check b/tests/neg-custom-args/nowarn/nowarn-parser-typer.check index aa48042997f1..0e6cf8a2c5a8 100644 --- a/tests/neg-custom-args/nowarn/nowarn-parser-typer.check +++ b/tests/neg-custom-args/nowarn/nowarn-parser-typer.check @@ -4,6 +4,13 @@ | 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 Error: tests/neg-custom-args/nowarn/nowarn-parser-typer.scala:22:26 ----------------------------------- +22 |@nowarn("id=1") def t5d = try 1 // error, 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` -- [E129] Potential Issue Error: tests/neg-custom-args/nowarn/nowarn-parser-typer.scala:16:11 -------------------------- 16 |def t3 = { 1; 2 } // error, the invalid nowarn doesn't silence this warning diff --git a/tests/neg-custom-args/nowarn/nowarn-parser-typer.scala b/tests/neg-custom-args/nowarn/nowarn-parser-typer.scala index c70e5e5bdfe8..0e5cecf5ee6c 100644 --- a/tests/neg-custom-args/nowarn/nowarn-parser-typer.scala +++ b/tests/neg-custom-args/nowarn/nowarn-parser-typer.scala @@ -14,3 +14,9 @@ def t2 = f // not reported because refchecks doesn't run @nowarn("wat?") // error, invalid filter def t3 = { 1; 2 } // error, the invalid nowarn doesn't silence this warning + +@nowarn("id=E129") def t4 = { 1; 2 } +@nowarn("id=E000") def t5a = try 1 +@nowarn("id=E0") def t5b = try 1 +@nowarn("id=0") def t5c = try 1 +@nowarn("id=1") def t5d = try 1 // error, wrong id From 8b1cc9a1a4344d4e213e3bdacc0a5a38c3b625d0 Mon Sep 17 00:00:00 2001 From: Lukas Rytz Date: Wed, 21 Jul 2021 16:16:19 +0200 Subject: [PATCH 06/21] display setting:help messages for helping settings --- .../tools/dotc/config/CompilerCommand.scala | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) 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._ From cf596c494ffbe09b9bb2395617cfdf8eb6bc3350 Mon Sep 17 00:00:00 2001 From: Lukas Rytz Date: Wed, 21 Jul 2021 16:17:27 +0200 Subject: [PATCH 07/21] use UncheckedWarning for unchecked warnings. enable -unchecked by default. --- compiler/src/dotty/tools/dotc/config/ScalaSettings.scala | 2 +- compiler/src/dotty/tools/dotc/transform/TypeTestsCasts.scala | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala b/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala index 992328938f44..f6b8d52ad73b 100644 --- a/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala +++ b/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala @@ -51,7 +51,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")) 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) From ccb8d286e276759a55002e0046f72e3f2137fc06 Mon Sep 17 00:00:00 2001 From: Lukas Rytz Date: Wed, 21 Jul 2021 16:19:10 +0200 Subject: [PATCH 08/21] enable cat=unchecked filter for WConf / nowarn --- compiler/src/dotty/tools/dotc/config/ScalaSettings.scala | 2 +- compiler/src/dotty/tools/dotc/reporting/WConf.scala | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala b/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala index f6b8d52ad73b..599c03b5b4ac 100644 --- a/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala +++ b/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala @@ -144,7 +144,7 @@ private sealed trait WarningSettings: | | - Any message: any | - | - Message categories: cat=deprecation, cat=feature + | - 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. diff --git a/compiler/src/dotty/tools/dotc/reporting/WConf.scala b/compiler/src/dotty/tools/dotc/reporting/WConf.scala index 781637cda365..791fc51fcbba 100644 --- a/compiler/src/dotty/tools/dotc/reporting/WConf.scala +++ b/compiler/src/dotty/tools/dotc/reporting/WConf.scala @@ -15,13 +15,14 @@ enum MessageFilter: 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, None + case Any, Deprecated, Feature, Unchecked, None case MessagePattern(pattern: Regex) case MessageID(errorId: ErrorMessageID) @@ -70,6 +71,7 @@ object WConf: 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") From 7de0d09303a7605b84f7bc09b7bcd6a666b191d4 Mon Sep 17 00:00:00 2001 From: Lukas Rytz Date: Wed, 21 Jul 2021 16:22:13 +0200 Subject: [PATCH 09/21] messageAndPos takes Diagnostic parameter (refactoring only) --- .../dotty/tools/dotc/reporting/ConsoleReporter.scala | 4 ++-- .../dotty/tools/dotc/reporting/MessageRendering.scala | 10 ++++++---- compiler/src/dotty/tools/repl/Rendering.scala | 2 +- .../test/dotty/tools/dotc/reporting/TestReporter.scala | 2 +- .../src/dotty/tools/xsbt/DelegatingReporter.java | 4 ++-- 5 files changed, 12 insertions(+), 10 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/reporting/ConsoleReporter.scala b/compiler/src/dotty/tools/dotc/reporting/ConsoleReporter.scala index 984a2104a697..0609dd8c643c 100644 --- a/compiler/src/dotty/tools/dotc/reporting/ConsoleReporter.scala +++ b/compiler/src/dotty/tools/dotc/reporting/ConsoleReporter.scala @@ -23,10 +23,10 @@ class ConsoleReporter( def doReport(dia: Diagnostic)(using Context): Unit = { 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) case dia => - printMessage(messageAndPos(dia.msg, dia.pos, diagnosticLevel(dia))) + printMessage(messageAndPos(dia)) if shouldExplain(dia) then printMessage(explanation(dia.msg)) diff --git a/compiler/src/dotty/tools/dotc/reporting/MessageRendering.scala b/compiler/src/dotty/tools/dotc/reporting/MessageRendering.scala index 4e7c7ce5bb7e..80d3ca30ba0c 100644 --- a/compiler/src/dotty/tools/dotc/reporting/MessageRendering.scala +++ b/compiler/src/dotty/tools/dotc/reporting/MessageRendering.scala @@ -143,15 +143,17 @@ trait MessageRendering { } /** 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)) } 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/test/dotty/tools/dotc/reporting/TestReporter.scala b/compiler/test/dotty/tools/dotc/reporting/TestReporter.scala index 24890279accd..8e88c52f442d 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) { 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)); From 18086fc80ee4f1a0abda77c7ec588d7e45bb36a2 Mon Sep 17 00:00:00 2001 From: Lukas Rytz Date: Wed, 21 Jul 2021 16:30:17 +0200 Subject: [PATCH 10/21] Add verbose mode to WConf / nowarn to display matching message filters In "verbose" mode warnings are printed with a list of matching message filters that can be used in `-Wconf` and/or `@nowarn`. Example: ``` -- [E000] Syntax Warning: Test.scala:5:12 -------------------------------------- 5 | def bar = try 1 | ^^^^^ | 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` ``` Verbose mode can be enabled in `-Wconf`, for example for all warnings using `-Wconf:any:v`. Howeber, changing compiler flags is slow. To make the workflow more convenient verbose mode can be enabled by annotating the surrounding code with `@nowarn("verbose")` or `@nowarn("v")`: ``` @nowarn("v") def bar = try 1 ``` --- compiler/src/dotty/tools/dotc/Run.scala | 15 +++++--- .../tools/dotc/config/ScalaSettings.scala | 5 +++ .../tools/dotc/reporting/Diagnostic.scala | 19 ++++++---- .../dotc/reporting/MessageRendering.scala | 18 ++++++++++ .../dotty/tools/dotc/reporting/Reporter.scala | 36 ++++++++++--------- .../dotty/tools/dotc/reporting/WConf.scala | 7 ++-- .../src/dotty/tools/dotc/typer/Typer.scala | 19 +++++----- 7 files changed, 80 insertions(+), 39 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/Run.scala b/compiler/src/dotty/tools/dotc/Run.scala index 131631107509..7d0a7089f0d6 100644 --- a/compiler/src/dotty/tools/dotc/Run.scala +++ b/compiler/src/dotty/tools/dotc/Run.scala @@ -16,7 +16,8 @@ import io.{AbstractFile, PlainFile, VirtualFile} import Phases.unfusedPhases import util._ -import reporting.{Reporter, Suppression} +import reporting.{Reporter, Suppression, Action} +import reporting.Diagnostic import reporting.Diagnostic.Warning import rewrites.Rewrites @@ -110,10 +111,14 @@ class Run(comp: Compiler, ictx: Context) extends ImplicitRunInfo with Constraint def addSuspendedMessage(warning: Warning) = mySuspendedMessages.getOrElseUpdate(warning.pos.source, mutable.LinkedHashSet.empty) += warning - def isSuppressed(warning: Warning): Boolean = - mySuppressions.getOrElse(warning.pos.source, Nil).find(_.matches(warning)) match { - case Some(s) => s.markUsed(); true - case _ => false + 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 = diff --git a/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala b/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala index 599c03b5b4ac..fd8c7201b162 100644 --- a/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala +++ b/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala @@ -152,9 +152,14 @@ private sealed trait WarningSettings: | - Message id: id=E129 | The message id is printed with the warning. | + |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 | diff --git a/compiler/src/dotty/tools/dotc/reporting/Diagnostic.scala b/compiler/src/dotty/tools/dotc/reporting/Diagnostic.scala index 5afe6f4bd992..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,8 +36,8 @@ object Diagnostic: msg: Message, pos: SourcePosition ) extends Diagnostic(msg, pos, WARNING) { - def toError: Error = new Error(msg, pos) - def toInfo: Info = new Info(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 } @@ -84,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/MessageRendering.scala b/compiler/src/dotty/tools/dotc/reporting/MessageRendering.scala index 80d3ca30ba0c..c97ad609e162 100644 --- a/compiler/src/dotty/tools/dotc/reporting/MessageRendering.scala +++ b/compiler/src/dotty/tools/dotc/reporting/MessageRendering.scala @@ -142,6 +142,22 @@ 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) + if (category.nonEmpty) + sb.append(EOL).append(" - cat=").append(category) + /** The whole message rendered from `msg` */ def messageAndPos(dia: Diagnostic)(using Context): String = { import dia._ @@ -160,6 +176,8 @@ trait MessageRendering { 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 d3571af5454f..f41250c34701 100644 --- a/compiler/src/dotty/tools/dotc/reporting/Reporter.scala +++ b/compiler/src/dotty/tools/dotc/reporting/Reporter.scala @@ -2,20 +2,19 @@ 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 */ @@ -142,12 +141,13 @@ abstract class Reporter extends interfaces.ReporterResult { 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 if ctx.settings.XfatalWarnings.value && !w.isSummarizedConditional then Some(w.toError) else WConf.parsed.action(dia) match case Silent => None case Info => Some(w.toInfo) - case Warning => Some(w) + case Warning => fatal(w) + case Verbose => fatal(w).tap(_.foreach(_.setVerbose())) case Error => Some(w.toError) case _ => Some(dia) @@ -175,8 +175,10 @@ abstract class Reporter extends interfaces.ReporterResult { dia match case w: Warning if ctx.run != null => val sup = ctx.run.suppressions - if sup.suppressionsComplete(w.pos.source) then - if !sup.isSuppressed(w) then go() + 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 sup.addSuspendedMessage(w) case _ => go() diff --git a/compiler/src/dotty/tools/dotc/reporting/WConf.scala b/compiler/src/dotty/tools/dotc/reporting/WConf.scala index 791fc51fcbba..ddabb36bd94f 100644 --- a/compiler/src/dotty/tools/dotc/reporting/WConf.scala +++ b/compiler/src/dotty/tools/dotc/reporting/WConf.scala @@ -27,7 +27,7 @@ enum MessageFilter: case MessageID(errorId: ErrorMessageID) enum Action: - case Error, Warning, Info, Silent + case Error, Warning, Verbose, Info, Silent final case class WConf(confs: List[(List[MessageFilter], Action)]): def action(message: Diagnostic): Action = confs.collectFirst { @@ -43,6 +43,7 @@ object WConf: 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`")) @@ -107,12 +108,12 @@ object WConf: else Right(WConf(fs)) } -case class Suppression(annotPos: SourcePosition, filters: List[MessageFilter], start: Int, end: Int): +case class Suppression(annotPos: SourcePosition, filters: List[MessageFilter], start: Int, end: Int, 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)) + pos.exists && start <= pos.start && pos.end <= end && (verbose || filters.forall(_.matches(dia))) } diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 736bd031d955..2fbb0dac9bb4 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -2089,6 +2089,7 @@ class Typer extends Namer 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 = if annot.arguments.isEmpty then List(MessageFilter.Any) else annot.argumentConstantString(0) match @@ -2097,17 +2098,19 @@ class Typer extends Namer case _ => report.warning(s"filter needs to be a compile-time constant string", argPos) Nil + case Some("") => Nil + case Some("verbose") | Some("v") => + verbose = true + Nil case Some(s) => - if s.isEmpty then Nil + val (ms, fs) = s.split('&').map(WConf.parseFilter).toList.partitionMap(identity) + if ms.nonEmpty then + report.warning(s"Invalid message filter\n${ms.mkString("\n")}", argPos) + List(MessageFilter.None) else - val (ms, fs) = s.split('&').map(WConf.parseFilter).toList.partitionMap(identity) - if ms.nonEmpty then - report.warning(s"Invalid message filter\n${ms.mkString("\n")}", argPos) - List(MessageFilter.None) - else - fs + fs val range = mdef.sourcePos - ctx.run.suppressions.addSuppression(Suppression(tree.sourcePos, filters, range.start, range.end)) + ctx.run.suppressions.addSuppression(Suppression(tree.sourcePos, filters, range.start, range.end, verbose)) def typedValDef(vdef: untpd.ValDef, sym: Symbol)(using Context): Tree = { val ValDef(name, tpt, _) = vdef From 8827455aaf5edcc140be865b922b6d8d2dce1e4f Mon Sep 17 00:00:00 2001 From: Lukas Rytz Date: Wed, 21 Jul 2021 16:45:23 +0200 Subject: [PATCH 11/21] enable filtering warnings by their ErrorMessageID name --- compiler/src/dotty/tools/dotc/config/ScalaSettings.scala | 3 +++ .../src/dotty/tools/dotc/reporting/MessageRendering.scala | 1 + compiler/src/dotty/tools/dotc/reporting/WConf.scala | 4 ++++ 3 files changed, 8 insertions(+) diff --git a/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala b/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala index fd8c7201b162..7056e41927fb 100644 --- a/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala +++ b/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala @@ -152,6 +152,9 @@ private sealed trait WarningSettings: | - 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`). diff --git a/compiler/src/dotty/tools/dotc/reporting/MessageRendering.scala b/compiler/src/dotty/tools/dotc/reporting/MessageRendering.scala index c97ad609e162..d16d15f42070 100644 --- a/compiler/src/dotty/tools/dotc/reporting/MessageRendering.scala +++ b/compiler/src/dotty/tools/dotc/reporting/MessageRendering.scala @@ -155,6 +155,7 @@ trait MessageRendering { 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) diff --git a/compiler/src/dotty/tools/dotc/reporting/WConf.scala b/compiler/src/dotty/tools/dotc/reporting/WConf.scala index ddabb36bd94f..c50533010c32 100644 --- a/compiler/src/dotty/tools/dotc/reporting/WConf.scala +++ b/compiler/src/dotty/tools/dotc/reporting/WConf.scala @@ -69,6 +69,10 @@ object WConf: Left(s"unknonw error message id: E$n") 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) From 4648aab5d59b284f4197b62cb1fdc0c356ca8a7e Mon Sep 17 00:00:00 2001 From: Lukas Rytz Date: Wed, 21 Jul 2021 16:56:58 +0200 Subject: [PATCH 12/21] update tests (more tests to come) --- .../nowarn/nowarn-parser-typer.check | 14 ++++++++++++-- .../nowarn/nowarn-parser-typer.scala | 9 ++++++++- 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/tests/neg-custom-args/nowarn/nowarn-parser-typer.check b/tests/neg-custom-args/nowarn/nowarn-parser-typer.check index 0e6cf8a2c5a8..747374d56182 100644 --- a/tests/neg-custom-args/nowarn/nowarn-parser-typer.check +++ b/tests/neg-custom-args/nowarn/nowarn-parser-typer.check @@ -5,12 +5,22 @@ | its body in a block; no exceptions are handled. longer explanation available when compiling with `-explain` --- [E000] Syntax Error: tests/neg-custom-args/nowarn/nowarn-parser-typer.scala:22:26 ----------------------------------- -22 |@nowarn("id=1") def t5d = try 1 // error, wrong id +-- [E000] Syntax Error: tests/neg-custom-args/nowarn/nowarn-parser-typer.scala:23:26 ----------------------------------- +23 |@nowarn("id=1") def t5d = try 1 // error, 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 Error: tests/neg-custom-args/nowarn/nowarn-parser-typer.scala:25:28 ----------------------------------- +25 |@nowarn("verbose") def t6 = try 1 // error 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 Error: tests/neg-custom-args/nowarn/nowarn-parser-typer.scala:16:11 -------------------------- 16 |def t3 = { 1; 2 } // error, the invalid nowarn doesn't silence this warning diff --git a/tests/neg-custom-args/nowarn/nowarn-parser-typer.scala b/tests/neg-custom-args/nowarn/nowarn-parser-typer.scala index 0e5cecf5ee6c..1e83ce7bf1cd 100644 --- a/tests/neg-custom-args/nowarn/nowarn-parser-typer.scala +++ b/tests/neg-custom-args/nowarn/nowarn-parser-typer.scala @@ -15,8 +15,15 @@ def t2 = f // not reported because refchecks doesn't run @nowarn("wat?") // error, invalid filter def t3 = { 1; 2 } // error, the invalid nowarn doesn't silence this warning -@nowarn("id=E129") def t4 = { 1; 2 } +@nowarn("id=E129") def t4a = { 1; 2 } +@nowarn("name=PureExpressionInStatementPosition") def t4b = { 1; 2 } @nowarn("id=E000") def t5a = try 1 @nowarn("id=E0") def t5b = try 1 @nowarn("id=0") def t5c = try 1 @nowarn("id=1") def t5d = try 1 // error, wrong id + +@nowarn("verbose") def t6 = try 1 // error with details + +@nowarn("cat=unchecked") def t7(x: Any) = x match + case _: List[Int] => 0 + case _ => 1 From 938e294ee75b8ad07e3ce14b1eff61c53e32e579 Mon Sep 17 00:00:00 2001 From: Lukas Rytz Date: Thu, 22 Jul 2021 14:15:39 +0200 Subject: [PATCH 13/21] Add `-Wunused` flag with `-Wunused:nowarn` option Scala 2 has a bunch of options for `-Wunused` warnings (imports, locals, ...). This commit adds the flag and a first warning, for unused `@nowarn` annotations that don't silence any warnings. --- compiler/src/dotty/tools/dotc/Run.scala | 22 +++++++++++++---- .../tools/dotc/config/ScalaSettings.scala | 22 +++++++++++++---- .../dotty/tools/dotc/config/Settings.scala | 13 ++++++++-- .../src/dotty/tools/dotc/typer/Typer.scala | 4 +++- .../dotty/tools/dotc/typer/TyperPhase.scala | 2 +- .../dotty/tools/dotc/CompilationTests.scala | 2 +- .../nowarn/nowarn-parser-typer.check | 18 ++++++++------ .../nowarn/nowarn-parser-typer.scala | 7 ++---- .../nowarn/nowarn-refchecks.check | 24 ++++++++++++++----- .../nowarn/nowarn-refchecks.scala | 14 ++++++++--- 10 files changed, 93 insertions(+), 35 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/Run.scala b/compiler/src/dotty/tools/dotc/Run.scala index 7d0a7089f0d6..a89515e5abc6 100644 --- a/compiler/src/dotty/tools/dotc/Run.scala +++ b/compiler/src/dotty/tools/dotc/Run.scala @@ -125,14 +125,25 @@ class Run(comp: Compiler, ictx: Context) extends ImplicitRunInfo with Constraint val source = sup.annotPos.source mySuppressions.getOrElseUpdate(source, mutable.ListBuffer.empty) += sup - def reportSuspendedMessages(unit: CompilationUnit)(using Context): Unit = { + 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(unit.source)) - mySuppressions(unit.source) = sups.sortBy(sup => 0 - sup.start) - mySuppressionsComplete += unit.source - mySuspendedMessages.remove(unit.source).foreach(_.foreach(ctx.reporter.issueIfNotSuppressed)) + for (sups <- mySuppressions.get(source)) + mySuppressions(source) = sups.sortBy(sup => 0 - sup.start) + mySuppressionsComplete += source + mySuspendedMessages.remove(source).foreach(_.foreach(ctx.reporter.issueIfNotSuppressed)) } + def warnUnusedSuppressions(): Unit = + // if we stop before typer completes (errors in parser, Ystop), report all suspended messages + mySuspendedMessages.keysIterator.toList.foreach(reportSuspendedMessages) + if 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. */ @@ -264,6 +275,7 @@ class Run(comp: Compiler, ictx: Context) extends ImplicitRunInfo with Constraint val action = finalizeActions.remove(0) action() } + suppressions.warnUnusedSuppressions() compiling = false } diff --git a/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala b/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala index 7056e41927fb..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 @@ -132,6 +134,18 @@ private sealed trait WarningSettings: self: SettingGroup => 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", diff --git a/compiler/src/dotty/tools/dotc/config/Settings.scala b/compiler/src/dotty/tools/dotc/config/Settings.scala index 5b794255df48..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,6 +257,9 @@ 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)) diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 2fbb0dac9bb4..88cbe05306b1 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -2110,7 +2110,9 @@ class Typer extends Namer else fs val range = mdef.sourcePos - ctx.run.suppressions.addSuppression(Suppression(tree.sourcePos, filters, range.start, range.end, verbose)) + val sup = Suppression(tree.sourcePos, filters, range.start, range.end, verbose) + 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 diff --git a/compiler/src/dotty/tools/dotc/typer/TyperPhase.scala b/compiler/src/dotty/tools/dotc/typer/TyperPhase.scala index c6bcf14ddbe5..8141bfabc3fb 100644 --- a/compiler/src/dotty/tools/dotc/typer/TyperPhase.scala +++ b/compiler/src/dotty/tools/dotc/typer/TyperPhase.scala @@ -57,7 +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) + ctx.run.suppressions.reportSuspendedMessages(unit.source) catch case ex: CompilationUnit.SuspendException => } diff --git a/compiler/test/dotty/tools/dotc/CompilationTests.scala b/compiler/test/dotty/tools/dotc/CompilationTests.scala index 50303d1c1159..8e37d641b4e5 100644 --- a/compiler/test/dotty/tools/dotc/CompilationTests.scala +++ b/compiler/test/dotty/tools/dotc/CompilationTests.scala @@ -129,7 +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", "-Werror")), + compileFilesInDir("tests/neg-custom-args/nowarn", defaultOptions.and("-deprecation", "-Werror", "-Wunused:nowarn")), 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/tests/neg-custom-args/nowarn/nowarn-parser-typer.check b/tests/neg-custom-args/nowarn/nowarn-parser-typer.check index 747374d56182..34b45826eca5 100644 --- a/tests/neg-custom-args/nowarn/nowarn-parser-typer.check +++ b/tests/neg-custom-args/nowarn/nowarn-parser-typer.check @@ -5,15 +5,15 @@ | its body in a block; no exceptions are handled. longer explanation available when compiling with `-explain` --- [E000] Syntax Error: tests/neg-custom-args/nowarn/nowarn-parser-typer.scala:23:26 ----------------------------------- -23 |@nowarn("id=1") def t5d = try 1 // error, wrong id - | ^^^^^ - | A try without catch or finally is equivalent to putting - | its body in a block; no exceptions are handled. +-- [E000] Syntax Error: tests/neg-custom-args/nowarn/nowarn-parser-typer.scala:24:10 ----------------------------------- +24 |def t5d = try 1 // error, 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 Error: tests/neg-custom-args/nowarn/nowarn-parser-typer.scala:25:28 ----------------------------------- -25 |@nowarn("verbose") def t6 = try 1 // error with details +-- [E000] Syntax Error: tests/neg-custom-args/nowarn/nowarn-parser-typer.scala:26:28 ----------------------------------- +26 |@nowarn("verbose") def t6 = try 1 // error with details | ^^^^^ | A try without catch or finally is equivalent to putting | its body in a block; no exceptions are handled. @@ -33,3 +33,7 @@ longer explanation available when compiling with `-explain` | ^^^^^^ | Invalid message filter | unknown filter: wat? +-- Error: tests/neg-custom-args/nowarn/nowarn-parser-typer.scala:23:1 -------------------------------------------------- +23 |@nowarn("id=1") // error, unused nowarn + |^^^^^^^^^^^^^^^ + |@nowarn annotation does not suppress any warnings diff --git a/tests/neg-custom-args/nowarn/nowarn-parser-typer.scala b/tests/neg-custom-args/nowarn/nowarn-parser-typer.scala index 1e83ce7bf1cd..24ae4d4cfc91 100644 --- a/tests/neg-custom-args/nowarn/nowarn-parser-typer.scala +++ b/tests/neg-custom-args/nowarn/nowarn-parser-typer.scala @@ -20,10 +20,7 @@ def t3 = { 1; 2 } // error, the invalid nowarn doesn't silence this warning @nowarn("id=E000") def t5a = try 1 @nowarn("id=E0") def t5b = try 1 @nowarn("id=0") def t5c = try 1 -@nowarn("id=1") def t5d = try 1 // error, wrong id +@nowarn("id=1") // error, unused nowarn +def t5d = try 1 // error, wrong id @nowarn("verbose") def t6 = try 1 // error with details - -@nowarn("cat=unchecked") def t7(x: Any) = x match - case _: List[Int] => 0 - case _ => 1 diff --git a/tests/neg-custom-args/nowarn/nowarn-refchecks.check b/tests/neg-custom-args/nowarn/nowarn-refchecks.check index 425730c0f5a5..fc3a9da50356 100644 --- a/tests/neg-custom-args/nowarn/nowarn-refchecks.check +++ b/tests/neg-custom-args/nowarn/nowarn-refchecks.check @@ -2,11 +2,23 @@ 5 |def t1 = f // error | ^ | method f is deprecated --- Error: tests/neg-custom-args/nowarn/nowarn-refchecks.scala:9:29 ----------------------------------------------------- -9 |@nowarn("msg=fish") def t4 = f // error - | ^ - | method f is deprecated --- Error: tests/neg-custom-args/nowarn/nowarn-refchecks.scala:15:9 ----------------------------------------------------- -15 |def t9 = f: @nowarn("msg=fish") // error +-- Error: tests/neg-custom-args/nowarn/nowarn-refchecks.scala:10:9 ----------------------------------------------------- +10 |def t4 = f // error | ^ | method f is deprecated +-- Error: tests/neg-custom-args/nowarn/nowarn-refchecks.scala:17:9 ----------------------------------------------------- +17 |def t9 = f: // error + | ^ + | method f is deprecated +-- Error: tests/neg-custom-args/nowarn/nowarn-refchecks.scala:9:1 ------------------------------------------------------ +9 |@nowarn("msg=fish") // error, unused nowarn + |^^^^^^^^^^^^^^^^^^^ + |@nowarn annotation does not suppress any warnings +-- Error: tests/neg-custom-args/nowarn/nowarn-refchecks.scala:18:3 ----------------------------------------------------- +18 | @nowarn("msg=fish") // error, unused nowarn + | ^^^^^^^^^^^^^^^^^^^ + | @nowarn annotation does not suppress any warnings +-- Error: tests/neg-custom-args/nowarn/nowarn-refchecks.scala:23:1 ----------------------------------------------------- +23 |@nowarn("cat=unchecked") def t7b(x: Any) = x match // error + |^^^^^^^^^^^^^^^^^^^^^^^^ + |@nowarn annotation does not suppress any warnings diff --git a/tests/neg-custom-args/nowarn/nowarn-refchecks.scala b/tests/neg-custom-args/nowarn/nowarn-refchecks.scala index 89de259b4249..a2ffc747fd40 100644 --- a/tests/neg-custom-args/nowarn/nowarn-refchecks.scala +++ b/tests/neg-custom-args/nowarn/nowarn-refchecks.scala @@ -6,12 +6,20 @@ def t1 = f // error @nowarn("cat=deprecation") def t2 = f @nowarn("msg=deprecated") def t3 = f -@nowarn("msg=fish") def t4 = f // error +@nowarn("msg=fish") // error, unused nowarn +def t4 = f // error @nowarn("") def t5 = f @nowarn def t6 = f def t7 = f: @nowarn("cat=deprecation") -def t8 = f: @nowarn("msg=deprecated") -def t9 = f: @nowarn("msg=fish") // error +def t8 = f: + @nowarn("msg=deprecated") +def t9 = f: // error + @nowarn("msg=fish") // error, unused nowarn def t10 = f: @nowarn("") def t11 = f: @nowarn + +// unused nowarn; this test only runs until refchecks with Werror, the unchecked warning is issued in a later phase +@nowarn("cat=unchecked") def t7b(x: Any) = x match // error + case _: List[Int] => 0 + case _ => 1 From 41e56397327c757e658d21df75707c96138c3282 Mon Sep 17 00:00:00 2001 From: Lukas Rytz Date: Fri, 23 Jul 2021 14:21:16 +0200 Subject: [PATCH 14/21] fix line numbers in vulpix output --- compiler/test/dotty/tools/vulpix/ParallelTesting.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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") From 49edfa1bef8d5e1e63d7f05f3fbff9d8b319179c Mon Sep 17 00:00:00 2001 From: Lukas Rytz Date: Fri, 23 Jul 2021 14:22:22 +0200 Subject: [PATCH 15/21] Don't discard warnings in TestReporter for .check files of neg tests --- .../dotty/tools/dotc/reporting/TestReporter.scala | 12 +++--------- tests/neg-scalajs/jsname-argument.check | 4 ++++ tests/neg/i12150.check | 6 ++++++ tests/neg/i2033.check | 4 ++++ tests/neg/spaces-vs-tabs.check | 6 ++++++ tests/neg/t6810.check | 8 ++++++++ 6 files changed, 31 insertions(+), 9 deletions(-) diff --git a/compiler/test/dotty/tools/dotc/reporting/TestReporter.scala b/compiler/test/dotty/tools/dotc/reporting/TestReporter.scala index 8e88c52f442d..da9b2498d528 100644 --- a/compiler/test/dotty/tools/dotc/reporting/TestReporter.scala +++ b/compiler/test/dotty/tools/dotc/reporting/TestReporter.scala @@ -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/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 From e34c9bcff71ff0d1ca2d6f3e2ab79a7d6e25a2ee Mon Sep 17 00:00:00 2001 From: Lukas Rytz Date: Fri, 23 Jul 2021 14:23:04 +0200 Subject: [PATCH 16/21] Issue unused nowarn warning only if the compiler run has no errors --- compiler/src/dotty/tools/dotc/Run.scala | 6 +- .../dotty/tools/dotc/CompilationTests.scala | 2 +- .../nowarn/nowarn-parser-typer.check | 39 ------------ .../nowarn/nowarn-parser-typer.scala | 26 -------- .../nowarn/nowarn-refchecks.check | 24 ------- .../nowarn/nowarn-refchecks.scala | 25 -------- tests/neg-custom-args/nowarn/nowarn.check | 63 +++++++++++++++++++ tests/neg-custom-args/nowarn/nowarn.scala | 48 ++++++++++++++ 8 files changed, 116 insertions(+), 117 deletions(-) delete mode 100644 tests/neg-custom-args/nowarn/nowarn-parser-typer.check delete mode 100644 tests/neg-custom-args/nowarn/nowarn-parser-typer.scala delete mode 100644 tests/neg-custom-args/nowarn/nowarn-refchecks.check delete mode 100644 tests/neg-custom-args/nowarn/nowarn-refchecks.scala create mode 100644 tests/neg-custom-args/nowarn/nowarn.check create mode 100644 tests/neg-custom-args/nowarn/nowarn.scala diff --git a/compiler/src/dotty/tools/dotc/Run.scala b/compiler/src/dotty/tools/dotc/Run.scala index a89515e5abc6..00cda8da399b 100644 --- a/compiler/src/dotty/tools/dotc/Run.scala +++ b/compiler/src/dotty/tools/dotc/Run.scala @@ -270,12 +270,14 @@ 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() + // later phases don't run when there are errors, which would lead to stale `unused @nowarn` warnings + suppressions.warnUnusedSuppressions() while (finalizeActions.nonEmpty) { val action = finalizeActions.remove(0) action() } - suppressions.warnUnusedSuppressions() compiling = false } diff --git a/compiler/test/dotty/tools/dotc/CompilationTests.scala b/compiler/test/dotty/tools/dotc/CompilationTests.scala index 8e37d641b4e5..ada3c899fad7 100644 --- a/compiler/test/dotty/tools/dotc/CompilationTests.scala +++ b/compiler/test/dotty/tools/dotc/CompilationTests.scala @@ -129,7 +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", "-Werror", "-Wunused:nowarn")), + 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/tests/neg-custom-args/nowarn/nowarn-parser-typer.check b/tests/neg-custom-args/nowarn/nowarn-parser-typer.check deleted file mode 100644 index 34b45826eca5..000000000000 --- a/tests/neg-custom-args/nowarn/nowarn-parser-typer.check +++ /dev/null @@ -1,39 +0,0 @@ --- [E000] Syntax Error: tests/neg-custom-args/nowarn/nowarn-parser-typer.scala:8:10 ------------------------------------ -8 |def t1a = try 1 // error, parser warning - | ^^^^^ - | 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 Error: tests/neg-custom-args/nowarn/nowarn-parser-typer.scala:24:10 ----------------------------------- -24 |def t5d = try 1 // error, 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 Error: tests/neg-custom-args/nowarn/nowarn-parser-typer.scala:26:28 ----------------------------------- -26 |@nowarn("verbose") def t6 = try 1 // error 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 Error: tests/neg-custom-args/nowarn/nowarn-parser-typer.scala:16:11 -------------------------- -16 |def t3 = { 1; 2 } // error, the invalid nowarn doesn't silence this warning - | ^ - | A pure expression does nothing in statement position; you may be omitting necessary parentheses - -longer explanation available when compiling with `-explain` --- Error: tests/neg-custom-args/nowarn/nowarn-parser-typer.scala:15:8 -------------------------------------------------- -15 |@nowarn("wat?") // error, invalid filter - | ^^^^^^ - | Invalid message filter - | unknown filter: wat? --- Error: tests/neg-custom-args/nowarn/nowarn-parser-typer.scala:23:1 -------------------------------------------------- -23 |@nowarn("id=1") // error, unused nowarn - |^^^^^^^^^^^^^^^ - |@nowarn annotation does not suppress any warnings diff --git a/tests/neg-custom-args/nowarn/nowarn-parser-typer.scala b/tests/neg-custom-args/nowarn/nowarn-parser-typer.scala deleted file mode 100644 index 24ae4d4cfc91..000000000000 --- a/tests/neg-custom-args/nowarn/nowarn-parser-typer.scala +++ /dev/null @@ -1,26 +0,0 @@ -import annotation.nowarn - -// parser warnigns are buffered until the end of typer (where `@nowarn` annotations are handled). - -// separate test file, because with `-Werror` this test only runs to the end of type checking. -// later warnings (eg deprecations in refchecks) are not issued. - -def t1a = try 1 // error, parser warning -@nowarn("msg=try without catch") def t1b = try 1 - -@deprecated def f = 0 - -def t2 = f // not reported because refchecks doesn't run - -@nowarn("wat?") // error, invalid filter -def t3 = { 1; 2 } // error, the invalid nowarn doesn't silence this warning - -@nowarn("id=E129") def t4a = { 1; 2 } -@nowarn("name=PureExpressionInStatementPosition") def t4b = { 1; 2 } -@nowarn("id=E000") def t5a = try 1 -@nowarn("id=E0") def t5b = try 1 -@nowarn("id=0") def t5c = try 1 -@nowarn("id=1") // error, unused nowarn -def t5d = try 1 // error, wrong id - -@nowarn("verbose") def t6 = try 1 // error with details diff --git a/tests/neg-custom-args/nowarn/nowarn-refchecks.check b/tests/neg-custom-args/nowarn/nowarn-refchecks.check deleted file mode 100644 index fc3a9da50356..000000000000 --- a/tests/neg-custom-args/nowarn/nowarn-refchecks.check +++ /dev/null @@ -1,24 +0,0 @@ --- Error: tests/neg-custom-args/nowarn/nowarn-refchecks.scala:5:9 ------------------------------------------------------ -5 |def t1 = f // error - | ^ - | method f is deprecated --- Error: tests/neg-custom-args/nowarn/nowarn-refchecks.scala:10:9 ----------------------------------------------------- -10 |def t4 = f // error - | ^ - | method f is deprecated --- Error: tests/neg-custom-args/nowarn/nowarn-refchecks.scala:17:9 ----------------------------------------------------- -17 |def t9 = f: // error - | ^ - | method f is deprecated --- Error: tests/neg-custom-args/nowarn/nowarn-refchecks.scala:9:1 ------------------------------------------------------ -9 |@nowarn("msg=fish") // error, unused nowarn - |^^^^^^^^^^^^^^^^^^^ - |@nowarn annotation does not suppress any warnings --- Error: tests/neg-custom-args/nowarn/nowarn-refchecks.scala:18:3 ----------------------------------------------------- -18 | @nowarn("msg=fish") // error, unused nowarn - | ^^^^^^^^^^^^^^^^^^^ - | @nowarn annotation does not suppress any warnings --- Error: tests/neg-custom-args/nowarn/nowarn-refchecks.scala:23:1 ----------------------------------------------------- -23 |@nowarn("cat=unchecked") def t7b(x: Any) = x match // error - |^^^^^^^^^^^^^^^^^^^^^^^^ - |@nowarn annotation does not suppress any warnings diff --git a/tests/neg-custom-args/nowarn/nowarn-refchecks.scala b/tests/neg-custom-args/nowarn/nowarn-refchecks.scala deleted file mode 100644 index a2ffc747fd40..000000000000 --- a/tests/neg-custom-args/nowarn/nowarn-refchecks.scala +++ /dev/null @@ -1,25 +0,0 @@ -import annotation.nowarn - -@deprecated def f = 1 - -def t1 = f // error - -@nowarn("cat=deprecation") def t2 = f -@nowarn("msg=deprecated") def t3 = f -@nowarn("msg=fish") // error, unused nowarn -def t4 = f // error -@nowarn("") def t5 = f -@nowarn def t6 = f - -def t7 = f: @nowarn("cat=deprecation") -def t8 = f: - @nowarn("msg=deprecated") -def t9 = f: // error - @nowarn("msg=fish") // error, unused nowarn -def t10 = f: @nowarn("") -def t11 = f: @nowarn - -// unused nowarn; this test only runs until refchecks with Werror, the unchecked warning is issued in a later phase -@nowarn("cat=unchecked") def t7b(x: Any) = x match // error - case _: List[Int] => 0 - case _ => 1 diff --git a/tests/neg-custom-args/nowarn/nowarn.check b/tests/neg-custom-args/nowarn/nowarn.check new file mode 100644 index 000000000000..0e7e7660a23b --- /dev/null +++ b/tests/neg-custom-args/nowarn/nowarn.check @@ -0,0 +1,63 @@ +-- [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:21:26 ---------------------------------------------- +21 |@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:23:28 ---------------------------------------------- +23 |@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? +-- Deprecation Warning: tests/neg-custom-args/nowarn/nowarn.scala:27:10 ------------------------------------------------ +27 |def t6a = f // warning (refchecks, deprecation) + | ^ + | method f is deprecated +-- Deprecation Warning: tests/neg-custom-args/nowarn/nowarn.scala:30:30 ------------------------------------------------ +30 |@nowarn("msg=fish") def t6d = f // error (unused nowarn), warning (deprecation) + | ^ + | method f is deprecated +-- Deprecation Warning: tests/neg-custom-args/nowarn/nowarn.scala:37:10 ------------------------------------------------ +37 |def t7c = f: // warning (deprecation) + | ^ + | method f is deprecated +-- Unchecked Warning: tests/neg-custom-args/nowarn/nowarn.scala:43:7 --------------------------------------------------- +43 | 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:21:1 --------------------------------------------------------------- +21 |@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:30:1 --------------------------------------------------------------- +30 |@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:38:3 --------------------------------------------------------------- +38 | @nowarn("msg=fish") // 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..1e33a19eb8bc --- /dev/null +++ b/tests/neg-custom-args/nowarn/nowarn.scala @@ -0,0 +1,48 @@ +import annotation.nowarn + +// 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("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 From 312b8c0146f81c7104c4166cd20727bb1869f464 Mon Sep 17 00:00:00 2001 From: Lukas Rytz Date: Fri, 23 Jul 2021 15:44:28 +0200 Subject: [PATCH 17/21] add more tests for nowarn --- tests/neg-custom-args/nowarn/nowarn.check | 24 ++++++++++++++++++ tests/neg-custom-args/nowarn/nowarn.scala | 31 ++++++++++++++++++++++- 2 files changed, 54 insertions(+), 1 deletion(-) diff --git a/tests/neg-custom-args/nowarn/nowarn.check b/tests/neg-custom-args/nowarn/nowarn.check index 0e7e7660a23b..f6c1312ea566 100644 --- a/tests/neg-custom-args/nowarn/nowarn.check +++ b/tests/neg-custom-args/nowarn/nowarn.check @@ -61,3 +61,27 @@ longer explanation available when compiling with `-explain` 38 | @nowarn("msg=fish") // error (unused nowarn) | ^^^^^^^^^^^^^^^^^^^ | @nowarn annotation does not suppress any warnings +-- Error: tests/neg-custom-args/nowarn/nowarn.scala:50:0 --------------------------------------------------------------- +50 |@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:51:27 -------------------------------------------------------------- +51 |@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:56:0 --------------------------------------------------------------- +56 |@nowarn @ann(f) def t10b = 0 // error (unused nowarn) + |^^^^^^^ + |@nowarn annotation does not suppress any warnings +-- Error: tests/neg-custom-args/nowarn/nowarn.scala:57:8 --------------------------------------------------------------- +57 |@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:60:0 --------------------------------------------------------------- +60 |@nowarn class I1a { // error (unused nowarn) + |^^^^^^^ + |@nowarn annotation does not suppress any warnings +-- Error: tests/neg-custom-args/nowarn/nowarn.scala:65:0 --------------------------------------------------------------- +65 |@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 index 1e33a19eb8bc..692426d6a4f8 100644 --- a/tests/neg-custom-args/nowarn/nowarn.scala +++ b/tests/neg-custom-args/nowarn/nowarn.scala @@ -1,4 +1,4 @@ -import annotation.nowarn +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. @@ -46,3 +46,32 @@ def t8a(x: Any) = x match @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 From 7c84a3ec0da102f7e07c40a56dad898ae5c805dc Mon Sep 17 00:00:00 2001 From: Lukas Rytz Date: Fri, 30 Jul 2021 16:58:25 +0200 Subject: [PATCH 18/21] make parser warnings and @nowarn work in the repl --- compiler/src/dotty/tools/dotc/Run.scala | 7 +++ .../dotty/tools/dotc/reporting/Reporter.scala | 11 +++- .../src/dotty/tools/repl/ReplCompiler.scala | 54 ++++++++++--------- compiler/test-resources/repl/nowarn.scala | 24 +++++++++ 4 files changed, 69 insertions(+), 27 deletions(-) create mode 100644 compiler/test-resources/repl/nowarn.scala diff --git a/compiler/src/dotty/tools/dotc/Run.scala b/compiler/src/dotty/tools/dotc/Run.scala index 00cda8da399b..13216f54e56a 100644 --- a/compiler/src/dotty/tools/dotc/Run.scala +++ b/compiler/src/dotty/tools/dotc/Run.scala @@ -106,6 +106,13 @@ class Run(comp: Compiler, ictx: Context) extends ImplicitRunInfo with Constraint 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) = diff --git a/compiler/src/dotty/tools/dotc/reporting/Reporter.scala b/compiler/src/dotty/tools/dotc/reporting/Reporter.scala index f41250c34701..8c736993de8e 100644 --- a/compiler/src/dotty/tools/dotc/reporting/Reporter.scala +++ b/compiler/src/dotty/tools/dotc/reporting/Reporter.scala @@ -90,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 @@ -172,6 +174,9 @@ abstract class Reporter extends interfaces.ReporterResult { } 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 @@ -180,7 +185,11 @@ abstract class Reporter extends interfaces.ReporterResult { case Action.Verbose => w.setVerbose(); go() case Action.Silent => else - sup.addSuspendedMessage(w) + // 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 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 From 745ee1ebe3f34bb76a8e4cd1d0695ecd46136e6c Mon Sep 17 00:00:00 2001 From: Lukas Rytz Date: Mon, 9 Aug 2021 17:43:48 +0200 Subject: [PATCH 19/21] show parser warnings when there are parser errors --- compiler/src/dotty/tools/dotc/Run.scala | 10 +++++----- .../nowarn/nowarn-parser-error.check | 13 +++++++++++++ .../nowarn/nowarn-parser-error.scala | 4 ++++ .../neg-custom-args/nowarn/nowarn-typer-error.check | 6 ++++++ .../neg-custom-args/nowarn/nowarn-typer-error.scala | 7 +++++++ 5 files changed, 35 insertions(+), 5 deletions(-) create mode 100644 tests/neg-custom-args/nowarn/nowarn-parser-error.check create mode 100644 tests/neg-custom-args/nowarn/nowarn-parser-error.scala create mode 100644 tests/neg-custom-args/nowarn/nowarn-typer-error.check create mode 100644 tests/neg-custom-args/nowarn/nowarn-typer-error.scala diff --git a/compiler/src/dotty/tools/dotc/Run.scala b/compiler/src/dotty/tools/dotc/Run.scala index 13216f54e56a..166bf5ad9342 100644 --- a/compiler/src/dotty/tools/dotc/Run.scala +++ b/compiler/src/dotty/tools/dotc/Run.scala @@ -140,10 +140,11 @@ class Run(comp: Compiler, ictx: Context) extends ImplicitRunInfo with Constraint mySuspendedMessages.remove(source).foreach(_.foreach(ctx.reporter.issueIfNotSuppressed)) } - def warnUnusedSuppressions(): Unit = - // if we stop before typer completes (errors in parser, Ystop), report all suspended messages + def runFinished(hasErrors: Boolean): Unit = + // report suspended messages (in case the run finished before typer) mySuspendedMessages.keysIterator.toList.foreach(reportSuspendedMessages) - if ctx.settings.WunusedHas.nowarn then + // 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) @@ -279,8 +280,7 @@ class Run(comp: Compiler, ictx: Context) extends ImplicitRunInfo with Constraint runPhases(using runCtx) if (!ctx.reporter.hasErrors) Rewrites.writeBack() - // later phases don't run when there are errors, which would lead to stale `unused @nowarn` warnings - suppressions.warnUnusedSuppressions() + suppressions.runFinished(hasErrors = ctx.reporter.hasErrors) while (finalizeActions.nonEmpty) { val action = finalizeActions.remove(0) action() 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 +} From 7aa41caf93e6defcf22e3b42b3d6bb1f9782ce02 Mon Sep 17 00:00:00 2001 From: Lukas Rytz Date: Mon, 16 Aug 2021 12:21:07 +0200 Subject: [PATCH 20/21] review feedback --- .../tools/dotc/reporting/ErrorMessageID.scala | 6 +- .../dotty/tools/dotc/reporting/WConf.scala | 53 +++++++------- .../src/dotty/tools/dotc/typer/Typer.scala | 23 +++--- tests/neg-custom-args/nowarn/nowarn.check | 70 +++++++++++-------- tests/neg-custom-args/nowarn/nowarn.scala | 3 + 5 files changed, 88 insertions(+), 67 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/reporting/ErrorMessageID.scala b/compiler/src/dotty/tools/dotc/reporting/ErrorMessageID.scala index ccdce7cfb1a4..681362c7df0e 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,6 @@ enum ErrorMessageID extends java.lang.Enum[ErrorMessageID] { CannotExtendFunctionID def errorNumber = ordinal - 2 -} + +object ErrorMessageID: + def fromErrorNumber(n: Int) = fromOrdinal(n + 2) diff --git a/compiler/src/dotty/tools/dotc/reporting/WConf.scala b/compiler/src/dotty/tools/dotc/reporting/WConf.scala index c50533010c32..05b59354a76a 100644 --- a/compiler/src/dotty/tools/dotc/reporting/WConf.scala +++ b/compiler/src/dotty/tools/dotc/reporting/WConf.scala @@ -11,7 +11,7 @@ import scala.collection.mutable.ListBuffer import scala.util.matching.Regex enum MessageFilter: - def matches(message: Diagnostic): Boolean = this match { + def matches(message: Diagnostic): Boolean = this match case Any => true case Deprecated => message.isInstanceOf[Diagnostic.DeprecationWarning] case Feature => message.isInstanceOf[Diagnostic.FeatureWarning] @@ -21,7 +21,7 @@ enum MessageFilter: 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) @@ -40,31 +40,37 @@ object WConf: private type Conf = (List[MessageFilter], Action) - def parseAction(s: String): Either[List[String], Action] = s match { + 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}") } + 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('&').view.map(parseFilter).toList.partitionMap(identity) + 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) => - val n = num.toInt + 2 + val n = num.toInt if n < ErrorMessageID.values.length then - Right(MessageID(ErrorMessageID.fromOrdinal(n))) + Right(MessageID(ErrorMessageID.fromErrorNumber(n))) else Left(s"unknonw error message id: E$n") case _ => @@ -99,25 +105,22 @@ object WConf: 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 parts = conf.split("[&:]") // TODO: don't split on escaped \& - val (ms, fs) = parts.view.init.map(parseFilter).toList.partitionMap(identity) - if (ms.nonEmpty) Left(ms) - else if (fs.isEmpty) Left(List("no filters or no action defined")) - else parseAction(parts.last).map((fs, _)) - }) - val (ms, fs) = parsedConfs.partitionMap(identity) - if (ms.nonEmpty) Left(ms.flatten) - else Right(WConf(fs)) - } - -case class Suppression(annotPos: SourcePosition, filters: List[MessageFilter], start: Int, end: Int, verbose: Boolean): + 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 markUsed(): Unit = _used = true - def matches(dia: Diagnostic): Boolean = { + def matches(dia: Diagnostic): Boolean = val pos = dia.pos - pos.exists && start <= pos.start && pos.end <= end && (verbose || filters.forall(_.matches(dia))) - } + pos.exists && start <= pos.start && pos.end <= end && filters.forall(_.matches(dia)) diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 88cbe05306b1..4b265b1591d1 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -2090,27 +2090,30 @@ class Typer extends Namer val annot = Annotations.Annotation(tree) def argPos = annot.argument(0).getOrElse(tree).sourcePos var verbose = false + var erroneous = false val filters = if annot.arguments.isEmpty then List(MessageFilter.Any) else annot.argumentConstantString(0) match case None => annot.argument(0) match - case Some(t: Select) if t.name.is(DefaultGetterName) => List(MessageFilter.Any) + 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) - Nil - case Some("") => Nil + List(MessageFilter.None) + case Some("") => + List(MessageFilter.Any) case Some("verbose") | Some("v") => verbose = true - Nil + List(MessageFilter.Any) case Some(s) => - val (ms, fs) = s.split('&').map(WConf.parseFilter).toList.partitionMap(identity) - if ms.nonEmpty then - report.warning(s"Invalid message filter\n${ms.mkString("\n")}", argPos) - List(MessageFilter.None) - else - fs + WConf.parseFilters(s).fold(parseErrors => + report.warning (s"Invalid message filter\n${parseErrors.mkString ("\n")}", argPos) + List(MessageFilter.None), + identity) 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) diff --git a/tests/neg-custom-args/nowarn/nowarn.check b/tests/neg-custom-args/nowarn/nowarn.check index f6c1312ea566..6d2e152c5005 100644 --- a/tests/neg-custom-args/nowarn/nowarn.check +++ b/tests/neg-custom-args/nowarn/nowarn.check @@ -5,15 +5,15 @@ | 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:21:26 ---------------------------------------------- -21 |@nowarn("id=1") def t4d = try 1 // error and warning (unused nowarn, wrong id) +-- [E000] Syntax Warning: tests/neg-custom-args/nowarn/nowarn.scala:24:26 ---------------------------------------------- +24 |@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:23:28 ---------------------------------------------- -23 |@nowarn("verbose") def t5 = try 1 // warning with details +-- [E000] Syntax Warning: tests/neg-custom-args/nowarn/nowarn.scala:26:28 ---------------------------------------------- +26 |@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. @@ -33,55 +33,65 @@ longer explanation available when compiling with `-explain` | ^^^^^^ | Invalid message filter | unknown filter: wat? --- Deprecation Warning: tests/neg-custom-args/nowarn/nowarn.scala:27:10 ------------------------------------------------ -27 |def t6a = f // warning (refchecks, deprecation) +-- [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 +-- Deprecation Warning: tests/neg-custom-args/nowarn/nowarn.scala:30:10 ------------------------------------------------ +30 |def t6a = f // warning (refchecks, deprecation) | ^ | method f is deprecated --- Deprecation Warning: tests/neg-custom-args/nowarn/nowarn.scala:30:30 ------------------------------------------------ -30 |@nowarn("msg=fish") def t6d = f // error (unused nowarn), warning (deprecation) +-- Deprecation Warning: tests/neg-custom-args/nowarn/nowarn.scala:33:30 ------------------------------------------------ +33 |@nowarn("msg=fish") def t6d = f // error (unused nowarn), warning (deprecation) | ^ | method f is deprecated --- Deprecation Warning: tests/neg-custom-args/nowarn/nowarn.scala:37:10 ------------------------------------------------ -37 |def t7c = f: // warning (deprecation) +-- Deprecation Warning: tests/neg-custom-args/nowarn/nowarn.scala:40:10 ------------------------------------------------ +40 |def t7c = f: // warning (deprecation) | ^ | method f is deprecated --- Unchecked Warning: tests/neg-custom-args/nowarn/nowarn.scala:43:7 --------------------------------------------------- -43 | case _: List[Int] => 0 // warning (patmat, unchecked) +-- Unchecked Warning: tests/neg-custom-args/nowarn/nowarn.scala:46:7 --------------------------------------------------- +46 | 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:21:1 --------------------------------------------------------------- -21 |@nowarn("id=1") def t4d = try 1 // error and warning (unused nowarn, wrong id) +-- Error: tests/neg-custom-args/nowarn/nowarn.scala:24:1 --------------------------------------------------------------- +24 |@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:30:1 --------------------------------------------------------------- -30 |@nowarn("msg=fish") def t6d = f // error (unused nowarn), warning (deprecation) +-- Error: tests/neg-custom-args/nowarn/nowarn.scala:33:1 --------------------------------------------------------------- +33 |@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:38:3 --------------------------------------------------------------- -38 | @nowarn("msg=fish") // error (unused nowarn) +-- Error: tests/neg-custom-args/nowarn/nowarn.scala:41:3 --------------------------------------------------------------- +41 | @nowarn("msg=fish") // error (unused nowarn) | ^^^^^^^^^^^^^^^^^^^ | @nowarn annotation does not suppress any warnings --- Error: tests/neg-custom-args/nowarn/nowarn.scala:50:0 --------------------------------------------------------------- -50 |@nowarn def t9a = { 1: @nowarn; 2 } // error (outer @nowarn is unused) +-- Error: tests/neg-custom-args/nowarn/nowarn.scala:53:0 --------------------------------------------------------------- +53 |@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:51:27 -------------------------------------------------------------- -51 |@nowarn def t9b = { 1: Int @nowarn; 2 } // error (inner @nowarn is unused, it covers the type, not the expression) +-- Error: tests/neg-custom-args/nowarn/nowarn.scala:54:27 -------------------------------------------------------------- +54 |@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:56:0 --------------------------------------------------------------- -56 |@nowarn @ann(f) def t10b = 0 // error (unused nowarn) +-- Error: tests/neg-custom-args/nowarn/nowarn.scala:59:0 --------------------------------------------------------------- +59 |@nowarn @ann(f) def t10b = 0 // error (unused nowarn) |^^^^^^^ |@nowarn annotation does not suppress any warnings --- Error: tests/neg-custom-args/nowarn/nowarn.scala:57:8 --------------------------------------------------------------- -57 |@ann(f: @nowarn) def t10c = 0 // error (unused nowarn), should be silent +-- Error: tests/neg-custom-args/nowarn/nowarn.scala:60:8 --------------------------------------------------------------- +60 |@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:60:0 --------------------------------------------------------------- -60 |@nowarn class I1a { // error (unused nowarn) +-- Error: tests/neg-custom-args/nowarn/nowarn.scala:63:0 --------------------------------------------------------------- +63 |@nowarn class I1a { // error (unused nowarn) |^^^^^^^ |@nowarn annotation does not suppress any warnings --- Error: tests/neg-custom-args/nowarn/nowarn.scala:65:0 --------------------------------------------------------------- -65 |@nowarn class I1b { // error (unused nowarn) +-- Error: tests/neg-custom-args/nowarn/nowarn.scala:68:0 --------------------------------------------------------------- +68 |@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 index 692426d6a4f8..21ee8eae7175 100644 --- a/tests/neg-custom-args/nowarn/nowarn.scala +++ b/tests/neg-custom-args/nowarn/nowarn.scala @@ -12,6 +12,9 @@ def t1a = try 1 // warning (parser) @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) + @nowarn("id=E129") def t3a = { 1; 2 } @nowarn("name=PureExpressionInStatementPosition") def t3b = { 1; 2 } From a1cde6d6fb0b64eb0083c5396276014845455096 Mon Sep 17 00:00:00 2001 From: Lukas Rytz Date: Mon, 16 Aug 2021 21:26:00 +0200 Subject: [PATCH 21/21] more review feedback --- .../tools/dotc/reporting/ErrorMessageID.scala | 5 +- .../dotty/tools/dotc/reporting/WConf.scala | 12 ++-- .../src/dotty/tools/dotc/typer/Typer.scala | 37 +++++----- tests/neg-custom-args/nowarn/nowarn.check | 71 +++++++++++-------- tests/neg-custom-args/nowarn/nowarn.scala | 7 ++ 5 files changed, 74 insertions(+), 58 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/reporting/ErrorMessageID.scala b/compiler/src/dotty/tools/dotc/reporting/ErrorMessageID.scala index 681362c7df0e..d1b95d31dd62 100644 --- a/compiler/src/dotty/tools/dotc/reporting/ErrorMessageID.scala +++ b/compiler/src/dotty/tools/dotc/reporting/ErrorMessageID.scala @@ -179,4 +179,7 @@ enum ErrorMessageID extends java.lang.Enum[ErrorMessageID]: def errorNumber = ordinal - 2 object ErrorMessageID: - def fromErrorNumber(n: Int) = fromOrdinal(n + 2) + 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/WConf.scala b/compiler/src/dotty/tools/dotc/reporting/WConf.scala index 05b59354a76a..34a47fa3db9d 100644 --- a/compiler/src/dotty/tools/dotc/reporting/WConf.scala +++ b/compiler/src/dotty/tools/dotc/reporting/WConf.scala @@ -57,7 +57,7 @@ object WConf: def parseFilters(s: String): Either[List[String], List[MessageFilter]] = // TODO: don't split on escaped \& - val (parseErrors, filters) = s.split('&').view.map(parseFilter).toList.partitionMap(identity) + 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) @@ -68,11 +68,9 @@ object WConf: case "msg" => regex(conf).map(MessagePattern.apply) case "id" => conf match case ErrorId(num) => - val n = num.toInt - if n < ErrorMessageID.values.length then - Right(MessageID(ErrorMessageID.fromErrorNumber(n))) - else - Left(s"unknonw error message id: E$n") + 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" => @@ -119,7 +117,7 @@ object WConf: 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 markUsed(): Unit = { _used = true } def matches(dia: Diagnostic): Boolean = val pos = dia.pos diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 4b265b1591d1..866bac6b47ae 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -2090,27 +2090,24 @@ class Typer extends Namer val annot = Annotations.Annotation(tree) def argPos = annot.argument(0).getOrElse(tree).sourcePos var verbose = false - var erroneous = false - val filters = - if annot.arguments.isEmpty then List(MessageFilter.Any) - else 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 + 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 Some(s) => - WConf.parseFilters(s).fold(parseErrors => - report.warning (s"Invalid message filter\n${parseErrors.mkString ("\n")}", argPos) - List(MessageFilter.None), - identity) + 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 diff --git a/tests/neg-custom-args/nowarn/nowarn.check b/tests/neg-custom-args/nowarn/nowarn.check index 6d2e152c5005..736d9f82e910 100644 --- a/tests/neg-custom-args/nowarn/nowarn.check +++ b/tests/neg-custom-args/nowarn/nowarn.check @@ -5,15 +5,22 @@ | 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:24:26 ---------------------------------------------- -24 |@nowarn("id=1") def t4d = try 1 // error and warning (unused nowarn, wrong id) +-- [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:26:28 ---------------------------------------------- -26 |@nowarn("verbose") def t5 = try 1 // warning with details +-- [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. @@ -43,55 +50,59 @@ longer explanation available when compiling with `-explain` 15 |@nowarn(t1a.toString) // warning (typer, argument not a compile-time constant) | ^^^^^^^^^^^^ | filter needs to be a compile-time constant string --- Deprecation Warning: tests/neg-custom-args/nowarn/nowarn.scala:30:10 ------------------------------------------------ -30 |def t6a = f // warning (refchecks, deprecation) +-- 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:33:30 ------------------------------------------------ -33 |@nowarn("msg=fish") def t6d = f // error (unused nowarn), warning (deprecation) +-- 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:40:10 ------------------------------------------------ -40 |def t7c = f: // warning (deprecation) +-- 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:46:7 --------------------------------------------------- -46 | case _: List[Int] => 0 // warning (patmat, unchecked) +-- 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:24:1 --------------------------------------------------------------- -24 |@nowarn("id=1") def t4d = try 1 // error and warning (unused nowarn, wrong id) +-- 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:33:1 --------------------------------------------------------------- -33 |@nowarn("msg=fish") def t6d = f // error (unused nowarn), warning (deprecation) +-- 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:41:3 --------------------------------------------------------------- -41 | @nowarn("msg=fish") // error (unused nowarn) +-- 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:53:0 --------------------------------------------------------------- -53 |@nowarn def t9a = { 1: @nowarn; 2 } // error (outer @nowarn is unused) +-- 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:54:27 -------------------------------------------------------------- -54 |@nowarn def t9b = { 1: Int @nowarn; 2 } // error (inner @nowarn is unused, it covers the type, not the expression) +-- 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:59:0 --------------------------------------------------------------- -59 |@nowarn @ann(f) def t10b = 0 // error (unused nowarn) +-- 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:60:8 --------------------------------------------------------------- -60 |@ann(f: @nowarn) def t10c = 0 // error (unused nowarn), should be silent +-- 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:63:0 --------------------------------------------------------------- -63 |@nowarn class I1a { // error (unused nowarn) +-- 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:68:0 --------------------------------------------------------------- -68 |@nowarn class I1b { // error (unused nowarn) +-- 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 index 21ee8eae7175..39ecde91517f 100644 --- a/tests/neg-custom-args/nowarn/nowarn.scala +++ b/tests/neg-custom-args/nowarn/nowarn.scala @@ -15,6 +15,13 @@ 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 }