Skip to content

Commit 40843f7

Browse files
authored
Add suppression if nowarn differs (#23652)
Fixes #23651 The [previous fix](#22383) for the same `@nowarn` attached to multiple elements should have compared the `annotPos` to identify duplicates (instead of the target range). This commit defers detecting "bad" or duplicate suppressions (which originate with the same annotation) to report time, after the suppression is "unused"; there are few nowarns per file and fewer that are unused. While checking for a suppression, mark matching unused suppressions as "superseded", so that if they remain unused, the warning can add an "audit" that the nowarn matched a diagnostic (but was superseded by some other nowarn). ~This commit goes further and checks for duplicates (including whether the filters look the same).~ ~If it finds a duplicate where the `annotPos` differs, warn about the user-written annotation.~ ~Filters match each other if they are the same type and, if they have a pattern, the string representations of the patterns are equal.~
1 parent 408298d commit 40843f7

File tree

7 files changed

+257
-216
lines changed

7 files changed

+257
-216
lines changed

compiler/src/dotty/tools/dotc/Run.scala

Lines changed: 25 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -92,14 +92,21 @@ extends ImplicitRunInfo, ConstraintRunInfo, cc.CaptureRunInfo {
9292
mySuspendedMessages.getOrElseUpdate(warning.pos.source, mutable.LinkedHashSet.empty) += warning
9393

9494
def nowarnAction(dia: Diagnostic): Action.Warning.type | Action.Verbose.type | Action.Silent.type =
95-
mySuppressions.getOrElse(dia.pos.source, Nil).find(_.matches(dia)) match {
96-
case Some(s) =>
95+
mySuppressions.get(dia.pos.source) match
96+
case Some(suppressions) =>
97+
val matching = suppressions.iterator.filter(_.matches(dia))
98+
if matching.hasNext then
99+
val s = matching.next()
100+
for other <- matching do
101+
if !other.used then
102+
other.markSuperseded() // superseded unless marked used later
97103
s.markUsed()
98-
if (s.verbose) Action.Verbose
104+
if s.verbose then Action.Verbose
99105
else Action.Silent
100-
case _ =>
106+
else
101107
Action.Warning
102-
}
108+
case none =>
109+
Action.Warning
103110

104111
def registerNowarn(annotPos: SourcePosition, range: Span)(conf: String, pos: SrcPos)(using Context): Unit =
105112
var verbose = false
@@ -118,12 +125,10 @@ extends ImplicitRunInfo, ConstraintRunInfo, cc.CaptureRunInfo {
118125
.merge
119126
addSuppression:
120127
Suppression(annotPos, filters, range.start, range.end, verbose)
121-
.tap: sup =>
122-
if filters == List(MessageFilter.None) then sup.markUsed() // invalid suppressions, don't report as unused
123128

124129
def addSuppression(sup: Suppression): Unit =
125130
val suppressions = mySuppressions.getOrElseUpdate(sup.annotPos.source, ListBuffer.empty)
126-
if sup.start != sup.end && suppressions.forall(x => x.start != sup.start || x.end != sup.end) then
131+
if sup.start != sup.end then
127132
suppressions += sup
128133

129134
def reportSuspendedMessages(source: SourceFile)(using Context): Unit = {
@@ -134,18 +139,25 @@ extends ImplicitRunInfo, ConstraintRunInfo, cc.CaptureRunInfo {
134139
mySuspendedMessages.remove(source).foreach(_.foreach(ctx.reporter.issueIfNotSuppressed))
135140
}
136141

137-
def runFinished(hasErrors: Boolean): Unit =
142+
def runFinished()(using Context): Unit =
143+
val hasErrors = ctx.reporter.hasErrors
138144
// report suspended messages (in case the run finished before typer)
139145
mySuspendedMessages.keysIterator.toList.foreach(reportSuspendedMessages)
140146
// report unused nowarns only if all all phases are done
141147
if !hasErrors && ctx.settings.WunusedHas.nowarn then
142148
for
143149
source <- mySuppressions.keysIterator.toList
144150
sups <- mySuppressions.remove(source)
145-
sup <- sups.reverse
146-
if !sup.used
147151
do
148-
report.warning("@nowarn annotation does not suppress any warnings", sup.annotPos)
152+
val suppressions = sups.reverse.toList
153+
for sup <- suppressions do
154+
if !sup.used
155+
&& !suppressions.exists(s => s.ne(sup) && s.used && s.annotPos == sup.annotPos) // duplicate
156+
&& sup.filters != List(MessageFilter.None) // invalid suppression, don't report as unused
157+
then
158+
val more = if sup.superseded then " but matches a diagnostic" else ""
159+
report.warning("@nowarn annotation does not suppress any warnings"+more, sup.annotPos)
160+
end suppressions
149161

150162
/** The compilation units currently being compiled, this may return different
151163
* results over time.
@@ -411,7 +423,7 @@ extends ImplicitRunInfo, ConstraintRunInfo, cc.CaptureRunInfo {
411423
ctx.reporter.finalizeReporting()
412424
if (!ctx.reporter.hasErrors)
413425
Rewrites.writeBack()
414-
suppressions.runFinished(hasErrors = ctx.reporter.hasErrors)
426+
suppressions.runFinished()
415427
while (finalizeActions.nonEmpty && canProgress()) {
416428
val action = finalizeActions.remove(0)
417429
action()

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

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import dotty.tools.dotc.interfaces.SourceFile
1010
import dotty.tools.dotc.reporting.MessageFilter.SourcePattern
1111

1212
import java.util.regex.PatternSyntaxException
13+
import scala.PartialFunction.cond
1314
import scala.annotation.internal.sharable
1415
import scala.util.matching.Regex
1516

@@ -136,13 +137,20 @@ object WConf:
136137
if (parseErrorss.nonEmpty) Left(parseErrorss.flatten)
137138
else Right(WConf(configs))
138139

139-
class Suppression(val annotPos: SourcePosition, filters: List[MessageFilter], val start: Int, val end: Int, val verbose: Boolean):
140-
private var _used = false
141-
def used: Boolean = _used
140+
class Suppression(val annotPos: SourcePosition, val filters: List[MessageFilter], val start: Int, val end: Int, val verbose: Boolean):
141+
inline def unusedState = 0
142+
inline def usedState = 1
143+
inline def supersededState = 2
144+
private var _used = unusedState
145+
def used: Boolean = _used == usedState
146+
def superseded: Boolean = _used == supersededState
142147
def markUsed(): Unit =
143-
_used = true
148+
_used = usedState
149+
def markSuperseded(): Unit =
150+
_used = supersededState
144151
def matches(dia: Diagnostic): Boolean =
145152
val pos = dia.pos
146153
pos.exists && start <= pos.start && pos.end <= end && filters.forall(_.matches(dia))
147154

148155
override def toString = s"Suppress in ${annotPos.source} $start..$end [${filters.mkString(", ")}]"
156+
end Suppression

tests/neg/nowarn.check

Lines changed: 0 additions & 110 deletions
This file was deleted.

tests/neg/nowarn.scala

Lines changed: 0 additions & 89 deletions
This file was deleted.

tests/warn/i23651.scala

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
//> using options -deprecation -Wunused:nowarn
2+
3+
import scala.annotation.nowarn
4+
5+
@deprecated
6+
class A
7+
8+
@deprecated
9+
class B
10+
11+
@nowarn("msg=trait C is deprecated") // warn
12+
// @nowarn annotation does not suppress any warnings
13+
@nowarn("msg=class A is deprecated")
14+
@nowarn("cat=deprecation&msg=class A is deprecated") // warn
15+
// @nowarn annotation does not suppress any warnings but matches a diagnostic
16+
@nowarn("cat=deprecation&msg=class B is deprecated")
17+
trait C1:
18+
def a: A
19+
def b: B
20+
21+
@nowarn("cat=deprecation&msg=class B is deprecated")
22+
@nowarn("cat=deprecation&msg=class B is deprecated") // warn
23+
// @nowarn annotation does not suppress any warnings but matches a diagnostic
24+
@nowarn("cat=deprecation&msg=class A is deprecated")
25+
trait C2:
26+
def a: A
27+
def b: B

0 commit comments

Comments
 (0)