about summary refs log tree commit diff
diff options
context:
space:
mode:
authorbors <bors@rust-lang.org>2023-06-19 13:39:46 +0000
committerbors <bors@rust-lang.org>2023-06-19 13:39:46 +0000
commit689511047a75a30825e367d4fd45c74604d0b15e (patch)
tree8132b93ba37d4920ed8a5f463587630e7f04a735
parent18a6d911caba59605eb03db1452848a85d2e5879 (diff)
parent5049743be2e278e48fcdbb8385820de6cc705cf5 (diff)
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.
-rw-r--r--compiler/rustc_builtin_macros/src/test.rs45
-rw-r--r--tests/ui/test-attrs/test-function-signature.rs31
-rw-r--r--tests/ui/test-attrs/test-function-signature.stderr39
3 files changed, 91 insertions, 24 deletions
diff --git a/compiler/rustc_builtin_macros/src/test.rs b/compiler/rustc_builtin_macros/src/test.rs
index 49ee276af4e..fcc68010a34 100644
--- a/compiler/rustc_builtin_macros/src/test.rs
+++ b/compiler/rustc_builtin_macros/src/test.rs
@@ -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
diff --git a/tests/ui/test-attrs/test-function-signature.rs b/tests/ui/test-attrs/test-function-signature.rs
new file mode 100644
index 00000000000..9e86e9209e3
--- /dev/null
+++ b/tests/ui/test-attrs/test-function-signature.rs
@@ -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
+}
diff --git a/tests/ui/test-attrs/test-function-signature.stderr b/tests/ui/test-attrs/test-function-signature.stderr
new file mode 100644
index 00000000000..abdb30dc931
--- /dev/null
+++ b/tests/ui/test-attrs/test-function-signature.stderr
@@ -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`.