Skip to main content

compiletest/directives/
handlers.rs

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