Skip to content

Commit 04ae55d

Browse files
pietroalbinipvdrz
authored andcommitted
implement range support in //@ edition
1 parent 51ff895 commit 04ae55d

File tree

5 files changed

+268
-14
lines changed

5 files changed

+268
-14
lines changed

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};
@@ -610,10 +611,7 @@ pub struct Config {
610611
pub git_hash: bool,
611612

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

618616
// Configuration for various run-make tests frobbing things like C compilers or querying about
619617
// 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;
@@ -432,10 +433,13 @@ impl TestProps {
432433
panic!("`compiler-flags` directive should be spelled `compile-flags`");
433434
}
434435

435-
if let Some(edition) = config.parse_edition(ln, testfile, line_number) {
436+
if let Some(range) = parse_edition_range(config, ln, testfile, line_number) {
436437
// The edition is added at the start, since flags from //@compile-flags must
437438
// be passed to rustc last.
438-
self.compile_flags.insert(0, format!("--edition={}", edition.trim()));
439+
self.compile_flags.insert(
440+
0,
441+
format!("--edition={}", range.edition_to_test(config.edition)),
442+
);
439443
has_edition = true;
440444
}
441445

@@ -1125,10 +1129,6 @@ impl Config {
11251129
}
11261130
}
11271131

1128-
fn parse_edition(&self, line: &str, testfile: &Utf8Path, line_number: usize) -> Option<String> {
1129-
self.parse_name_value_directive(line, "edition", testfile, line_number)
1130-
}
1131-
11321132
fn set_name_directive(&self, line: &str, directive: &str, value: &mut bool) {
11331133
match value {
11341134
true => {
@@ -1784,3 +1784,86 @@ enum IgnoreDecision {
17841784
Continue,
17851785
Error { message: String },
17861786
}
1787+
1788+
fn parse_edition_range(
1789+
config: &Config,
1790+
line: &str,
1791+
testfile: &Utf8Path,
1792+
line_number: usize,
1793+
) -> Option<EditionRange> {
1794+
let raw = config.parse_name_value_directive(line, "edition", testfile, line_number)?;
1795+
1796+
// Edition range is half-open: `[lower_bound, upper_bound)`
1797+
if let Some((lower_bound, upper_bound)) = raw.split_once("..") {
1798+
Some(match (maybe_parse_edition(lower_bound), maybe_parse_edition(upper_bound)) {
1799+
(Some(lower_bound), Some(upper_bound)) if upper_bound <= lower_bound => {
1800+
fatal!(
1801+
"{testfile}:{line_number}: the left side of `//@ edition` cannot be greater than or equal to the right side"
1802+
);
1803+
}
1804+
(Some(lower_bound), Some(upper_bound)) => {
1805+
EditionRange::Range { lower_bound, upper_bound }
1806+
}
1807+
(Some(lower_bound), None) => EditionRange::RangeFrom(lower_bound),
1808+
(None, Some(_)) => {
1809+
fatal!(
1810+
"{testfile}:{line_number}: `..edition` is not a supported range in `//@ edition`"
1811+
);
1812+
}
1813+
(None, None) => {
1814+
fatal!("{testfile}:{line_number}: `..` is not a supported range in `//@ edition`");
1815+
}
1816+
})
1817+
} else {
1818+
match maybe_parse_edition(&raw) {
1819+
Some(edition) => Some(EditionRange::Exact(edition)),
1820+
None => {
1821+
fatal!("{testfile}:{line_number}: empty value for `//@ edition`");
1822+
}
1823+
}
1824+
}
1825+
}
1826+
1827+
fn maybe_parse_edition(mut input: &str) -> Option<Edition> {
1828+
input = input.trim();
1829+
if input.is_empty() {
1830+
return None;
1831+
}
1832+
Some(parse_edition(input))
1833+
}
1834+
1835+
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
1836+
enum EditionRange {
1837+
Exact(Edition),
1838+
RangeFrom(Edition),
1839+
/// Half-open range: `[lower_bound, upper_bound)`
1840+
Range {
1841+
lower_bound: Edition,
1842+
upper_bound: Edition,
1843+
},
1844+
}
1845+
1846+
impl EditionRange {
1847+
fn edition_to_test(&self, requested: impl Into<Option<Edition>>) -> Edition {
1848+
let min_edition = Edition::Year(2015);
1849+
let requested = requested.into().unwrap_or(min_edition);
1850+
1851+
match *self {
1852+
EditionRange::Exact(exact) => exact,
1853+
EditionRange::RangeFrom(lower_bound) => {
1854+
if requested >= lower_bound {
1855+
requested
1856+
} else {
1857+
lower_bound
1858+
}
1859+
}
1860+
EditionRange::Range { lower_bound, upper_bound } => {
1861+
if requested >= lower_bound && requested < upper_bound {
1862+
requested
1863+
} else {
1864+
lower_bound
1865+
}
1866+
}
1867+
}
1868+
}
1869+
}

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>(
@@ -71,6 +72,7 @@ fn test_parse_normalize_rule() {
7172
struct ConfigBuilder {
7273
mode: Option<String>,
7374
channel: Option<String>,
75+
edition: Option<Edition>,
7476
host: Option<String>,
7577
target: Option<String>,
7678
stage: Option<u32>,
@@ -94,6 +96,11 @@ impl ConfigBuilder {
9496
self
9597
}
9698

99+
fn edition(&mut self, e: Edition) -> &mut Self {
100+
self.edition = Some(e);
101+
self
102+
}
103+
97104
fn host(&mut self, s: &str) -> &mut Self {
98105
self.host = Some(s.to_owned());
99106
self
@@ -181,6 +188,10 @@ impl ConfigBuilder {
181188
];
182189
let mut args: Vec<String> = args.iter().map(ToString::to_string).collect();
183190

191+
if let Some(edition) = &self.edition {
192+
args.push(format!("--edition={edition}"));
193+
}
194+
184195
if let Some(ref llvm_version) = self.llvm_version {
185196
args.push("--llvm-version".to_owned());
186197
args.push(llvm_version.clone());
@@ -939,3 +950,128 @@ fn test_needs_target_std() {
939950
let config = cfg().target("x86_64-unknown-linux-gnu").build();
940951
assert!(!check_ignore(&config, "//@ needs-target-std"));
941952
}
953+
954+
fn parse_edition_range(line: &str) -> Option<EditionRange> {
955+
let config = cfg().build();
956+
super::parse_edition_range(&config, line, "tmp.rs".into(), 0)
957+
}
958+
959+
#[test]
960+
fn test_parse_edition_range() {
961+
assert_eq!(None, parse_edition_range("hello-world"));
962+
assert_eq!(None, parse_edition_range("edition"));
963+
964+
assert_eq!(Some(EditionRange::Exact(2018.into())), parse_edition_range("edition: 2018"));
965+
assert_eq!(Some(EditionRange::Exact(2021.into())), parse_edition_range("edition:2021"));
966+
assert_eq!(Some(EditionRange::Exact(2024.into())), parse_edition_range("edition: 2024 "));
967+
assert_eq!(Some(EditionRange::Exact(Edition::Future)), parse_edition_range("edition: future"));
968+
969+
assert_eq!(Some(EditionRange::RangeFrom(2018.into())), parse_edition_range("edition: 2018.."));
970+
assert_eq!(Some(EditionRange::RangeFrom(2021.into())), parse_edition_range("edition:2021 .."));
971+
assert_eq!(
972+
Some(EditionRange::RangeFrom(2024.into())),
973+
parse_edition_range("edition: 2024 .. ")
974+
);
975+
assert_eq!(
976+
Some(EditionRange::RangeFrom(Edition::Future)),
977+
parse_edition_range("edition: future.. ")
978+
);
979+
980+
assert_eq!(
981+
Some(EditionRange::Range { lower_bound: 2018.into(), upper_bound: 2024.into() }),
982+
parse_edition_range("edition: 2018..2024")
983+
);
984+
assert_eq!(
985+
Some(EditionRange::Range { lower_bound: 2015.into(), upper_bound: 2021.into() }),
986+
parse_edition_range("edition:2015 .. 2021 ")
987+
);
988+
assert_eq!(
989+
Some(EditionRange::Range { lower_bound: 2021.into(), upper_bound: 2027.into() }),
990+
parse_edition_range("edition: 2021 .. 2027 ")
991+
);
992+
assert_eq!(
993+
Some(EditionRange::Range { lower_bound: 2021.into(), upper_bound: Edition::Future }),
994+
parse_edition_range("edition: 2021..future")
995+
);
996+
}
997+
998+
#[test]
999+
#[should_panic]
1000+
fn test_parse_edition_range_empty() {
1001+
parse_edition_range("edition:");
1002+
}
1003+
1004+
#[test]
1005+
#[should_panic]
1006+
fn test_parse_edition_range_invalid_edition() {
1007+
parse_edition_range("edition: hello");
1008+
}
1009+
1010+
#[test]
1011+
#[should_panic]
1012+
fn test_parse_edition_range_double_dots() {
1013+
parse_edition_range("edition: ..");
1014+
}
1015+
1016+
#[test]
1017+
#[should_panic]
1018+
fn test_parse_edition_range_inverted_range() {
1019+
parse_edition_range("edition: 2021..2015");
1020+
}
1021+
1022+
#[test]
1023+
#[should_panic]
1024+
fn test_parse_edition_range_inverted_range_future() {
1025+
parse_edition_range("edition: future..2015");
1026+
}
1027+
1028+
#[test]
1029+
#[should_panic]
1030+
fn test_parse_edition_range_empty_range() {
1031+
parse_edition_range("edition: 2021..2021");
1032+
}
1033+
1034+
#[track_caller]
1035+
fn assert_edition_to_test(
1036+
expected: impl Into<Edition>,
1037+
range: EditionRange,
1038+
default: Option<Edition>,
1039+
) {
1040+
let mut cfg = cfg();
1041+
if let Some(default) = default {
1042+
cfg.edition(default);
1043+
}
1044+
assert_eq!(expected.into(), range.edition_to_test(cfg.build().edition));
1045+
}
1046+
1047+
#[test]
1048+
fn test_edition_range_edition_to_test() {
1049+
let e2015 = parse_edition("2015");
1050+
let e2018 = parse_edition("2018");
1051+
let e2021 = parse_edition("2021");
1052+
let e2024 = parse_edition("2024");
1053+
let efuture = parse_edition("future");
1054+
1055+
let exact = EditionRange::Exact(2021.into());
1056+
assert_edition_to_test(2021, exact, None);
1057+
assert_edition_to_test(2021, exact, Some(e2018));
1058+
assert_edition_to_test(2021, exact, Some(efuture));
1059+
1060+
assert_edition_to_test(Edition::Future, EditionRange::Exact(Edition::Future), None);
1061+
1062+
let greater_equal_than = EditionRange::RangeFrom(2021.into());
1063+
assert_edition_to_test(2021, greater_equal_than, None);
1064+
assert_edition_to_test(2021, greater_equal_than, Some(e2015));
1065+
assert_edition_to_test(2021, greater_equal_than, Some(e2018));
1066+
assert_edition_to_test(2021, greater_equal_than, Some(e2021));
1067+
assert_edition_to_test(2024, greater_equal_than, Some(e2024));
1068+
assert_edition_to_test(Edition::Future, greater_equal_than, Some(efuture));
1069+
1070+
let range = EditionRange::Range { lower_bound: 2018.into(), upper_bound: 2024.into() };
1071+
assert_edition_to_test(2018, range, None);
1072+
assert_edition_to_test(2018, range, Some(e2015));
1073+
assert_edition_to_test(2018, range, Some(e2018));
1074+
assert_edition_to_test(2021, range, Some(e2021));
1075+
assert_edition_to_test(2018, range, Some(e2024));
1076+
assert_edition_to_test(2018, range, Some(efuture));
1077+
}
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+
}

src/tools/compiletest/src/lib.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ pub mod common;
1212
mod debuggers;
1313
pub mod diagnostics;
1414
pub mod directives;
15+
pub mod edition;
1516
pub mod errors;
1617
mod executor;
1718
mod json;
@@ -43,6 +44,7 @@ use crate::common::{
4344
expected_output_path, output_base_dir, output_relative_path,
4445
};
4546
use crate::directives::DirectivesCache;
47+
use crate::edition::parse_edition;
4648
use crate::executor::{CollectedTest, ColorConfig};
4749

4850
/// Creates the `Config` instance for this invocation of compiletest.
@@ -441,7 +443,7 @@ pub fn parse_config(args: Vec<String>) -> Config {
441443
has_enzyme,
442444
channel: matches.opt_str("channel").unwrap(),
443445
git_hash: matches.opt_present("git-hash"),
444-
edition: matches.opt_str("edition"),
446+
edition: matches.opt_str("edition").as_deref().map(parse_edition),
445447

446448
cc: matches.opt_str("cc").unwrap(),
447449
cxx: matches.opt_str("cxx").unwrap(),

0 commit comments

Comments
 (0)