Skip to content

Commit 1ecc9aa

Browse files
committed
[WIP] compiletest: Make diagnostic kind mandatory on line annotations
1 parent 2205455 commit 1ecc9aa

File tree

192 files changed

+803
-764
lines changed

Some content is hidden

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

192 files changed

+803
-764
lines changed

src/tools/compiletest/src/errors.rs

+21-24
Original file line numberDiff line numberDiff line change
@@ -30,24 +30,18 @@ impl ErrorKind {
3030

3131
/// Either the canonical uppercase string, or some additional versions for compatibility.
3232
/// FIXME: consider keeping only the canonical versions here.
33-
fn from_user_str(s: &str) -> Option<ErrorKind> {
34-
Some(match s {
35-
"HELP" | "help" => ErrorKind::Help,
33+
pub fn from_user_str(s: &str) -> ErrorKind {
34+
match s {
35+
"HELP" | "help" | "HELP_NONVIRAL" => ErrorKind::Help,
3636
"ERROR" | "error" => ErrorKind::Error,
37-
"NOTE" | "note" => ErrorKind::Note,
37+
"NOTE" | "note" | "NOTE_NONVIRAL" => ErrorKind::Note,
3838
"SUGGESTION" => ErrorKind::Suggestion,
3939
"WARN" | "WARNING" | "warn" | "warning" => ErrorKind::Warning,
40-
_ => return None,
41-
})
42-
}
43-
44-
pub fn expect_from_user_str(s: &str) -> ErrorKind {
45-
ErrorKind::from_user_str(s).unwrap_or_else(|| {
46-
panic!(
40+
_ => panic!(
4741
"unexpected diagnostic kind `{s}`, expected \
4842
`ERROR`, `WARN`, `NOTE`, `HELP` or `SUGGESTION`"
49-
)
50-
})
43+
),
44+
}
5145
}
5246
}
5347

@@ -67,24 +61,22 @@ impl fmt::Display for ErrorKind {
6761
pub struct Error {
6862
pub line_num: Option<usize>,
6963
/// What kind of message we expect (e.g., warning, error, suggestion).
70-
/// `None` if not specified or unknown message kind.
71-
pub kind: Option<ErrorKind>,
64+
pub kind: ErrorKind,
7265
pub msg: String,
7366
/// For some `Error`s, like secondary lines of multi-line diagnostics, line annotations
7467
/// are not mandatory, even if they would otherwise be mandatory for primary errors.
7568
/// Only makes sense for "actual" errors, not for "expected" errors.
7669
pub require_annotation: bool,
70+
/// A temporary measure to avoid adding too many `NOTE` and `HELP` annotations in #NNNNNN.
71+
/// Only makes sense for "expected" errors, not for "found" errors.
72+
pub viral: bool,
73+
pub parent: Option<usize>,
7774
}
7875

7976
impl Error {
8077
pub fn render_for_expected(&self) -> String {
8178
use colored::Colorize;
82-
format!(
83-
"{: <10}line {: >3}: {}",
84-
self.kind.map(|kind| kind.to_string()).unwrap_or_default(),
85-
self.line_num_str(),
86-
self.msg.cyan(),
87-
)
79+
format!("{: <10}line {: >3}: {}", self.kind, self.line_num_str(), self.msg.cyan())
8880
}
8981

9082
pub fn line_num_str(&self) -> String {
@@ -173,9 +165,11 @@ fn parse_expected(
173165
// Get the part of the comment after the sigil (e.g. `~^^` or ~|).
174166
let tag = captures.get(0).unwrap();
175167
let rest = line[tag.end()..].trim_start();
176-
let (kind_str, _) = rest.split_once(|c: char| !c.is_ascii_alphabetic()).unwrap_or((rest, ""));
168+
let (kind_str, _) =
169+
rest.split_once(|c: char| c != '_' && !c.is_ascii_alphabetic()).unwrap_or((rest, ""));
170+
let viral = kind_str != "NOTE_NONVIRAL" && kind_str != "HELP_NONVIRAL";
177171
let kind = ErrorKind::from_user_str(kind_str);
178-
let untrimmed_msg = if kind.is_some() { &rest[kind_str.len()..] } else { rest };
172+
let untrimmed_msg = &rest[kind_str.len()..];
179173
let msg = untrimmed_msg.strip_prefix(':').unwrap_or(untrimmed_msg).trim().to_owned();
180174

181175
let line_num_adjust = &captures["adjust"];
@@ -197,7 +191,10 @@ fn parse_expected(
197191
kind,
198192
msg
199193
);
200-
Some((follow_prev, Error { line_num, kind, msg, require_annotation: true }))
194+
Some((
195+
follow_prev,
196+
Error { line_num, kind, msg, require_annotation: true, viral, parent: None },
197+
))
201198
}
202199

203200
#[cfg(test)]

src/tools/compiletest/src/header.rs

+1-2
Original file line numberDiff line numberDiff line change
@@ -585,8 +585,7 @@ impl TestProps {
585585
if let Some(err_kind) =
586586
config.parse_name_value_directive(ln, DONT_REQUIRE_ANNOTATIONS)
587587
{
588-
self.require_annotations
589-
.insert(ErrorKind::expect_from_user_str(&err_kind), false);
588+
self.require_annotations.insert(ErrorKind::from_user_str(&err_kind), false);
590589
}
591590
},
592591
);

src/tools/compiletest/src/json.rs

+33-8
Original file line numberDiff line numberDiff line change
@@ -147,7 +147,9 @@ pub fn parse_output(file_name: &str, output: &str, proc_res: &ProcRes) -> Vec<Er
147147
// output. This hack just skips over such lines. Yuck.
148148
if line.starts_with('{') {
149149
match serde_json::from_str::<Diagnostic>(line) {
150-
Ok(diagnostic) => push_actual_errors(&mut errors, &diagnostic, &[], file_name),
150+
Ok(diagnostic) => {
151+
push_actual_errors(&mut errors, &diagnostic, &[], file_name, None)
152+
}
151153
Err(error) => {
152154
// Ignore the future compat report message - this is handled
153155
// by `extract_rendered`
@@ -172,6 +174,7 @@ fn push_actual_errors(
172174
diagnostic: &Diagnostic,
173175
default_spans: &[&DiagnosticSpan],
174176
file_name: &str,
177+
prev_parent: Option<usize>,
175178
) {
176179
// In case of macro expansions, we need to get the span of the callsite
177180
let spans_info_in_this_file: Vec<_> = diagnostic
@@ -229,26 +232,37 @@ fn push_actual_errors(
229232
// Convert multi-line messages into multiple errors.
230233
// We expect to replace these with something more structured anyhow.
231234
let mut message_lines = diagnostic.message.lines();
232-
let kind = Some(ErrorKind::from_compiler_str(&diagnostic.level));
235+
let kind = ErrorKind::from_compiler_str(&diagnostic.level);
233236
let first_line = message_lines.next().unwrap_or(&diagnostic.message);
237+
let mut parent = None;
234238
if primary_spans.is_empty() {
235239
static RE: OnceLock<Regex> = OnceLock::new();
236240
let re_init =
237241
|| Regex::new(r"aborting due to \d+ previous errors?|\d+ warnings? emitted").unwrap();
242+
if parent.is_none() {
243+
parent = Some(errors.len());
244+
}
238245
errors.push(Error {
239246
line_num: None,
240247
kind,
241248
msg: with_code(None, first_line),
242249
require_annotation: diagnostic.level != "failure-note"
243250
&& !RE.get_or_init(re_init).is_match(first_line),
251+
viral: true,
252+
parent: prev_parent,
244253
});
245254
} else {
246255
for span in primary_spans {
256+
if parent.is_none() {
257+
parent = Some(errors.len());
258+
}
247259
errors.push(Error {
248260
line_num: Some(span.line_start),
249261
kind,
250262
msg: with_code(Some(span), first_line),
251263
require_annotation: true,
264+
viral: true,
265+
parent: prev_parent,
252266
});
253267
}
254268
}
@@ -259,6 +273,8 @@ fn push_actual_errors(
259273
kind,
260274
msg: with_code(None, next_line),
261275
require_annotation: false,
276+
viral: true,
277+
parent: prev_parent,
262278
});
263279
} else {
264280
for span in primary_spans {
@@ -267,6 +283,8 @@ fn push_actual_errors(
267283
kind,
268284
msg: with_code(Some(span), next_line),
269285
require_annotation: false,
286+
viral: true,
287+
parent: prev_parent,
270288
});
271289
}
272290
}
@@ -278,9 +296,11 @@ fn push_actual_errors(
278296
for (index, line) in suggested_replacement.lines().enumerate() {
279297
errors.push(Error {
280298
line_num: Some(span.line_start + index),
281-
kind: Some(ErrorKind::Suggestion),
299+
kind: ErrorKind::Suggestion,
282300
msg: line.to_string(),
283301
require_annotation: true,
302+
viral: true,
303+
parent,
284304
});
285305
}
286306
}
@@ -289,41 +309,46 @@ fn push_actual_errors(
289309
// Add notes for the backtrace
290310
for span in primary_spans {
291311
if let Some(frame) = &span.expansion {
292-
push_backtrace(errors, frame, file_name);
312+
push_backtrace(errors, frame, file_name, parent);
293313
}
294314
}
295315

296316
// Add notes for any labels that appear in the message.
297317
for span in spans_in_this_file.iter().filter(|span| span.label.is_some()) {
298318
errors.push(Error {
299319
line_num: Some(span.line_start),
300-
kind: Some(ErrorKind::Note),
320+
kind: ErrorKind::Note,
301321
msg: span.label.clone().unwrap(),
302322
require_annotation: true,
323+
viral: true,
324+
parent,
303325
});
304326
}
305327

306328
// Flatten out the children.
307329
for child in &diagnostic.children {
308-
push_actual_errors(errors, child, primary_spans, file_name);
330+
push_actual_errors(errors, child, primary_spans, file_name, parent);
309331
}
310332
}
311333

312334
fn push_backtrace(
313335
errors: &mut Vec<Error>,
314336
expansion: &DiagnosticSpanMacroExpansion,
315337
file_name: &str,
338+
parent: Option<usize>,
316339
) {
317340
if Path::new(&expansion.span.file_name) == Path::new(&file_name) {
318341
errors.push(Error {
319342
line_num: Some(expansion.span.line_start),
320-
kind: Some(ErrorKind::Note),
343+
kind: ErrorKind::Note,
321344
msg: format!("in this expansion of {}", expansion.macro_decl_name),
322345
require_annotation: true,
346+
viral: true,
347+
parent,
323348
});
324349
}
325350

326351
if let Some(previous_expansion) = &expansion.span.expansion {
327-
push_backtrace(errors, previous_expansion, file_name);
352+
push_backtrace(errors, previous_expansion, file_name, parent);
328353
}
329354
}

src/tools/compiletest/src/runtest.rs

+54-36
Original file line numberDiff line numberDiff line change
@@ -679,9 +679,7 @@ impl<'test> TestCx<'test> {
679679
"check_expected_errors: expected_errors={:?} proc_res.status={:?}",
680680
expected_errors, proc_res.status
681681
);
682-
if proc_res.status.success()
683-
&& expected_errors.iter().any(|x| x.kind == Some(ErrorKind::Error))
684-
{
682+
if proc_res.status.success() && expected_errors.iter().any(|x| x.kind == ErrorKind::Error) {
685683
self.fatal_proc_rec("process did not return an error status", proc_res);
686684
}
687685

@@ -709,22 +707,28 @@ impl<'test> TestCx<'test> {
709707
self.testpaths.file.display().to_string()
710708
};
711709

712-
let expect_help = expected_errors.iter().any(|ee| ee.kind == Some(ErrorKind::Help));
713-
let expect_note = expected_errors.iter().any(|ee| ee.kind == Some(ErrorKind::Note));
710+
// If the testcase being checked contains at least one expected "help"
711+
// message, then we'll ensure that all "help" messages are expected.
712+
// Otherwise, all "help" messages reported by the compiler will be ignored.
713+
// This logic also applies to "note" messages.
714+
let expect_help = expected_errors.iter().any(|ee| ee.kind == ErrorKind::Help && ee.viral);
715+
// let expect_note = expected_errors.iter().any(|ee| ee.kind == ErrorKind::Note && ee.viral);
714716

715717
// Parse the JSON output from the compiler and extract out the messages.
716-
let actual_errors = json::parse_output(&diagnostic_file_name, &proc_res.stderr, proc_res);
718+
let mut actual_errors =
719+
json::parse_output(&diagnostic_file_name, &proc_res.stderr, proc_res);
717720
let mut unexpected = Vec::new();
718721
let mut found = vec![false; expected_errors.len()];
719-
for mut actual_error in actual_errors {
722+
let mut unexpected_actual_indices = Vec::new();
723+
let mut poisoned_parents = HashSet::new();
724+
for (actual_index, actual_error) in actual_errors.iter_mut().enumerate() {
720725
actual_error.msg = self.normalize_output(&actual_error.msg, &[]);
721726

722727
let opt_index =
723728
expected_errors.iter().enumerate().position(|(index, expected_error)| {
724729
!found[index]
725730
&& actual_error.line_num == expected_error.line_num
726-
&& (expected_error.kind.is_none()
727-
|| actual_error.kind == expected_error.kind)
731+
&& actual_error.kind == expected_error.kind
728732
&& actual_error.msg.contains(&expected_error.msg)
729733
});
730734

@@ -733,25 +737,41 @@ impl<'test> TestCx<'test> {
733737
// found a match, everybody is happy
734738
assert!(!found[index]);
735739
found[index] = true;
736-
}
737740

738-
None => {
739-
// If the test is a known bug, don't require that the error is annotated
740-
if self.is_unexpected_compiler_message(&actual_error, expect_help, expect_note)
741-
{
742-
self.error(&format!(
743-
"{}:{}: unexpected {}: '{}'",
744-
file_name,
745-
actual_error.line_num_str(),
746-
actual_error
747-
.kind
748-
.as_ref()
749-
.map_or(String::from("message"), |k| k.to_string()),
750-
actual_error.msg
751-
));
752-
unexpected.push(actual_error);
741+
if actual_error.kind == ErrorKind::Note && expected_errors[index].viral {
742+
poisoned_parents.insert(actual_error.parent);
753743
}
754744
}
745+
746+
None => unexpected_actual_indices.push(actual_index),
747+
}
748+
}
749+
750+
for unexpected_actual_index in unexpected_actual_indices {
751+
// If the test is a known bug, don't require that the error is annotated
752+
let actual_error = &actual_errors[unexpected_actual_index];
753+
let mut expect_note = false;
754+
let mut next_parent = actual_error.parent;
755+
loop {
756+
if poisoned_parents.contains(&next_parent) {
757+
expect_note = true;
758+
break;
759+
}
760+
match next_parent {
761+
Some(next) => next_parent = actual_errors[next].parent,
762+
None => break,
763+
}
764+
}
765+
766+
if self.is_unexpected_compiler_message(&actual_error, expect_help, expect_note) {
767+
self.error(&format!(
768+
"{}:{}: unexpected {}: '{}'",
769+
file_name,
770+
actual_error.line_num_str(),
771+
actual_error.kind,
772+
actual_error.msg
773+
));
774+
unexpected.push(actual_error);
755775
}
756776
}
757777

@@ -763,7 +783,7 @@ impl<'test> TestCx<'test> {
763783
"{}:{}: expected {} not found: {}",
764784
file_name,
765785
expected_error.line_num_str(),
766-
expected_error.kind.as_ref().map_or("message".into(), |k| k.to_string()),
786+
expected_error.kind,
767787
expected_error.msg
768788
));
769789
not_found.push(expected_error);
@@ -803,17 +823,15 @@ impl<'test> TestCx<'test> {
803823
expect_help: bool,
804824
expect_note: bool,
805825
) -> bool {
826+
// If the test being checked doesn't contain any "help" or "note" annotations, then
827+
// we don't require annotating "help" or "note" (respecively) diagnostics at all.
806828
actual_error.require_annotation
807-
&& actual_error.kind.map_or(false, |err_kind| {
808-
// If the test being checked doesn't contain any "help" or "note" annotations, then
809-
// we don't require annotating "help" or "note" (respecively) diagnostics at all.
810-
let default_require_annotations = self.props.require_annotations[&err_kind];
811-
match err_kind {
812-
ErrorKind::Help => expect_help && default_require_annotations,
813-
ErrorKind::Note => expect_note && default_require_annotations,
814-
_ => default_require_annotations,
815-
}
816-
})
829+
&& self.props.require_annotations[&actual_error.kind]
830+
&& match actual_error.kind {
831+
ErrorKind::Help => expect_help,
832+
ErrorKind::Note => expect_note,
833+
_ => true,
834+
}
817835
}
818836

819837
fn should_emit_metadata(&self, pm: Option<PassMode>) -> Emit {

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

+3-3
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@ use rustfix::{Filter, apply_suggestions, get_suggestions_from_json};
66
use tracing::debug;
77

88
use super::{
9-
AllowUnused, Emit, ErrorKind, FailMode, LinkToAux, PassMode, TargetLocation, TestCx,
10-
TestOutput, Truncated, UI_FIXED, WillExecute,
9+
AllowUnused, Emit, FailMode, LinkToAux, PassMode, TargetLocation, TestCx, TestOutput,
10+
Truncated, UI_FIXED, WillExecute,
1111
};
1212
use crate::{errors, json};
1313

@@ -176,7 +176,7 @@ impl TestCx<'_> {
176176
let msg = format!(
177177
"line {}: cannot combine `--error-format` with {} annotations; use `error-pattern` instead",
178178
expected_errors[0].line_num_str(),
179-
expected_errors[0].kind.unwrap_or(ErrorKind::Error),
179+
expected_errors[0].kind,
180180
);
181181
self.fatal(&msg);
182182
}

0 commit comments

Comments
 (0)