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
32struct NamedHandler {
34 names: Vec<&'static str>,
35 handler: Handler,
36}
37
38fn 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
47fn 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 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 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 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 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 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 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 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}