]>
Commit | Line | Data |
---|---|---|
dfeec247 XL |
1 | /// The expansion from a test function to the appropriate test struct for libtest |
2 | /// Ideally, this code would be in libtest but for efficiency and error messages it lives here. | |
a2a8927a | 3 | use crate::util::{check_builtin_macro_attribute, warn_on_duplicate_attribute}; |
fc512014 | 4 | use rustc_ast::ptr::P; |
353b0b11 | 5 | use rustc_ast::{self as ast, attr}; |
74b04a01 | 6 | use rustc_ast_pretty::pprust; |
5099ac24 | 7 | use rustc_errors::Applicability; |
dfeec247 | 8 | use rustc_expand::base::*; |
f9f354fc | 9 | use rustc_span::symbol::{sym, Ident, Symbol}; |
353b0b11 | 10 | use rustc_span::{FileNameDisplayPreference, Span}; |
dfeec247 | 11 | use std::iter; |
9ffffee4 | 12 | use thin_vec::{thin_vec, ThinVec}; |
dfeec247 | 13 | |
487cf647 FG |
14 | /// #[test_case] is used by custom test authors to mark tests |
15 | /// When building for test, it needs to make the item public and gensym the name | |
16 | /// Otherwise, we'll omit the item. This behavior means that any item annotated | |
17 | /// with #[test_case] is never addressable. | |
18 | /// | |
19 | /// We mark item with an inert attribute "rustc_test_marker" which the test generation | |
20 | /// logic will pick up on. | |
dfeec247 XL |
21 | pub fn expand_test_case( |
22 | ecx: &mut ExtCtxt<'_>, | |
23 | attr_sp: Span, | |
24 | meta_item: &ast::MetaItem, | |
25 | anno_item: Annotatable, | |
26 | ) -> Vec<Annotatable> { | |
27 | check_builtin_macro_attribute(ecx, meta_item, sym::test_case); | |
a2a8927a | 28 | warn_on_duplicate_attribute(&ecx, &anno_item, sym::test_case); |
dfeec247 XL |
29 | |
30 | if !ecx.ecfg.should_test { | |
31 | return vec![]; | |
32 | } | |
33 | ||
34 | let sp = ecx.with_def_site_ctxt(attr_sp); | |
353b0b11 FG |
35 | let (mut item, is_stmt) = match anno_item { |
36 | Annotatable::Item(item) => (item, false), | |
37 | Annotatable::Stmt(stmt) if let ast::StmtKind::Item(_) = stmt.kind => if let ast::StmtKind::Item(i) = stmt.into_inner().kind { | |
38 | (i, true) | |
39 | } else { | |
40 | unreachable!() | |
41 | }, | |
42 | _ => { | |
43 | ecx.struct_span_err( | |
44 | anno_item.span(), | |
45 | "`#[test_case]` attribute is only allowed on items", | |
46 | ) | |
47 | .emit(); | |
48 | ||
49 | return vec![]; | |
50 | } | |
51 | }; | |
dfeec247 | 52 | item = item.map(|mut item| { |
2b03887a FG |
53 | let test_path_symbol = Symbol::intern(&item_path( |
54 | // skip the name of the root module | |
55 | &ecx.current_expansion.module.mod_path[1..], | |
56 | &item.ident, | |
57 | )); | |
1b1a35ee XL |
58 | item.vis = ast::Visibility { |
59 | span: item.vis.span, | |
60 | kind: ast::VisibilityKind::Public, | |
61 | tokens: None, | |
62 | }; | |
dfeec247 | 63 | item.ident.span = item.ident.span.with_ctxt(sp.ctxt()); |
487cf647 | 64 | item.attrs.push(ecx.attr_name_value_str(sym::rustc_test_marker, test_path_symbol, sp)); |
dfeec247 XL |
65 | item |
66 | }); | |
67 | ||
353b0b11 FG |
68 | let ret = if is_stmt { |
69 | Annotatable::Stmt(P(ecx.stmt_item(item.span, item))) | |
70 | } else { | |
71 | Annotatable::Item(item) | |
72 | }; | |
73 | ||
74 | vec![ret] | |
dfeec247 XL |
75 | } |
76 | ||
77 | pub fn expand_test( | |
78 | cx: &mut ExtCtxt<'_>, | |
79 | attr_sp: Span, | |
80 | meta_item: &ast::MetaItem, | |
81 | item: Annotatable, | |
82 | ) -> Vec<Annotatable> { | |
83 | check_builtin_macro_attribute(cx, meta_item, sym::test); | |
a2a8927a | 84 | warn_on_duplicate_attribute(&cx, &item, sym::test); |
dfeec247 XL |
85 | expand_test_or_bench(cx, attr_sp, item, false) |
86 | } | |
87 | ||
88 | pub fn expand_bench( | |
89 | cx: &mut ExtCtxt<'_>, | |
90 | attr_sp: Span, | |
91 | meta_item: &ast::MetaItem, | |
92 | item: Annotatable, | |
93 | ) -> Vec<Annotatable> { | |
94 | check_builtin_macro_attribute(cx, meta_item, sym::bench); | |
a2a8927a | 95 | warn_on_duplicate_attribute(&cx, &item, sym::bench); |
dfeec247 XL |
96 | expand_test_or_bench(cx, attr_sp, item, true) |
97 | } | |
98 | ||
99 | pub fn expand_test_or_bench( | |
100 | cx: &mut ExtCtxt<'_>, | |
101 | attr_sp: Span, | |
102 | item: Annotatable, | |
103 | is_bench: bool, | |
104 | ) -> Vec<Annotatable> { | |
105 | // If we're not in test configuration, remove the annotated item | |
106 | if !cx.ecfg.should_test { | |
107 | return vec![]; | |
108 | } | |
109 | ||
fc512014 XL |
110 | let (item, is_stmt) = match item { |
111 | Annotatable::Item(i) => (i, false), | |
112 | Annotatable::Stmt(stmt) if matches!(stmt.kind, ast::StmtKind::Item(_)) => { | |
113 | // FIXME: Use an 'if let' guard once they are implemented | |
114 | if let ast::StmtKind::Item(i) = stmt.into_inner().kind { | |
115 | (i, true) | |
116 | } else { | |
117 | unreachable!() | |
118 | } | |
119 | } | |
ba9703b0 | 120 | other => { |
353b0b11 | 121 | not_testable_error(cx, attr_sp, None); |
ba9703b0 XL |
122 | return vec![other]; |
123 | } | |
dfeec247 XL |
124 | }; |
125 | ||
487cf647 | 126 | let ast::ItemKind::Fn(fn_) = &item.kind else { |
353b0b11 FG |
127 | not_testable_error(cx, attr_sp, Some(&item)); |
128 | return if is_stmt { | |
129 | vec![Annotatable::Stmt(P(ast::Stmt { | |
130 | id: ast::DUMMY_NODE_ID, | |
131 | span: item.span, | |
132 | kind: ast::StmtKind::Item(item), | |
133 | }))] | |
134 | } else { | |
135 | vec![Annotatable::Item(item)] | |
5099ac24 | 136 | }; |
487cf647 | 137 | }; |
dfeec247 XL |
138 | |
139 | // has_*_signature will report any errors in the type so compilation | |
140 | // will fail. We shouldn't try to expand in this case because the errors | |
141 | // would be spurious. | |
142 | if (!is_bench && !has_test_signature(cx, &item)) | |
143 | || (is_bench && !has_bench_signature(cx, &item)) | |
144 | { | |
145 | return vec![Annotatable::Item(item)]; | |
146 | } | |
147 | ||
487cf647 FG |
148 | let sp = cx.with_def_site_ctxt(item.span); |
149 | let ret_ty_sp = cx.with_def_site_ctxt(fn_.sig.decl.output.span()); | |
150 | let attr_sp = cx.with_def_site_ctxt(attr_sp); | |
dfeec247 | 151 | |
f9f354fc | 152 | let test_id = Ident::new(sym::test, attr_sp); |
dfeec247 XL |
153 | |
154 | // creates test::$name | |
487cf647 | 155 | let test_path = |name| cx.path(ret_ty_sp, vec![test_id, Ident::from_str_and_span(name, sp)]); |
dfeec247 XL |
156 | |
157 | // creates test::ShouldPanic::$name | |
3dfed10e XL |
158 | let should_panic_path = |name| { |
159 | cx.path( | |
160 | sp, | |
161 | vec![ | |
162 | test_id, | |
163 | Ident::from_str_and_span("ShouldPanic", sp), | |
164 | Ident::from_str_and_span(name, sp), | |
165 | ], | |
166 | ) | |
167 | }; | |
dfeec247 XL |
168 | |
169 | // creates test::TestType::$name | |
3dfed10e XL |
170 | let test_type_path = |name| { |
171 | cx.path( | |
172 | sp, | |
173 | vec![ | |
174 | test_id, | |
175 | Ident::from_str_and_span("TestType", sp), | |
176 | Ident::from_str_and_span(name, sp), | |
177 | ], | |
178 | ) | |
179 | }; | |
dfeec247 XL |
180 | |
181 | // creates $name: $expr | |
3dfed10e | 182 | let field = |name, expr| cx.field_imm(sp, Ident::from_str_and_span(name, sp), expr); |
dfeec247 XL |
183 | |
184 | let test_fn = if is_bench { | |
185 | // A simple ident for a lambda | |
3dfed10e | 186 | let b = Ident::from_str_and_span("b", attr_sp); |
dfeec247 XL |
187 | |
188 | cx.expr_call( | |
189 | sp, | |
190 | cx.expr_path(test_path("StaticBenchFn")), | |
9ffffee4 | 191 | thin_vec![ |
dfeec247 XL |
192 | // |b| self::test::assert_test_result( |
193 | cx.lambda1( | |
194 | sp, | |
195 | cx.expr_call( | |
196 | sp, | |
197 | cx.expr_path(test_path("assert_test_result")), | |
9ffffee4 | 198 | thin_vec![ |
dfeec247 XL |
199 | // super::$test_fn(b) |
200 | cx.expr_call( | |
487cf647 | 201 | ret_ty_sp, |
dfeec247 | 202 | cx.expr_path(cx.path(sp, vec![item.ident])), |
9ffffee4 | 203 | thin_vec![cx.expr_ident(sp, b)], |
dfeec247 XL |
204 | ), |
205 | ], | |
206 | ), | |
207 | b, | |
208 | ), // ) | |
209 | ], | |
210 | ) | |
211 | } else { | |
212 | cx.expr_call( | |
213 | sp, | |
214 | cx.expr_path(test_path("StaticTestFn")), | |
9ffffee4 | 215 | thin_vec![ |
dfeec247 XL |
216 | // || { |
217 | cx.lambda0( | |
218 | sp, | |
219 | // test::assert_test_result( | |
220 | cx.expr_call( | |
221 | sp, | |
222 | cx.expr_path(test_path("assert_test_result")), | |
9ffffee4 | 223 | thin_vec![ |
dfeec247 | 224 | // $test_fn() |
487cf647 FG |
225 | cx.expr_call( |
226 | ret_ty_sp, | |
227 | cx.expr_path(cx.path(sp, vec![item.ident])), | |
9ffffee4 | 228 | ThinVec::new(), |
487cf647 | 229 | ), // ) |
dfeec247 XL |
230 | ], |
231 | ), // } | |
232 | ), // ) | |
233 | ], | |
234 | ) | |
235 | }; | |
236 | ||
2b03887a FG |
237 | let test_path_symbol = Symbol::intern(&item_path( |
238 | // skip the name of the root module | |
239 | &cx.current_expansion.module.mod_path[1..], | |
240 | &item.ident, | |
241 | )); | |
242 | ||
353b0b11 FG |
243 | let location_info = get_location_info(cx, &item); |
244 | ||
245 | let mut test_const = | |
246 | cx.item( | |
247 | sp, | |
248 | Ident::new(item.ident.name, sp), | |
249 | thin_vec![ | |
250 | // #[cfg(test)] | |
251 | cx.attr_nested_word(sym::cfg, sym::test, attr_sp), | |
252 | // #[rustc_test_marker = "test_case_sort_key"] | |
253 | cx.attr_name_value_str(sym::rustc_test_marker, test_path_symbol, attr_sp), | |
254 | ], | |
255 | // const $ident: test::TestDescAndFn = | |
256 | ast::ItemKind::Const( | |
257 | ast::ConstItem { | |
258 | defaultness: ast::Defaultness::Final, | |
259 | ty: cx.ty(sp, ast::TyKind::Path(None, test_path("TestDescAndFn"))), | |
260 | // test::TestDescAndFn { | |
261 | expr: Some( | |
262 | cx.expr_struct( | |
263 | sp, | |
264 | test_path("TestDescAndFn"), | |
265 | thin_vec![ | |
74b04a01 XL |
266 | // desc: test::TestDesc { |
267 | field( | |
268 | "desc", | |
269 | cx.expr_struct( | |
270 | sp, | |
271 | test_path("TestDesc"), | |
9ffffee4 | 272 | thin_vec![ |
74b04a01 XL |
273 | // name: "path::to::test" |
274 | field( | |
275 | "name", | |
276 | cx.expr_call( | |
dfeec247 | 277 | sp, |
74b04a01 | 278 | cx.expr_path(test_path("StaticTestName")), |
9ffffee4 | 279 | thin_vec![cx.expr_str(sp, test_path_symbol)], |
dfeec247 | 280 | ), |
74b04a01 XL |
281 | ), |
282 | // ignore: true | false | |
353b0b11 | 283 | field("ignore", cx.expr_bool(sp, should_ignore(&item)),), |
5e7ed085 FG |
284 | // ignore_message: Some("...") | None |
285 | field( | |
286 | "ignore_message", | |
353b0b11 | 287 | if let Some(msg) = should_ignore_message(&item) { |
5e7ed085 FG |
288 | cx.expr_some(sp, cx.expr_str(sp, msg)) |
289 | } else { | |
290 | cx.expr_none(sp) | |
291 | }, | |
292 | ), | |
353b0b11 FG |
293 | // source_file: <relative_path_of_source_file> |
294 | field("source_file", cx.expr_str(sp, location_info.0)), | |
295 | // start_line: start line of the test fn identifier. | |
296 | field("start_line", cx.expr_usize(sp, location_info.1)), | |
297 | // start_col: start column of the test fn identifier. | |
298 | field("start_col", cx.expr_usize(sp, location_info.2)), | |
299 | // end_line: end line of the test fn identifier. | |
300 | field("end_line", cx.expr_usize(sp, location_info.3)), | |
301 | // end_col: end column of the test fn identifier. | |
302 | field("end_col", cx.expr_usize(sp, location_info.4)), | |
17df50a5 XL |
303 | // compile_fail: true | false |
304 | field("compile_fail", cx.expr_bool(sp, false)), | |
305 | // no_run: true | false | |
306 | field("no_run", cx.expr_bool(sp, false)), | |
74b04a01 XL |
307 | // should_panic: ... |
308 | field( | |
309 | "should_panic", | |
310 | match should_panic(cx, &item) { | |
311 | // test::ShouldPanic::No | |
312 | ShouldPanic::No => { | |
313 | cx.expr_path(should_panic_path("No")) | |
314 | } | |
315 | // test::ShouldPanic::Yes | |
316 | ShouldPanic::Yes(None) => { | |
317 | cx.expr_path(should_panic_path("Yes")) | |
318 | } | |
319 | // test::ShouldPanic::YesWithMessage("...") | |
320 | ShouldPanic::Yes(Some(sym)) => cx.expr_call( | |
321 | sp, | |
322 | cx.expr_path(should_panic_path("YesWithMessage")), | |
9ffffee4 | 323 | thin_vec![cx.expr_str(sp, sym)], |
74b04a01 XL |
324 | ), |
325 | }, | |
326 | ), | |
327 | // test_type: ... | |
328 | field( | |
329 | "test_type", | |
330 | match test_type(cx) { | |
331 | // test::TestType::UnitTest | |
332 | TestType::UnitTest => { | |
333 | cx.expr_path(test_type_path("UnitTest")) | |
334 | } | |
335 | // test::TestType::IntegrationTest | |
336 | TestType::IntegrationTest => { | |
337 | cx.expr_path(test_type_path("IntegrationTest")) | |
338 | } | |
339 | // test::TestPath::Unknown | |
340 | TestType::Unknown => { | |
341 | cx.expr_path(test_type_path("Unknown")) | |
342 | } | |
343 | }, | |
344 | ), | |
345 | // }, | |
346 | ], | |
347 | ), | |
dfeec247 | 348 | ), |
74b04a01 XL |
349 | // testfn: test::StaticTestFn(...) | test::StaticBenchFn(...) |
350 | field("testfn", test_fn), // } | |
351 | ], | |
353b0b11 FG |
352 | ), // } |
353 | ), | |
354 | } | |
355 | .into(), | |
74b04a01 | 356 | ), |
353b0b11 | 357 | ); |
dfeec247 | 358 | test_const = test_const.map(|mut tc| { |
1b1a35ee | 359 | tc.vis.kind = ast::VisibilityKind::Public; |
dfeec247 XL |
360 | tc |
361 | }); | |
362 | ||
363 | // extern crate test | |
f2b60f7d | 364 | let test_extern = cx.item(sp, test_id, ast::AttrVec::new(), ast::ItemKind::ExternCrate(None)); |
dfeec247 | 365 | |
f2b60f7d | 366 | debug!("synthetic test item:\n{}\n", pprust::item_to_string(&test_const)); |
dfeec247 | 367 | |
fc512014 XL |
368 | if is_stmt { |
369 | vec![ | |
370 | // Access to libtest under a hygienic name | |
371 | Annotatable::Stmt(P(cx.stmt_item(sp, test_extern))), | |
372 | // The generated test case | |
373 | Annotatable::Stmt(P(cx.stmt_item(sp, test_const))), | |
374 | // The original item | |
375 | Annotatable::Stmt(P(cx.stmt_item(sp, item))), | |
376 | ] | |
377 | } else { | |
378 | vec![ | |
379 | // Access to libtest under a hygienic name | |
380 | Annotatable::Item(test_extern), | |
381 | // The generated test case | |
382 | Annotatable::Item(test_const), | |
383 | // The original item | |
384 | Annotatable::Item(item), | |
385 | ] | |
386 | } | |
dfeec247 XL |
387 | } |
388 | ||
353b0b11 FG |
389 | fn not_testable_error(cx: &ExtCtxt<'_>, attr_sp: Span, item: Option<&ast::Item>) { |
390 | let diag = &cx.sess.parse_sess.span_diagnostic; | |
391 | let msg = "the `#[test]` attribute may only be used on a non-associated function"; | |
392 | let mut err = match item.map(|i| &i.kind) { | |
393 | // These were a warning before #92959 and need to continue being that to avoid breaking | |
394 | // stable user code (#94508). | |
395 | Some(ast::ItemKind::MacCall(_)) => diag.struct_span_warn(attr_sp, msg), | |
396 | // `.forget_guarantee()` needed to get these two arms to match types. Because of how | |
397 | // locally close the `.emit()` call is I'm comfortable with it, but if it can be | |
398 | // reworked in the future to not need it, it'd be nice. | |
399 | _ => diag.struct_span_err(attr_sp, msg).forget_guarantee(), | |
400 | }; | |
401 | if let Some(item) = item { | |
402 | err.span_label( | |
403 | item.span, | |
404 | format!( | |
405 | "expected a non-associated function, found {} {}", | |
406 | item.kind.article(), | |
407 | item.kind.descr() | |
408 | ), | |
409 | ); | |
410 | } | |
411 | err.span_label(attr_sp, "the `#[test]` macro causes a function to be run as a test and has no effect on non-functions") | |
412 | .span_suggestion(attr_sp, | |
413 | "replace with conditional compilation to make the item only exist when tests are being run", | |
414 | "#[cfg(test)]", | |
415 | Applicability::MaybeIncorrect) | |
416 | .emit(); | |
417 | } | |
418 | ||
419 | fn get_location_info(cx: &ExtCtxt<'_>, item: &ast::Item) -> (Symbol, usize, usize, usize, usize) { | |
420 | let span = item.ident.span; | |
421 | let (source_file, lo_line, lo_col, hi_line, hi_col) = | |
422 | cx.sess.source_map().span_to_location_info(span); | |
423 | ||
424 | let file_name = match source_file { | |
425 | Some(sf) => sf.name.display(FileNameDisplayPreference::Remapped).to_string(), | |
426 | None => "no-location".to_string(), | |
427 | }; | |
428 | ||
429 | (Symbol::intern(&file_name), lo_line, lo_col, hi_line, hi_col) | |
430 | } | |
431 | ||
f9f354fc | 432 | fn item_path(mod_path: &[Ident], item_ident: &Ident) -> String { |
dfeec247 XL |
433 | mod_path |
434 | .iter() | |
435 | .chain(iter::once(item_ident)) | |
436 | .map(|x| x.to_string()) | |
437 | .collect::<Vec<String>>() | |
438 | .join("::") | |
439 | } | |
440 | ||
441 | enum ShouldPanic { | |
442 | No, | |
443 | Yes(Option<Symbol>), | |
444 | } | |
445 | ||
353b0b11 FG |
446 | fn should_ignore(i: &ast::Item) -> bool { |
447 | attr::contains_name(&i.attrs, sym::ignore) | |
dfeec247 XL |
448 | } |
449 | ||
353b0b11 FG |
450 | fn should_ignore_message(i: &ast::Item) -> Option<Symbol> { |
451 | match attr::find_by_name(&i.attrs, sym::ignore) { | |
5e7ed085 FG |
452 | Some(attr) => { |
453 | match attr.meta_item_list() { | |
454 | // Handle #[ignore(bar = "foo")] | |
455 | Some(_) => None, | |
456 | // Handle #[ignore] and #[ignore = "message"] | |
457 | None => attr.value_str(), | |
458 | } | |
459 | } | |
460 | None => None, | |
461 | } | |
462 | } | |
463 | ||
dfeec247 | 464 | fn should_panic(cx: &ExtCtxt<'_>, i: &ast::Item) -> ShouldPanic { |
353b0b11 | 465 | match attr::find_by_name(&i.attrs, sym::should_panic) { |
dfeec247 | 466 | Some(attr) => { |
3dfed10e | 467 | let sd = &cx.sess.parse_sess.span_diagnostic; |
dfeec247 XL |
468 | |
469 | match attr.meta_item_list() { | |
470 | // Handle #[should_panic(expected = "foo")] | |
471 | Some(list) => { | |
472 | let msg = list | |
473 | .iter() | |
3dfed10e | 474 | .find(|mi| mi.has_name(sym::expected)) |
dfeec247 XL |
475 | .and_then(|mi| mi.meta_item()) |
476 | .and_then(|mi| mi.value_str()); | |
477 | if list.len() != 1 || msg.is_none() { | |
478 | sd.struct_span_warn( | |
479 | attr.span, | |
480 | "argument must be of the form: \ | |
481 | `expected = \"error message\"`", | |
482 | ) | |
483 | .note( | |
484 | "errors in this attribute were erroneously \ | |
485 | allowed and will become a hard error in a \ | |
c295e0f8 | 486 | future release", |
dfeec247 XL |
487 | ) |
488 | .emit(); | |
489 | ShouldPanic::Yes(None) | |
490 | } else { | |
491 | ShouldPanic::Yes(msg) | |
492 | } | |
493 | } | |
494 | // Handle #[should_panic] and #[should_panic = "expected"] | |
495 | None => ShouldPanic::Yes(attr.value_str()), | |
496 | } | |
497 | } | |
498 | None => ShouldPanic::No, | |
499 | } | |
500 | } | |
501 | ||
502 | enum TestType { | |
503 | UnitTest, | |
504 | IntegrationTest, | |
505 | Unknown, | |
506 | } | |
507 | ||
508 | /// Attempts to determine the type of test. | |
509 | /// Since doctests are created without macro expanding, only possible variants here | |
510 | /// are `UnitTest`, `IntegrationTest` or `Unknown`. | |
511 | fn test_type(cx: &ExtCtxt<'_>) -> TestType { | |
512 | // Root path from context contains the topmost sources directory of the crate. | |
513 | // I.e., for `project` with sources in `src` and tests in `tests` folders | |
514 | // (no matter how many nested folders lie inside), | |
515 | // there will be two different root paths: `/project/src` and `/project/tests`. | |
516 | let crate_path = cx.root_path.as_path(); | |
517 | ||
518 | if crate_path.ends_with("src") { | |
519 | // `/src` folder contains unit-tests. | |
520 | TestType::UnitTest | |
521 | } else if crate_path.ends_with("tests") { | |
522 | // `/tests` folder contains integration tests. | |
523 | TestType::IntegrationTest | |
524 | } else { | |
525 | // Crate layout doesn't match expected one, test type is unknown. | |
526 | TestType::Unknown | |
527 | } | |
528 | } | |
529 | ||
530 | fn has_test_signature(cx: &ExtCtxt<'_>, i: &ast::Item) -> bool { | |
353b0b11 | 531 | let has_should_panic_attr = attr::contains_name(&i.attrs, sym::should_panic); |
3dfed10e | 532 | let sd = &cx.sess.parse_sess.span_diagnostic; |
487cf647 FG |
533 | match &i.kind { |
534 | ast::ItemKind::Fn(box ast::Fn { sig, generics, .. }) => { | |
535 | if let ast::Unsafe::Yes(span) = sig.header.unsafety { | |
536 | sd.struct_span_err(i.span, "unsafe functions cannot be used for tests") | |
537 | .span_label(span, "`unsafe` because of this") | |
538 | .emit(); | |
539 | return false; | |
540 | } | |
541 | if let ast::Async::Yes { span, .. } = sig.header.asyncness { | |
542 | sd.struct_span_err(i.span, "async functions cannot be used for tests") | |
543 | .span_label(span, "`async` because of this") | |
544 | .emit(); | |
545 | return false; | |
546 | } | |
dfeec247 | 547 | |
487cf647 FG |
548 | // If the termination trait is active, the compiler will check that the output |
549 | // type implements the `Termination` trait as `libtest` enforces that. | |
550 | let has_output = match &sig.decl.output { | |
551 | ast::FnRetTy::Default(..) => false, | |
552 | ast::FnRetTy::Ty(t) if t.kind.is_unit() => false, | |
553 | _ => true, | |
554 | }; | |
555 | ||
556 | if !sig.decl.inputs.is_empty() { | |
557 | sd.span_err(i.span, "functions used as tests can not have any arguments"); | |
558 | return false; | |
dfeec247 | 559 | } |
487cf647 FG |
560 | |
561 | match (has_output, has_should_panic_attr) { | |
562 | (true, true) => { | |
563 | sd.span_err(i.span, "functions using `#[should_panic]` must return `()`"); | |
dfeec247 | 564 | false |
dfeec247 | 565 | } |
487cf647 FG |
566 | (true, false) => { |
567 | if !generics.params.is_empty() { | |
568 | sd.span_err( | |
569 | i.span, | |
570 | "functions used as tests must have signature fn() -> ()", | |
571 | ); | |
572 | false | |
573 | } else { | |
574 | true | |
575 | } | |
576 | } | |
577 | (false, _) => true, | |
dfeec247 | 578 | } |
dfeec247 | 579 | } |
487cf647 FG |
580 | _ => { |
581 | // should be unreachable because `is_test_fn_item` should catch all non-fn items | |
582 | debug_assert!(false); | |
583 | false | |
584 | } | |
dfeec247 XL |
585 | } |
586 | } | |
587 | ||
588 | fn has_bench_signature(cx: &ExtCtxt<'_>, i: &ast::Item) -> bool { | |
487cf647 | 589 | let has_sig = match &i.kind { |
dfeec247 XL |
590 | // N.B., inadequate check, but we're running |
591 | // well before resolve, can't get too deep. | |
487cf647 FG |
592 | ast::ItemKind::Fn(box ast::Fn { sig, .. }) => sig.decl.inputs.len() == 1, |
593 | _ => false, | |
dfeec247 XL |
594 | }; |
595 | ||
596 | if !has_sig { | |
3dfed10e | 597 | cx.sess.parse_sess.span_diagnostic.span_err( |
dfeec247 XL |
598 | i.span, |
599 | "functions used as benches must have \ | |
600 | signature `fn(&mut Bencher) -> impl Termination`", | |
601 | ); | |
602 | } | |
603 | ||
604 | has_sig | |
605 | } |