From d7276908d13b7a8872205415f5ee5fe15c615113 Mon Sep 17 00:00:00 2001 From: Jason Newcomb Date: Wed, 20 Sep 2023 07:46:14 -0400 Subject: [PATCH 1/2] Allow matching errors and warnings by error code. --- CHANGELOG.md | 1 + README.md | 19 +-- src/error.rs | 7 ++ src/lib.rs | 65 ++++++---- src/parser.rs | 91 ++++++++------ src/parser/tests.rs | 29 ++++- src/rustc_stderr.rs | 8 ++ src/status_emitter.rs | 39 +++++- src/tests.rs | 111 ++++++++++++++++++ tests/integrations/basic-fail/Cargo.stdout | 61 +++++++++- .../wrong_diagnostic_code.rs | 9 ++ .../wrong_diagnostic_code.stderr | 28 +++++ tests/integrations/basic/Cargo.stdout | 3 +- .../actual_tests/match_diagnostic_code.fixed | 3 + .../actual_tests/match_diagnostic_code.rs | 3 + .../actual_tests/match_diagnostic_code.stderr | 16 +++ 16 files changed, 414 insertions(+), 79 deletions(-) create mode 100644 tests/integrations/basic-fail/tests/actual_tests_bless/wrong_diagnostic_code.rs create mode 100644 tests/integrations/basic-fail/tests/actual_tests_bless/wrong_diagnostic_code.stderr create mode 100644 tests/integrations/basic/tests/actual_tests/match_diagnostic_code.fixed create mode 100644 tests/integrations/basic/tests/actual_tests/match_diagnostic_code.rs create mode 100644 tests/integrations/basic/tests/actual_tests/match_diagnostic_code.stderr diff --git a/CHANGELOG.md b/CHANGELOG.md index 644b1d3d..8207a4be 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 * Started maintaining a changelog * `Config::comment_defaults` allows setting `//@` comments for all tests +* `//~` comments can now specify just an error code or lint name, without any message. ERROR level is implied ### Fixed diff --git a/README.md b/README.md index 2c69403c..e92791ec 100644 --- a/README.md +++ b/README.md @@ -11,16 +11,17 @@ A stable version of compiletest-rs ## Supported magic comment annotations If your test tests for failure, you need to add a `//~` annotation where the error is happening -to make sure that the test will always keep failing with a specific message at the annotated line. +to ensure that the test will always keep failing at the annotated line. These comments can take two forms: -`//~ ERROR: XXX` make sure the stderr output contains `XXX` for an error in the line where this comment is written - -* Also supports `HELP`, `WARN` or `NOTE` for different kind of message - * if one of those levels is specified explicitly, *all* diagnostics of this level or higher need an annotation. If you want to avoid this, just leave out the all caps level note entirely. -* If the all caps note is left out, a message of any level is matched. Leaving it out is not allowed for `ERROR` levels. -* This checks the output *before* normalization, so you can check things that get normalized away, but need to - be careful not to accidentally have a pattern that differs between platforms. -* if `XXX` is of the form `/XXX/` it is treated as a regex instead of a substring and will succeed if the regex matches. +* `//~ LEVEL: XXX` matches by error level and message text + * `LEVEL` can be one of the following (descending order): `ERROR`, `HELP`, `WARN` or `NOTE` + * If a level is specified explicitly, *all* diagnostics of that level or higher need an annotation. To avoid this see `//@require-annotations-for-level` + * This checks the output *before* normalization, so you can check things that get normalized away, but need to + be careful not to accidentally have a pattern that differs between platforms. + * if `XXX` is of the form `/XXX/` it is treated as a regex instead of a substring and will succeed if the regex matches. +* `//~ CODE` matches by diagnostic code. + * `CODE` can take multiple forms such as: `E####`, `lint_name`, `tool::lint_name`. + * This will only match a diagnostic at the `ERROR` level. In order to change how a single test is tested, you can add various `//@` comments to the test. Any other comments will be ignored, and all `//@` comments must be formatted precisely as diff --git a/src/error.rs b/src/error.rs index d46753ce..aa74a1a5 100644 --- a/src/error.rs +++ b/src/error.rs @@ -25,6 +25,13 @@ pub enum Error { /// Can be `None` when it is expected outside the current file expected_line: Option, }, + /// A diagnostic code matcher was declared but had no matching error. + CodeNotFound { + /// The code that was not found, and the span of where that code was declared. + code: Spanned, + /// Can be `None` when it is expected outside the current file + expected_line: Option, + }, /// A ui test checking for failure does not have any failure patterns NoPatternsFound, /// A ui test checking for success has failure patterns diff --git a/src/lib.rs b/src/lib.rs index e82117f7..1aad3558 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -14,7 +14,7 @@ use color_eyre::eyre::{eyre, Result}; use crossbeam_channel::{unbounded, Receiver, Sender}; use dependencies::{Build, BuildManager}; use lazy_static::lazy_static; -use parser::{ErrorMatch, OptWithLine, Revisioned, Spanned}; +use parser::{ErrorMatch, ErrorMatchKind, OptWithLine, Revisioned, Spanned}; use regex::bytes::{Captures, Regex}; use rustc_stderr::{Level, Message}; use spanned::Span; @@ -1076,35 +1076,58 @@ fn check_annotations( // We will ensure that *all* diagnostics of level at least `lowest_annotation_level` // are matched. let mut lowest_annotation_level = Level::Error; - for &ErrorMatch { - ref pattern, - level, - line, - } in comments + for &ErrorMatch { ref kind, line } in comments .for_revision(revision) .flat_map(|r| r.error_matches.iter()) { - seen_error_match = Some(pattern.span()); - // If we found a diagnostic with a level annotation, make sure that all - // diagnostics of that level have annotations, even if we don't end up finding a matching diagnostic - // for this pattern. - if lowest_annotation_level > level { - lowest_annotation_level = level; + match kind { + ErrorMatchKind::Code(code) => { + seen_error_match = Some(code.span()); + } + &ErrorMatchKind::Pattern { ref pattern, level } => { + seen_error_match = Some(pattern.span()); + // If we found a diagnostic with a level annotation, make sure that all + // diagnostics of that level have annotations, even if we don't end up finding a matching diagnostic + // for this pattern. + if lowest_annotation_level > level { + lowest_annotation_level = level; + } + } } if let Some(msgs) = messages.get_mut(line.get()) { - let found = msgs - .iter() - .position(|msg| pattern.matches(&msg.message) && msg.level == level); - if let Some(found) = found { - msgs.remove(found); - continue; + match kind { + &ErrorMatchKind::Pattern { ref pattern, level } => { + let found = msgs + .iter() + .position(|msg| pattern.matches(&msg.message) && msg.level == level); + if let Some(found) = found { + msgs.remove(found); + continue; + } + } + ErrorMatchKind::Code(code) => { + let found = msgs.iter().position(|msg| { + msg.level == Level::Error + && msg.code.as_ref().is_some_and(|msg| *msg == **code) + }); + if let Some(found) = found { + msgs.remove(found); + continue; + } + } } } - errors.push(Error::PatternNotFound { - pattern: pattern.clone(), - expected_line: Some(line), + errors.push(match kind { + ErrorMatchKind::Pattern { pattern, .. } => Error::PatternNotFound { + pattern: pattern.clone(), + expected_line: Some(line), + }, + ErrorMatchKind::Code(code) => Error::CodeNotFound { + code: code.clone(), + expected_line: Some(line), + }, }); } diff --git a/src/parser.rs b/src/parser.rs index 3d240c26..7c66ff67 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -212,10 +212,20 @@ pub enum Pattern { Regex(Regex), } +#[derive(Debug, Clone)] +pub(crate) enum ErrorMatchKind { + /// A level and pattern pair parsed from a `//~ LEVEL: Message` comment. + Pattern { + pattern: Spanned, + level: Level, + }, + /// An error code parsed from a `//~ error_code` comment. + Code(Spanned), +} + #[derive(Debug, Clone)] pub(crate) struct ErrorMatch { - pub pattern: Spanned, - pub level: Level, + pub(crate) kind: ErrorMatchKind, /// The line this pattern is expecting to find a message in. pub line: NonZeroUsize, } @@ -302,6 +312,7 @@ impl Comments { } } } + let Revisioned { span, ignore, @@ -788,7 +799,10 @@ impl CommentParser { } impl CommentParser<&mut Revisioned> { - // parse something like (\[[a-z]+(,[a-z]+)*\])?(?P\||[\^]+)? *(?PERROR|HELP|WARN|NOTE): (?P.*) + // parse something like: + // (\[[a-z]+(,[a-z]+)*\])? + // (?P\||[\^]+)? * + // ((?PERROR|HELP|WARN|NOTE): (?P.*))|(?P[a-z0-9_:]+) fn parse_pattern(&mut self, pattern: Spanned<&str>, fallthrough_to: &mut Option) { let (match_line, pattern) = match pattern.chars().next() { Some('|') => ( @@ -831,43 +845,52 @@ impl CommentParser<&mut Revisioned> { }; let pattern = pattern.trim_start(); - let offset = match pattern.chars().position(|c| !c.is_ascii_alphabetic()) { - Some(offset) => offset, - None => { - self.error(pattern.span(), "pattern without level"); - return; - } - }; + let offset = pattern + .bytes() + .position(|c| !(c.is_ascii_alphanumeric() || c == b'_' || c == b':')) + .unwrap_or(pattern.len()); + + let (level_or_code, pattern) = pattern.split_at(offset); + if let Some(level) = level_or_code.strip_suffix(":") { + let level = match (*level).parse() { + Ok(level) => level, + Err(msg) => { + self.error(level.span(), msg); + return; + } + }; - let (level, pattern) = pattern.split_at(offset); - let level = match (*level).parse() { - Ok(level) => level, - Err(msg) => { - self.error(level.span(), msg); - return; - } - }; - let pattern = match pattern.strip_prefix(":") { - Some(offset) => offset, - None => { - self.error(pattern.span(), "no `:` after level found"); - return; - } - }; + let pattern = pattern.trim(); - let pattern = pattern.trim(); + self.check(pattern.span(), !pattern.is_empty(), "no pattern specified"); - self.check(pattern.span(), !pattern.is_empty(), "no pattern specified"); + let pattern = self.parse_error_pattern(pattern); - let pattern = self.parse_error_pattern(pattern); + self.error_matches.push(ErrorMatch { + kind: ErrorMatchKind::Pattern { pattern, level }, + line: match_line, + }); + } else if (*level_or_code).parse::().is_ok() { + // Shouldn't conflict with any real diagnostic code + self.error(level_or_code.span(), "no `:` after level found"); + return; + } else if !pattern.trim_start().is_empty() { + self.error( + pattern.span(), + format!("text found after error code `{}`", *level_or_code), + ); + return; + } else { + self.error_matches.push(ErrorMatch { + kind: ErrorMatchKind::Code(Spanned::new( + level_or_code.to_string(), + level_or_code.span(), + )), + line: match_line, + }); + }; *fallthrough_to = Some(match_line); - - self.error_matches.push(ErrorMatch { - pattern, - level, - line: match_line, - }); } } diff --git a/src/parser/tests.rs b/src/parser/tests.rs index 8c058a10..2aaa345a 100644 --- a/src/parser/tests.rs +++ b/src/parser/tests.rs @@ -1,7 +1,7 @@ use std::path::Path; use crate::{ - parser::{Condition, Pattern}, + parser::{Condition, ErrorMatchKind, Pattern}, Error, }; @@ -20,8 +20,11 @@ fn main() { println!("parsed comments: {:#?}", comments); assert_eq!(comments.revisioned.len(), 1); let revisioned = &comments.revisioned[&vec![]]; - assert_eq!(revisioned.error_matches[0].pattern.line().get(), 5); - match &*revisioned.error_matches[0].pattern { + let ErrorMatchKind::Pattern { pattern, .. } = &revisioned.error_matches[0].kind else { + panic!("expected pattern matcher"); + }; + assert_eq!(pattern.line().get(), 5); + match &**pattern { Pattern::SubString(s) => { assert_eq!( s, @@ -32,6 +35,24 @@ fn main() { } } +#[test] +fn parse_error_code_comment() { + let s = r" +fn main() { + let _x: i32 = 0u32; //~ E0308 +} + "; + let comments = Comments::parse(s, Comments::default(), Path::new("")).unwrap(); + println!("parsed comments: {:#?}", comments); + assert_eq!(comments.revisioned.len(), 1); + let revisioned = &comments.revisioned[&vec![]]; + let ErrorMatchKind::Code(code) = &revisioned.error_matches[0].kind else { + panic!("expected diagnostic code matcher"); + }; + assert_eq!(code.line().get(), 3); + assert_eq!(**code, "E0308"); +} + #[test] fn parse_missing_level() { let s = r" @@ -46,7 +67,7 @@ fn main() { assert_eq!(errors.len(), 1); match &errors[0] { Error::InvalidComment { msg, span } if span.line_start.get() == 5 => { - assert_eq!(msg, "unknown level `encountered`") + assert_eq!(msg, "text found after error code `encountered`") } _ => unreachable!(), } diff --git a/src/rustc_stderr.rs b/src/rustc_stderr.rs index ec2d4ef4..291208e6 100644 --- a/src/rustc_stderr.rs +++ b/src/rustc_stderr.rs @@ -5,6 +5,11 @@ use std::{ path::{Path, PathBuf}, }; +#[derive(serde::Deserialize, Debug)] +struct RustcDiagnosticCode { + code: String, +} + #[derive(serde::Deserialize, Debug)] struct RustcMessage { rendered: Option, @@ -12,6 +17,7 @@ struct RustcMessage { level: String, message: String, children: Vec, + code: Option, } #[derive(Copy, Clone, Debug, PartialOrd, Ord, PartialEq, Eq)] @@ -37,6 +43,7 @@ pub struct Message { pub(crate) level: Level, pub(crate) message: String, pub(crate) line_col: Option, + pub(crate) code: Option, } /// Information about macro expansion. @@ -107,6 +114,7 @@ impl RustcMessage { level: self.level.parse().unwrap(), message: self.message, line_col: line.clone(), + code: self.code.map(|x| x.code), }; if let Some(line) = line.clone() { if messages.len() <= line.line_start.get() { diff --git a/src/status_emitter.rs b/src/status_emitter.rs index de27ac6b..c39ff3b1 100644 --- a/src/status_emitter.rs +++ b/src/status_emitter.rs @@ -11,8 +11,8 @@ use indicatif::{MultiProgress, ProgressBar, ProgressDrawTarget, ProgressStyle}; use spanned::Span; use crate::{ - github_actions, parser::Pattern, rustc_stderr::Message, Error, Errored, Errors, Format, TestOk, - TestResult, + github_actions, parser::Pattern, rustc_stderr::Level, Error, Errored, Errors, Format, Message, + TestOk, TestResult, }; use std::{ collections::HashMap, @@ -446,6 +446,23 @@ fn print_error(error: &Error, path: &Path) { path, ); } + Error::CodeNotFound { + code, + expected_line, + } => { + let line = match expected_line { + Some(line) => format!("on line {line}"), + None => format!("outside the testfile"), + }; + create_error( + format!("diagnostic code `{}` not found {line}", &**code), + &[( + &[("expected because of this pattern", Some(code.span()))], + code.line(), + )], + path, + ); + } Error::NoPatternsFound => { print_error_header("no error patterns found in fail test"); } @@ -488,10 +505,13 @@ fn print_error(error: &Error, path: &Path) { let msgs = msgs .iter() .map(|msg| { - ( - format!("{:?}: {}", msg.level, msg.message), - msg.line_col.clone(), - ) + let text = match (&msg.code, msg.level) { + (Some(code), Level::Error) => { + format!("Error[{code}]: {}", msg.message) + } + _ => format!("{:?}: {}", msg.level, msg.message), + }; + (text, msg.line_col.clone()) }) .collect::>(); // This will print a suitable error header. @@ -515,6 +535,7 @@ fn print_error(error: &Error, path: &Path) { level, message, line_col: _, + code: _, } in msgs { println!(" {level:?}: {message}") @@ -647,6 +668,10 @@ fn gha_error(error: &Error, test_path: &str, revision: &str) { github_actions::error(test_path, format!("Pattern not found{revision}")) .line(pattern.line()); } + Error::CodeNotFound { code, .. } => { + github_actions::error(test_path, format!("Diagnostic code not found{revision}")) + .line(code.line()); + } Error::NoPatternsFound => { github_actions::error( test_path, @@ -735,6 +760,7 @@ fn gha_error(error: &Error, test_path: &str, revision: &str) { level, message, line_col: _, + code: _, } in msgs { writeln!(err, "{level:?}: {message}").unwrap(); @@ -748,6 +774,7 @@ fn gha_error(error: &Error, test_path: &str, revision: &str) { level, message, line_col: _, + code: _, } in msgs { writeln!(err, "{level:?}: {message}").unwrap(); diff --git a/src/tests.rs b/src/tests.rs index bf931bdd..b58dd3cc 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -32,6 +32,7 @@ fn main() { message:"Undefined Behavior: type validation failed: encountered a dangling reference (address 0x10 is unallocated)".to_string(), level: Level::Error, line_col: None, + code: None, } ] ]; @@ -68,6 +69,7 @@ fn main() { message: "Undefined Behavior: type validation failed: encountered a dangling reference (address 0x10 is unallocated)".to_string(), level: Level::Error, line_col: None, + code: None, } ] ]; @@ -94,6 +96,7 @@ fn main() { message: "Undefined Behavior: type validation failed: encountered a dangling reference (address 0x10 is unallocated)".to_string(), level: Level::Error, line_col: None, + code: None, } ] ]; @@ -124,6 +127,7 @@ fn main() { message: "Undefined Behavior: type validation failed: encountered a dangling reference (address 0x10 is unallocated)".to_string(), level: Level::Note, line_col: None, + code: None, } ] ]; @@ -164,6 +168,7 @@ fn main() { message: "Undefined Behavior: type validation failed: encountered a dangling reference (address 0x10 is unallocated)".to_string(), level: Level::Error, line_col: None, + code: None, } ] ]; @@ -201,11 +206,13 @@ fn main() { message: "Undefined Behavior: type validation failed: encountered a dangling reference (address 0x10 is unallocated)".to_string(), level: Level::Error, line_col: None, + code: None, }, Message { message: "Undefined Behavior: type validation failed: encountered a dangling reference (address 0x10 is unallocated)".to_string(), level: Level::Error, line_col: None, + code: None, } ] ]; @@ -249,16 +256,19 @@ fn main() { message: "Undefined Behavior: type validation failed: encountered a dangling reference (address 0x10 is unallocated)".to_string(), level: Level::Error, line_col: None, + code: None, }, Message { message: "kaboom".to_string(), level: Level::Warn, line_col: None, + code: None, }, Message { message: "cake".to_string(), level: Level::Warn, line_col: None, + code: None, }, ], ]; @@ -281,6 +291,7 @@ fn main() { message, level: Level::Warn, line_col: _, + code: None, }] if message == "kaboom" => {} _ => panic!("{:#?}", msgs), } @@ -312,16 +323,19 @@ fn main() { message: "Undefined Behavior: type validation failed: encountered a dangling reference (address 0x10 is unallocated)".to_string(), level: Level::Error, line_col: None, + code: None, }, Message { message: "kaboom".to_string(), level: Level::Warn, line_col: None, + code: None, }, Message { message: "cake".to_string(), level: Level::Warn, line_col: None, + code: None, }, ], ]; @@ -340,3 +354,100 @@ fn main() { _ => panic!("{:#?}", errors), } } + +#[test] +fn find_code() { + let s = r" +fn main() { + let _x: i32 = 0u32; //~ E0308 +} + "; + let config = config(); + let comments = Comments::parse(s, config.comment_defaults.clone(), Path::new("")).unwrap(); + { + let messages = vec![ + vec![], + vec![], + vec![], + vec![Message { + message: "mismatched types".to_string(), + level: Level::Error, + line_col: None, + code: Some("E0308".into()), + }], + ]; + let mut errors = vec![]; + check_annotations( + messages, + vec![], + Path::new("moobar"), + &mut errors, + "", + &comments, + ) + .unwrap(); + match &errors[..] { + [] => {} + _ => panic!("{:#?}", errors), + } + } + + // different error code + { + let messages = vec![ + vec![], + vec![], + vec![], + vec![Message { + message: "mismatched types".to_string(), + level: Level::Error, + line_col: None, + code: Some("SomeError".into()), + }], + ]; + let mut errors = vec![]; + check_annotations( + messages, + vec![], + Path::new("moobar"), + &mut errors, + "", + &comments, + ) + .unwrap(); + match &errors[..] { + [Error::CodeNotFound { code, .. }, Error::ErrorsWithoutPattern { msgs, .. }] + if **code == "E0308" && code.line().get() == 3 && msgs.len() == 1 => {} + _ => panic!("{:#?}", errors), + } + } + + // warning instead of error + { + let messages = vec![ + vec![], + vec![], + vec![], + vec![Message { + message: "mismatched types".to_string(), + level: Level::Warn, + line_col: None, + code: Some("E0308".into()), + }], + ]; + let mut errors = vec![]; + check_annotations( + messages, + vec![], + Path::new("moobar"), + &mut errors, + "", + &comments, + ) + .unwrap(); + match &errors[..] { + [Error::CodeNotFound { code, .. }] if **code == "E0308" && code.line().get() == 3 => {} + _ => panic!("{:#?}", errors), + } + } +} diff --git a/tests/integrations/basic-fail/Cargo.stdout b/tests/integrations/basic-fail/Cargo.stdout index 7970e56a..3c974645 100644 --- a/tests/integrations/basic-fail/Cargo.stdout +++ b/tests/integrations/basic-fail/Cargo.stdout @@ -28,7 +28,7 @@ error: there were 1 unmatched diagnostics --> tests/actual_tests/bad_pattern.rs:4:9 | 4 | add("42", 3); - | ^^^^ Error: mismatched types + | ^^^^ Error[E0308]: mismatched types | full stderr: @@ -329,6 +329,7 @@ tests/actual_tests_bless/rustfix-fail-revisions.rs (revision `b`) ... FAILED tests/actual_tests_bless/rustfix-fail.rs ... FAILED tests/actual_tests_bless/unknown_revision.rs ... FAILED tests/actual_tests_bless/unknown_revision2.rs ... FAILED +tests/actual_tests_bless/wrong_diagnostic_code.rs ... FAILED FAILED TEST: tests/actual_tests_bless/aux_build_not_found.rs command: "$CMD" @@ -474,7 +475,7 @@ error: there were 1 unmatched diagnostics --> tests/actual_tests_bless/no_main_manual.rs:3:16 | 3 | pub fn foo() {} - | ^ Error: `main` function not found in crate `no_main_manual` + | ^ Error[E0601]: `main` function not found in crate `no_main_manual` | error: no error patterns found in fail test @@ -616,7 +617,7 @@ error: there were 1 unmatched diagnostics --> tests/actual_tests_bless/revisions_bad.rs:10:2 | 10 | } - | ^ Error: `main` function not found in crate `revisions_bad` + | ^ Error[E0601]: `main` function not found in crate `revisions_bad` | full stderr: @@ -755,6 +756,57 @@ full stderr: full stdout: + +FAILED TEST: tests/actual_tests_bless/wrong_diagnostic_code.rs +command: "rustc" "--error-format=json" "--extern" "basic_fail=$DIR/tests/integrations/basic-fail/../../../target/$TMP/$TRIPLE/debug/libbasic_fail.rlib" "--extern" "basic_fail=$DIR/tests/integrations/basic-fail/../../../target/$TMP/$TRIPLE/debug/libbasic_fail-$HASH.rmeta" "-L" "$DIR/tests/integrations/basic-fail/../../../target/$TMP/$TRIPLE/debug" "-L" "$DIR/tests/integrations/basic-fail/../../../target/$TMP/$TRIPLE/debug" "--out-dir" "$TMP "tests/actual_tests_bless/wrong_diagnostic_code.rs" "--edition" "2021" + +error: diagnostic code `should_be_dead_code` not found on line 3 + --> tests/actual_tests_bless/wrong_diagnostic_code.rs:4:10 + | +4 | //~^ should_be_dead_code + | ^^^^^^^^^^^^^^^^^^^ expected because of this pattern + | + +error: there were 1 unmatched diagnostics + --> tests/actual_tests_bless/wrong_diagnostic_code.rs:3:4 + | +3 | fn foo() -> i32 { + | ^^^ Error[dead_code]: function `foo` is never used + | + +full stderr: +error: unreachable expression + --> tests/actual_tests_bless/wrong_diagnostic_code.rs:6:5 + | +5 | panic!(); + | -------- any code following this expression is unreachable +6 | 0 + | ^ unreachable expression + | +note: the lint level is defined here + --> tests/actual_tests_bless/wrong_diagnostic_code.rs:1:20 + | +1 | #![deny(dead_code, unreachable_code)] + | ^^^^^^^^^^^^^^^^ + +error: function `foo` is never used + --> tests/actual_tests_bless/wrong_diagnostic_code.rs:3:4 + | +3 | fn foo() -> i32 { + | ^^^ + | +note: the lint level is defined here + --> tests/actual_tests_bless/wrong_diagnostic_code.rs:1:9 + | +1 | #![deny(dead_code, unreachable_code)] + | ^^^^^^^^^ + +error: aborting due to 2 previous errors + + +full stdout: + + FAILURES: tests/actual_tests_bless/aux_build_not_found.rs tests/actual_tests_bless/aux_proc_macro_misuse.rs @@ -776,8 +828,9 @@ FAILURES: tests/actual_tests_bless/rustfix-fail.rs tests/actual_tests_bless/unknown_revision.rs tests/actual_tests_bless/unknown_revision2.rs + tests/actual_tests_bless/wrong_diagnostic_code.rs -test result: FAIL. 20 failed; 14 passed; 3 ignored; +test result: FAIL. 21 failed; 14 passed; 3 ignored; Building dependencies ... ok tests/actual_tests_bless_yolo/revisions_bad.rs (revision `foo`) ... ok diff --git a/tests/integrations/basic-fail/tests/actual_tests_bless/wrong_diagnostic_code.rs b/tests/integrations/basic-fail/tests/actual_tests_bless/wrong_diagnostic_code.rs new file mode 100644 index 00000000..81958d46 --- /dev/null +++ b/tests/integrations/basic-fail/tests/actual_tests_bless/wrong_diagnostic_code.rs @@ -0,0 +1,9 @@ +#![deny(dead_code, unreachable_code)] + +fn foo() -> i32 { + //~^ should_be_dead_code + panic!(); + 0 //~ unreachable_code +} + +fn main() {} diff --git a/tests/integrations/basic-fail/tests/actual_tests_bless/wrong_diagnostic_code.stderr b/tests/integrations/basic-fail/tests/actual_tests_bless/wrong_diagnostic_code.stderr new file mode 100644 index 00000000..4e5a9065 --- /dev/null +++ b/tests/integrations/basic-fail/tests/actual_tests_bless/wrong_diagnostic_code.stderr @@ -0,0 +1,28 @@ +error: unreachable expression + --> tests/actual_tests_bless/wrong_diagnostic_code.rs:6:5 + | +5 | panic!(); + | -------- any code following this expression is unreachable +6 | 0 + | ^ unreachable expression + | +note: the lint level is defined here + --> tests/actual_tests_bless/wrong_diagnostic_code.rs:1:20 + | +1 | #![deny(dead_code, unreachable_code)] + | ^^^^^^^^^^^^^^^^ + +error: function `foo` is never used + --> tests/actual_tests_bless/wrong_diagnostic_code.rs:3:4 + | +3 | fn foo() -> i32 { + | ^^^ + | +note: the lint level is defined here + --> tests/actual_tests_bless/wrong_diagnostic_code.rs:1:9 + | +1 | #![deny(dead_code, unreachable_code)] + | ^^^^^^^^^ + +error: aborting due to 2 previous errors + diff --git a/tests/integrations/basic/Cargo.stdout b/tests/integrations/basic/Cargo.stdout index 10461098..5ca4da88 100644 --- a/tests/integrations/basic/Cargo.stdout +++ b/tests/integrations/basic/Cargo.stdout @@ -16,13 +16,14 @@ tests/actual_tests/aux_proc_macro.rs ... ok tests/actual_tests/executable.rs ... ok tests/actual_tests/foomp-rustfix.rs ... ok tests/actual_tests/foomp.rs ... ok +tests/actual_tests/match_diagnostic_code.rs ... ok tests/actual_tests/no_rustfix.rs ... ok tests/actual_tests/stdin.rs ... ok tests/actual_tests/unicode.rs ... ok tests/actual_tests/windows_paths.rs ... ok tests/actual_tests/subdir/aux_proc_macro.rs ... ok -test result: ok. 10 passed; +test result: ok. 11 passed; running 0 tests diff --git a/tests/integrations/basic/tests/actual_tests/match_diagnostic_code.fixed b/tests/integrations/basic/tests/actual_tests/match_diagnostic_code.fixed new file mode 100644 index 00000000..d26e6217 --- /dev/null +++ b/tests/integrations/basic/tests/actual_tests/match_diagnostic_code.fixed @@ -0,0 +1,3 @@ +fn main() { + let _x: i32 = 0i32; //~ E0308 +} diff --git a/tests/integrations/basic/tests/actual_tests/match_diagnostic_code.rs b/tests/integrations/basic/tests/actual_tests/match_diagnostic_code.rs new file mode 100644 index 00000000..8da2b3a8 --- /dev/null +++ b/tests/integrations/basic/tests/actual_tests/match_diagnostic_code.rs @@ -0,0 +1,3 @@ +fn main() { + let _x: i32 = 0u32; //~ E0308 +} diff --git a/tests/integrations/basic/tests/actual_tests/match_diagnostic_code.stderr b/tests/integrations/basic/tests/actual_tests/match_diagnostic_code.stderr new file mode 100644 index 00000000..035a3e92 --- /dev/null +++ b/tests/integrations/basic/tests/actual_tests/match_diagnostic_code.stderr @@ -0,0 +1,16 @@ +error[E0308]: mismatched types + --> tests/actual_tests/match_diagnostic_code.rs:2:19 + | +2 | let _x: i32 = 0u32; + | --- ^^^^ expected `i32`, found `u32` + | | + | expected due to this + | +help: change the type of the numeric literal from `u32` to `i32` + | +2 | let _x: i32 = 0i32; + | ~~~ + +error: aborting due to previous error + +For more information about this error, try `rustc --explain E0308`. From b69da28bab57ce492813089bf8fd637be305a6bb Mon Sep 17 00:00:00 2001 From: Jason Newcomb Date: Fri, 6 Oct 2023 02:55:55 -0400 Subject: [PATCH 2/2] Allow global prefix when matching diagnostic codes. --- CHANGELOG.md | 1 + src/lib.rs | 31 ++++++++++++++++------- src/parser.rs | 7 ++++++ src/tests.rs | 70 +++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 100 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8207a4be..961951a6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 * Started maintaining a changelog * `Config::comment_defaults` allows setting `//@` comments for all tests * `//~` comments can now specify just an error code or lint name, without any message. ERROR level is implied +* `Revisioned::diagnostic_code_prefix` allows stripping a prefix of diagnostic codes to avoid having to repeat `clippy::` in all messages ### Fixed diff --git a/src/lib.rs b/src/lib.rs index 1aad3558..13ee37ba 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -915,6 +915,7 @@ fn run_rustfix( edition, mode: OptWithLine::new(Mode::Pass, Span::default()), no_rustfix: OptWithLine::new((), Span::default()), + diagnostic_code_prefix: OptWithLine::new(String::new(), Span::default()), needs_asm_support: false, }, )) @@ -1071,12 +1072,19 @@ fn check_annotations( }); } } + let diagnostic_code_prefix = comments + .find_one_for_revision(revision, "diagnostic_code_prefix", |r| { + r.diagnostic_code_prefix.clone() + })? + .into_inner() + .map(|s| s.content) + .unwrap_or_default(); // The order on `Level` is such that `Error` is the highest level. // We will ensure that *all* diagnostics of level at least `lowest_annotation_level` // are matched. let mut lowest_annotation_level = Level::Error; - for &ErrorMatch { ref kind, line } in comments + 'err: for &ErrorMatch { ref kind, line } in comments .for_revision(revision) .flat_map(|r| r.error_matches.iter()) { @@ -1107,13 +1115,18 @@ fn check_annotations( } } ErrorMatchKind::Code(code) => { - let found = msgs.iter().position(|msg| { - msg.level == Level::Error - && msg.code.as_ref().is_some_and(|msg| *msg == **code) - }); - if let Some(found) = found { - msgs.remove(found); - continue; + for (i, msg) in msgs.iter().enumerate() { + if msg.level != Level::Error { + continue; + } + let Some(msg_code) = &msg.code else { continue }; + let Some(msg) = msg_code.strip_prefix(&diagnostic_code_prefix) else { + continue; + }; + if msg == **code { + msgs.remove(i); + continue 'err; + } } } } @@ -1125,7 +1138,7 @@ fn check_annotations( expected_line: Some(line), }, ErrorMatchKind::Code(code) => Error::CodeNotFound { - code: code.clone(), + code: Spanned::new(format!("{}{}", diagnostic_code_prefix, **code), code.span()), expected_line: Some(line), }, }); diff --git a/src/parser.rs b/src/parser.rs index 7c66ff67..3ed8569a 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -164,6 +164,9 @@ pub struct Revisioned { pub(crate) needs_asm_support: bool, /// Don't run [`rustfix`] for this test pub no_rustfix: OptWithLine<()>, + /// Prefix added to all diagnostic code matchers. Note this will make it impossible + /// match codes which do not contain this prefix. + pub diagnostic_code_prefix: OptWithLine, } #[derive(Debug)] @@ -330,6 +333,7 @@ impl Comments { mode, needs_asm_support, no_rustfix, + diagnostic_code_prefix, } = parser.comments.base(); if span.is_dummy() { *span = defaults.span; @@ -356,6 +360,9 @@ impl Comments { if no_rustfix.is_none() { *no_rustfix = defaults.no_rustfix; } + if diagnostic_code_prefix.is_none() { + *diagnostic_code_prefix = defaults.diagnostic_code_prefix; + } *needs_asm_support |= defaults.needs_asm_support; if parser.errors.is_empty() { diff --git a/src/tests.rs b/src/tests.rs index b58dd3cc..344d55c8 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -451,3 +451,73 @@ fn main() { } } } + +#[test] +fn find_code_with_prefix() { + let s = r" +fn main() { + let _x: i32 = 0u32; //~ E0308 +} + "; + let mut config = config(); + config.comment_defaults.base().diagnostic_code_prefix = + Spanned::dummy("prefix::".to_string()).into(); + let comments = Comments::parse(s, config.comment_defaults.clone(), Path::new("")).unwrap(); + { + let messages = vec![ + vec![], + vec![], + vec![], + vec![Message { + message: "mismatched types".to_string(), + level: Level::Error, + line_col: None, + code: Some("prefix::E0308".into()), + }], + ]; + let mut errors = vec![]; + check_annotations( + messages, + vec![], + Path::new("moobar"), + &mut errors, + "", + &comments, + ) + .unwrap(); + match &errors[..] { + [] => {} + _ => panic!("{:#?}", errors), + } + } + + // missing prefix + { + let messages = vec![ + vec![], + vec![], + vec![], + vec![Message { + message: "mismatched types".to_string(), + level: Level::Error, + line_col: None, + code: Some("E0308".into()), + }], + ]; + let mut errors = vec![]; + check_annotations( + messages, + vec![], + Path::new("moobar"), + &mut errors, + "", + &comments, + ) + .unwrap(); + match &errors[..] { + [Error::CodeNotFound { code, .. }, Error::ErrorsWithoutPattern { msgs, .. }] + if **code == "prefix::E0308" && code.line().get() == 3 && msgs.len() == 1 => {} + _ => panic!("{:#?}", errors), + } + } +}