Skip to content

Commit 55fd13b

Browse files
authored
Rollup merge of #86137 - GuillaumeGomez:error-code-cleanup, r=Mark-Simulacrum
Error code cleanup and enforce checks Fixes #86097. It now checks if an error code is unused, and if so, will report an error if the error code wasn't commented out in the `error_codes.rs` file. It also checks that the constant used in the tidy check is up-to-date. r? `@Mark-Simulacrum`
2 parents f1e691d + 0ab9d01 commit 55fd13b

File tree

2 files changed

+97
-26
lines changed

2 files changed

+97
-26
lines changed

compiler/rustc_error_codes/src/error_codes.rs

+4-5
Original file line numberDiff line numberDiff line change
@@ -609,7 +609,7 @@ E0783: include_str!("./error_codes/E0783.md"),
609609
// E0540, // multiple rustc_deprecated attributes
610610
E0544, // multiple stability levels
611611
// E0548, // replaced with a generic attribute input check
612-
E0553, // multiple rustc_const_unstable attributes
612+
// E0553, // multiple rustc_const_unstable attributes
613613
// E0555, // replaced with a generic attribute input check
614614
// E0558, // replaced with a generic attribute input check
615615
// E0563, // cannot determine a type for this `impl Trait` removed in 6383de15
@@ -620,10 +620,9 @@ E0783: include_str!("./error_codes/E0783.md"),
620620
// E0612, // merged into E0609
621621
// E0613, // Removed (merged with E0609)
622622
E0625, // thread-local statics cannot be accessed at compile-time
623-
E0629, // missing 'feature' (rustc_const_unstable)
624-
// rustc_const_unstable attribute must be paired with stable/unstable
625-
// attribute
626-
E0630,
623+
// E0629, // missing 'feature' (rustc_const_unstable)
624+
// E0630, // rustc_const_unstable attribute must be paired with stable/unstable
625+
// attribute
627626
E0632, // cannot provide explicit generic arguments when `impl Trait` is
628627
// used in argument position
629628
E0640, // infer outlives requirements

src/tools/tidy/src/error_codes_check.rs

+93-21
Original file line numberDiff line numberDiff line change
@@ -6,20 +6,33 @@ use std::ffi::OsStr;
66
use std::fs::read_to_string;
77
use std::path::Path;
88

9+
use regex::Regex;
10+
911
// A few of those error codes can't be tested but all the others can and *should* be tested!
1012
const EXEMPTED_FROM_TEST: &[&str] = &[
11-
"E0227", "E0279", "E0280", "E0313", "E0314", "E0315", "E0377", "E0461", "E0462", "E0464",
12-
"E0465", "E0473", "E0474", "E0475", "E0476", "E0479", "E0480", "E0481", "E0482", "E0483",
13-
"E0484", "E0485", "E0486", "E0487", "E0488", "E0489", "E0514", "E0519", "E0523", "E0553",
14-
"E0554", "E0570", "E0629", "E0630", "E0640", "E0717", "E0729",
13+
"E0227", "E0279", "E0280", "E0313", "E0377", "E0461", "E0462", "E0464", "E0465", "E0476",
14+
"E0482", "E0514", "E0519", "E0523", "E0554", "E0570", "E0640", "E0717", "E0729",
1515
];
1616

1717
// Some error codes don't have any tests apparently...
1818
const IGNORE_EXPLANATION_CHECK: &[&str] = &["E0570", "E0601", "E0602", "E0729"];
1919

20+
// If the file path contains any of these, we don't want to try to extract error codes from it.
21+
//
22+
// We need to declare each path in the windows version (with backslash).
23+
const PATHS_TO_IGNORE_FOR_EXTRACTION: &[&str] =
24+
&["src/test/", "src\\test\\", "src/doc/", "src\\doc\\", "src/tools/", "src\\tools\\"];
25+
26+
#[derive(Default, Debug)]
27+
struct ErrorCodeStatus {
28+
has_test: bool,
29+
has_explanation: bool,
30+
is_used: bool,
31+
}
32+
2033
fn check_error_code_explanation(
2134
f: &str,
22-
error_codes: &mut HashMap<String, bool>,
35+
error_codes: &mut HashMap<String, ErrorCodeStatus>,
2336
err_code: String,
2437
) -> bool {
2538
let mut invalid_compile_fail_format = false;
@@ -30,15 +43,15 @@ fn check_error_code_explanation(
3043
if s.starts_with("```") {
3144
if s.contains("compile_fail") && s.contains('E') {
3245
if !found_error_code {
33-
error_codes.insert(err_code.clone(), true);
46+
error_codes.get_mut(&err_code).map(|x| x.has_test = true);
3447
found_error_code = true;
3548
}
3649
} else if s.contains("compile-fail") {
3750
invalid_compile_fail_format = true;
3851
}
3952
} else if s.starts_with("#### Note: this error code is no longer emitted by the compiler") {
4053
if !found_error_code {
41-
error_codes.get_mut(&err_code).map(|x| *x = true);
54+
error_codes.get_mut(&err_code).map(|x| x.has_test = true);
4255
found_error_code = true;
4356
}
4457
}
@@ -77,7 +90,7 @@ macro_rules! some_or_continue {
7790

7891
fn extract_error_codes(
7992
f: &str,
80-
error_codes: &mut HashMap<String, bool>,
93+
error_codes: &mut HashMap<String, ErrorCodeStatus>,
8194
path: &Path,
8295
errors: &mut Vec<String>,
8396
) {
@@ -90,15 +103,16 @@ fn extract_error_codes(
90103
.split_once(':')
91104
.expect(
92105
format!(
93-
"Expected a line with the format `E0xxx: include_str!(\"..\")`, but got {} without a `:` delimiter",
106+
"Expected a line with the format `E0xxx: include_str!(\"..\")`, but got {} \
107+
without a `:` delimiter",
94108
s,
95-
).as_str()
109+
)
110+
.as_str(),
96111
)
97112
.0
98113
.to_owned();
99-
if !error_codes.contains_key(&err_code) {
100-
error_codes.insert(err_code.clone(), false);
101-
}
114+
error_codes.entry(err_code.clone()).or_default().has_explanation = true;
115+
102116
// Now we extract the tests from the markdown file!
103117
let md_file_name = match s.split_once("include_str!(\"") {
104118
None => continue,
@@ -145,15 +159,15 @@ fn extract_error_codes(
145159
.to_string();
146160
if !error_codes.contains_key(&err_code) {
147161
// this check should *never* fail!
148-
error_codes.insert(err_code, false);
162+
error_codes.insert(err_code, ErrorCodeStatus::default());
149163
}
150164
} else if s == ";" {
151165
reached_no_explanation = true;
152166
}
153167
}
154168
}
155169

156-
fn extract_error_codes_from_tests(f: &str, error_codes: &mut HashMap<String, bool>) {
170+
fn extract_error_codes_from_tests(f: &str, error_codes: &mut HashMap<String, ErrorCodeStatus>) {
157171
for line in f.lines() {
158172
let s = line.trim();
159173
if s.starts_with("error[E") || s.starts_with("warning[E") {
@@ -164,8 +178,24 @@ fn extract_error_codes_from_tests(f: &str, error_codes: &mut HashMap<String, boo
164178
Some((_, err_code)) => err_code,
165179
},
166180
};
167-
let nb = error_codes.entry(err_code.to_owned()).or_insert(false);
168-
*nb = true;
181+
error_codes.entry(err_code.to_owned()).or_default().has_test = true;
182+
}
183+
}
184+
}
185+
186+
fn extract_error_codes_from_source(
187+
f: &str,
188+
error_codes: &mut HashMap<String, ErrorCodeStatus>,
189+
regex: &Regex,
190+
) {
191+
for line in f.lines() {
192+
if line.trim_start().starts_with("//") {
193+
continue;
194+
}
195+
for cap in regex.captures_iter(line) {
196+
if let Some(error_code) = cap.get(1) {
197+
error_codes.entry(error_code.as_str().to_owned()).or_default().is_used = true;
198+
}
169199
}
170200
}
171201
}
@@ -174,8 +204,17 @@ pub fn check(paths: &[&Path], bad: &mut bool) {
174204
let mut errors = Vec::new();
175205
let mut found_explanations = 0;
176206
let mut found_tests = 0;
207+
let mut error_codes: HashMap<String, ErrorCodeStatus> = HashMap::new();
208+
// We want error codes which match the following cases:
209+
//
210+
// * foo(a, E0111, a)
211+
// * foo(a, E0111)
212+
// * foo(E0111, a)
213+
// * #[error = "E0111"]
214+
let regex = Regex::new(r#"[(,"\s](E\d{4})[,)"]"#).unwrap();
215+
177216
println!("Checking which error codes lack tests...");
178-
let mut error_codes: HashMap<String, bool> = HashMap::new();
217+
179218
for path in paths {
180219
super::walk(path, &mut |path| super::filter_dirs(path), &mut |entry, contents| {
181220
let file_name = entry.file_name();
@@ -185,6 +224,11 @@ pub fn check(paths: &[&Path], bad: &mut bool) {
185224
} else if entry.path().extension() == Some(OsStr::new("stderr")) {
186225
extract_error_codes_from_tests(contents, &mut error_codes);
187226
found_tests += 1;
227+
} else if entry.path().extension() == Some(OsStr::new("rs")) {
228+
let path = entry.path().to_string_lossy();
229+
if PATHS_TO_IGNORE_FOR_EXTRACTION.iter().all(|c| !path.contains(c)) {
230+
extract_error_codes_from_source(contents, &mut error_codes, &regex);
231+
}
188232
}
189233
});
190234
}
@@ -199,15 +243,43 @@ pub fn check(paths: &[&Path], bad: &mut bool) {
199243
if errors.is_empty() {
200244
println!("Found {} error codes", error_codes.len());
201245

202-
for (err_code, nb) in &error_codes {
203-
if !*nb && !EXEMPTED_FROM_TEST.contains(&err_code.as_str()) {
246+
for (err_code, error_status) in &error_codes {
247+
if !error_status.has_test && !EXEMPTED_FROM_TEST.contains(&err_code.as_str()) {
204248
errors.push(format!("Error code {} needs to have at least one UI test!", err_code));
205-
} else if *nb && EXEMPTED_FROM_TEST.contains(&err_code.as_str()) {
249+
} else if error_status.has_test && EXEMPTED_FROM_TEST.contains(&err_code.as_str()) {
206250
errors.push(format!(
207251
"Error code {} has a UI test, it shouldn't be listed into EXEMPTED_FROM_TEST!",
208252
err_code
209253
));
210254
}
255+
if !error_status.is_used && !error_status.has_explanation {
256+
errors.push(format!(
257+
"Error code {} isn't used and doesn't have an error explanation, it should be \
258+
commented in error_codes.rs file",
259+
err_code
260+
));
261+
}
262+
}
263+
}
264+
if errors.is_empty() {
265+
// Checking if local constants need to be cleaned.
266+
for err_code in EXEMPTED_FROM_TEST {
267+
match error_codes.get(err_code.to_owned()) {
268+
Some(status) => {
269+
if status.has_test {
270+
errors.push(format!(
271+
"{} error code has a test and therefore should be \
272+
removed from the `EXEMPTED_FROM_TEST` constant",
273+
err_code
274+
));
275+
}
276+
}
277+
None => errors.push(format!(
278+
"{} error code isn't used anymore and therefore should be removed \
279+
from `EXEMPTED_FROM_TEST` constant",
280+
err_code
281+
)),
282+
}
211283
}
212284
}
213285
errors.sort();

0 commit comments

Comments
 (0)