]> git.proxmox.com Git - rustc.git/blob - src/tools/compiletest/src/header/cfg.rs
New upstream version 1.76.0+dfsg1
[rustc.git] / src / tools / compiletest / src / header / cfg.rs
1 use crate::common::{CompareMode, Config, Debugger};
2 use crate::header::IgnoreDecision;
3 use std::collections::HashSet;
4
5 const EXTRA_ARCHS: &[&str] = &["spirv"];
6
7 pub(super) fn handle_ignore(config: &Config, line: &str) -> IgnoreDecision {
8 let parsed = parse_cfg_name_directive(config, line, "ignore");
9 match parsed.outcome {
10 MatchOutcome::NoMatch => IgnoreDecision::Continue,
11 MatchOutcome::Match => IgnoreDecision::Ignore {
12 reason: match parsed.comment {
13 Some(comment) => format!("ignored {} ({comment})", parsed.pretty_reason.unwrap()),
14 None => format!("ignored {}", parsed.pretty_reason.unwrap()),
15 },
16 },
17 MatchOutcome::Invalid => IgnoreDecision::Error { message: format!("invalid line: {line}") },
18 MatchOutcome::External => IgnoreDecision::Continue,
19 MatchOutcome::NotADirective => IgnoreDecision::Continue,
20 }
21 }
22
23 pub(super) fn handle_only(config: &Config, line: &str) -> IgnoreDecision {
24 let parsed = parse_cfg_name_directive(config, line, "only");
25 match parsed.outcome {
26 MatchOutcome::Match => IgnoreDecision::Continue,
27 MatchOutcome::NoMatch => IgnoreDecision::Ignore {
28 reason: match parsed.comment {
29 Some(comment) => {
30 format!("only executed {} ({comment})", parsed.pretty_reason.unwrap())
31 }
32 None => format!("only executed {}", parsed.pretty_reason.unwrap()),
33 },
34 },
35 MatchOutcome::Invalid => IgnoreDecision::Error { message: format!("invalid line: {line}") },
36 MatchOutcome::External => IgnoreDecision::Continue,
37 MatchOutcome::NotADirective => IgnoreDecision::Continue,
38 }
39 }
40
41 /// Parses a name-value directive which contains config-specific information, e.g., `ignore-x86`
42 /// or `normalize-stderr-32bit`.
43 pub(super) fn parse_cfg_name_directive<'a>(
44 config: &Config,
45 line: &'a str,
46 prefix: &str,
47 ) -> ParsedNameDirective<'a> {
48 if !line.as_bytes().starts_with(prefix.as_bytes()) {
49 return ParsedNameDirective::not_a_directive();
50 }
51 if line.as_bytes().get(prefix.len()) != Some(&b'-') {
52 return ParsedNameDirective::not_a_directive();
53 }
54 let line = &line[prefix.len() + 1..];
55
56 let (name, comment) =
57 line.split_once(&[':', ' ']).map(|(l, c)| (l, Some(c))).unwrap_or((line, None));
58
59 // Some of the matchers might be "" depending on what the target information is. To avoid
60 // problems we outright reject empty directives.
61 if name == "" {
62 return ParsedNameDirective::not_a_directive();
63 }
64
65 let mut outcome = MatchOutcome::Invalid;
66 let mut message = None;
67
68 macro_rules! condition {
69 (
70 name: $name:expr,
71 $(allowed_names: $allowed_names:expr,)?
72 $(condition: $condition:expr,)?
73 message: $($message:tt)*
74 ) => {{
75 // This is not inlined to avoid problems with macro repetitions.
76 let format_message = || format!($($message)*);
77
78 if outcome != MatchOutcome::Invalid {
79 // Ignore all other matches if we already found one
80 } else if $name.custom_matches(name) {
81 message = Some(format_message());
82 if true $(&& $condition)? {
83 outcome = MatchOutcome::Match;
84 } else {
85 outcome = MatchOutcome::NoMatch;
86 }
87 }
88 $(else if $allowed_names.custom_contains(name) {
89 message = Some(format_message());
90 outcome = MatchOutcome::NoMatch;
91 })?
92 }};
93 }
94
95 let target_cfgs = config.target_cfgs();
96 let target_cfg = config.target_cfg();
97
98 condition! {
99 name: "test",
100 message: "always"
101 }
102 condition! {
103 name: &config.target,
104 allowed_names: &target_cfgs.all_targets,
105 message: "when the target is {name}"
106 }
107 condition! {
108 name: &[
109 Some(&*target_cfg.os),
110 // If something is ignored for emscripten, it likely also needs to be
111 // ignored for wasm32-unknown-unknown.
112 (config.target == "wasm32-unknown-unknown").then_some("emscripten"),
113 ],
114 allowed_names: &target_cfgs.all_oses,
115 message: "when the operating system is {name}"
116 }
117 condition! {
118 name: &target_cfg.env,
119 allowed_names: &target_cfgs.all_envs,
120 message: "when the target environment is {name}"
121 }
122 condition! {
123 name: &target_cfg.os_and_env(),
124 allowed_names: &target_cfgs.all_oses_and_envs,
125 message: "when the operating system and target environment are {name}"
126 }
127 condition! {
128 name: &target_cfg.abi,
129 allowed_names: &target_cfgs.all_abis,
130 message: "when the ABI is {name}"
131 }
132 condition! {
133 name: &target_cfg.arch,
134 allowed_names: ContainsEither { a: &target_cfgs.all_archs, b: &EXTRA_ARCHS },
135 message: "when the architecture is {name}"
136 }
137 condition! {
138 name: format!("{}bit", target_cfg.pointer_width),
139 allowed_names: &target_cfgs.all_pointer_widths,
140 message: "when the pointer width is {name}"
141 }
142 condition! {
143 name: &*target_cfg.families,
144 allowed_names: &target_cfgs.all_families,
145 message: "when the target family is {name}"
146 }
147
148 // `wasm32-bare` is an alias to refer to just wasm32-unknown-unknown
149 // (in contrast to `wasm32` which also matches non-bare targets)
150 condition! {
151 name: "wasm32-bare",
152 condition: config.target == "wasm32-unknown-unknown",
153 message: "when the target is WASM"
154 }
155
156 condition! {
157 name: "thumb",
158 condition: config.target.starts_with("thumb"),
159 message: "when the architecture is part of the Thumb family"
160 }
161
162 // Technically the locally built compiler uses the "dev" channel rather than the "nightly"
163 // channel, even though most people don't know or won't care about it. To avoid confusion, we
164 // treat the "dev" channel as the "nightly" channel when processing the directive.
165 condition! {
166 name: if config.channel == "dev" { "nightly" } else { &config.channel },
167 allowed_names: &["stable", "beta", "nightly"],
168 message: "when the release channel is {name}",
169 }
170
171 condition! {
172 name: "cross-compile",
173 condition: config.target != config.host,
174 message: "when cross-compiling"
175 }
176 condition! {
177 name: "endian-big",
178 condition: config.is_big_endian(),
179 message: "on big-endian targets",
180 }
181 condition! {
182 name: config.stage_id.split('-').next().unwrap(),
183 allowed_names: &["stage0", "stage1", "stage2"],
184 message: "when the bootstrapping stage is {name}",
185 }
186 condition! {
187 name: "remote",
188 condition: config.remote_test_client.is_some(),
189 message: "when running tests remotely",
190 }
191 condition! {
192 name: "debug",
193 condition: config.with_debug_assertions,
194 message: "when running tests with `ignore-debug` header",
195 }
196 condition! {
197 name: config.debugger.as_ref().map(|d| d.to_str()),
198 allowed_names: &Debugger::STR_VARIANTS,
199 message: "when the debugger is {name}",
200 }
201 condition! {
202 name: config.compare_mode
203 .as_ref()
204 .map(|d| format!("compare-mode-{}", d.to_str())),
205 allowed_names: ContainsPrefixed {
206 prefix: "compare-mode-",
207 inner: CompareMode::STR_VARIANTS,
208 },
209 message: "when comparing with {name}",
210 }
211
212 if prefix == "ignore" && outcome == MatchOutcome::Invalid {
213 // Don't error out for ignore-tidy-* diretives, as those are not handled by compiletest.
214 if name.starts_with("tidy-") {
215 outcome = MatchOutcome::External;
216 }
217
218 // Don't error out for ignore-pass, as that is handled elsewhere.
219 if name == "pass" {
220 outcome = MatchOutcome::External;
221 }
222
223 // Don't error out for ignore-llvm-version, that has a custom syntax and is handled
224 // elsewhere.
225 if name == "llvm-version" {
226 outcome = MatchOutcome::External;
227 }
228
229 // Don't error out for ignore-llvm-version, that has a custom syntax and is handled
230 // elsewhere.
231 if name == "gdb-version" {
232 outcome = MatchOutcome::External;
233 }
234 }
235
236 ParsedNameDirective {
237 name: Some(name),
238 comment: comment.map(|c| c.trim().trim_start_matches('-').trim()),
239 outcome,
240 pretty_reason: message,
241 }
242 }
243
244 /// The result of parse_cfg_name_directive.
245 #[derive(Clone, PartialEq, Debug)]
246 pub(super) struct ParsedNameDirective<'a> {
247 pub(super) name: Option<&'a str>,
248 pub(super) pretty_reason: Option<String>,
249 pub(super) comment: Option<&'a str>,
250 pub(super) outcome: MatchOutcome,
251 }
252
253 impl ParsedNameDirective<'_> {
254 fn not_a_directive() -> Self {
255 Self {
256 name: None,
257 pretty_reason: None,
258 comment: None,
259 outcome: MatchOutcome::NotADirective,
260 }
261 }
262 }
263
264 #[derive(Clone, Copy, PartialEq, Debug)]
265 pub(super) enum MatchOutcome {
266 /// No match.
267 NoMatch,
268 /// Match.
269 Match,
270 /// The directive was invalid.
271 Invalid,
272 /// The directive is handled by other parts of our tooling.
273 External,
274 /// The line is not actually a directive.
275 NotADirective,
276 }
277
278 trait CustomContains {
279 fn custom_contains(&self, item: &str) -> bool;
280 }
281
282 impl CustomContains for HashSet<String> {
283 fn custom_contains(&self, item: &str) -> bool {
284 self.contains(item)
285 }
286 }
287
288 impl CustomContains for &[&str] {
289 fn custom_contains(&self, item: &str) -> bool {
290 self.contains(&item)
291 }
292 }
293
294 impl<const N: usize> CustomContains for [&str; N] {
295 fn custom_contains(&self, item: &str) -> bool {
296 self.contains(&item)
297 }
298 }
299
300 struct ContainsPrefixed<T: CustomContains> {
301 prefix: &'static str,
302 inner: T,
303 }
304
305 impl<T: CustomContains> CustomContains for ContainsPrefixed<T> {
306 fn custom_contains(&self, item: &str) -> bool {
307 match item.strip_prefix(self.prefix) {
308 Some(stripped) => self.inner.custom_contains(stripped),
309 None => false,
310 }
311 }
312 }
313
314 struct ContainsEither<'a, A: CustomContains, B: CustomContains> {
315 a: &'a A,
316 b: &'a B,
317 }
318
319 impl<A: CustomContains, B: CustomContains> CustomContains for ContainsEither<'_, A, B> {
320 fn custom_contains(&self, item: &str) -> bool {
321 self.a.custom_contains(item) || self.b.custom_contains(item)
322 }
323 }
324
325 trait CustomMatches {
326 fn custom_matches(&self, name: &str) -> bool;
327 }
328
329 impl CustomMatches for &str {
330 fn custom_matches(&self, name: &str) -> bool {
331 name == *self
332 }
333 }
334
335 impl CustomMatches for String {
336 fn custom_matches(&self, name: &str) -> bool {
337 name == self
338 }
339 }
340
341 impl<T: CustomMatches> CustomMatches for &[T] {
342 fn custom_matches(&self, name: &str) -> bool {
343 self.iter().any(|m| m.custom_matches(name))
344 }
345 }
346
347 impl<const N: usize, T: CustomMatches> CustomMatches for [T; N] {
348 fn custom_matches(&self, name: &str) -> bool {
349 self.iter().any(|m| m.custom_matches(name))
350 }
351 }
352
353 impl<T: CustomMatches> CustomMatches for Option<T> {
354 fn custom_matches(&self, name: &str) -> bool {
355 match self {
356 Some(inner) => inner.custom_matches(name),
357 None => false,
358 }
359 }
360 }