Skip to content

Commit 2a46928

Browse files
authored
Rollup merge of rust-lang#146166 - ferrocene:pvdrz/edition-range, r=fmease,jieyouxu
Implement range support in `//@ edition` First step to solve rust-lang#145364
2 parents 1e1a394 + 3dcbda0 commit 2a46928

File tree

6 files changed

+282
-14
lines changed

6 files changed

+282
-14
lines changed

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

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -262,6 +262,20 @@ Consider writing the test as a proper incremental test instead.
262262

263263
</div>
264264

265+
#### The edition directive
266+
267+
The `//@ edition` directive can take an exact edition, a bounded half-open range of editions or a left-bounded half-open range of editions, this affects which edition is used by `./x test` to run the test. For example:
268+
269+
- A test with the `//@ edition: 2018` directive will only run under the 2018 edition.
270+
- A test with the `//@ edition: 2015..2021` directive can be run under both the 2015 and 2018 editions. However, CI will only run the test with the lowest edition possible (2015 in this case).
271+
- A test with the `//@ edition: 2018..` directive will run under any edition greater or equal than 2018. However, CI will only run the test with the lowest edition possible (2018 in this case).
272+
273+
You can also force `./x test` to use a specific edition by passing the `-- --edition=` argument. However, tests with the `//@ edition` directive will clamp the value passed to the argument. For example, if we run `./x test -- --edition=2015`:
274+
275+
- A test with the `//@ edition: 2018` will run with the 2018 edition.
276+
- A test with the `//@ edition: 2015..2021` will be run with the 2015 edition.
277+
- A test with the `//@ edition: 2018..` will run with the 2018 edition.
278+
265279
### Rustdoc
266280

267281
| Directive | Explanation | Supported test suites | Possible values |

src/tools/compiletest/src/common.rs

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ use build_helper::git::GitConfig;
77
use camino::{Utf8Path, Utf8PathBuf};
88
use semver::Version;
99

10+
use crate::edition::Edition;
1011
use crate::executor::ColorConfig;
1112
use crate::fatal;
1213
use crate::util::{Utf8PathBufExt, add_dylib_path, string_enum};
@@ -612,10 +613,7 @@ pub struct Config {
612613
pub git_hash: bool,
613614

614615
/// The default Rust edition.
615-
///
616-
/// FIXME: perform stronger validation for this. There are editions that *definitely* exists,
617-
/// but there might also be "future" edition.
618-
pub edition: Option<String>,
616+
pub edition: Option<Edition>,
619617

620618
// Configuration for various run-make tests frobbing things like C compilers or querying about
621619
// various LLVM component information.

src/tools/compiletest/src/directives.rs

Lines changed: 90 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,11 @@ use crate::directives::directive_names::{
1616
KNOWN_DIRECTIVE_NAMES, KNOWN_HTMLDOCCK_DIRECTIVE_NAMES, KNOWN_JSONDOCCK_DIRECTIVE_NAMES,
1717
};
1818
use crate::directives::needs::CachedNeedsConditions;
19+
use crate::edition::{Edition, parse_edition};
1920
use crate::errors::ErrorKind;
2021
use crate::executor::{CollectedTestDesc, ShouldPanic};
21-
use crate::help;
2222
use crate::util::static_regex;
23+
use crate::{fatal, help};
2324

2425
pub(crate) mod auxiliary;
2526
mod cfg;
@@ -436,10 +437,13 @@ impl TestProps {
436437
panic!("`compiler-flags` directive should be spelled `compile-flags`");
437438
}
438439

439-
if let Some(edition) = config.parse_edition(ln, testfile, line_number) {
440+
if let Some(range) = parse_edition_range(config, ln, testfile, line_number) {
440441
// The edition is added at the start, since flags from //@compile-flags must
441442
// be passed to rustc last.
442-
self.compile_flags.insert(0, format!("--edition={}", edition.trim()));
443+
self.compile_flags.insert(
444+
0,
445+
format!("--edition={}", range.edition_to_test(config.edition)),
446+
);
443447
has_edition = true;
444448
}
445449

@@ -1144,10 +1148,6 @@ impl Config {
11441148
}
11451149
}
11461150

1147-
fn parse_edition(&self, line: &str, testfile: &Utf8Path, line_number: usize) -> Option<String> {
1148-
self.parse_name_value_directive(line, "edition", testfile, line_number)
1149-
}
1150-
11511151
fn set_name_directive(&self, line: &str, directive: &str, value: &mut bool) {
11521152
match value {
11531153
true => {
@@ -1810,3 +1810,86 @@ enum IgnoreDecision {
18101810
Continue,
18111811
Error { message: String },
18121812
}
1813+
1814+
fn parse_edition_range(
1815+
config: &Config,
1816+
line: &str,
1817+
testfile: &Utf8Path,
1818+
line_number: usize,
1819+
) -> Option<EditionRange> {
1820+
let raw = config.parse_name_value_directive(line, "edition", testfile, line_number)?;
1821+
1822+
// Edition range is half-open: `[lower_bound, upper_bound)`
1823+
if let Some((lower_bound, upper_bound)) = raw.split_once("..") {
1824+
Some(match (maybe_parse_edition(lower_bound), maybe_parse_edition(upper_bound)) {
1825+
(Some(lower_bound), Some(upper_bound)) if upper_bound <= lower_bound => {
1826+
fatal!(
1827+
"{testfile}:{line_number}: the left side of `//@ edition` cannot be greater than or equal to the right side"
1828+
);
1829+
}
1830+
(Some(lower_bound), Some(upper_bound)) => {
1831+
EditionRange::Range { lower_bound, upper_bound }
1832+
}
1833+
(Some(lower_bound), None) => EditionRange::RangeFrom(lower_bound),
1834+
(None, Some(_)) => {
1835+
fatal!(
1836+
"{testfile}:{line_number}: `..edition` is not a supported range in `//@ edition`"
1837+
);
1838+
}
1839+
(None, None) => {
1840+
fatal!("{testfile}:{line_number}: `..` is not a supported range in `//@ edition`");
1841+
}
1842+
})
1843+
} else {
1844+
match maybe_parse_edition(&raw) {
1845+
Some(edition) => Some(EditionRange::Exact(edition)),
1846+
None => {
1847+
fatal!("{testfile}:{line_number}: empty value for `//@ edition`");
1848+
}
1849+
}
1850+
}
1851+
}
1852+
1853+
fn maybe_parse_edition(mut input: &str) -> Option<Edition> {
1854+
input = input.trim();
1855+
if input.is_empty() {
1856+
return None;
1857+
}
1858+
Some(parse_edition(input))
1859+
}
1860+
1861+
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
1862+
enum EditionRange {
1863+
Exact(Edition),
1864+
RangeFrom(Edition),
1865+
/// Half-open range: `[lower_bound, upper_bound)`
1866+
Range {
1867+
lower_bound: Edition,
1868+
upper_bound: Edition,
1869+
},
1870+
}
1871+
1872+
impl EditionRange {
1873+
fn edition_to_test(&self, requested: impl Into<Option<Edition>>) -> Edition {
1874+
let min_edition = Edition::Year(2015);
1875+
let requested = requested.into().unwrap_or(min_edition);
1876+
1877+
match *self {
1878+
EditionRange::Exact(exact) => exact,
1879+
EditionRange::RangeFrom(lower_bound) => {
1880+
if requested >= lower_bound {
1881+
requested
1882+
} else {
1883+
lower_bound
1884+
}
1885+
}
1886+
EditionRange::Range { lower_bound, upper_bound } => {
1887+
if requested >= lower_bound && requested < upper_bound {
1888+
requested
1889+
} else {
1890+
lower_bound
1891+
}
1892+
}
1893+
}
1894+
}
1895+
}

src/tools/compiletest/src/directives/tests.rs

Lines changed: 138 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,11 @@ use camino::Utf8Path;
44
use semver::Version;
55

66
use super::{
7-
DirectivesCache, EarlyProps, extract_llvm_version, extract_version_range, iter_directives,
8-
parse_normalize_rule,
7+
DirectivesCache, EarlyProps, Edition, EditionRange, extract_llvm_version,
8+
extract_version_range, iter_directives, parse_normalize_rule,
99
};
1010
use crate::common::{Config, Debugger, TestMode};
11+
use crate::directives::parse_edition;
1112
use crate::executor::{CollectedTestDesc, ShouldPanic};
1213

1314
fn make_test_description<R: Read>(
@@ -73,6 +74,7 @@ fn test_parse_normalize_rule() {
7374
struct ConfigBuilder {
7475
mode: Option<String>,
7576
channel: Option<String>,
77+
edition: Option<Edition>,
7678
host: Option<String>,
7779
target: Option<String>,
7880
stage: Option<u32>,
@@ -96,6 +98,11 @@ impl ConfigBuilder {
9698
self
9799
}
98100

101+
fn edition(&mut self, e: Edition) -> &mut Self {
102+
self.edition = Some(e);
103+
self
104+
}
105+
99106
fn host(&mut self, s: &str) -> &mut Self {
100107
self.host = Some(s.to_owned());
101108
self
@@ -183,6 +190,10 @@ impl ConfigBuilder {
183190
];
184191
let mut args: Vec<String> = args.iter().map(ToString::to_string).collect();
185192

193+
if let Some(edition) = &self.edition {
194+
args.push(format!("--edition={edition}"));
195+
}
196+
186197
if let Some(ref llvm_version) = self.llvm_version {
187198
args.push("--llvm-version".to_owned());
188199
args.push(llvm_version.clone());
@@ -941,3 +952,128 @@ fn test_needs_target_std() {
941952
let config = cfg().target("x86_64-unknown-linux-gnu").build();
942953
assert!(!check_ignore(&config, "//@ needs-target-std"));
943954
}
955+
956+
fn parse_edition_range(line: &str) -> Option<EditionRange> {
957+
let config = cfg().build();
958+
super::parse_edition_range(&config, line, "tmp.rs".into(), 0)
959+
}
960+
961+
#[test]
962+
fn test_parse_edition_range() {
963+
assert_eq!(None, parse_edition_range("hello-world"));
964+
assert_eq!(None, parse_edition_range("edition"));
965+
966+
assert_eq!(Some(EditionRange::Exact(2018.into())), parse_edition_range("edition: 2018"));
967+
assert_eq!(Some(EditionRange::Exact(2021.into())), parse_edition_range("edition:2021"));
968+
assert_eq!(Some(EditionRange::Exact(2024.into())), parse_edition_range("edition: 2024 "));
969+
assert_eq!(Some(EditionRange::Exact(Edition::Future)), parse_edition_range("edition: future"));
970+
971+
assert_eq!(Some(EditionRange::RangeFrom(2018.into())), parse_edition_range("edition: 2018.."));
972+
assert_eq!(Some(EditionRange::RangeFrom(2021.into())), parse_edition_range("edition:2021 .."));
973+
assert_eq!(
974+
Some(EditionRange::RangeFrom(2024.into())),
975+
parse_edition_range("edition: 2024 .. ")
976+
);
977+
assert_eq!(
978+
Some(EditionRange::RangeFrom(Edition::Future)),
979+
parse_edition_range("edition: future.. ")
980+
);
981+
982+
assert_eq!(
983+
Some(EditionRange::Range { lower_bound: 2018.into(), upper_bound: 2024.into() }),
984+
parse_edition_range("edition: 2018..2024")
985+
);
986+
assert_eq!(
987+
Some(EditionRange::Range { lower_bound: 2015.into(), upper_bound: 2021.into() }),
988+
parse_edition_range("edition:2015 .. 2021 ")
989+
);
990+
assert_eq!(
991+
Some(EditionRange::Range { lower_bound: 2021.into(), upper_bound: 2027.into() }),
992+
parse_edition_range("edition: 2021 .. 2027 ")
993+
);
994+
assert_eq!(
995+
Some(EditionRange::Range { lower_bound: 2021.into(), upper_bound: Edition::Future }),
996+
parse_edition_range("edition: 2021..future")
997+
);
998+
}
999+
1000+
#[test]
1001+
#[should_panic]
1002+
fn test_parse_edition_range_empty() {
1003+
parse_edition_range("edition:");
1004+
}
1005+
1006+
#[test]
1007+
#[should_panic]
1008+
fn test_parse_edition_range_invalid_edition() {
1009+
parse_edition_range("edition: hello");
1010+
}
1011+
1012+
#[test]
1013+
#[should_panic]
1014+
fn test_parse_edition_range_double_dots() {
1015+
parse_edition_range("edition: ..");
1016+
}
1017+
1018+
#[test]
1019+
#[should_panic]
1020+
fn test_parse_edition_range_inverted_range() {
1021+
parse_edition_range("edition: 2021..2015");
1022+
}
1023+
1024+
#[test]
1025+
#[should_panic]
1026+
fn test_parse_edition_range_inverted_range_future() {
1027+
parse_edition_range("edition: future..2015");
1028+
}
1029+
1030+
#[test]
1031+
#[should_panic]
1032+
fn test_parse_edition_range_empty_range() {
1033+
parse_edition_range("edition: 2021..2021");
1034+
}
1035+
1036+
#[track_caller]
1037+
fn assert_edition_to_test(
1038+
expected: impl Into<Edition>,
1039+
range: EditionRange,
1040+
default: Option<Edition>,
1041+
) {
1042+
let mut cfg = cfg();
1043+
if let Some(default) = default {
1044+
cfg.edition(default);
1045+
}
1046+
assert_eq!(expected.into(), range.edition_to_test(cfg.build().edition));
1047+
}
1048+
1049+
#[test]
1050+
fn test_edition_range_edition_to_test() {
1051+
let e2015 = parse_edition("2015");
1052+
let e2018 = parse_edition("2018");
1053+
let e2021 = parse_edition("2021");
1054+
let e2024 = parse_edition("2024");
1055+
let efuture = parse_edition("future");
1056+
1057+
let exact = EditionRange::Exact(2021.into());
1058+
assert_edition_to_test(2021, exact, None);
1059+
assert_edition_to_test(2021, exact, Some(e2018));
1060+
assert_edition_to_test(2021, exact, Some(efuture));
1061+
1062+
assert_edition_to_test(Edition::Future, EditionRange::Exact(Edition::Future), None);
1063+
1064+
let greater_equal_than = EditionRange::RangeFrom(2021.into());
1065+
assert_edition_to_test(2021, greater_equal_than, None);
1066+
assert_edition_to_test(2021, greater_equal_than, Some(e2015));
1067+
assert_edition_to_test(2021, greater_equal_than, Some(e2018));
1068+
assert_edition_to_test(2021, greater_equal_than, Some(e2021));
1069+
assert_edition_to_test(2024, greater_equal_than, Some(e2024));
1070+
assert_edition_to_test(Edition::Future, greater_equal_than, Some(efuture));
1071+
1072+
let range = EditionRange::Range { lower_bound: 2018.into(), upper_bound: 2024.into() };
1073+
assert_edition_to_test(2018, range, None);
1074+
assert_edition_to_test(2018, range, Some(e2015));
1075+
assert_edition_to_test(2018, range, Some(e2018));
1076+
assert_edition_to_test(2021, range, Some(e2021));
1077+
assert_edition_to_test(2018, range, Some(e2024));
1078+
assert_edition_to_test(2018, range, Some(efuture));
1079+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
use crate::fatal;
2+
3+
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
4+
pub enum Edition {
5+
// Note that the ordering here is load-bearing, as we want the future edition to be greater than
6+
// any year-based edition.
7+
Year(u32),
8+
Future,
9+
}
10+
11+
impl std::fmt::Display for Edition {
12+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
13+
match self {
14+
Edition::Year(year) => write!(f, "{year}"),
15+
Edition::Future => f.write_str("future"),
16+
}
17+
}
18+
}
19+
20+
impl From<u32> for Edition {
21+
fn from(value: u32) -> Self {
22+
Edition::Year(value)
23+
}
24+
}
25+
26+
pub fn parse_edition(mut input: &str) -> Edition {
27+
input = input.trim();
28+
if input == "future" {
29+
Edition::Future
30+
} else {
31+
Edition::Year(input.parse().unwrap_or_else(|_| {
32+
fatal!("`{input}` doesn't look like an edition");
33+
}))
34+
}
35+
}

0 commit comments

Comments
 (0)