Auto merge of #112366 - lukas-code:test, r=Nilstrieb

`#[test]` function signature verification improvements

This PR contains two improvements to the expansion of the `#[test]` macro.

The first one fixes https://github.com/rust-lang/rust/issues/112360 by correctly recovering item statements if the signature verification fails.

The second one forbids non-lifetime generics on `#[test]` functions. These were previously allowed if the function returned `()`, but always caused an inference error:

before:
```text
error[E0282]: type annotations needed
 --> src/lib.rs:2:1
  |
1 | #[test]
  | ------- in this procedural macro expansion
2 | fn foo<T>() {}
  | ^^^^^^^^^^^^^^ cannot infer type
```

after:
```text
error: functions used as tests can not have any non-lifetime generic parameters
 --> src/lib.rs:2:1
  |
2 | fn foo<T>() {}
  | ^^^^^^^^^^^^^^
```

Also includes some basic tests for test function signature verification, because I couldn't find any (???) in the test suite.
This commit is contained in:
bors 2023-06-19 13:39:46 +00:00
commit 689511047a
3 changed files with 91 additions and 24 deletions

View File

@ -3,7 +3,7 @@ use crate::errors;
/// Ideally, this code would be in libtest but for efficiency and error messages it lives here.
use crate::util::{check_builtin_macro_attribute, warn_on_duplicate_attribute};
use rustc_ast::ptr::P;
use rustc_ast::{self as ast, attr};
use rustc_ast::{self as ast, attr, GenericParamKind};
use rustc_ast_pretty::pprust;
use rustc_errors::Applicability;
use rustc_expand::base::*;
@ -122,11 +122,7 @@ pub fn expand_test_or_bench(
let ast::ItemKind::Fn(fn_) = &item.kind else {
not_testable_error(cx, attr_sp, Some(&item));
return if is_stmt {
vec![Annotatable::Stmt(P(ast::Stmt {
id: ast::DUMMY_NODE_ID,
span: item.span,
kind: ast::StmtKind::Item(item),
}))]
vec![Annotatable::Stmt(P(cx.stmt_item(item.span, item)))]
} else {
vec![Annotatable::Item(item)]
};
@ -138,7 +134,11 @@ pub fn expand_test_or_bench(
if (!is_bench && !has_test_signature(cx, &item))
|| (is_bench && !has_bench_signature(cx, &item))
{
return vec![Annotatable::Item(item)];
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);
@ -550,24 +550,21 @@ fn has_test_signature(cx: &ExtCtxt<'_>, i: &ast::Item) -> bool {
return false;
}
match (has_output, has_should_panic_attr) {
(true, true) => {
sd.span_err(i.span, "functions using `#[should_panic]` must return `()`");
false
}
(true, false) => {
if !generics.params.is_empty() {
sd.span_err(
i.span,
"functions used as tests must have signature fn() -> ()",
);
false
} else {
true
}
}
(false, _) => true,
if has_should_panic_attr && has_output {
sd.span_err(i.span, "functions using `#[should_panic]` must return `()`");
return false;
}
if generics.params.iter().any(|param| !matches!(param.kind, GenericParamKind::Lifetime))
{
sd.span_err(
i.span,
"functions used as tests can not have any non-lifetime generic parameters",
);
return false;
}
true
}
_ => {
// should be unreachable because `is_test_fn_item` should catch all non-fn items

View File

@ -0,0 +1,31 @@
// compile-flags: --test
#[test]
fn foo() -> Result<(), ()> {
Ok(())
}
#[test]
fn bar() -> i32 { //~ ERROR the trait bound `i32: Termination` is not satisfied
0
}
#[test]
fn baz(val: i32) {} //~ ERROR functions used as tests can not have any arguments
#[test]
fn lifetime_generic<'a>() -> Result<(), &'a str> {
Err("coerce me to any lifetime")
}
#[test]
fn type_generic<T>() {} //~ ERROR functions used as tests can not have any non-lifetime generic parameters
#[test]
fn const_generic<const N: usize>() {} //~ ERROR functions used as tests can not have any non-lifetime generic parameters
// Regression test for <https://github.com/rust-lang/rust/issues/112360>. This used to ICE.
fn nested() {
#[test]
fn foo(arg: ()) {} //~ ERROR functions used as tests can not have any arguments
}

View File

@ -0,0 +1,39 @@
error: functions used as tests can not have any arguments
--> $DIR/test-function-signature.rs:14:1
|
LL | fn baz(val: i32) {}
| ^^^^^^^^^^^^^^^^^^^
error: functions used as tests can not have any non-lifetime generic parameters
--> $DIR/test-function-signature.rs:22:1
|
LL | fn type_generic<T>() {}
| ^^^^^^^^^^^^^^^^^^^^^^^
error: functions used as tests can not have any non-lifetime generic parameters
--> $DIR/test-function-signature.rs:25:1
|
LL | fn const_generic<const N: usize>() {}
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
error: functions used as tests can not have any arguments
--> $DIR/test-function-signature.rs:30:5
|
LL | fn foo(arg: ()) {}
| ^^^^^^^^^^^^^^^^^^
error[E0277]: the trait bound `i32: Termination` is not satisfied
--> $DIR/test-function-signature.rs:9:13
|
LL | #[test]
| ------- in this procedural macro expansion
LL | fn bar() -> i32 {
| ^^^ the trait `Termination` is not implemented for `i32`
|
note: required by a bound in `assert_test_result`
--> $SRC_DIR/test/src/lib.rs:LL:COL
= note: this error originates in the attribute macro `test` (in Nightly builds, run with -Z macro-backtrace for more info)
error: aborting due to 5 previous errors
For more information about this error, try `rustc --explain E0277`.