Skip to content

Commit 04389c6

Browse files
authored
Suppression matches inlined positions (#24092)
Fixes #24082 Makes local warning suppression aware of inlined positions. How does `@nowarn` know what to suppress? A `Suppression` is registered with the current `Run`, and when reporting, a `Suppression` `matches` a given diagnostic based on position. The diagnostic position knows its "enclosing" inlined positions, so a suppression should also match those positions.
1 parent 071c10b commit 04389c6

File tree

5 files changed

+87
-56
lines changed

5 files changed

+87
-56
lines changed

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

Lines changed: 57 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -30,11 +30,6 @@ trait MessageRendering {
3030
def stripColor(str: String): String =
3131
str.replaceAll("\u001b\\[.*?m", "")
3232

33-
/** List of all the inline calls that surround the position */
34-
def inlinePosStack(pos: SourcePosition): List[SourcePosition] =
35-
if pos.outer != null && pos.outer.exists then pos :: inlinePosStack(pos.outer)
36-
else Nil
37-
3833
/** Get the sourcelines before and after the position, as well as the offset
3934
* for rendering line numbers
4035
*
@@ -236,17 +231,27 @@ trait MessageRendering {
236231
if origin.nonEmpty then
237232
addHelp("origin=")(origin)
238233

239-
/** The whole message rendered from `msg` */
240-
def messageAndPos(dia: Diagnostic)(using Context): String = {
241-
import dia.*
242-
val pos1 = pos.nonInlined
243-
val inlineStack = inlinePosStack(pos).filter(_ != pos1)
244-
val maxLineNumber =
245-
if pos.exists then (pos1 :: inlineStack).map(_.endLine).max + 1
246-
else 0
247-
given Level = Level(level)
248-
given Offset = Offset(maxLineNumber.toString.length + 2)
249-
val sb = StringBuilder()
234+
/** The whole message rendered from `dia.msg`.
235+
*
236+
* For a position in an inline expansion, choose `pos1`
237+
* which is the most specific position in the call written
238+
* by the user. For a diagnostic at EOF, where the last char
239+
* of source text is a newline, adjust the position to render
240+
* before the newline, at the end of the last line of text.
241+
*
242+
* The rendering begins with a label and position (`posString`).
243+
* Then `sourceLines` with embedded caret `positionMarker`
244+
* and rendered message.
245+
*
246+
* Then an `Inline stack trace` showing context for inlined code.
247+
* Inlined positions are taken which don't contain `pos1`.
248+
* (That should probably be positions not contained by outermost.)
249+
* Note that position equality includes `outer` position;
250+
* usually we intend to test `contains` or `coincidesWith`.
251+
*
252+
*/
253+
def messageAndPos(dia: Diagnostic)(using Context): String =
254+
// adjust a pos at EOF if preceded by newline
250255
def adjust(pos: SourcePosition): SourcePosition =
251256
if pos.span.isSynthetic
252257
&& pos.span.isZeroExtent
@@ -257,54 +262,57 @@ trait MessageRendering {
257262
pos.withSpan(pos.span.shift(-1))
258263
else
259264
pos
260-
val adjusted = adjust(pos)
261-
val posString = posStr(adjusted, msg, diagnosticLevel(dia))
262-
if (posString.nonEmpty) sb.append(posString).append(EOL)
263-
if (pos.exists) {
264-
val pos1 = pos.nonInlined
265-
if (pos1.exists && pos1.source.file.exists) {
266-
val readjusted =
267-
if pos1 == pos then adjusted
268-
else adjust(pos1)
269-
val (srcBefore, srcAfter, offset) = sourceLines(readjusted)
270-
val marker = positionMarker(readjusted)
271-
val err = errorMsg(readjusted, msg.message)
272-
sb.append((srcBefore ::: marker :: err :: srcAfter).mkString(EOL))
273-
274-
if inlineStack.nonEmpty then
275-
sb.append(EOL).append(newBox())
276-
sb.append(EOL).append(offsetBox).append(i"Inline stack trace")
277-
for inlinedPos <- inlineStack if inlinedPos != pos1 do
278-
sb.append(EOL).append(newBox(soft = true))
279-
sb.append(EOL).append(offsetBox).append(i"This location contains code that was inlined from $pos")
280-
if inlinedPos.source.file.exists then
281-
val (srcBefore, srcAfter, _) = sourceLines(inlinedPos)
282-
val marker = positionMarker(inlinedPos)
283-
sb.append(EOL).append((srcBefore ::: marker :: srcAfter).mkString(EOL))
284-
sb.append(EOL).append(endBox)
285-
}
286-
else sb.append(msg.message)
287-
}
265+
val msg = dia.msg
266+
val pos = dia.pos
267+
val pos1 = adjust(pos.nonInlined) // innermost pos contained by call.pos
268+
val outermost = pos.outermost // call.pos
269+
val inlineStack = pos.inlinePosStack.filterNot(outermost.contains(_))
270+
given Level = Level(dia.level)
271+
given Offset =
272+
val maxLineNumber =
273+
if pos.exists then (pos1 :: inlineStack).map(_.endLine).max + 1
274+
else 0
275+
Offset(maxLineNumber.toString.length + 2)
276+
val sb = StringBuilder()
277+
val posString = posStr(pos1, msg, diagnosticLevel(dia))
278+
if posString.nonEmpty then sb.append(posString).append(EOL)
279+
if pos.exists && pos1.exists && pos1.source.file.exists then
280+
val (srcBefore, srcAfter, offset) = sourceLines(pos1)
281+
val marker = positionMarker(pos1)
282+
val err = errorMsg(pos1, msg.message)
283+
sb.append((srcBefore ::: marker :: err :: srcAfter).mkString(EOL))
284+
285+
if inlineStack.nonEmpty then
286+
sb.append(EOL).append(newBox())
287+
sb.append(EOL).append(offsetBox).append(i"Inline stack trace")
288+
for inlinedPos <- inlineStack do
289+
sb.append(EOL).append(newBox(soft = true))
290+
sb.append(EOL).append(offsetBox).append(i"This location contains code that was inlined from $pos")
291+
if inlinedPos.source.file.exists then
292+
val (srcBefore, srcAfter, _) = sourceLines(inlinedPos)
293+
val marker = positionMarker(inlinedPos)
294+
sb.append(EOL).append((srcBefore ::: marker :: srcAfter).mkString(EOL))
295+
sb.append(EOL).append(endBox)
296+
end if
288297
else sb.append(msg.message)
289-
if (dia.isVerbose)
298+
if dia.isVerbose then
290299
appendFilterHelp(dia, sb)
291300

292301
if Diagnostic.shouldExplain(dia) then
293302
sb.append(EOL).append(newBox())
294303
sb.append(EOL).append(offsetBox).append(" Explanation (enabled by `-explain`)")
295304
sb.append(EOL).append(newBox(soft = true))
296-
dia.msg.explanation.split(raw"\R").foreach { line =>
305+
dia.msg.explanation.split(raw"\R").foreach: line =>
297306
sb.append(EOL).append(offsetBox).append(if line.isEmpty then "" else " ").append(line)
298-
}
299307
sb.append(EOL).append(endBox)
300308
else if dia.msg.canExplain then
301309
sb.append(EOL).append(offsetBox)
302310
sb.append(EOL).append(offsetBox).append(" longer explanation available when compiling with `-explain`")
303311

304312
sb.toString
305-
}
313+
end messageAndPos
306314

307-
private def hl(str: String)(using Context, Level): String =
315+
private def hl(str: String)(using Context, Level): String =
308316
summon[Level].value match
309317
case interfaces.Diagnostic.ERROR => Red(str).show
310318
case interfaces.Diagnostic.WARNING => Yellow(str).show

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

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -151,7 +151,10 @@ class Suppression(val annotPos: SourcePosition, val filters: List[MessageFilter]
151151
_used = supersededState
152152
def matches(dia: Diagnostic): Boolean =
153153
val pos = dia.pos
154-
pos.exists && start <= pos.start && pos.end <= end && filters.forall(_.matches(dia))
154+
def posMatches =
155+
start <= pos.start && pos.end <= end
156+
|| pos.inlinePosStack.exists(p => start <= p.start && p.end <= end)
157+
pos.exists && posMatches && filters.forall(_.matches(dia))
155158

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

compiler/src/dotty/tools/dotc/util/SourcePosition.scala

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,11 @@ import scala.annotation.internal.sharable
1111

1212
/** A source position is comprised of a span and a source file */
1313
case class SourcePosition(source: SourceFile, span: Span, outer: SourcePosition = NoSourcePosition)
14-
extends SrcPos, interfaces.SourcePosition, Showable {
14+
extends SrcPos, interfaces.SourcePosition, Showable:
1515

1616
def sourcePos(using Context) = this
1717

18-
/** Is `that` a source position contained in this source position ?
18+
/** Is `that` a source position contained in this source position?
1919
* `outer` is not taken into account. */
2020
def contains(that: SourcePosition): Boolean =
2121
this.source == that.source && this.span.contains(that.span)
@@ -82,7 +82,14 @@ extends SrcPos, interfaces.SourcePosition, Showable {
8282
s"${if (source.exists) source.file.toString else "(no source)"}:$span"
8383

8484
def toText(printer: Printer): Text = printer.toText(this)
85-
}
85+
86+
object SourcePosition:
87+
extension (pos: SourcePosition)
88+
/** List of all the inline calls that surround the position. */
89+
def inlinePosStack: List[SourcePosition] =
90+
if pos.outer != null && pos.outer.exists then pos :: pos.outer.inlinePosStack
91+
else pos :: Nil
92+
end SourcePosition
8693

8794
/** A sentinel for a non-existing source position */
8895
@sharable object NoSourcePosition extends SourcePosition(NoSource, NoSpan) {

tests/neg/i23815.check

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,6 @@
44
| ':' or '{' expected, but 'end of statement' found
55
| Nested package statements that are not at the beginning of the file require braces or ':' with an indented body.
66
-- [E040] Syntax Error: tests/neg/i23815.scala:9:8 ---------------------------------------------------------------------
7-
9 |// error
8-
| ^
9-
| '}' expected, but eof found
7+
9 |// error
8+
| ^
9+
| '}' expected, but eof found

tests/warn/i24082.scala

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
//> using options -deprecation -Werror
2+
3+
import annotation.*
4+
5+
@deprecated
6+
case object A {
7+
inline def use: Any = A
8+
}
9+
10+
@nowarn
11+
object test {
12+
A.use
13+
}

0 commit comments

Comments
 (0)