Skip to content

Commit 449bbf6

Browse files
authored
Unrolled build for #147288
Rollup merge of #147288 - Zalathar:directive, r=jieyouxu compiletest: Make `DirectiveLine` responsible for name/value splitting - Follow-up to #147170. --- Now that all of the directive-parsing functions have access to a `DirectiveLine`, we can move all of the ad-hoc name/value splitting code into `DirectiveLine` itself, making directive parsing simpler and more consistent. The first commit is just moving code into a submodule, so the actual changes can be seen in the subsequent commits. r? jieyouxu
2 parents 227ac7c + 33c99a0 commit 449bbf6

File tree

6 files changed

+188
-156
lines changed

6 files changed

+188
-156
lines changed

src/tools/compiletest/src/directives.rs

Lines changed: 58 additions & 122 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ use crate::directives::auxiliary::{AuxProps, parse_and_update_aux};
1515
use crate::directives::directive_names::{
1616
KNOWN_DIRECTIVE_NAMES, KNOWN_HTMLDOCCK_DIRECTIVE_NAMES, KNOWN_JSONDOCCK_DIRECTIVE_NAMES,
1717
};
18+
use crate::directives::line::{DirectiveLine, line_directive};
1819
use crate::directives::needs::CachedNeedsConditions;
1920
use crate::edition::{Edition, parse_edition};
2021
use crate::errors::ErrorKind;
@@ -25,6 +26,7 @@ use crate::{fatal, help};
2526
pub(crate) mod auxiliary;
2627
mod cfg;
2728
mod directive_names;
29+
mod line;
2830
mod needs;
2931
#[cfg(test)]
3032
mod tests;
@@ -824,70 +826,6 @@ impl TestProps {
824826
}
825827
}
826828

827-
/// If the given line begins with the appropriate comment prefix for a directive,
828-
/// returns a struct containing various parts of the directive.
829-
fn line_directive<'line>(
830-
line_number: usize,
831-
original_line: &'line str,
832-
) -> Option<DirectiveLine<'line>> {
833-
// Ignore lines that don't start with the comment prefix.
834-
let after_comment =
835-
original_line.trim_start().strip_prefix(COMPILETEST_DIRECTIVE_PREFIX)?.trim_start();
836-
837-
let revision;
838-
let raw_directive;
839-
840-
if let Some(after_open_bracket) = after_comment.strip_prefix('[') {
841-
// A comment like `//@[foo]` only applies to revision `foo`.
842-
let Some((line_revision, after_close_bracket)) = after_open_bracket.split_once(']') else {
843-
panic!(
844-
"malformed condition directive: expected `{COMPILETEST_DIRECTIVE_PREFIX}[foo]`, found `{original_line}`"
845-
)
846-
};
847-
848-
revision = Some(line_revision);
849-
raw_directive = after_close_bracket.trim_start();
850-
} else {
851-
revision = None;
852-
raw_directive = after_comment;
853-
};
854-
855-
Some(DirectiveLine { line_number, revision, raw_directive })
856-
}
857-
858-
/// The (partly) broken-down contents of a line containing a test directive,
859-
/// which [`iter_directives`] passes to its callback function.
860-
///
861-
/// For example:
862-
///
863-
/// ```text
864-
/// //@ compile-flags: -O
865-
/// ^^^^^^^^^^^^^^^^^ raw_directive
866-
///
867-
/// //@ [foo] compile-flags: -O
868-
/// ^^^ revision
869-
/// ^^^^^^^^^^^^^^^^^ raw_directive
870-
/// ```
871-
struct DirectiveLine<'ln> {
872-
line_number: usize,
873-
/// Some test directives start with a revision name in square brackets
874-
/// (e.g. `[foo]`), and only apply to that revision of the test.
875-
/// If present, this field contains the revision name (e.g. `foo`).
876-
revision: Option<&'ln str>,
877-
/// The main part of the directive, after removing the comment prefix
878-
/// and the optional revision specifier.
879-
///
880-
/// This is "raw" because the directive's name and colon-separated value
881-
/// (if present) have not yet been extracted or checked.
882-
raw_directive: &'ln str,
883-
}
884-
885-
impl<'ln> DirectiveLine<'ln> {
886-
fn applies_to_test_revision(&self, test_revision: Option<&str>) -> bool {
887-
self.revision.is_none() || self.revision == test_revision
888-
}
889-
}
890-
891829
pub(crate) struct CheckDirectiveResult<'ln> {
892830
is_known_directive: bool,
893831
trailing_directive: Option<&'ln str>,
@@ -897,9 +835,7 @@ fn check_directive<'a>(
897835
directive_ln: &DirectiveLine<'a>,
898836
mode: TestMode,
899837
) -> CheckDirectiveResult<'a> {
900-
let &DirectiveLine { raw_directive: directive_ln, .. } = directive_ln;
901-
902-
let (directive_name, post) = directive_ln.split_once([':', ' ']).unwrap_or((directive_ln, ""));
838+
let &DirectiveLine { name: directive_name, .. } = directive_ln;
903839

904840
let is_known_directive = KNOWN_DIRECTIVE_NAMES.contains(&directive_name)
905841
|| match mode {
@@ -908,20 +844,17 @@ fn check_directive<'a>(
908844
_ => false,
909845
};
910846

911-
let trailing = post.trim().split_once(' ').map(|(pre, _)| pre).unwrap_or(post);
912-
let trailing_directive = {
913-
// 1. is the directive name followed by a space? (to exclude `:`)
914-
directive_ln.get(directive_name.len()..).is_some_and(|s| s.starts_with(' '))
915-
// 2. is what is after that directive also a directive (ex: "only-x86 only-arm")
916-
&& KNOWN_DIRECTIVE_NAMES.contains(&trailing)
917-
}
918-
.then_some(trailing);
847+
// If it looks like the user tried to put two directives on the same line
848+
// (e.g. `//@ only-linux only-x86_64`), signal an error, because the
849+
// second "directive" would actually be ignored with no effect.
850+
let trailing_directive = directive_ln
851+
.remark_after_space()
852+
.map(|remark| remark.trim_start().split(' ').next().unwrap())
853+
.filter(|token| KNOWN_DIRECTIVE_NAMES.contains(token));
919854

920855
CheckDirectiveResult { is_known_directive, trailing_directive }
921856
}
922857

923-
const COMPILETEST_DIRECTIVE_PREFIX: &str = "//@";
924-
925858
fn iter_directives(
926859
mode: TestMode,
927860
poisoned: &mut bool,
@@ -939,15 +872,17 @@ fn iter_directives(
939872
// FIXME(jieyouxu): I feel like there's a better way to do this, leaving for later.
940873
if mode == TestMode::CoverageRun {
941874
let extra_directives: &[&str] = &[
942-
"needs-profiler-runtime",
875+
"//@ needs-profiler-runtime",
943876
// FIXME(pietroalbini): this test currently does not work on cross-compiled targets
944877
// because remote-test is not capable of sending back the *.profraw files generated by
945878
// the LLVM instrumentation.
946-
"ignore-cross-compile",
879+
"//@ ignore-cross-compile",
947880
];
948881
// Process the extra implied directives, with a dummy line number of 0.
949-
for raw_directive in extra_directives {
950-
it(DirectiveLine { line_number: 0, revision: None, raw_directive });
882+
for directive_str in extra_directives {
883+
let directive_line = line_directive(0, directive_str)
884+
.unwrap_or_else(|| panic!("bad extra-directive line: {directive_str:?}"));
885+
it(directive_line);
951886
}
952887
}
953888

@@ -977,7 +912,7 @@ fn iter_directives(
977912

978913
error!(
979914
"{testfile}:{line_number}: detected unknown compiletest test directive `{}`",
980-
directive_line.raw_directive,
915+
directive_line.display(),
981916
);
982917

983918
return;
@@ -1073,35 +1008,30 @@ impl Config {
10731008
}
10741009

10751010
fn parse_custom_normalization(&self, line: &DirectiveLine<'_>) -> Option<NormalizeRule> {
1076-
let &DirectiveLine { raw_directive, .. } = line;
1077-
1078-
// FIXME(Zalathar): Integrate name/value splitting into `DirectiveLine`
1079-
// instead of doing it here.
1080-
let (directive_name, raw_value) = raw_directive.split_once(':')?;
1011+
let &DirectiveLine { name, .. } = line;
10811012

1082-
let kind = match directive_name {
1013+
let kind = match name {
10831014
"normalize-stdout" => NormalizeKind::Stdout,
10841015
"normalize-stderr" => NormalizeKind::Stderr,
10851016
"normalize-stderr-32bit" => NormalizeKind::Stderr32bit,
10861017
"normalize-stderr-64bit" => NormalizeKind::Stderr64bit,
10871018
_ => return None,
10881019
};
10891020

1090-
let Some((regex, replacement)) = parse_normalize_rule(raw_value) else {
1091-
error!("couldn't parse custom normalization rule: `{raw_directive}`");
1092-
help!("expected syntax is: `{directive_name}: \"REGEX\" -> \"REPLACEMENT\"`");
1021+
let Some((regex, replacement)) = line.value_after_colon().and_then(parse_normalize_rule)
1022+
else {
1023+
error!("couldn't parse custom normalization rule: `{}`", line.display());
1024+
help!("expected syntax is: `{name}: \"REGEX\" -> \"REPLACEMENT\"`");
10931025
panic!("invalid normalization rule detected");
10941026
};
10951027
Some(NormalizeRule { kind, regex, replacement })
10961028
}
10971029

10981030
fn parse_name_directive(&self, line: &DirectiveLine<'_>, directive: &str) -> bool {
1099-
let &DirectiveLine { raw_directive: line, .. } = line;
1100-
1101-
// Ensure the directive is a whole word. Do not match "ignore-x86" when
1102-
// the line says "ignore-x86_64".
1103-
line.starts_with(directive)
1104-
&& matches!(line.as_bytes().get(directive.len()), None | Some(&b' ') | Some(&b':'))
1031+
// FIXME(Zalathar): Ideally, this should raise an error if a name-only
1032+
// directive is followed by a colon, since that's the wrong syntax.
1033+
// But we would need to fix tests that rely on the current behaviour.
1034+
line.name == directive
11051035
}
11061036

11071037
fn parse_name_value_directive(
@@ -1110,22 +1040,26 @@ impl Config {
11101040
directive: &str,
11111041
testfile: &Utf8Path,
11121042
) -> Option<String> {
1113-
let &DirectiveLine { line_number, raw_directive: line, .. } = line;
1114-
1115-
let colon = directive.len();
1116-
if line.starts_with(directive) && line.as_bytes().get(colon) == Some(&b':') {
1117-
let value = line[(colon + 1)..].to_owned();
1118-
debug!("{}: {}", directive, value);
1119-
let value = expand_variables(value, self);
1120-
if value.is_empty() {
1121-
error!("{testfile}:{line_number}: empty value for directive `{directive}`");
1122-
help!("expected syntax is: `{directive}: value`");
1123-
panic!("empty directive value detected");
1124-
}
1125-
Some(value)
1126-
} else {
1127-
None
1043+
let &DirectiveLine { line_number, .. } = line;
1044+
1045+
if line.name != directive {
1046+
return None;
1047+
};
1048+
1049+
// FIXME(Zalathar): This silently discards directives with a matching
1050+
// name but no colon. Unfortunately, some directives (e.g. "pp-exact")
1051+
// currently rely on _not_ panicking here.
1052+
let value = line.value_after_colon()?;
1053+
debug!("{}: {}", directive, value);
1054+
let value = expand_variables(value.to_owned(), self);
1055+
1056+
if value.is_empty() {
1057+
error!("{testfile}:{line_number}: empty value for directive `{directive}`");
1058+
help!("expected syntax is: `{directive}: value`");
1059+
panic!("empty directive value detected");
11281060
}
1061+
1062+
Some(value)
11291063
}
11301064

11311065
fn set_name_directive(&self, line: &DirectiveLine<'_>, directive: &str, value: &mut bool) {
@@ -1515,14 +1449,14 @@ pub(crate) fn make_test_description<R: Read>(
15151449
}
15161450

15171451
fn ignore_cdb(config: &Config, line: &DirectiveLine<'_>) -> IgnoreDecision {
1518-
let &DirectiveLine { raw_directive: line, .. } = line;
1519-
15201452
if config.debugger != Some(Debugger::Cdb) {
15211453
return IgnoreDecision::Continue;
15221454
}
15231455

15241456
if let Some(actual_version) = config.cdb_version {
1525-
if let Some(rest) = line.strip_prefix("min-cdb-version:").map(str::trim) {
1457+
if line.name == "min-cdb-version"
1458+
&& let Some(rest) = line.value_after_colon().map(str::trim)
1459+
{
15261460
let min_version = extract_cdb_version(rest).unwrap_or_else(|| {
15271461
panic!("couldn't parse version range: {:?}", rest);
15281462
});
@@ -1540,14 +1474,14 @@ fn ignore_cdb(config: &Config, line: &DirectiveLine<'_>) -> IgnoreDecision {
15401474
}
15411475

15421476
fn ignore_gdb(config: &Config, line: &DirectiveLine<'_>) -> IgnoreDecision {
1543-
let &DirectiveLine { raw_directive: line, .. } = line;
1544-
15451477
if config.debugger != Some(Debugger::Gdb) {
15461478
return IgnoreDecision::Continue;
15471479
}
15481480

15491481
if let Some(actual_version) = config.gdb_version {
1550-
if let Some(rest) = line.strip_prefix("min-gdb-version:").map(str::trim) {
1482+
if line.name == "min-gdb-version"
1483+
&& let Some(rest) = line.value_after_colon().map(str::trim)
1484+
{
15511485
let (start_ver, end_ver) = extract_version_range(rest, extract_gdb_version)
15521486
.unwrap_or_else(|| {
15531487
panic!("couldn't parse version range: {:?}", rest);
@@ -1563,7 +1497,9 @@ fn ignore_gdb(config: &Config, line: &DirectiveLine<'_>) -> IgnoreDecision {
15631497
reason: format!("ignored when the GDB version is lower than {rest}"),
15641498
};
15651499
}
1566-
} else if let Some(rest) = line.strip_prefix("ignore-gdb-version:").map(str::trim) {
1500+
} else if line.name == "ignore-gdb-version"
1501+
&& let Some(rest) = line.value_after_colon().map(str::trim)
1502+
{
15671503
let (min_version, max_version) = extract_version_range(rest, extract_gdb_version)
15681504
.unwrap_or_else(|| {
15691505
panic!("couldn't parse version range: {:?}", rest);
@@ -1590,14 +1526,14 @@ fn ignore_gdb(config: &Config, line: &DirectiveLine<'_>) -> IgnoreDecision {
15901526
}
15911527

15921528
fn ignore_lldb(config: &Config, line: &DirectiveLine<'_>) -> IgnoreDecision {
1593-
let &DirectiveLine { raw_directive: line, .. } = line;
1594-
15951529
if config.debugger != Some(Debugger::Lldb) {
15961530
return IgnoreDecision::Continue;
15971531
}
15981532

15991533
if let Some(actual_version) = config.lldb_version {
1600-
if let Some(rest) = line.strip_prefix("min-lldb-version:").map(str::trim) {
1534+
if line.name == "min-lldb-version"
1535+
&& let Some(rest) = line.value_after_colon().map(str::trim)
1536+
{
16011537
let min_version = rest.parse().unwrap_or_else(|e| {
16021538
panic!("Unexpected format of LLDB version string: {}\n{:?}", rest, e);
16031539
});

src/tools/compiletest/src/directives/auxiliary.rs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -50,9 +50,7 @@ pub(super) fn parse_and_update_aux(
5050
testfile: &Utf8Path,
5151
aux: &mut AuxProps,
5252
) {
53-
let &DirectiveLine { raw_directive: ln, .. } = directive_line;
54-
55-
if !(ln.starts_with("aux-") || ln.starts_with("proc-macro")) {
53+
if !(directive_line.name.starts_with("aux-") || directive_line.name == "proc-macro") {
5654
return;
5755
}
5856

src/tools/compiletest/src/directives/cfg.rs

Lines changed: 11 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@ use crate::directives::{DirectiveLine, IgnoreDecision};
66
const EXTRA_ARCHS: &[&str] = &["spirv"];
77

88
pub(super) fn handle_ignore(config: &Config, line: &DirectiveLine<'_>) -> IgnoreDecision {
9-
let parsed = parse_cfg_name_directive(config, line, "ignore");
10-
let &DirectiveLine { raw_directive: line, .. } = line;
9+
let parsed = parse_cfg_name_directive(config, line, "ignore-");
10+
let line = line.display();
1111

1212
match parsed.outcome {
1313
MatchOutcome::NoMatch => IgnoreDecision::Continue,
@@ -24,8 +24,8 @@ pub(super) fn handle_ignore(config: &Config, line: &DirectiveLine<'_>) -> Ignore
2424
}
2525

2626
pub(super) fn handle_only(config: &Config, line: &DirectiveLine<'_>) -> IgnoreDecision {
27-
let parsed = parse_cfg_name_directive(config, line, "only");
28-
let &DirectiveLine { raw_directive: line, .. } = line;
27+
let parsed = parse_cfg_name_directive(config, line, "only-");
28+
let line = line.display();
2929

3030
match parsed.outcome {
3131
MatchOutcome::Match => IgnoreDecision::Continue,
@@ -50,18 +50,14 @@ fn parse_cfg_name_directive<'a>(
5050
line: &'a DirectiveLine<'a>,
5151
prefix: &str,
5252
) -> ParsedNameDirective<'a> {
53-
let &DirectiveLine { raw_directive: line, .. } = line;
54-
55-
if !line.as_bytes().starts_with(prefix.as_bytes()) {
56-
return ParsedNameDirective::not_a_directive();
57-
}
58-
if line.as_bytes().get(prefix.len()) != Some(&b'-') {
53+
let Some(name) = line.name.strip_prefix(prefix) else {
5954
return ParsedNameDirective::not_a_directive();
60-
}
61-
let line = &line[prefix.len() + 1..];
55+
};
6256

63-
let (name, comment) =
64-
line.split_once(&[':', ' ']).map(|(l, c)| (l, Some(c))).unwrap_or((line, None));
57+
// FIXME(Zalathar): This currently allows either a space or a colon, and
58+
// treats any "value" after a colon as though it were a remark.
59+
// We should instead forbid the colon syntax for these directives.
60+
let comment = line.remark_after_space().or_else(|| line.value_after_colon());
6561

6662
// Some of the matchers might be "" depending on what the target information is. To avoid
6763
// problems we outright reject empty directives.
@@ -269,7 +265,7 @@ fn parse_cfg_name_directive<'a>(
269265
message: "when performing tests on dist toolchain"
270266
}
271267

272-
if prefix == "ignore" && outcome == MatchOutcome::Invalid {
268+
if prefix == "ignore-" && outcome == MatchOutcome::Invalid {
273269
// Don't error out for ignore-tidy-* diretives, as those are not handled by compiletest.
274270
if name.starts_with("tidy-") {
275271
outcome = MatchOutcome::External;

0 commit comments

Comments
 (0)