Skip to content

Commit afb94d7

Browse files
committed
Wconf filtered messages
1 parent 79fae19 commit afb94d7

File tree

8 files changed

+195
-23
lines changed

8 files changed

+195
-23
lines changed

compiler/src/dotty/tools/dotc/config/ScalaSettings.scala

+35
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,41 @@ private sealed trait WarningSettings:
132132
self: SettingGroup =>
133133
val Whelp: Setting[Boolean] = BooleanSetting("-W", "Print a synopsis of warning options.")
134134
val XfatalWarnings: Setting[Boolean] = BooleanSetting("-Werror", "Fail the compilation if there are any warnings.", aliases = List("-Xfatal-warnings"))
135+
val Wconf: Setting[List[String]] = MultiStringSetting(
136+
"-Wconf",
137+
"patterns",
138+
default = List(),
139+
descr =
140+
s"""Configure compiler warnings.
141+
|Syntax: -Wconf:<filters>:<action>,<filters>:<action>,...
142+
|multiple <filters> are combined with &, i.e., <filter>&...&<filter>
143+
|
144+
|<filter>
145+
| - Any message: any
146+
|
147+
| - Message categories: cat=deprecation, cat=feature
148+
|
149+
| - Message content: msg=regex
150+
| The regex need only match some part of the message, not all of it.
151+
|
152+
|<action>
153+
| - error / e
154+
| - warning / w
155+
| - info / i (infos are not counted as warnings and don't affect `-Werror`)
156+
| - silent / s
157+
|
158+
|The default configuration is empty.
159+
|
160+
|User-defined configurations are added to the left. The leftmost rule matching
161+
|a warning message defines the action.
162+
|
163+
|Examples:
164+
| - change every warning into an error: -Wconf:any:error
165+
| - silence deprecations: -Wconf:cat=deprecation:s
166+
|
167+
|Note: on the command-line you might need to quote configurations containing `*` or `&`
168+
|to prevent the shell from expanding patterns.""".stripMargin,
169+
)
135170

136171
/** -X "Extended" or "Advanced" settings */
137172
private sealed trait XSettings:

compiler/src/dotty/tools/dotc/config/Settings.scala

+2-2
Original file line numberDiff line numberDiff line change
@@ -256,8 +256,8 @@ object Settings:
256256
def IntChoiceSetting(name: String, descr: String, choices: Seq[Int], default: Int): Setting[Int] =
257257
publish(Setting(name, descr, default, choices = Some(choices)))
258258

259-
def MultiStringSetting(name: String, helpArg: String, descr: String, aliases: List[String] = Nil): Setting[List[String]] =
260-
publish(Setting(name, descr, Nil, helpArg, aliases = aliases))
259+
def MultiStringSetting(name: String, helpArg: String, descr: String, default: List[String] = Nil, aliases: List[String] = Nil): Setting[List[String]] =
260+
publish(Setting(name, descr, default, helpArg, aliases = aliases))
261261

262262
def OutputSetting(name: String, helpArg: String, descr: String, default: AbstractFile): Setting[AbstractFile] =
263263
publish(Setting(name, descr, default, helpArg))

compiler/src/dotty/tools/dotc/parsing/Parsers.scala

+4-3
Original file line numberDiff line numberDiff line change
@@ -1166,10 +1166,11 @@ object Parsers {
11661166
}
11671167
else
11681168
if !in.featureEnabled(Feature.symbolLiterals) then
1169+
val name = in.name // capture name (not `in`) in the warning message closure
11691170
report.errorOrMigrationWarning(
1170-
em"""symbol literal '${in.name} is no longer supported,
1171-
|use a string literal "${in.name}" or an application Symbol("${in.name}") instead,
1172-
|or enclose in braces '{${in.name}} if you want a quoted expression.
1171+
em"""symbol literal '$name is no longer supported,
1172+
|use a string literal "$name" or an application Symbol("$name") instead,
1173+
|or enclose in braces '{$name} if you want a quoted expression.
11731174
|For now, you can also `import language.deprecated.symbolLiterals` to accept
11741175
|the idiom, but this possibility might no longer be available in the future.""",
11751176
in.sourcePos())

compiler/src/dotty/tools/dotc/reporting/Diagnostic.scala

+1
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ object Diagnostic:
3636
pos: SourcePosition
3737
) extends Diagnostic(msg, pos, WARNING) {
3838
def toError: Error = new Error(msg, pos)
39+
def toInfo: Info = new Info(msg, pos)
3940
}
4041

4142
class Info(

compiler/src/dotty/tools/dotc/reporting/Reporter.scala

+29-18
Original file line numberDiff line numberDiff line change
@@ -141,24 +141,35 @@ abstract class Reporter extends interfaces.ReporterResult {
141141
var unreportedWarnings: Map[String, Int] = Map.empty
142142

143143
def report(dia: Diagnostic)(using Context): Unit =
144-
val isSummarized = dia match
145-
case dia: ConditionalWarning => !dia.enablingOption.value
146-
case _ => false
147-
if isSummarized // avoid isHidden test for summarized warnings so that message is not forced
148-
|| !isHidden(dia)
149-
then
150-
withMode(Mode.Printing)(doReport(dia))
151-
dia match
152-
case dia: ConditionalWarning if !dia.enablingOption.value =>
153-
val key = dia.enablingOption.name
154-
unreportedWarnings =
155-
unreportedWarnings.updated(key, unreportedWarnings.getOrElse(key, 0) + 1)
156-
case dia: Warning => _warningCount += 1
157-
case dia: Error =>
158-
errors = dia :: errors
159-
_errorCount += 1
160-
case dia: Info => // nothing to do here
161-
// match error if d is something else
144+
import Action._
145+
val toReport = dia match {
146+
case w: Warning => WConf.parsed.action(dia) match {
147+
case Silent => None
148+
case Info => Some(w.toInfo)
149+
case Warning => Some(w)
150+
case Error => Some(w.toError)
151+
}
152+
case _ => Some(dia)
153+
}
154+
for (d <- toReport) {
155+
val isSummarized = d match
156+
case cw: ConditionalWarning => !cw.enablingOption.value
157+
case _ => false
158+
// avoid isHidden test for summarized warnings so that message is not forced
159+
if isSummarized || !isHidden(d) then
160+
withMode(Mode.Printing)(doReport(d))
161+
d match
162+
case cw: ConditionalWarning if !cw.enablingOption.value =>
163+
val key = cw.enablingOption.name
164+
unreportedWarnings =
165+
unreportedWarnings.updated(key, unreportedWarnings.getOrElse(key, 0) + 1)
166+
case _: Warning => _warningCount += 1
167+
case e: Error =>
168+
errors = e :: errors
169+
_errorCount += 1
170+
case dia: Info => // nothing to do here
171+
// match error if d is something else
172+
}
162173

163174
def incomplete(dia: Diagnostic)(using Context): Unit =
164175
incompleteHandler(dia, ctx)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
package dotty.tools
2+
package dotc
3+
package reporting
4+
5+
import dotty.tools.dotc.core.Contexts._
6+
7+
import java.util.regex.PatternSyntaxException
8+
import scala.annotation.internal.sharable
9+
import scala.collection.mutable.ListBuffer
10+
import scala.util.matching.Regex
11+
12+
enum MessageFilter:
13+
def matches(message: Diagnostic): Boolean = this match {
14+
case Any => true
15+
case Deprecated => message.isInstanceOf[Diagnostic.DeprecationWarning]
16+
case Feature => message.isInstanceOf[Diagnostic.FeatureWarning]
17+
case MessagePattern(pattern) => pattern.findFirstIn(message.msg.rawMessage).nonEmpty
18+
}
19+
case Any, Deprecated, Feature
20+
case MessagePattern(pattern: Regex)
21+
22+
enum Action:
23+
case Error, Warning, Info, Silent
24+
25+
final case class WConf(confs: List[(List[MessageFilter], Action)]):
26+
def action(message: Diagnostic): Action = confs.collectFirst {
27+
case (filters, action) if filters.forall(_.matches(message)) => action
28+
}.getOrElse(Action.Warning)
29+
30+
@sharable object WConf:
31+
import Action._
32+
import MessageFilter._
33+
34+
private type Conf = (List[MessageFilter], Action)
35+
36+
def parseAction(s: String): Either[List[String], Action] = s match {
37+
case "error" | "e" => Right(Error)
38+
case "warning" | "w" => Right(Warning)
39+
case "info" | "i" => Right(Info)
40+
case "silent" | "s" => Right(Silent)
41+
case _ => Left(List(s"unknown action: `$s`"))
42+
}
43+
44+
private def regex(s: String) =
45+
try Right(s.r)
46+
catch { case e: PatternSyntaxException => Left(s"invalid pattern `$s`: ${e.getMessage}") }
47+
48+
val splitter = raw"([^=]+)=(.+)".r
49+
50+
def parseFilter(s: String): Either[String, MessageFilter] = s match {
51+
case "any" => Right(Any)
52+
case splitter(filter, conf) => filter match {
53+
case "msg" => regex(conf).map(MessagePattern.apply)
54+
case "cat" =>
55+
conf match {
56+
case "deprecation" => Right(Deprecated)
57+
case "feature" => Right(Feature)
58+
case _ => Left(s"unknown category: $conf")
59+
}
60+
case _ => Left(s"unknown filter: $filter")
61+
}
62+
case _ => Left(s"unknown filter: $s")
63+
}
64+
65+
private var parsedCache: (List[String], WConf) = null
66+
def parsed(using Context): WConf =
67+
val setting = ctx.settings.Wconf.value
68+
if parsedCache == null || parsedCache._1 != setting then
69+
val conf = fromSettings(setting)
70+
parsedCache = (setting, conf.getOrElse(WConf(Nil)))
71+
conf.swap.foreach(msgs =>
72+
val multiHelp =
73+
if (setting.sizeIs > 1)
74+
"""
75+
|Note: for multiple filters, use `-Wconf:filter1:action1,filter2:action2`
76+
| or alternatively `-Wconf:filter1:action1 -Wconf:filter2:action2`""".stripMargin
77+
else ""
78+
report.warning(s"Failed to parse `-Wconf` configuration: ${ctx.settings.Wconf.value.mkString(",")}\n${msgs.mkString("\n")}$multiHelp"))
79+
parsedCache._2
80+
81+
def fromSettings(settings: List[String]): Either[List[String], WConf] =
82+
if (settings.isEmpty) Right(WConf(Nil))
83+
else {
84+
val parsedConfs: List[Either[List[String], (List[MessageFilter], Action)]] = settings.map(conf => {
85+
val parts = conf.split("[&:]") // TODO: don't split on escaped \&
86+
val (ms, fs) = parts.view.init.map(parseFilter).toList.partitionMap(identity)
87+
if (ms.nonEmpty) Left(ms)
88+
else if (fs.isEmpty) Left(List("no filters or no action defined"))
89+
else parseAction(parts.last).map((fs, _))
90+
})
91+
val (ms, fs) = parsedConfs.partitionMap(identity)
92+
if (ms.nonEmpty) Left(ms.flatten)
93+
else Right(WConf(fs))
94+
}

compiler/test/dotty/tools/dotc/config/ScalaSettingsTests.scala

+14
Original file line numberDiff line numberDiff line change
@@ -64,4 +64,18 @@ class ScalaSettingsTests:
6464
assertTrue("Has the feature", set.contains("implicitConversions"))
6565
assertTrue("Has the feature", set.contains("dynamics"))
6666

67+
@Test def `WConf setting is parsed`: Unit =
68+
import reporting.{Action, Diagnostic, NoExplanation}
69+
val sets = new ScalaSettings
70+
val args = tokenize("-Wconf:cat=deprecation:w,cat=feature:w -Wconf:msg=message\\.pattern:s")
71+
val sumy = ArgsSummary(sets.defaultState, args, errors = Nil, warnings = Nil)
72+
val proc = sets.processArguments(sumy, processAll = true, skipped = Nil)
73+
val conf = sets.Wconf.valueIn(proc.sstate)
74+
val sut = reporting.WConf.fromSettings(conf).getOrElse(???)
75+
val msg = NoExplanation("There was a problem!")
76+
val diag = new Diagnostic.DeprecationWarning(msg, util.NoSourcePosition)
77+
assertEquals("Warns deprecation", Action.Warning, sut.action(diag))
78+
val feat = new Diagnostic.FeatureWarning(msg, util.NoSourcePosition)
79+
assertEquals("Warns feature", Action.Warning, sut.action(feat))
80+
6781
end ScalaSettingsTests
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package dotty.tools.dotc
2+
package reporting
3+
4+
import config.CommandLineParser.tokenize
5+
import config.Settings._
6+
7+
import org.junit.Test
8+
import org.junit.Assert._
9+
10+
class WConfTest:
11+
12+
@Test def `WConf setting is parsed`: Unit =
13+
assertEquals(1, 1)
14+
assertTrue("No warnings!", true)
15+
16+
end WConfTest

0 commit comments

Comments
 (0)