mirror of
https://github.com/rust-lang/rust.git
synced 2025-04-30 12:07:40 +00:00

`ast::Item` has an `ident` field. - It's always non-empty for these item kinds: `ExternCrate`, `Static`, `Const`, `Fn`, `Mod`, `TyAlias`, `Enum`, `Struct`, `Union`, `Trait`, `TraitAlias`, `MacroDef`, `Delegation`. - It's always empty for these item kinds: `Use`, `ForeignMod`, `GlobalAsm`, `Impl`, `MacCall`, `DelegationMac`. There is a similar story for `AssocItemKind` and `ForeignItemKind`. Some sites that handle items check for an empty ident, some don't. This is a very C-like way of doing things, but this is Rust, we have sum types, we can do this properly and never forget to check for the exceptional case and never YOLO possibly empty identifiers (or possibly dummy spans) around and hope that things will work out. The commit is large but it's mostly obvious plumbing work. Some notable things. - `ast::Item` got 8 bytes bigger. This could be avoided by boxing the fields within some of the `ast::ItemKind` variants (specifically: `Struct`, `Union`, `Enum`). I might do that in a follow-up; this commit is big enough already. - For the visitors: `FnKind` no longer needs an `ident` field because the `Fn` within how has one. - In the parser, the `ItemInfo` typedef is no longer needed. It was used in various places to return an `Ident` alongside an `ItemKind`, but now the `Ident` (if present) is within the `ItemKind`. - In a few places I renamed identifier variables called `name` (or `foo_name`) as `ident` (or `foo_ident`), to better match the type, and because `name` is normally used for `Symbol`s. It's confusing to see something like `foo_name.name`.
631 lines
23 KiB
Rust
631 lines
23 KiB
Rust
//! The expansion from a test function to the appropriate test struct for libtest
|
|
//! Ideally, this code would be in libtest but for efficiency and error messages it lives here.
|
|
|
|
use std::assert_matches::assert_matches;
|
|
use std::iter;
|
|
|
|
use rustc_ast::ptr::P;
|
|
use rustc_ast::{self as ast, GenericParamKind, attr};
|
|
use rustc_ast_pretty::pprust;
|
|
use rustc_errors::{Applicability, Diag, Level};
|
|
use rustc_expand::base::*;
|
|
use rustc_span::{ErrorGuaranteed, FileNameDisplayPreference, Ident, Span, Symbol, sym};
|
|
use thin_vec::{ThinVec, thin_vec};
|
|
use tracing::debug;
|
|
|
|
use crate::errors;
|
|
use crate::util::{check_builtin_macro_attribute, warn_on_duplicate_attribute};
|
|
|
|
/// #[test_case] is used by custom test authors to mark tests
|
|
/// When building for test, it needs to make the item public and gensym the name
|
|
/// Otherwise, we'll omit the item. This behavior means that any item annotated
|
|
/// with #[test_case] is never addressable.
|
|
///
|
|
/// We mark item with an inert attribute "rustc_test_marker" which the test generation
|
|
/// logic will pick up on.
|
|
pub(crate) fn expand_test_case(
|
|
ecx: &mut ExtCtxt<'_>,
|
|
attr_sp: Span,
|
|
meta_item: &ast::MetaItem,
|
|
anno_item: Annotatable,
|
|
) -> Vec<Annotatable> {
|
|
check_builtin_macro_attribute(ecx, meta_item, sym::test_case);
|
|
warn_on_duplicate_attribute(ecx, &anno_item, sym::test_case);
|
|
|
|
if !ecx.ecfg.should_test {
|
|
return vec![];
|
|
}
|
|
|
|
let sp = ecx.with_def_site_ctxt(attr_sp);
|
|
let (mut item, is_stmt) = match anno_item {
|
|
Annotatable::Item(item) => (item, false),
|
|
Annotatable::Stmt(stmt) if let ast::StmtKind::Item(_) = stmt.kind => {
|
|
if let ast::StmtKind::Item(i) = stmt.into_inner().kind {
|
|
(i, true)
|
|
} else {
|
|
unreachable!()
|
|
}
|
|
}
|
|
_ => {
|
|
ecx.dcx().emit_err(errors::TestCaseNonItem { span: anno_item.span() });
|
|
return vec![];
|
|
}
|
|
};
|
|
|
|
// `#[test_case]` is valid on functions, consts, and statics. Only modify
|
|
// the item in those cases.
|
|
match &mut item.kind {
|
|
ast::ItemKind::Fn(box ast::Fn { ident, .. })
|
|
| ast::ItemKind::Const(box ast::ConstItem { ident, .. })
|
|
| ast::ItemKind::Static(box ast::StaticItem { ident, .. }) => {
|
|
ident.span = ident.span.with_ctxt(sp.ctxt());
|
|
let test_path_symbol = Symbol::intern(&item_path(
|
|
// skip the name of the root module
|
|
&ecx.current_expansion.module.mod_path[1..],
|
|
ident,
|
|
));
|
|
item.vis = ast::Visibility {
|
|
span: item.vis.span,
|
|
kind: ast::VisibilityKind::Public,
|
|
tokens: None,
|
|
};
|
|
item.attrs.push(ecx.attr_name_value_str(sym::rustc_test_marker, test_path_symbol, sp));
|
|
}
|
|
_ => {}
|
|
}
|
|
|
|
let ret = if is_stmt {
|
|
Annotatable::Stmt(P(ecx.stmt_item(item.span, item)))
|
|
} else {
|
|
Annotatable::Item(item)
|
|
};
|
|
|
|
vec![ret]
|
|
}
|
|
|
|
pub(crate) fn expand_test(
|
|
cx: &mut ExtCtxt<'_>,
|
|
attr_sp: Span,
|
|
meta_item: &ast::MetaItem,
|
|
item: Annotatable,
|
|
) -> Vec<Annotatable> {
|
|
check_builtin_macro_attribute(cx, meta_item, sym::test);
|
|
warn_on_duplicate_attribute(cx, &item, sym::test);
|
|
expand_test_or_bench(cx, attr_sp, item, false)
|
|
}
|
|
|
|
pub(crate) fn expand_bench(
|
|
cx: &mut ExtCtxt<'_>,
|
|
attr_sp: Span,
|
|
meta_item: &ast::MetaItem,
|
|
item: Annotatable,
|
|
) -> Vec<Annotatable> {
|
|
check_builtin_macro_attribute(cx, meta_item, sym::bench);
|
|
warn_on_duplicate_attribute(cx, &item, sym::bench);
|
|
expand_test_or_bench(cx, attr_sp, item, true)
|
|
}
|
|
|
|
pub(crate) fn expand_test_or_bench(
|
|
cx: &ExtCtxt<'_>,
|
|
attr_sp: Span,
|
|
item: Annotatable,
|
|
is_bench: bool,
|
|
) -> Vec<Annotatable> {
|
|
// If we're not in test configuration, remove the annotated item
|
|
if !cx.ecfg.should_test {
|
|
return vec![];
|
|
}
|
|
|
|
let (item, is_stmt) = match item {
|
|
Annotatable::Item(i) => (i, false),
|
|
Annotatable::Stmt(stmt) if matches!(stmt.kind, ast::StmtKind::Item(_)) => {
|
|
// FIXME: Use an 'if let' guard once they are implemented
|
|
if let ast::StmtKind::Item(i) = stmt.into_inner().kind {
|
|
(i, true)
|
|
} else {
|
|
unreachable!()
|
|
}
|
|
}
|
|
other => {
|
|
not_testable_error(cx, attr_sp, None);
|
|
return vec![other];
|
|
}
|
|
};
|
|
|
|
let ast::ItemKind::Fn(fn_) = &item.kind else {
|
|
not_testable_error(cx, attr_sp, Some(&item));
|
|
return if is_stmt {
|
|
vec![Annotatable::Stmt(P(cx.stmt_item(item.span, item)))]
|
|
} else {
|
|
vec![Annotatable::Item(item)]
|
|
};
|
|
};
|
|
|
|
if let Some(attr) = attr::find_by_name(&item.attrs, sym::naked) {
|
|
cx.dcx().emit_err(errors::NakedFunctionTestingAttribute {
|
|
testing_span: attr_sp,
|
|
naked_span: attr.span,
|
|
});
|
|
return vec![Annotatable::Item(item)];
|
|
}
|
|
|
|
// check_*_signature will report any errors in the type so compilation
|
|
// will fail. We shouldn't try to expand in this case because the errors
|
|
// would be spurious.
|
|
let check_result = if is_bench {
|
|
check_bench_signature(cx, &item, fn_)
|
|
} else {
|
|
check_test_signature(cx, &item, fn_)
|
|
};
|
|
if check_result.is_err() {
|
|
return if is_stmt {
|
|
vec![Annotatable::Stmt(P(cx.stmt_item(item.span, item)))]
|
|
} else {
|
|
vec![Annotatable::Item(item)]
|
|
};
|
|
}
|
|
|
|
let sp = cx.with_def_site_ctxt(item.span);
|
|
let ret_ty_sp = cx.with_def_site_ctxt(fn_.sig.decl.output.span());
|
|
let attr_sp = cx.with_def_site_ctxt(attr_sp);
|
|
|
|
let test_ident = Ident::new(sym::test, attr_sp);
|
|
|
|
// creates test::$name
|
|
let test_path = |name| cx.path(ret_ty_sp, vec![test_ident, Ident::from_str_and_span(name, sp)]);
|
|
|
|
// creates test::ShouldPanic::$name
|
|
let should_panic_path = |name| {
|
|
cx.path(
|
|
sp,
|
|
vec![
|
|
test_ident,
|
|
Ident::from_str_and_span("ShouldPanic", sp),
|
|
Ident::from_str_and_span(name, sp),
|
|
],
|
|
)
|
|
};
|
|
|
|
// creates test::TestType::$name
|
|
let test_type_path = |name| {
|
|
cx.path(
|
|
sp,
|
|
vec![
|
|
test_ident,
|
|
Ident::from_str_and_span("TestType", sp),
|
|
Ident::from_str_and_span(name, sp),
|
|
],
|
|
)
|
|
};
|
|
|
|
// creates $name: $expr
|
|
let field = |name, expr| cx.field_imm(sp, Ident::from_str_and_span(name, sp), expr);
|
|
|
|
// Adds `#[coverage(off)]` to a closure, so it won't be instrumented in
|
|
// `-Cinstrument-coverage` builds.
|
|
// This requires `#[allow_internal_unstable(coverage_attribute)]` on the
|
|
// corresponding macro declaration in `core::macros`.
|
|
let coverage_off = |mut expr: P<ast::Expr>| {
|
|
assert_matches!(expr.kind, ast::ExprKind::Closure(_));
|
|
expr.attrs.push(cx.attr_nested_word(sym::coverage, sym::off, sp));
|
|
expr
|
|
};
|
|
|
|
let test_fn = if is_bench {
|
|
// A simple ident for a lambda
|
|
let b = Ident::from_str_and_span("b", attr_sp);
|
|
|
|
cx.expr_call(
|
|
sp,
|
|
cx.expr_path(test_path("StaticBenchFn")),
|
|
thin_vec![
|
|
// #[coverage(off)]
|
|
// |b| self::test::assert_test_result(
|
|
coverage_off(cx.lambda1(
|
|
sp,
|
|
cx.expr_call(
|
|
sp,
|
|
cx.expr_path(test_path("assert_test_result")),
|
|
thin_vec![
|
|
// super::$test_fn(b)
|
|
cx.expr_call(
|
|
ret_ty_sp,
|
|
cx.expr_path(cx.path(sp, vec![fn_.ident])),
|
|
thin_vec![cx.expr_ident(sp, b)],
|
|
),
|
|
],
|
|
),
|
|
b,
|
|
)), // )
|
|
],
|
|
)
|
|
} else {
|
|
cx.expr_call(
|
|
sp,
|
|
cx.expr_path(test_path("StaticTestFn")),
|
|
thin_vec![
|
|
// #[coverage(off)]
|
|
// || {
|
|
coverage_off(cx.lambda0(
|
|
sp,
|
|
// test::assert_test_result(
|
|
cx.expr_call(
|
|
sp,
|
|
cx.expr_path(test_path("assert_test_result")),
|
|
thin_vec![
|
|
// $test_fn()
|
|
cx.expr_call(
|
|
ret_ty_sp,
|
|
cx.expr_path(cx.path(sp, vec![fn_.ident])),
|
|
ThinVec::new(),
|
|
), // )
|
|
],
|
|
), // }
|
|
)), // )
|
|
],
|
|
)
|
|
};
|
|
|
|
let test_path_symbol = Symbol::intern(&item_path(
|
|
// skip the name of the root module
|
|
&cx.current_expansion.module.mod_path[1..],
|
|
&fn_.ident,
|
|
));
|
|
|
|
let location_info = get_location_info(cx, &fn_);
|
|
|
|
let mut test_const =
|
|
cx.item(
|
|
sp,
|
|
thin_vec![
|
|
// #[cfg(test)]
|
|
cx.attr_nested_word(sym::cfg, sym::test, attr_sp),
|
|
// #[rustc_test_marker = "test_case_sort_key"]
|
|
cx.attr_name_value_str(sym::rustc_test_marker, test_path_symbol, attr_sp),
|
|
// #[doc(hidden)]
|
|
cx.attr_nested_word(sym::doc, sym::hidden, attr_sp),
|
|
],
|
|
// const $ident: test::TestDescAndFn =
|
|
ast::ItemKind::Const(
|
|
ast::ConstItem {
|
|
defaultness: ast::Defaultness::Final,
|
|
ident: Ident::new(fn_.ident.name, sp),
|
|
generics: ast::Generics::default(),
|
|
ty: cx.ty(sp, ast::TyKind::Path(None, test_path("TestDescAndFn"))),
|
|
define_opaque: None,
|
|
// test::TestDescAndFn {
|
|
expr: Some(
|
|
cx.expr_struct(
|
|
sp,
|
|
test_path("TestDescAndFn"),
|
|
thin_vec![
|
|
// desc: test::TestDesc {
|
|
field(
|
|
"desc",
|
|
cx.expr_struct(sp, test_path("TestDesc"), thin_vec![
|
|
// name: "path::to::test"
|
|
field(
|
|
"name",
|
|
cx.expr_call(
|
|
sp,
|
|
cx.expr_path(test_path("StaticTestName")),
|
|
thin_vec![cx.expr_str(sp, test_path_symbol)],
|
|
),
|
|
),
|
|
// ignore: true | false
|
|
field("ignore", cx.expr_bool(sp, should_ignore(&item)),),
|
|
// ignore_message: Some("...") | None
|
|
field(
|
|
"ignore_message",
|
|
if let Some(msg) = should_ignore_message(&item) {
|
|
cx.expr_some(sp, cx.expr_str(sp, msg))
|
|
} else {
|
|
cx.expr_none(sp)
|
|
},
|
|
),
|
|
// source_file: <relative_path_of_source_file>
|
|
field("source_file", cx.expr_str(sp, location_info.0)),
|
|
// start_line: start line of the test fn identifier.
|
|
field("start_line", cx.expr_usize(sp, location_info.1)),
|
|
// start_col: start column of the test fn identifier.
|
|
field("start_col", cx.expr_usize(sp, location_info.2)),
|
|
// end_line: end line of the test fn identifier.
|
|
field("end_line", cx.expr_usize(sp, location_info.3)),
|
|
// end_col: end column of the test fn identifier.
|
|
field("end_col", cx.expr_usize(sp, location_info.4)),
|
|
// compile_fail: true | false
|
|
field("compile_fail", cx.expr_bool(sp, false)),
|
|
// no_run: true | false
|
|
field("no_run", cx.expr_bool(sp, false)),
|
|
// should_panic: ...
|
|
field("should_panic", match should_panic(cx, &item) {
|
|
// test::ShouldPanic::No
|
|
ShouldPanic::No => {
|
|
cx.expr_path(should_panic_path("No"))
|
|
}
|
|
// test::ShouldPanic::Yes
|
|
ShouldPanic::Yes(None) => {
|
|
cx.expr_path(should_panic_path("Yes"))
|
|
}
|
|
// test::ShouldPanic::YesWithMessage("...")
|
|
ShouldPanic::Yes(Some(sym)) => cx.expr_call(
|
|
sp,
|
|
cx.expr_path(should_panic_path("YesWithMessage")),
|
|
thin_vec![cx.expr_str(sp, sym)],
|
|
),
|
|
},),
|
|
// test_type: ...
|
|
field("test_type", match test_type(cx) {
|
|
// test::TestType::UnitTest
|
|
TestType::UnitTest => {
|
|
cx.expr_path(test_type_path("UnitTest"))
|
|
}
|
|
// test::TestType::IntegrationTest
|
|
TestType::IntegrationTest => {
|
|
cx.expr_path(test_type_path("IntegrationTest"))
|
|
}
|
|
// test::TestPath::Unknown
|
|
TestType::Unknown => {
|
|
cx.expr_path(test_type_path("Unknown"))
|
|
}
|
|
},),
|
|
// },
|
|
],),
|
|
),
|
|
// testfn: test::StaticTestFn(...) | test::StaticBenchFn(...)
|
|
field("testfn", test_fn), // }
|
|
],
|
|
), // }
|
|
),
|
|
}
|
|
.into(),
|
|
),
|
|
);
|
|
test_const = test_const.map(|mut tc| {
|
|
tc.vis.kind = ast::VisibilityKind::Public;
|
|
tc
|
|
});
|
|
|
|
// extern crate test
|
|
let test_extern =
|
|
cx.item(sp, ast::AttrVec::new(), ast::ItemKind::ExternCrate(None, test_ident));
|
|
|
|
debug!("synthetic test item:\n{}\n", pprust::item_to_string(&test_const));
|
|
|
|
if is_stmt {
|
|
vec![
|
|
// Access to libtest under a hygienic name
|
|
Annotatable::Stmt(P(cx.stmt_item(sp, test_extern))),
|
|
// The generated test case
|
|
Annotatable::Stmt(P(cx.stmt_item(sp, test_const))),
|
|
// The original item
|
|
Annotatable::Stmt(P(cx.stmt_item(sp, item))),
|
|
]
|
|
} else {
|
|
vec![
|
|
// Access to libtest under a hygienic name
|
|
Annotatable::Item(test_extern),
|
|
// The generated test case
|
|
Annotatable::Item(test_const),
|
|
// The original item
|
|
Annotatable::Item(item),
|
|
]
|
|
}
|
|
}
|
|
|
|
fn not_testable_error(cx: &ExtCtxt<'_>, attr_sp: Span, item: Option<&ast::Item>) {
|
|
let dcx = cx.dcx();
|
|
let msg = "the `#[test]` attribute may only be used on a non-associated function";
|
|
let level = match item.map(|i| &i.kind) {
|
|
// These were a warning before #92959 and need to continue being that to avoid breaking
|
|
// stable user code (#94508).
|
|
Some(ast::ItemKind::MacCall(_)) => Level::Warning,
|
|
_ => Level::Error,
|
|
};
|
|
let mut err = Diag::<()>::new(dcx, level, msg);
|
|
err.span(attr_sp);
|
|
if let Some(item) = item {
|
|
err.span_label(
|
|
item.span,
|
|
format!(
|
|
"expected a non-associated function, found {} {}",
|
|
item.kind.article(),
|
|
item.kind.descr()
|
|
),
|
|
);
|
|
}
|
|
err.with_span_label(attr_sp, "the `#[test]` macro causes a function to be run as a test and has no effect on non-functions")
|
|
.with_span_suggestion(attr_sp,
|
|
"replace with conditional compilation to make the item only exist when tests are being run",
|
|
"#[cfg(test)]",
|
|
Applicability::MaybeIncorrect)
|
|
.emit();
|
|
}
|
|
|
|
fn get_location_info(cx: &ExtCtxt<'_>, fn_: &ast::Fn) -> (Symbol, usize, usize, usize, usize) {
|
|
let span = fn_.ident.span;
|
|
let (source_file, lo_line, lo_col, hi_line, hi_col) =
|
|
cx.sess.source_map().span_to_location_info(span);
|
|
|
|
let file_name = match source_file {
|
|
Some(sf) => sf.name.display(FileNameDisplayPreference::Remapped).to_string(),
|
|
None => "no-location".to_string(),
|
|
};
|
|
|
|
(Symbol::intern(&file_name), lo_line, lo_col, hi_line, hi_col)
|
|
}
|
|
|
|
fn item_path(mod_path: &[Ident], item_ident: &Ident) -> String {
|
|
mod_path
|
|
.iter()
|
|
.chain(iter::once(item_ident))
|
|
.map(|x| x.to_string())
|
|
.collect::<Vec<String>>()
|
|
.join("::")
|
|
}
|
|
|
|
enum ShouldPanic {
|
|
No,
|
|
Yes(Option<Symbol>),
|
|
}
|
|
|
|
fn should_ignore(i: &ast::Item) -> bool {
|
|
attr::contains_name(&i.attrs, sym::ignore)
|
|
}
|
|
|
|
fn should_ignore_message(i: &ast::Item) -> Option<Symbol> {
|
|
match attr::find_by_name(&i.attrs, sym::ignore) {
|
|
Some(attr) => {
|
|
match attr.meta_item_list() {
|
|
// Handle #[ignore(bar = "foo")]
|
|
Some(_) => None,
|
|
// Handle #[ignore] and #[ignore = "message"]
|
|
None => attr.value_str(),
|
|
}
|
|
}
|
|
None => None,
|
|
}
|
|
}
|
|
|
|
fn should_panic(cx: &ExtCtxt<'_>, i: &ast::Item) -> ShouldPanic {
|
|
match attr::find_by_name(&i.attrs, sym::should_panic) {
|
|
Some(attr) => {
|
|
match attr.meta_item_list() {
|
|
// Handle #[should_panic(expected = "foo")]
|
|
Some(list) => {
|
|
let msg = list
|
|
.iter()
|
|
.find(|mi| mi.has_name(sym::expected))
|
|
.and_then(|mi| mi.meta_item())
|
|
.and_then(|mi| mi.value_str());
|
|
if list.len() != 1 || msg.is_none() {
|
|
cx.dcx()
|
|
.struct_span_warn(
|
|
attr.span,
|
|
"argument must be of the form: \
|
|
`expected = \"error message\"`",
|
|
)
|
|
.with_note(
|
|
"errors in this attribute were erroneously \
|
|
allowed and will become a hard error in a \
|
|
future release",
|
|
)
|
|
.emit();
|
|
ShouldPanic::Yes(None)
|
|
} else {
|
|
ShouldPanic::Yes(msg)
|
|
}
|
|
}
|
|
// Handle #[should_panic] and #[should_panic = "expected"]
|
|
None => ShouldPanic::Yes(attr.value_str()),
|
|
}
|
|
}
|
|
None => ShouldPanic::No,
|
|
}
|
|
}
|
|
|
|
enum TestType {
|
|
UnitTest,
|
|
IntegrationTest,
|
|
Unknown,
|
|
}
|
|
|
|
/// Attempts to determine the type of test.
|
|
/// Since doctests are created without macro expanding, only possible variants here
|
|
/// are `UnitTest`, `IntegrationTest` or `Unknown`.
|
|
fn test_type(cx: &ExtCtxt<'_>) -> TestType {
|
|
// Root path from context contains the topmost sources directory of the crate.
|
|
// I.e., for `project` with sources in `src` and tests in `tests` folders
|
|
// (no matter how many nested folders lie inside),
|
|
// there will be two different root paths: `/project/src` and `/project/tests`.
|
|
let crate_path = cx.root_path.as_path();
|
|
|
|
if crate_path.ends_with("src") {
|
|
// `/src` folder contains unit-tests.
|
|
TestType::UnitTest
|
|
} else if crate_path.ends_with("tests") {
|
|
// `/tests` folder contains integration tests.
|
|
TestType::IntegrationTest
|
|
} else {
|
|
// Crate layout doesn't match expected one, test type is unknown.
|
|
TestType::Unknown
|
|
}
|
|
}
|
|
|
|
fn check_test_signature(
|
|
cx: &ExtCtxt<'_>,
|
|
i: &ast::Item,
|
|
f: &ast::Fn,
|
|
) -> Result<(), ErrorGuaranteed> {
|
|
let has_should_panic_attr = attr::contains_name(&i.attrs, sym::should_panic);
|
|
let dcx = cx.dcx();
|
|
|
|
if let ast::Safety::Unsafe(span) = f.sig.header.safety {
|
|
return Err(dcx.emit_err(errors::TestBadFn { span: i.span, cause: span, kind: "unsafe" }));
|
|
}
|
|
|
|
if let Some(coroutine_kind) = f.sig.header.coroutine_kind {
|
|
match coroutine_kind {
|
|
ast::CoroutineKind::Async { span, .. } => {
|
|
return Err(dcx.emit_err(errors::TestBadFn {
|
|
span: i.span,
|
|
cause: span,
|
|
kind: "async",
|
|
}));
|
|
}
|
|
ast::CoroutineKind::Gen { span, .. } => {
|
|
return Err(dcx.emit_err(errors::TestBadFn {
|
|
span: i.span,
|
|
cause: span,
|
|
kind: "gen",
|
|
}));
|
|
}
|
|
ast::CoroutineKind::AsyncGen { span, .. } => {
|
|
return Err(dcx.emit_err(errors::TestBadFn {
|
|
span: i.span,
|
|
cause: span,
|
|
kind: "async gen",
|
|
}));
|
|
}
|
|
}
|
|
}
|
|
|
|
// If the termination trait is active, the compiler will check that the output
|
|
// type implements the `Termination` trait as `libtest` enforces that.
|
|
let has_output = match &f.sig.decl.output {
|
|
ast::FnRetTy::Default(..) => false,
|
|
ast::FnRetTy::Ty(t) if t.kind.is_unit() => false,
|
|
_ => true,
|
|
};
|
|
|
|
if !f.sig.decl.inputs.is_empty() {
|
|
return Err(dcx.span_err(i.span, "functions used as tests can not have any arguments"));
|
|
}
|
|
|
|
if has_should_panic_attr && has_output {
|
|
return Err(dcx.span_err(i.span, "functions using `#[should_panic]` must return `()`"));
|
|
}
|
|
|
|
if f.generics.params.iter().any(|param| !matches!(param.kind, GenericParamKind::Lifetime)) {
|
|
return Err(dcx.span_err(
|
|
i.span,
|
|
"functions used as tests can not have any non-lifetime generic parameters",
|
|
));
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
fn check_bench_signature(
|
|
cx: &ExtCtxt<'_>,
|
|
i: &ast::Item,
|
|
f: &ast::Fn,
|
|
) -> Result<(), ErrorGuaranteed> {
|
|
// N.B., inadequate check, but we're running
|
|
// well before resolve, can't get too deep.
|
|
if f.sig.decl.inputs.len() != 1 {
|
|
return Err(cx.dcx().emit_err(errors::BenchSig { span: i.span }));
|
|
}
|
|
Ok(())
|
|
}
|