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