Skip to content

Commit 40507bd

Browse files
committed
Auto merge of #138865 - petrochenkov:errwhere, r=jieyouxu
compiletest: Support matching on diagnostics without a span Using `//~? ERROR my message` on any line of the test. The new checks are exhaustive, like all other `//~` checks, and unlike the `error-pattern` directive that is sometimes used now to check for span-less diagnostics. This will allow to eliminate most on `error-pattern` directives in compile-fail tests (except those that are intentionally imprecise due to platform-specific diagnostics). I didn't migrate any of `error-pattern`s in this PR though, except those where the migration was necessary for the tests to pass.
2 parents 48994b1 + 8d5109a commit 40507bd

File tree

116 files changed

+371
-105
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

116 files changed

+371
-105
lines changed

src/doc/rustc-dev-guide/src/tests/ui.md

+22-4
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,9 @@ several ways to match the message with the line (see the examples below):
202202
* `~|`: Associates the error level and message with the *same* line as the
203203
*previous comment*. This is more convenient than using multiple carets when
204204
there are multiple messages associated with the same line.
205+
* `~?`: Used to match error levels and messages with errors not having line
206+
information. These can be placed on any line in the test file, but are
207+
conventionally placed at the end.
205208

206209
Example:
207210

@@ -270,10 +273,23 @@ fn main() {
270273
//~| ERROR this pattern has 1 field, but the corresponding tuple struct has 3 fields [E0023]
271274
```
272275

276+
#### Error without line information
277+
278+
Use `//~?` to match an error without line information.
279+
`//~?` is precise and will not match errors if their line information is available.
280+
It should be preferred to using `error-pattern`, which is imprecise and non-exhaustive.
281+
282+
```rust,ignore
283+
//@ compile-flags: --print yyyy
284+
285+
//~? ERROR unknown print request: `yyyy`
286+
```
287+
273288
### `error-pattern`
274289

275-
The `error-pattern` [directive](directives.md) can be used for messages that don't
276-
have a specific span.
290+
The `error-pattern` [directive](directives.md) can be used for runtime messages, which don't
291+
have a specific span, or for compile time messages if imprecise matching is required due to
292+
multi-line platform specific diagnostics.
277293

278294
Let's think about this test:
279295

@@ -300,7 +316,9 @@ fn main() {
300316
}
301317
```
302318

303-
But for strict testing, try to use the `ERROR` annotation as much as possible.
319+
But for strict testing, try to use the `ERROR` annotation as much as possible,
320+
including `//~?` annotations for diagnostics without span.
321+
For compile time diagnostics `error-pattern` should very rarely be necessary.
304322

305323
### Error levels
306324

@@ -353,7 +371,7 @@ would be a `.mir.stderr` and `.thir.stderr` file with the different outputs of
353371
the different revisions.
354372

355373
> Note: cfg revisions also work inside the source code with `#[cfg]` attributes.
356-
>
374+
>
357375
> By convention, the `FALSE` cfg is used to have an always-false config.
358376
359377
## Controlling pass/fail expectations

src/tools/compiletest/src/errors.rs

+20-35
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,6 @@ use std::sync::OnceLock;
99
use regex::Regex;
1010
use tracing::*;
1111

12-
use self::WhichLine::*;
13-
1412
#[derive(Copy, Clone, Debug, PartialEq)]
1513
pub enum ErrorKind {
1614
Help,
@@ -50,7 +48,7 @@ impl fmt::Display for ErrorKind {
5048

5149
#[derive(Debug)]
5250
pub struct Error {
53-
pub line_num: usize,
51+
pub line_num: Option<usize>,
5452
/// What kind of message we expect (e.g., warning, error, suggestion).
5553
/// `None` if not specified or unknown message kind.
5654
pub kind: Option<ErrorKind>,
@@ -63,17 +61,14 @@ impl Error {
6361
format!(
6462
"{: <10}line {: >3}: {}",
6563
self.kind.map(|kind| kind.to_string()).unwrap_or_default().to_uppercase(),
66-
self.line_num,
64+
self.line_num_str(),
6765
self.msg.cyan(),
6866
)
6967
}
70-
}
7168

72-
#[derive(PartialEq, Debug)]
73-
enum WhichLine {
74-
ThisLine,
75-
FollowPrevious(usize),
76-
AdjustBackward(usize),
69+
pub fn line_num_str(&self) -> String {
70+
self.line_num.map_or("?".to_string(), |line_num| line_num.to_string())
71+
}
7772
}
7873

7974
/// Looks for either "//~| KIND MESSAGE" or "//~^^... KIND MESSAGE"
@@ -105,12 +100,10 @@ pub fn load_errors(testfile: &Path, revision: Option<&str>) -> Vec<Error> {
105100
.filter(|(_, line)| line.is_ok())
106101
.filter_map(|(line_num, line)| {
107102
parse_expected(last_nonfollow_error, line_num + 1, &line.unwrap(), revision).map(
108-
|(which, error)| {
109-
match which {
110-
FollowPrevious(_) => {}
111-
_ => last_nonfollow_error = Some(error.line_num),
103+
|(follow_prev, error)| {
104+
if !follow_prev {
105+
last_nonfollow_error = error.line_num;
112106
}
113-
114107
error
115108
},
116109
)
@@ -123,18 +116,19 @@ fn parse_expected(
123116
line_num: usize,
124117
line: &str,
125118
test_revision: Option<&str>,
126-
) -> Option<(WhichLine, Error)> {
119+
) -> Option<(bool, Error)> {
127120
// Matches comments like:
128121
// //~
129122
// //~|
130123
// //~^
131124
// //~^^^^^
125+
// //~?
132126
// //[rev1]~
133127
// //[rev1,rev2]~^^
134128
static RE: OnceLock<Regex> = OnceLock::new();
135129

136130
let captures = RE
137-
.get_or_init(|| Regex::new(r"//(?:\[(?P<revs>[\w\-,]+)])?~(?P<adjust>\||\^*)").unwrap())
131+
.get_or_init(|| Regex::new(r"//(?:\[(?P<revs>[\w\-,]+)])?~(?P<adjust>\?|\||\^*)").unwrap())
138132
.captures(line)?;
139133

140134
match (test_revision, captures.name("revs")) {
@@ -151,11 +145,6 @@ fn parse_expected(
151145
(Some(_), None) | (None, None) => {}
152146
}
153147

154-
let (follow, adjusts) = match &captures["adjust"] {
155-
"|" => (true, 0),
156-
circumflexes => (false, circumflexes.len()),
157-
};
158-
159148
// Get the part of the comment after the sigil (e.g. `~^^` or ~|).
160149
let whole_match = captures.get(0).unwrap();
161150
let (_, mut msg) = line.split_at(whole_match.end());
@@ -170,28 +159,24 @@ fn parse_expected(
170159

171160
let msg = msg.trim().to_owned();
172161

173-
let (which, line_num) = if follow {
174-
assert_eq!(adjusts, 0, "use either //~| or //~^, not both.");
175-
let line_num = last_nonfollow_error.expect(
176-
"encountered //~| without \
177-
preceding //~^ line.",
178-
);
179-
(FollowPrevious(line_num), line_num)
162+
let line_num_adjust = &captures["adjust"];
163+
let (follow_prev, line_num) = if line_num_adjust == "|" {
164+
(true, Some(last_nonfollow_error.expect("encountered //~| without preceding //~^ line")))
165+
} else if line_num_adjust == "?" {
166+
(false, None)
180167
} else {
181-
let which = if adjusts > 0 { AdjustBackward(adjusts) } else { ThisLine };
182-
let line_num = line_num - adjusts;
183-
(which, line_num)
168+
(false, Some(line_num - line_num_adjust.len()))
184169
};
185170

186171
debug!(
187-
"line={} tag={:?} which={:?} kind={:?} msg={:?}",
172+
"line={:?} tag={:?} follow_prev={:?} kind={:?} msg={:?}",
188173
line_num,
189174
whole_match.as_str(),
190-
which,
175+
follow_prev,
191176
kind,
192177
msg
193178
);
194-
Some((which, Error { line_num, kind, msg }))
179+
Some((follow_prev, Error { line_num, kind, msg }))
195180
}
196181

197182
#[cfg(test)]

src/tools/compiletest/src/json.rs

+50-38
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@
22
33
use std::path::{Path, PathBuf};
44
use std::str::FromStr;
5+
use std::sync::OnceLock;
56

7+
use regex::Regex;
68
use serde::Deserialize;
79

810
use crate::errors::{Error, ErrorKind};
@@ -213,36 +215,24 @@ fn push_expected_errors(
213215
// also ensure that `//~ ERROR E123` *always* works. The
214216
// assumption is that these multi-line error messages are on their
215217
// way out anyhow.
216-
let with_code = |span: &DiagnosticSpan, text: &str| {
217-
match diagnostic.code {
218-
Some(ref code) =>
219-
// FIXME(#33000) -- it'd be better to use a dedicated
220-
// UI harness than to include the line/col number like
221-
// this, but some current tests rely on it.
222-
//
223-
// Note: Do NOT include the filename. These can easily
224-
// cause false matches where the expected message
225-
// appears in the filename, and hence the message
226-
// changes but the test still passes.
227-
{
228-
format!(
229-
"{}:{}: {}:{}: {} [{}]",
230-
span.line_start,
231-
span.column_start,
232-
span.line_end,
233-
span.column_end,
234-
text,
235-
code.code.clone()
236-
)
237-
}
238-
None =>
239-
// FIXME(#33000) -- it'd be better to use a dedicated UI harness
240-
{
241-
format!(
242-
"{}:{}: {}:{}: {}",
243-
span.line_start, span.column_start, span.line_end, span.column_end, text
244-
)
218+
let with_code = |span: Option<&DiagnosticSpan>, text: &str| {
219+
// FIXME(#33000) -- it'd be better to use a dedicated
220+
// UI harness than to include the line/col number like
221+
// this, but some current tests rely on it.
222+
//
223+
// Note: Do NOT include the filename. These can easily
224+
// cause false matches where the expected message
225+
// appears in the filename, and hence the message
226+
// changes but the test still passes.
227+
let span_str = match span {
228+
Some(DiagnosticSpan { line_start, column_start, line_end, column_end, .. }) => {
229+
format!("{line_start}:{column_start}: {line_end}:{column_end}")
245230
}
231+
None => format!("?:?: ?:?"),
232+
};
233+
match &diagnostic.code {
234+
Some(code) => format!("{span_str}: {text} [{}]", code.code),
235+
None => format!("{span_str}: {text}"),
246236
}
247237
};
248238

@@ -251,19 +241,41 @@ fn push_expected_errors(
251241
// more structured shortly anyhow.
252242
let mut message_lines = diagnostic.message.lines();
253243
if let Some(first_line) = message_lines.next() {
254-
for span in primary_spans {
255-
let msg = with_code(span, first_line);
244+
let ignore = |s| {
245+
static RE: OnceLock<Regex> = OnceLock::new();
246+
RE.get_or_init(|| {
247+
Regex::new(r"aborting due to \d+ previous errors?|\d+ warnings? emitted").unwrap()
248+
})
249+
.is_match(s)
250+
};
251+
252+
if primary_spans.is_empty() && !ignore(first_line) {
253+
let msg = with_code(None, first_line);
256254
let kind = ErrorKind::from_str(&diagnostic.level).ok();
257-
expected_errors.push(Error { line_num: span.line_start, kind, msg });
255+
expected_errors.push(Error { line_num: None, kind, msg });
256+
} else {
257+
for span in primary_spans {
258+
let msg = with_code(Some(span), first_line);
259+
let kind = ErrorKind::from_str(&diagnostic.level).ok();
260+
expected_errors.push(Error { line_num: Some(span.line_start), kind, msg });
261+
}
258262
}
259263
}
260264
for next_line in message_lines {
261-
for span in primary_spans {
265+
if primary_spans.is_empty() {
262266
expected_errors.push(Error {
263-
line_num: span.line_start,
267+
line_num: None,
264268
kind: None,
265-
msg: with_code(span, next_line),
269+
msg: with_code(None, next_line),
266270
});
271+
} else {
272+
for span in primary_spans {
273+
expected_errors.push(Error {
274+
line_num: Some(span.line_start),
275+
kind: None,
276+
msg: with_code(Some(span), next_line),
277+
});
278+
}
267279
}
268280
}
269281

@@ -272,7 +284,7 @@ fn push_expected_errors(
272284
if let Some(ref suggested_replacement) = span.suggested_replacement {
273285
for (index, line) in suggested_replacement.lines().enumerate() {
274286
expected_errors.push(Error {
275-
line_num: span.line_start + index,
287+
line_num: Some(span.line_start + index),
276288
kind: Some(ErrorKind::Suggestion),
277289
msg: line.to_string(),
278290
});
@@ -290,7 +302,7 @@ fn push_expected_errors(
290302
// Add notes for any labels that appear in the message.
291303
for span in spans_in_this_file.iter().filter(|span| span.label.is_some()) {
292304
expected_errors.push(Error {
293-
line_num: span.line_start,
305+
line_num: Some(span.line_start),
294306
kind: Some(ErrorKind::Note),
295307
msg: span.label.clone().unwrap(),
296308
});
@@ -309,7 +321,7 @@ fn push_backtrace(
309321
) {
310322
if Path::new(&expansion.span.file_name) == Path::new(&file_name) {
311323
expected_errors.push(Error {
312-
line_num: expansion.span.line_start,
324+
line_num: Some(expansion.span.line_start),
313325
kind: Some(ErrorKind::Note),
314326
msg: format!("in this expansion of {}", expansion.macro_decl_name),
315327
});

src/tools/compiletest/src/runtest.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -747,7 +747,7 @@ impl<'test> TestCx<'test> {
747747
self.error(&format!(
748748
"{}:{}: unexpected {}: '{}'",
749749
file_name,
750-
actual_error.line_num,
750+
actual_error.line_num_str(),
751751
actual_error
752752
.kind
753753
.as_ref()
@@ -767,7 +767,7 @@ impl<'test> TestCx<'test> {
767767
self.error(&format!(
768768
"{}:{}: expected {} not found: {}",
769769
file_name,
770-
expected_error.line_num,
770+
expected_error.line_num_str(),
771771
expected_error.kind.as_ref().map_or("message".into(), |k| k.to_string()),
772772
expected_error.msg
773773
));

src/tools/compiletest/src/runtest/ui.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -182,7 +182,7 @@ impl TestCx<'_> {
182182
} else if explicit && !expected_errors.is_empty() {
183183
let msg = format!(
184184
"line {}: cannot combine `--error-format` with {} annotations; use `error-pattern` instead",
185-
expected_errors[0].line_num,
185+
expected_errors[0].line_num_str(),
186186
expected_errors[0].kind.unwrap_or(ErrorKind::Error),
187187
);
188188
self.fatal(&msg);

tests/rustdoc-ui/coverage/html.rs

+2
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,5 @@
22

33
/// Foo
44
pub struct Xo;
5+
6+
//~? ERROR `--output-format=html` is not supported for the `--show-coverage` option

tests/rustdoc-ui/deprecated-attrs.rs

+4
Original file line numberDiff line numberDiff line change
@@ -19,3 +19,7 @@
1919
//~| NOTE see issue #44136
2020
//~| NOTE no longer functions
2121
//~| NOTE `doc(plugins)` is now a no-op
22+
23+
//~? WARN the `passes` flag no longer functions
24+
//~? NOTE see issue #44136
25+
//~? HELP you may want to use --document-private-items

tests/rustdoc-ui/doctest-output.rs

+2
Original file line numberDiff line numberDiff line change
@@ -1 +1,3 @@
11
//@ compile-flags:-Z unstable-options --show-coverage --output-format=doctest
2+
3+
//~? ERROR `--output-format=doctest` is not supported for the `--show-coverage` option

tests/rustdoc-ui/generate-link-to-definition/generate-link-to-definition-opt.rs

+2
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,5 @@
55
//@ check-pass
66

77
pub fn f() {}
8+
9+
//~? WARN `--generate-link-to-definition` option can only be used with HTML output format

tests/rustdoc-ui/include-str-bare-urls.rs

+2
Original file line numberDiff line numberDiff line change
@@ -13,3 +13,5 @@
1313

1414
#![deny(rustdoc::bare_urls)]
1515
#![doc=include_str!("auxiliary/include-str-bare-urls.md")]
16+
17+
//~? ERROR this URL is not a hyperlink

tests/rustdoc-ui/lints/check.rs

+2
Original file line numberDiff line numberDiff line change
@@ -12,3 +12,5 @@
1212
pub fn foo() {}
1313
//~^ WARN
1414
//~^^ WARN
15+
16+
//~? WARN no documentation found for this crate's top-level module

tests/rustdoc-ui/remap-path-prefix-lint.rs

+2
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,5 @@
88

99
/// </script>
1010
pub struct Bar;
11+
12+
//~? ERROR unopened HTML tag `script`

tests/rustdoc-ui/scrape-examples/scrape-examples-fail-if-type-error.rs

+2
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,5 @@ pub fn foo() {
55
INVALID_FUNC();
66
//~^ ERROR could not resolve path
77
}
8+
9+
//~? ERROR Compilation failed, aborting rustdoc

0 commit comments

Comments
 (0)