compiletest/directives/
handlers.rs

1use std::collections::HashMap;
2use std::sync::{Arc, LazyLock};
3
4use crate::common::Config;
5use crate::directives::{
6    DirectiveLine, NormalizeKind, NormalizeRule, TestProps, parse_and_update_aux,
7    parse_edition_range, split_flags,
8};
9use crate::errors::ErrorKind;
10
11pub(crate) static DIRECTIVE_HANDLERS_MAP: LazyLock<HashMap<&str, Handler>> =
12    LazyLock::new(make_directive_handlers_map);
13
14#[derive(Clone)]
15pub(crate) struct Handler {
16    handler_fn: Arc<dyn Fn(HandlerArgs<'_>) + Send + Sync>,
17}
18
19impl Handler {
20    pub(crate) fn handle(&self, config: &Config, line: &DirectiveLine<'_>, props: &mut TestProps) {
21        (self.handler_fn)(HandlerArgs { config, line, props })
22    }
23}
24
25struct HandlerArgs<'a> {
26    config: &'a Config,
27    line: &'a DirectiveLine<'a>,
28    props: &'a mut TestProps,
29}
30
31/// Intermediate data structure, used for defining a list of handlers.
32struct NamedHandler {
33    names: Vec<&'static str>,
34    handler: Handler,
35}
36
37/// Helper function to create a simple handler, so that changes can be made
38/// to the handler struct without disturbing existing handler declarations.
39fn handler(
40    name: &'static str,
41    handler_fn: impl Fn(&Config, &DirectiveLine<'_>, &mut TestProps) + Send + Sync + 'static,
42) -> NamedHandler {
43    multi_handler(&[name], handler_fn)
44}
45
46/// Associates the same handler function with multiple directive names.
47fn multi_handler(
48    names: &[&'static str],
49    handler_fn: impl Fn(&Config, &DirectiveLine<'_>, &mut TestProps) + Send + Sync + 'static,
50) -> NamedHandler {
51    NamedHandler {
52        names: names.to_owned(),
53        handler: Handler {
54            handler_fn: Arc::new(move |args| handler_fn(args.config, args.line, args.props)),
55        },
56    }
57}
58
59fn make_directive_handlers_map() -> HashMap<&'static str, Handler> {
60    use crate::directives::directives::*;
61
62    // FIXME(Zalathar): Now that most directive-processing has been extracted
63    // into individual handlers, there should be many opportunities to simplify
64    // these handlers, e.g. by getting rid of now-redundant name checks.
65
66    let handlers: Vec<NamedHandler> = vec![
67        handler(ERROR_PATTERN, |config, ln, props| {
68            config.push_name_value_directive(ln, ERROR_PATTERN, &mut props.error_patterns, |r| r);
69        }),
70        handler(REGEX_ERROR_PATTERN, |config, ln, props| {
71            config.push_name_value_directive(
72                ln,
73                REGEX_ERROR_PATTERN,
74                &mut props.regex_error_patterns,
75                |r| r,
76            );
77        }),
78        handler(DOC_FLAGS, |config, ln, props| {
79            config.push_name_value_directive(ln, DOC_FLAGS, &mut props.doc_flags, |r| r);
80        }),
81        handler(COMPILE_FLAGS, |config, ln, props| {
82            if let Some(flags) = config.parse_name_value_directive(ln, COMPILE_FLAGS) {
83                let flags = split_flags(&flags);
84                // FIXME(#147955): Extract and unify this with other handlers that
85                // check compiler flags, e.g. MINICORE_COMPILE_FLAGS.
86                for (i, flag) in flags.iter().enumerate() {
87                    if flag == "--edition" || flag.starts_with("--edition=") {
88                        panic!("you must use `//@ edition` to configure the edition");
89                    }
90                    if (flag == "-C"
91                        && flags.get(i + 1).is_some_and(|v| v.starts_with("incremental=")))
92                        || flag.starts_with("-Cincremental=")
93                    {
94                        panic!("you must use `//@ incremental` to enable incremental compilation");
95                    }
96                }
97                props.compile_flags.extend(flags);
98            }
99        }),
100        handler("edition", |config, ln, props| {
101            if let Some(range) = parse_edition_range(config, ln) {
102                props.edition = Some(range.edition_to_test(config.edition));
103            }
104        }),
105        handler("revisions", |config, ln, props| {
106            config.parse_and_update_revisions(ln, &mut props.revisions);
107        }),
108        handler(RUN_FLAGS, |config, ln, props| {
109            if let Some(flags) = config.parse_name_value_directive(ln, RUN_FLAGS) {
110                props.run_flags.extend(split_flags(&flags));
111            }
112        }),
113        handler("pp-exact", |config, ln, props| {
114            if props.pp_exact.is_none() {
115                props.pp_exact = config.parse_pp_exact(ln);
116            }
117        }),
118        handler(SHOULD_ICE, |config, ln, props| {
119            config.set_name_directive(ln, SHOULD_ICE, &mut props.should_ice);
120        }),
121        handler(BUILD_AUX_DOCS, |config, ln, props| {
122            config.set_name_directive(ln, BUILD_AUX_DOCS, &mut props.build_aux_docs);
123        }),
124        handler(UNIQUE_DOC_OUT_DIR, |config, ln, props| {
125            config.set_name_directive(ln, UNIQUE_DOC_OUT_DIR, &mut props.unique_doc_out_dir);
126        }),
127        handler(FORCE_HOST, |config, ln, props| {
128            config.set_name_directive(ln, FORCE_HOST, &mut props.force_host);
129        }),
130        handler(CHECK_STDOUT, |config, ln, props| {
131            config.set_name_directive(ln, CHECK_STDOUT, &mut props.check_stdout);
132        }),
133        handler(CHECK_RUN_RESULTS, |config, ln, props| {
134            config.set_name_directive(ln, CHECK_RUN_RESULTS, &mut props.check_run_results);
135        }),
136        handler(DONT_CHECK_COMPILER_STDOUT, |config, ln, props| {
137            config.set_name_directive(
138                ln,
139                DONT_CHECK_COMPILER_STDOUT,
140                &mut props.dont_check_compiler_stdout,
141            );
142        }),
143        handler(DONT_CHECK_COMPILER_STDERR, |config, ln, props| {
144            config.set_name_directive(
145                ln,
146                DONT_CHECK_COMPILER_STDERR,
147                &mut props.dont_check_compiler_stderr,
148            );
149        }),
150        handler(NO_PREFER_DYNAMIC, |config, ln, props| {
151            config.set_name_directive(ln, NO_PREFER_DYNAMIC, &mut props.no_prefer_dynamic);
152        }),
153        handler(PRETTY_MODE, |config, ln, props| {
154            if let Some(m) = config.parse_name_value_directive(ln, PRETTY_MODE) {
155                props.pretty_mode = m;
156            }
157        }),
158        handler(PRETTY_COMPARE_ONLY, |config, ln, props| {
159            config.set_name_directive(ln, PRETTY_COMPARE_ONLY, &mut props.pretty_compare_only);
160        }),
161        multi_handler(
162            &[AUX_BUILD, AUX_BIN, AUX_CRATE, PROC_MACRO, AUX_CODEGEN_BACKEND],
163            |config, ln, props| {
164                // Call a helper method to deal with aux-related directives.
165                parse_and_update_aux(config, ln, &mut props.aux);
166            },
167        ),
168        handler(EXEC_ENV, |config, ln, props| {
169            config.push_name_value_directive(ln, EXEC_ENV, &mut props.exec_env, Config::parse_env);
170        }),
171        handler(UNSET_EXEC_ENV, |config, ln, props| {
172            config.push_name_value_directive(ln, UNSET_EXEC_ENV, &mut props.unset_exec_env, |r| {
173                r.trim().to_owned()
174            });
175        }),
176        handler(RUSTC_ENV, |config, ln, props| {
177            config.push_name_value_directive(
178                ln,
179                RUSTC_ENV,
180                &mut props.rustc_env,
181                Config::parse_env,
182            );
183        }),
184        handler(UNSET_RUSTC_ENV, |config, ln, props| {
185            config.push_name_value_directive(
186                ln,
187                UNSET_RUSTC_ENV,
188                &mut props.unset_rustc_env,
189                |r| r.trim().to_owned(),
190            );
191        }),
192        handler(FORBID_OUTPUT, |config, ln, props| {
193            config.push_name_value_directive(ln, FORBID_OUTPUT, &mut props.forbid_output, |r| r);
194        }),
195        handler(CHECK_TEST_LINE_NUMBERS_MATCH, |config, ln, props| {
196            config.set_name_directive(
197                ln,
198                CHECK_TEST_LINE_NUMBERS_MATCH,
199                &mut props.check_test_line_numbers_match,
200            );
201        }),
202        multi_handler(&["check-pass", "build-pass", "run-pass"], |config, ln, props| {
203            props.update_pass_mode(ln, config);
204        }),
205        multi_handler(
206            &["check-fail", "build-fail", "run-fail", "run-crash", "run-fail-or-crash"],
207            |config, ln, props| {
208                props.update_fail_mode(ln, config);
209            },
210        ),
211        handler(IGNORE_PASS, |config, ln, props| {
212            config.set_name_directive(ln, IGNORE_PASS, &mut props.ignore_pass);
213        }),
214        multi_handler(
215            &[
216                "normalize-stdout",
217                "normalize-stderr",
218                "normalize-stderr-32bit",
219                "normalize-stderr-64bit",
220            ],
221            |config, ln, props| {
222                if let Some(NormalizeRule { kind, regex, replacement }) =
223                    config.parse_custom_normalization(ln)
224                {
225                    let rule_tuple = (regex, replacement);
226                    match kind {
227                        NormalizeKind::Stdout => props.normalize_stdout.push(rule_tuple),
228                        NormalizeKind::Stderr => props.normalize_stderr.push(rule_tuple),
229                        NormalizeKind::Stderr32bit => {
230                            if config.target_cfg().pointer_width == 32 {
231                                props.normalize_stderr.push(rule_tuple);
232                            }
233                        }
234                        NormalizeKind::Stderr64bit => {
235                            if config.target_cfg().pointer_width == 64 {
236                                props.normalize_stderr.push(rule_tuple);
237                            }
238                        }
239                    }
240                }
241            },
242        ),
243        handler(FAILURE_STATUS, |config, ln, props| {
244            if let Some(code) = config
245                .parse_name_value_directive(ln, FAILURE_STATUS)
246                .and_then(|code| code.trim().parse::<i32>().ok())
247            {
248                props.failure_status = Some(code);
249            }
250        }),
251        handler(DONT_CHECK_FAILURE_STATUS, |config, ln, props| {
252            config.set_name_directive(
253                ln,
254                DONT_CHECK_FAILURE_STATUS,
255                &mut props.dont_check_failure_status,
256            );
257        }),
258        handler(RUN_RUSTFIX, |config, ln, props| {
259            config.set_name_directive(ln, RUN_RUSTFIX, &mut props.run_rustfix);
260        }),
261        handler(RUSTFIX_ONLY_MACHINE_APPLICABLE, |config, ln, props| {
262            config.set_name_directive(
263                ln,
264                RUSTFIX_ONLY_MACHINE_APPLICABLE,
265                &mut props.rustfix_only_machine_applicable,
266            );
267        }),
268        handler(ASSEMBLY_OUTPUT, |config, ln, props| {
269            config.set_name_value_directive(ln, ASSEMBLY_OUTPUT, &mut props.assembly_output, |r| {
270                r.trim().to_string()
271            });
272        }),
273        handler(STDERR_PER_BITWIDTH, |config, ln, props| {
274            config.set_name_directive(ln, STDERR_PER_BITWIDTH, &mut props.stderr_per_bitwidth);
275        }),
276        handler(INCREMENTAL, |config, ln, props| {
277            config.set_name_directive(ln, INCREMENTAL, &mut props.incremental);
278        }),
279        handler(KNOWN_BUG, |config, ln, props| {
280            // Unlike the other `name_value_directive`s this needs to be handled manually,
281            // because it sets a `bool` flag.
282            if let Some(known_bug) = config.parse_name_value_directive(ln, KNOWN_BUG) {
283                let known_bug = known_bug.trim();
284                if known_bug == "unknown"
285                    || known_bug.split(',').all(|issue_ref| {
286                        issue_ref
287                            .trim()
288                            .split_once('#')
289                            .filter(|(_, number)| number.chars().all(|digit| digit.is_numeric()))
290                            .is_some()
291                    })
292                {
293                    props.known_bug = true;
294                } else {
295                    panic!(
296                        "Invalid known-bug value: {known_bug}\nIt requires comma-separated issue references (`#000` or `chalk#000`) or `known-bug: unknown`."
297                    );
298                }
299            } else if config.parse_name_directive(ln, KNOWN_BUG) {
300                panic!(
301                    "Invalid known-bug attribute, requires comma-separated issue references (`#000` or `chalk#000`) or `known-bug: unknown`."
302                );
303            }
304        }),
305        handler(TEST_MIR_PASS, |config, ln, props| {
306            config.set_name_value_directive(ln, TEST_MIR_PASS, &mut props.mir_unit_test, |s| {
307                s.trim().to_string()
308            });
309        }),
310        handler(REMAP_SRC_BASE, |config, ln, props| {
311            config.set_name_directive(ln, REMAP_SRC_BASE, &mut props.remap_src_base);
312        }),
313        handler(LLVM_COV_FLAGS, |config, ln, props| {
314            if let Some(flags) = config.parse_name_value_directive(ln, LLVM_COV_FLAGS) {
315                props.llvm_cov_flags.extend(split_flags(&flags));
316            }
317        }),
318        handler(FILECHECK_FLAGS, |config, ln, props| {
319            if let Some(flags) = config.parse_name_value_directive(ln, FILECHECK_FLAGS) {
320                props.filecheck_flags.extend(split_flags(&flags));
321            }
322        }),
323        handler(NO_AUTO_CHECK_CFG, |config, ln, props| {
324            config.set_name_directive(ln, NO_AUTO_CHECK_CFG, &mut props.no_auto_check_cfg);
325        }),
326        handler(ADD_MINICORE, |config, ln, props| {
327            props.update_add_minicore(ln, config);
328        }),
329        handler(MINICORE_COMPILE_FLAGS, |config, ln, props| {
330            if let Some(flags) = config.parse_name_value_directive(ln, MINICORE_COMPILE_FLAGS) {
331                let flags = split_flags(&flags);
332                // FIXME(#147955): Extract and unify this with other handlers that
333                // check compiler flags, e.g. COMPILE_FLAGS.
334                for flag in &flags {
335                    if flag == "--edition" || flag.starts_with("--edition=") {
336                        panic!("you must use `//@ edition` to configure the edition");
337                    }
338                }
339                props.minicore_compile_flags.extend(flags);
340            }
341        }),
342        handler(DONT_REQUIRE_ANNOTATIONS, |config, ln, props| {
343            if let Some(err_kind) = config.parse_name_value_directive(ln, DONT_REQUIRE_ANNOTATIONS)
344            {
345                props
346                    .dont_require_annotations
347                    .insert(ErrorKind::expect_from_user_str(err_kind.trim()));
348            }
349        }),
350        handler(DISABLE_GDB_PRETTY_PRINTERS, |config, ln, props| {
351            config.set_name_directive(
352                ln,
353                DISABLE_GDB_PRETTY_PRINTERS,
354                &mut props.disable_gdb_pretty_printers,
355            );
356        }),
357        handler(COMPARE_OUTPUT_BY_LINES, |config, ln, props| {
358            config.set_name_directive(
359                ln,
360                COMPARE_OUTPUT_BY_LINES,
361                &mut props.compare_output_by_lines,
362            );
363        }),
364    ];
365
366    handlers
367        .into_iter()
368        .flat_map(|NamedHandler { names, handler }| {
369            names.into_iter().map(move |name| (name, Handler::clone(&handler)))
370        })
371        .collect()
372}