Skip to content

Commit 8c7c151

Browse files
committed
Auto merge of #124706 - Zalathar:revision-checker, r=jieyouxu
Tidy check for test revisions that are mentioned but not declared If a `[revision]` name appears in a test header directive or error annotation, but isn't declared in the `//@ revisions:` header, that is almost always a mistake. In cases where a revision needs to be temporarily disabled, adding it to an `//@ unused-revision-names:` header will suppress these checks for that name. Adding the wildcard name `*` to the unused list will suppress these checks for the entire file. (None of the tests actually use `*`; it's just there because it was easy to add and could be handy as an escape hatch when dealing with other problems.) --- Most of the existing problems discovered by this check were fairly straightforward to fix (or ignore); the trickiest cases are in `borrowck` tests.
2 parents 6f7e00a + 14d56e8 commit 8c7c151

33 files changed

+211
-46
lines changed

src/tools/compiletest/src/header.rs

+2
Original file line numberDiff line numberDiff line change
@@ -932,6 +932,8 @@ const KNOWN_DIRECTIVE_NAMES: &[&str] = &[
932932
"test-mir-pass",
933933
"unset-exec-env",
934934
"unset-rustc-env",
935+
// Used by the tidy check `unknown_revision`.
936+
"unused-revision-names",
935937
// tidy-alphabetical-end
936938
];
937939

src/tools/tidy/src/iter_header.rs

+4-3
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ const COMMENT: &str = "//@";
33
/// A header line, like `//@name: value` consists of the prefix `//@` and the directive
44
/// `name: value`. It is also possibly revisioned, e.g. `//@[revision] name: value`.
55
pub(crate) struct HeaderLine<'ln> {
6+
pub(crate) line_number: usize,
67
pub(crate) revision: Option<&'ln str>,
78
pub(crate) directive: &'ln str,
89
}
@@ -11,7 +12,7 @@ pub(crate) struct HeaderLine<'ln> {
1112
///
1213
/// Adjusted from compiletest/src/header.rs.
1314
pub(crate) fn iter_header<'ln>(contents: &'ln str, it: &mut dyn FnMut(HeaderLine<'ln>)) {
14-
for ln in contents.lines() {
15+
for (line_number, ln) in (1..).zip(contents.lines()) {
1516
let ln = ln.trim();
1617

1718
// We're left with potentially `[rev]name: value`.
@@ -24,9 +25,9 @@ pub(crate) fn iter_header<'ln>(contents: &'ln str, it: &mut dyn FnMut(HeaderLine
2425
panic!("malformed revision directive: expected `//@[rev]`, found `{ln}`");
2526
};
2627
// We trimmed off the `[rev]` portion, left with `name: value`.
27-
it(HeaderLine { revision: Some(revision), directive: remainder.trim() });
28+
it(HeaderLine { line_number, revision: Some(revision), directive: remainder.trim() });
2829
} else {
29-
it(HeaderLine { revision: None, directive: remainder.trim() });
30+
it(HeaderLine { line_number, revision: None, directive: remainder.trim() });
3031
}
3132
}
3233
}

src/tools/tidy/src/lib.rs

+1
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@ pub mod tests_placement;
8787
pub mod tests_revision_unpaired_stdout_stderr;
8888
pub mod ui_tests;
8989
pub mod unit_tests;
90+
pub mod unknown_revision;
9091
pub mod unstable_book;
9192
pub mod walk;
9293
pub mod x_version;

src/tools/tidy/src/main.rs

+1
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,7 @@ fn main() {
110110
check!(rustdoc_gui_tests, &tests_path);
111111
check!(rustdoc_css_themes, &librustdoc_path);
112112
check!(known_bug, &crashes_path);
113+
check!(unknown_revision, &tests_path);
113114

114115
// Checks that only make sense for the compiler.
115116
check!(error_codes, &root_path, &[&compiler_path, &librustdoc_path], verbose);

src/tools/tidy/src/target_specific_tests.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ pub fn check(path: &Path, bad: &mut bool) {
2020
crate::walk::walk(path, |path, _is_dir| filter_not_rust(path), &mut |entry, content| {
2121
let file = entry.path().display();
2222
let mut header_map = BTreeMap::new();
23-
iter_header(content, &mut |HeaderLine { revision, directive }| {
23+
iter_header(content, &mut |HeaderLine { revision, directive, .. }| {
2424
if let Some(value) = directive.strip_prefix(LLVM_COMPONENTS_HEADER) {
2525
let info = header_map.entry(revision).or_insert(RevisionInfo::default());
2626
let comp_vec = info.llvm_components.get_or_insert(Vec::new());

src/tools/tidy/src/tests_revision_unpaired_stdout_stderr.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ pub fn check(tests_path: impl AsRef<Path>, bad: &mut bool) {
6161
let contents = std::fs::read_to_string(test).unwrap();
6262

6363
// Collect directives.
64-
iter_header(&contents, &mut |HeaderLine { revision, directive }| {
64+
iter_header(&contents, &mut |HeaderLine { revision, directive, .. }| {
6565
// We're trying to *find* `//@ revision: xxx` directives themselves, not revisioned
6666
// directives.
6767
if revision.is_some() {
+114
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
//! Checks that test revision names appearing in header directives and error
2+
//! annotations have actually been declared in `revisions`.
3+
4+
// FIXME(jieyouxu) Ideally these checks would be integrated into compiletest's
5+
// own directive and revision handling, but for now they've been split out as a
6+
// separate `tidy` check to avoid making compiletest even messier.
7+
8+
use std::collections::{BTreeSet, HashMap, HashSet};
9+
use std::path::Path;
10+
use std::sync::OnceLock;
11+
12+
use ignore::DirEntry;
13+
use regex::Regex;
14+
15+
use crate::iter_header::{iter_header, HeaderLine};
16+
use crate::walk::{filter_dirs, filter_not_rust, walk};
17+
18+
pub fn check(tests_path: impl AsRef<Path>, bad: &mut bool) {
19+
walk(
20+
tests_path.as_ref(),
21+
|path, is_dir| {
22+
filter_dirs(path) || filter_not_rust(path) || {
23+
// Auxiliary source files for incremental tests can refer to revisions
24+
// declared by the main file, which this check doesn't handle.
25+
is_dir && path.file_name().is_some_and(|name| name == "auxiliary")
26+
}
27+
},
28+
&mut |entry, contents| visit_test_file(entry, contents, bad),
29+
);
30+
}
31+
32+
fn visit_test_file(entry: &DirEntry, contents: &str, bad: &mut bool) {
33+
let mut revisions = HashSet::new();
34+
let mut unused_revision_names = HashSet::new();
35+
36+
// Maps each mentioned revision to the first line it was mentioned on.
37+
let mut mentioned_revisions = HashMap::<&str, usize>::new();
38+
let mut add_mentioned_revision = |line_number: usize, revision| {
39+
let first_line = mentioned_revisions.entry(revision).or_insert(line_number);
40+
*first_line = (*first_line).min(line_number);
41+
};
42+
43+
// Scan all `//@` headers to find declared revisions and mentioned revisions.
44+
iter_header(contents, &mut |HeaderLine { line_number, revision, directive }| {
45+
if let Some(revs) = directive.strip_prefix("revisions:") {
46+
revisions.extend(revs.split_whitespace());
47+
} else if let Some(revs) = directive.strip_prefix("unused-revision-names:") {
48+
unused_revision_names.extend(revs.split_whitespace());
49+
}
50+
51+
if let Some(revision) = revision {
52+
add_mentioned_revision(line_number, revision);
53+
}
54+
});
55+
56+
// If a wildcard appears in `unused-revision-names`, skip all revision name
57+
// checking for this file.
58+
if unused_revision_names.contains(&"*") {
59+
return;
60+
}
61+
62+
// Scan all `//[rev]~` error annotations to find mentioned revisions.
63+
for_each_error_annotation_revision(contents, &mut |ErrorAnnRev { line_number, revision }| {
64+
add_mentioned_revision(line_number, revision);
65+
});
66+
67+
let path = entry.path().display();
68+
69+
// Fail if any revision names appear in both places, since that's probably a mistake.
70+
for rev in revisions.intersection(&unused_revision_names).copied().collect::<BTreeSet<_>>() {
71+
tidy_error!(
72+
bad,
73+
"revision name [{rev}] appears in both `revisions` and `unused-revision-names` in {path}"
74+
);
75+
}
76+
77+
// Compute the set of revisions that were mentioned but not declared,
78+
// sorted by the first line number they appear on.
79+
let mut bad_revisions = mentioned_revisions
80+
.into_iter()
81+
.filter(|(rev, _)| !revisions.contains(rev) && !unused_revision_names.contains(rev))
82+
.map(|(rev, line_number)| (line_number, rev))
83+
.collect::<Vec<_>>();
84+
bad_revisions.sort();
85+
86+
for (line_number, rev) in bad_revisions {
87+
tidy_error!(bad, "unknown revision [{rev}] at {path}:{line_number}");
88+
}
89+
}
90+
91+
struct ErrorAnnRev<'a> {
92+
line_number: usize,
93+
revision: &'a str,
94+
}
95+
96+
fn for_each_error_annotation_revision<'a>(
97+
contents: &'a str,
98+
callback: &mut dyn FnMut(ErrorAnnRev<'a>),
99+
) {
100+
let error_regex = {
101+
// Simplified from the regex used by `parse_expected` in `src/tools/compiletest/src/errors.rs`,
102+
// because we only care about extracting revision names.
103+
static RE: OnceLock<Regex> = OnceLock::new();
104+
RE.get_or_init(|| Regex::new(r"//\[(?<revs>[^]]*)\]~").unwrap())
105+
};
106+
107+
for (line_number, line) in (1..).zip(contents.lines()) {
108+
let Some(captures) = error_regex.captures(line) else { continue };
109+
110+
for revision in captures.name("revs").unwrap().as_str().split(',') {
111+
callback(ErrorAnnRev { line_number, revision });
112+
}
113+
}
114+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
error: fatal error triggered by #[rustc_error]
2+
--> $DIR/bound-lifetime-constrained.rs:48:1
3+
|
4+
LL | fn main() { }
5+
| ^^^^^^^^^
6+
7+
error: aborting due to 1 previous error
8+

tests/ui/associated-types/bound-lifetime-constrained.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
//@ revisions: func object clause
1+
//@ revisions: func object clause ok
22

33
#![allow(dead_code)]
44
#![feature(rustc_attrs)]

tests/ui/borrowck/two-phase-activation-sharing-interference.nll_target.stderr

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
error[E0502]: cannot borrow `x` as immutable because it is also borrowed as mutable
2-
--> $DIR/two-phase-activation-sharing-interference.rs:28:15
2+
--> $DIR/two-phase-activation-sharing-interference.rs:29:15
33
|
44
LL | let y = &mut x;
55
| ------ mutable borrow occurs here
@@ -10,7 +10,7 @@ LL | *y += 1;
1010
| ------- mutable borrow later used here
1111

1212
error[E0502]: cannot borrow `x` as immutable because it is also borrowed as mutable
13-
--> $DIR/two-phase-activation-sharing-interference.rs:36:13
13+
--> $DIR/two-phase-activation-sharing-interference.rs:37:13
1414
|
1515
LL | let y = &mut x;
1616
| ------ mutable borrow occurs here
@@ -32,7 +32,7 @@ LL | *y += 1;
3232
| ------- mutable borrow later used here
3333

3434
error[E0502]: cannot borrow `x` as immutable because it is also borrowed as mutable
35-
--> $DIR/two-phase-activation-sharing-interference.rs:58:14
35+
--> $DIR/two-phase-activation-sharing-interference.rs:56:14
3636
|
3737
LL | let y = &mut x;
3838
| ------ mutable borrow occurs here

tests/ui/borrowck/two-phase-activation-sharing-interference.rs

+2-6
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
//@ revisions: nll_target
22

33
// The following revisions are disabled due to missing support from two-phase beyond autorefs
4+
//@ unused-revision-names: nll_beyond
45
//@[nll_beyond] compile-flags: -Z two-phase-beyond-autoref
56

67
// This is an important corner case pointed out by Niko: one is
@@ -36,8 +37,7 @@ fn not_ok() {
3637
let z = &x;
3738
//[nll_target]~^ ERROR cannot borrow `x` as immutable because it is also borrowed as mutable
3839
*y += 1;
39-
//[lxl_beyond]~^ ERROR cannot borrow `x` as mutable because it is also borrowed as immutable
40-
//[nll_beyond]~^^ ERROR cannot borrow `x` as mutable because it is also borrowed as immutable
40+
//[nll_beyond]~^ ERROR cannot borrow `x` as mutable because it is also borrowed as immutable
4141
read(z);
4242
}
4343

@@ -48,8 +48,6 @@ fn should_be_ok_with_nll() {
4848
//[nll_target]~^ ERROR cannot borrow `x` as immutable because it is also borrowed as mutable
4949
read(z);
5050
*y += 1;
51-
//[lxl_beyond]~^ ERROR cannot borrow `x` as mutable because it is also borrowed as immutable
52-
// (okay with (generalized) nll today)
5351
}
5452

5553
fn should_also_eventually_be_ok_with_nll() {
@@ -58,8 +56,6 @@ fn should_also_eventually_be_ok_with_nll() {
5856
let _z = &x;
5957
//[nll_target]~^ ERROR cannot borrow `x` as immutable because it is also borrowed as mutable
6058
*y += 1;
61-
//[lxl_beyond]~^ ERROR cannot borrow `x` as mutable because it is also borrowed as immutable
62-
// (okay with (generalized) nll today)
6359
}
6460

6561
fn main() { }

tests/ui/borrowck/two-phase-allow-access-during-reservation.nll_target.stderr

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
error[E0503]: cannot use `i` because it was mutably borrowed
2-
--> $DIR/two-phase-allow-access-during-reservation.rs:26:19
2+
--> $DIR/two-phase-allow-access-during-reservation.rs:27:19
33
|
44
LL | /*1*/ let p = &mut i; // (reservation of `i` starts here)
55
| ------ `i` is borrowed here
@@ -11,7 +11,7 @@ LL | /*3*/ *p += 1; // (mutable borrow of `i` starts here, since `p`
1111
| ------- borrow later used here
1212

1313
error[E0503]: cannot use `i` because it was mutably borrowed
14-
--> $DIR/two-phase-allow-access-during-reservation.rs:31:19
14+
--> $DIR/two-phase-allow-access-during-reservation.rs:32:19
1515
|
1616
LL | /*1*/ let p = &mut i; // (reservation of `i` starts here)
1717
| ------ `i` is borrowed here

tests/ui/borrowck/two-phase-allow-access-during-reservation.rs

+1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
//@ revisions: nll_target
22

33
// The following revisions are disabled due to missing support for two_phase_beyond_autoref
4+
//@ unused-revision-names: nll_beyond
45
//@[nll_beyond] compile-flags: -Z two_phase_beyond_autoref
56

67
// This is the second counter-example from Niko's blog post

tests/ui/borrowck/two-phase-nonrecv-autoref.base.stderr

+11-11
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
error[E0499]: cannot borrow `*f` as mutable more than once at a time
2-
--> $DIR/two-phase-nonrecv-autoref.rs:50:11
2+
--> $DIR/two-phase-nonrecv-autoref.rs:51:11
33
|
44
LL | f(f(10));
55
| - ^ second mutable borrow occurs here
@@ -8,7 +8,7 @@ LL | f(f(10));
88
| first borrow later used by call
99

1010
error[E0382]: use of moved value: `f`
11-
--> $DIR/two-phase-nonrecv-autoref.rs:57:11
11+
--> $DIR/two-phase-nonrecv-autoref.rs:58:11
1212
|
1313
LL | fn twice_ten_so<F: FnOnce(i32) -> i32>(f: Box<F>) {
1414
| - move occurs because `f` has type `Box<F>`, which does not implement the `Copy` trait
@@ -18,7 +18,7 @@ LL | f(f(10));
1818
| value moved here
1919

2020
error[E0499]: cannot borrow `*f` as mutable more than once at a time
21-
--> $DIR/two-phase-nonrecv-autoref.rs:62:11
21+
--> $DIR/two-phase-nonrecv-autoref.rs:63:11
2222
|
2323
LL | f(f(10));
2424
| - ^ second mutable borrow occurs here
@@ -27,7 +27,7 @@ LL | f(f(10));
2727
| first borrow later used by call
2828

2929
error[E0382]: use of moved value: `f`
30-
--> $DIR/two-phase-nonrecv-autoref.rs:69:11
30+
--> $DIR/two-phase-nonrecv-autoref.rs:70:11
3131
|
3232
LL | fn twice_ten_oo(f: Box<dyn FnOnce(i32) -> i32>) {
3333
| - move occurs because `f` has type `Box<dyn FnOnce(i32) -> i32>`, which does not implement the `Copy` trait
@@ -37,7 +37,7 @@ LL | f(f(10));
3737
| value moved here
3838

3939
error[E0502]: cannot borrow `a` as immutable because it is also borrowed as mutable
40-
--> $DIR/two-phase-nonrecv-autoref.rs:107:27
40+
--> $DIR/two-phase-nonrecv-autoref.rs:108:27
4141
|
4242
LL | double_access(&mut a, &a);
4343
| ------------- ------ ^^ immutable borrow occurs here
@@ -46,7 +46,7 @@ LL | double_access(&mut a, &a);
4646
| mutable borrow later used by call
4747

4848
error[E0502]: cannot borrow `i` as immutable because it is also borrowed as mutable
49-
--> $DIR/two-phase-nonrecv-autoref.rs:132:7
49+
--> $DIR/two-phase-nonrecv-autoref.rs:133:7
5050
|
5151
LL | i[i[3]] = 4;
5252
| --^----
@@ -56,18 +56,18 @@ LL | i[i[3]] = 4;
5656
| mutable borrow occurs here
5757
|
5858
help: try adding a local storing this...
59-
--> $DIR/two-phase-nonrecv-autoref.rs:132:8
59+
--> $DIR/two-phase-nonrecv-autoref.rs:133:8
6060
|
6161
LL | i[i[3]] = 4;
6262
| ^^^
6363
help: ...and then using that local here
64-
--> $DIR/two-phase-nonrecv-autoref.rs:132:6
64+
--> $DIR/two-phase-nonrecv-autoref.rs:133:6
6565
|
6666
LL | i[i[3]] = 4;
6767
| ^^^^^^
6868

6969
error[E0502]: cannot borrow `i` as immutable because it is also borrowed as mutable
70-
--> $DIR/two-phase-nonrecv-autoref.rs:138:7
70+
--> $DIR/two-phase-nonrecv-autoref.rs:139:7
7171
|
7272
LL | i[i[3]] = i[4];
7373
| --^----
@@ -77,12 +77,12 @@ LL | i[i[3]] = i[4];
7777
| mutable borrow occurs here
7878
|
7979
help: try adding a local storing this...
80-
--> $DIR/two-phase-nonrecv-autoref.rs:138:8
80+
--> $DIR/two-phase-nonrecv-autoref.rs:139:8
8181
|
8282
LL | i[i[3]] = i[4];
8383
| ^^^
8484
help: ...and then using that local here
85-
--> $DIR/two-phase-nonrecv-autoref.rs:138:6
85+
--> $DIR/two-phase-nonrecv-autoref.rs:139:6
8686
|
8787
LL | i[i[3]] = i[4];
8888
| ^^^^^^

tests/ui/borrowck/two-phase-nonrecv-autoref.rs

+1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
//@ revisions: base
22

3+
//@ unused-revision-names: g2p
34
//@[g2p]compile-flags: -Z two-phase-beyond-autoref
45
// the above revision is disabled until two-phase-beyond-autoref support is better
56

tests/ui/borrowck/two-phase-reservation-sharing-interference.nll_target.stderr

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
error[E0502]: cannot borrow `vec` as mutable because it is also borrowed as immutable
2-
--> $DIR/two-phase-reservation-sharing-interference.rs:32:17
2+
--> $DIR/two-phase-reservation-sharing-interference.rs:33:17
33
|
44
LL | let shared = &vec;
55
| ---- immutable borrow occurs here

tests/ui/borrowck/two-phase-reservation-sharing-interference.rs

+1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
//@ revisions: nll_target
22

33
// The nll_beyond revision is disabled due to missing support from two-phase beyond autorefs
4+
//@ unused-revision-names: nll_beyond
45
//@[nll_beyond]compile-flags: -Z two-phase-beyond-autoref
56
//@[nll_beyond]should-fail
67

0 commit comments

Comments
 (0)