Skip to content

Commit c61ec94

Browse files
pietroalbinipvdrz
authored andcommitted
implement range support in //@ edition
1 parent 94ecb52 commit c61ec94

File tree

5 files changed

+270
-14
lines changed

5 files changed

+270
-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};
@@ -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;
@@ -437,10 +438,13 @@ impl TestProps {
437438
panic!("`compiler-flags` directive should be spelled `compile-flags`");
438439
}
439440

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

@@ -1124,10 +1128,6 @@ impl Config {
11241128
}
11251129
}
11261130

1127-
fn parse_edition(&self, line: &DirectiveLine<'_>, testfile: &Utf8Path) -> Option<String> {
1128-
self.parse_name_value_directive(line, "edition", testfile)
1129-
}
1130-
11311131
fn set_name_directive(&self, line: &DirectiveLine<'_>, directive: &str, value: &mut bool) {
11321132
// If the flag is already true, don't bother looking at the directive.
11331133
*value = *value || self.parse_name_directive(line, directive);
@@ -1769,3 +1769,86 @@ enum IgnoreDecision {
17691769
Continue,
17701770
Error { message: String },
17711771
}
1772+
1773+
fn parse_edition_range(
1774+
config: &Config,
1775+
line: &DirectiveLine<'_>,
1776+
testfile: &Utf8Path,
1777+
) -> Option<EditionRange> {
1778+
let raw = config.parse_name_value_directive(line, "edition", testfile)?;
1779+
let line_number = line.line_number;
1780+
1781+
// Edition range is half-open: `[lower_bound, upper_bound)`
1782+
if let Some((lower_bound, upper_bound)) = raw.split_once("..") {
1783+
Some(match (maybe_parse_edition(lower_bound), maybe_parse_edition(upper_bound)) {
1784+
(Some(lower_bound), Some(upper_bound)) if upper_bound <= lower_bound => {
1785+
fatal!(
1786+
"{testfile}:{line_number}: the left side of `//@ edition` cannot be greater than or equal to the right side"
1787+
);
1788+
}
1789+
(Some(lower_bound), Some(upper_bound)) => {
1790+
EditionRange::Range { lower_bound, upper_bound }
1791+
}
1792+
(Some(lower_bound), None) => EditionRange::RangeFrom(lower_bound),
1793+
(None, Some(_)) => {
1794+
fatal!(
1795+
"{testfile}:{line_number}: `..edition` is not a supported range in `//@ edition`"
1796+
);
1797+
}
1798+
(None, None) => {
1799+
fatal!("{testfile}:{line_number}: `..` is not a supported range in `//@ edition`");
1800+
}
1801+
})
1802+
} else {
1803+
match maybe_parse_edition(&raw) {
1804+
Some(edition) => Some(EditionRange::Exact(edition)),
1805+
None => {
1806+
fatal!("{testfile}:{line_number}: empty value for `//@ edition`");
1807+
}
1808+
}
1809+
}
1810+
}
1811+
1812+
fn maybe_parse_edition(mut input: &str) -> Option<Edition> {
1813+
input = input.trim();
1814+
if input.is_empty() {
1815+
return None;
1816+
}
1817+
Some(parse_edition(input))
1818+
}
1819+
1820+
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
1821+
enum EditionRange {
1822+
Exact(Edition),
1823+
RangeFrom(Edition),
1824+
/// Half-open range: `[lower_bound, upper_bound)`
1825+
Range {
1826+
lower_bound: Edition,
1827+
upper_bound: Edition,
1828+
},
1829+
}
1830+
1831+
impl EditionRange {
1832+
fn edition_to_test(&self, requested: impl Into<Option<Edition>>) -> Edition {
1833+
let min_edition = Edition::Year(2015);
1834+
let requested = requested.into().unwrap_or(min_edition);
1835+
1836+
match *self {
1837+
EditionRange::Exact(exact) => exact,
1838+
EditionRange::RangeFrom(lower_bound) => {
1839+
if requested >= lower_bound {
1840+
requested
1841+
} else {
1842+
lower_bound
1843+
}
1844+
}
1845+
EditionRange::Range { lower_bound, upper_bound } => {
1846+
if requested >= lower_bound && requested < upper_bound {
1847+
requested
1848+
} else {
1849+
lower_bound
1850+
}
1851+
}
1852+
}
1853+
}
1854+
}

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

Lines changed: 140 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,130 @@ 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+
let line = super::DirectiveLine { line_number: 0, revision: None, raw_directive: line };
959+
960+
super::parse_edition_range(&config, &line, "tmp.rs".into())
961+
}
962+
963+
#[test]
964+
fn test_parse_edition_range() {
965+
assert_eq!(None, parse_edition_range("hello-world"));
966+
assert_eq!(None, parse_edition_range("edition"));
967+
968+
assert_eq!(Some(EditionRange::Exact(2018.into())), parse_edition_range("edition: 2018"));
969+
assert_eq!(Some(EditionRange::Exact(2021.into())), parse_edition_range("edition:2021"));
970+
assert_eq!(Some(EditionRange::Exact(2024.into())), parse_edition_range("edition: 2024 "));
971+
assert_eq!(Some(EditionRange::Exact(Edition::Future)), parse_edition_range("edition: future"));
972+
973+
assert_eq!(Some(EditionRange::RangeFrom(2018.into())), parse_edition_range("edition: 2018.."));
974+
assert_eq!(Some(EditionRange::RangeFrom(2021.into())), parse_edition_range("edition:2021 .."));
975+
assert_eq!(
976+
Some(EditionRange::RangeFrom(2024.into())),
977+
parse_edition_range("edition: 2024 .. ")
978+
);
979+
assert_eq!(
980+
Some(EditionRange::RangeFrom(Edition::Future)),
981+
parse_edition_range("edition: future.. ")
982+
);
983+
984+
assert_eq!(
985+
Some(EditionRange::Range { lower_bound: 2018.into(), upper_bound: 2024.into() }),
986+
parse_edition_range("edition: 2018..2024")
987+
);
988+
assert_eq!(
989+
Some(EditionRange::Range { lower_bound: 2015.into(), upper_bound: 2021.into() }),
990+
parse_edition_range("edition:2015 .. 2021 ")
991+
);
992+
assert_eq!(
993+
Some(EditionRange::Range { lower_bound: 2021.into(), upper_bound: 2027.into() }),
994+
parse_edition_range("edition: 2021 .. 2027 ")
995+
);
996+
assert_eq!(
997+
Some(EditionRange::Range { lower_bound: 2021.into(), upper_bound: Edition::Future }),
998+
parse_edition_range("edition: 2021..future")
999+
);
1000+
}
1001+
1002+
#[test]
1003+
#[should_panic]
1004+
fn test_parse_edition_range_empty() {
1005+
parse_edition_range("edition:");
1006+
}
1007+
1008+
#[test]
1009+
#[should_panic]
1010+
fn test_parse_edition_range_invalid_edition() {
1011+
parse_edition_range("edition: hello");
1012+
}
1013+
1014+
#[test]
1015+
#[should_panic]
1016+
fn test_parse_edition_range_double_dots() {
1017+
parse_edition_range("edition: ..");
1018+
}
1019+
1020+
#[test]
1021+
#[should_panic]
1022+
fn test_parse_edition_range_inverted_range() {
1023+
parse_edition_range("edition: 2021..2015");
1024+
}
1025+
1026+
#[test]
1027+
#[should_panic]
1028+
fn test_parse_edition_range_inverted_range_future() {
1029+
parse_edition_range("edition: future..2015");
1030+
}
1031+
1032+
#[test]
1033+
#[should_panic]
1034+
fn test_parse_edition_range_empty_range() {
1035+
parse_edition_range("edition: 2021..2021");
1036+
}
1037+
1038+
#[track_caller]
1039+
fn assert_edition_to_test(
1040+
expected: impl Into<Edition>,
1041+
range: EditionRange,
1042+
default: Option<Edition>,
1043+
) {
1044+
let mut cfg = cfg();
1045+
if let Some(default) = default {
1046+
cfg.edition(default);
1047+
}
1048+
assert_eq!(expected.into(), range.edition_to_test(cfg.build().edition));
1049+
}
1050+
1051+
#[test]
1052+
fn test_edition_range_edition_to_test() {
1053+
let e2015 = parse_edition("2015");
1054+
let e2018 = parse_edition("2018");
1055+
let e2021 = parse_edition("2021");
1056+
let e2024 = parse_edition("2024");
1057+
let efuture = parse_edition("future");
1058+
1059+
let exact = EditionRange::Exact(2021.into());
1060+
assert_edition_to_test(2021, exact, None);
1061+
assert_edition_to_test(2021, exact, Some(e2018));
1062+
assert_edition_to_test(2021, exact, Some(efuture));
1063+
1064+
assert_edition_to_test(Edition::Future, EditionRange::Exact(Edition::Future), None);
1065+
1066+
let greater_equal_than = EditionRange::RangeFrom(2021.into());
1067+
assert_edition_to_test(2021, greater_equal_than, None);
1068+
assert_edition_to_test(2021, greater_equal_than, Some(e2015));
1069+
assert_edition_to_test(2021, greater_equal_than, Some(e2018));
1070+
assert_edition_to_test(2021, greater_equal_than, Some(e2021));
1071+
assert_edition_to_test(2024, greater_equal_than, Some(e2024));
1072+
assert_edition_to_test(Edition::Future, greater_equal_than, Some(efuture));
1073+
1074+
let range = EditionRange::Range { lower_bound: 2018.into(), upper_bound: 2024.into() };
1075+
assert_edition_to_test(2018, range, None);
1076+
assert_edition_to_test(2018, range, Some(e2015));
1077+
assert_edition_to_test(2018, range, Some(e2018));
1078+
assert_edition_to_test(2021, range, Some(e2021));
1079+
assert_edition_to_test(2018, range, Some(e2024));
1080+
assert_edition_to_test(2018, range, Some(efuture));
1081+
}
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
@@ -7,6 +7,7 @@ pub mod common;
77
mod debuggers;
88
pub mod diagnostics;
99
pub mod directives;
10+
pub mod edition;
1011
pub mod errors;
1112
mod executor;
1213
mod json;
@@ -39,6 +40,7 @@ use crate::common::{
3940
expected_output_path, output_base_dir, output_relative_path,
4041
};
4142
use crate::directives::DirectivesCache;
43+
use crate::edition::parse_edition;
4244
use crate::executor::{CollectedTest, ColorConfig};
4345

4446
/// Creates the `Config` instance for this invocation of compiletest.
@@ -449,7 +451,7 @@ pub fn parse_config(args: Vec<String>) -> Config {
449451
has_enzyme,
450452
channel: matches.opt_str("channel").unwrap(),
451453
git_hash: matches.opt_present("git-hash"),
452-
edition: matches.opt_str("edition"),
454+
edition: matches.opt_str("edition").as_deref().map(parse_edition),
453455

454456
cc: matches.opt_str("cc").unwrap(),
455457
cxx: matches.opt_str("cxx").unwrap(),

0 commit comments

Comments
 (0)