Repository: embassy-rs/embassy
Pull request #3434 (2024-10-20 21:54:58)

executor: improve macro error messages, add compile-fail / ui tests.
- **executor-macros: improve error messages.**
- **executor: add compile-fail / ui tests.**



Patch:
diff --git a/embassy-executor-macros/src/macros/task.rs b/embassy-executor-macros/src/macros/task.rs
index 0404dba64..2f2aeda76 100644
--- a/embassy-executor-macros/src/macros/task.rs
+++ b/embassy-executor-macros/src/macros/task.rs
@@ -2,7 +2,7 @@ use darling::export::NestedMeta;
 use darling::FromMeta;
 use proc_macro2::{Span, TokenStream};
 use quote::{format_ident, quote};
-use syn::visit::Visit;
+use syn::visit::{self, Visit};
 use syn::{Expr, ExprLit, Lit, LitInt, ReturnType, Type};
 
 use crate::util::*;
@@ -76,7 +76,7 @@ pub fn run(args: TokenStream, item: TokenStream) -> TokenStream {
     for arg in fargs.iter_mut() {
         match arg {
             syn::FnArg::Receiver(_) => {
-                error(&mut errors, arg, "task functions must not have receiver arguments");
+                error(&mut errors, arg, "task functions must not have `self` arguments");
             }
             syn::FnArg::Typed(t) => {
                 check_arg_ty(&mut errors, &t.ty);
@@ -180,6 +180,28 @@ fn check_arg_ty(errors: &mut TokenStream, ty: &Type) {
     }
 
     impl<'a, 'ast> Visit<'ast> for Visitor<'a> {
+        fn visit_type_reference(&mut self, i: &'ast syn::TypeReference) {
+            // only check for elided lifetime here. If not elided, it's checked by `visit_lifetime`.
+            if i.lifetime.is_none() {
+                error(
+                    self.errors,
+                    i.and_token,
+                    "Arguments for tasks must live forever. Try using the `'static` lifetime.",
+                )
+            }
+            visit::visit_type_reference(self, i);
+        }
+
+        fn visit_lifetime(&mut self, i: &'ast syn::Lifetime) {
+            if i.ident.to_string() != "static" {
+                error(
+                    self.errors,
+                    i,
+                    "Arguments for tasks must live forever. Try using the `'static` lifetime.",
+                )
+            }
+        }
+
         fn visit_type_impl_trait(&mut self, i: &'ast syn::TypeImplTrait) {
             error(self.errors, i, "`impl Trait` is not allowed in task arguments. It is syntax sugar for generics, and tasks can't be generic.");
         }
diff --git a/embassy-executor/Cargo.toml b/embassy-executor/Cargo.toml
index e2fedce3c..e138f93b0 100644
--- a/embassy-executor/Cargo.toml
+++ b/embassy-executor/Cargo.toml
@@ -55,7 +55,7 @@ avr-device = { version = "0.5.3", optional = true }
 
 [dev-dependencies]
 critical-section = { version = "1.1", features = ["std"] }
-
+trybuild = "1.0"
 
 [features]
 


Test patch:
diff --git a/.github/ci/test-nightly.sh b/.github/ci/test-nightly.sh
index 1724ffe89..a03b55e8d 100755
--- a/.github/ci/test-nightly.sh
+++ b/.github/ci/test-nightly.sh
@@ -9,6 +9,9 @@ export CARGO_HOME=/ci/cache/cargo
 export CARGO_TARGET_DIR=/ci/cache/target
 mv rust-toolchain-nightly.toml rust-toolchain.toml
 
+cargo test --manifest-path ./embassy-executor/Cargo.toml
+cargo test --manifest-path ./embassy-executor/Cargo.toml --features nightly
+
 MIRIFLAGS=-Zmiri-ignore-leaks cargo miri test --manifest-path ./embassy-executor/Cargo.toml
 MIRIFLAGS=-Zmiri-ignore-leaks cargo miri test --manifest-path ./embassy-executor/Cargo.toml --features nightly
 MIRIFLAGS=-Zmiri-ignore-leaks cargo miri test --manifest-path ./embassy-sync/Cargo.toml
diff --git a/.github/ci/test.sh b/.github/ci/test.sh
index 0de265049..0fe088bfe 100755
--- a/.github/ci/test.sh
+++ b/.github/ci/test.sh
@@ -12,6 +12,7 @@ export CARGO_TARGET_DIR=/ci/cache/target
 # used when pointing stm32-metapac to a CI-built one.
 export CARGO_NET_GIT_FETCH_WITH_CLI=true
 
+cargo test --manifest-path ./embassy-executor/Cargo.toml
 cargo test --manifest-path ./embassy-futures/Cargo.toml
 cargo test --manifest-path ./embassy-sync/Cargo.toml
 cargo test --manifest-path ./embassy-embedded-hal/Cargo.toml
diff --git a/embassy-executor/tests/ui.rs b/embassy-executor/tests/ui.rs
new file mode 100644
index 000000000..be4679485
--- /dev/null
+++ b/embassy-executor/tests/ui.rs
@@ -0,0 +1,23 @@
+#[cfg(not(miri))]
+#[test]
+fn ui() {
+    let t = trybuild::TestCases::new();
+    t.compile_fail("tests/ui/abi.rs");
+    t.compile_fail("tests/ui/bad_return.rs");
+    t.compile_fail("tests/ui/generics.rs");
+    t.compile_fail("tests/ui/impl_trait_nested.rs");
+    t.compile_fail("tests/ui/impl_trait.rs");
+    t.compile_fail("tests/ui/impl_trait_static.rs");
+    t.compile_fail("tests/ui/nonstatic_ref_anon_nested.rs");
+    t.compile_fail("tests/ui/nonstatic_ref_anon.rs");
+    t.compile_fail("tests/ui/nonstatic_ref_elided.rs");
+    t.compile_fail("tests/ui/nonstatic_ref_generic.rs");
+    t.compile_fail("tests/ui/nonstatic_struct_anon.rs");
+    #[cfg(not(feature = "nightly"))] // we can't catch this case with the macro, so the output changes on nightly.
+    t.compile_fail("tests/ui/nonstatic_struct_elided.rs");
+    t.compile_fail("tests/ui/nonstatic_struct_generic.rs");
+    t.compile_fail("tests/ui/not_async.rs");
+    t.compile_fail("tests/ui/self_ref.rs");
+    t.compile_fail("tests/ui/self.rs");
+    t.compile_fail("tests/ui/where_clause.rs");
+}
diff --git a/embassy-executor/tests/ui/abi.rs b/embassy-executor/tests/ui/abi.rs
new file mode 100644
index 000000000..fd52f7e41
--- /dev/null
+++ b/embassy-executor/tests/ui/abi.rs
@@ -0,0 +1,8 @@
+#![cfg_attr(feature = "nightly", feature(impl_trait_in_assoc_type))]
+
+struct Foo<'a>(&'a ());
+
+#[embassy_executor::task]
+async extern "C" fn task() {}
+
+fn main() {}
diff --git a/embassy-executor/tests/ui/abi.stderr b/embassy-executor/tests/ui/abi.stderr
new file mode 100644
index 000000000..e264e371a
--- /dev/null
+++ b/embassy-executor/tests/ui/abi.stderr
@@ -0,0 +1,5 @@
+error: task functions must not have an ABI qualifier
+ --> tests/ui/abi.rs:6:1
+  |
+6 | async extern "C" fn task() {}
+  | ^^^^^^^^^^^^^^^^^^^^^^^^^^
diff --git a/embassy-executor/tests/ui/bad_return.rs b/embassy-executor/tests/ui/bad_return.rs
new file mode 100644
index 000000000..f09a5205b
--- /dev/null
+++ b/embassy-executor/tests/ui/bad_return.rs
@@ -0,0 +1,10 @@
+#![cfg_attr(feature = "nightly", feature(impl_trait_in_assoc_type))]
+
+struct Foo<'a>(&'a ());
+
+#[embassy_executor::task]
+async fn task() -> u32 {
+    5
+}
+
+fn main() {}
diff --git a/embassy-executor/tests/ui/bad_return.stderr b/embassy-executor/tests/ui/bad_return.stderr
new file mode 100644
index 000000000..e9d94dff8
--- /dev/null
+++ b/embassy-executor/tests/ui/bad_return.stderr
@@ -0,0 +1,5 @@
+error: task functions must either not return a value, return `()` or return `!`
+ --> tests/ui/bad_return.rs:6:1
+  |
+6 | async fn task() -> u32 {
+  | ^^^^^^^^^^^^^^^^^^^^^^
diff --git a/embassy-executor/tests/ui/generics.rs b/embassy-executor/tests/ui/generics.rs
new file mode 100644
index 000000000..b83123bb1
--- /dev/null
+++ b/embassy-executor/tests/ui/generics.rs
@@ -0,0 +1,8 @@
+#![cfg_attr(feature = "nightly", feature(impl_trait_in_assoc_type))]
+
+struct Foo<'a>(&'a ());
+
+#[embassy_executor::task]
+async fn task<T: Sized>(_t: T) {}
+
+fn main() {}
diff --git a/embassy-executor/tests/ui/generics.stderr b/embassy-executor/tests/ui/generics.stderr
new file mode 100644
index 000000000..197719a7b
--- /dev/null
+++ b/embassy-executor/tests/ui/generics.stderr
@@ -0,0 +1,5 @@
+error: task functions must not be generic
+ --> tests/ui/generics.rs:6:1
+  |
+6 | async fn task<T: Sized>(_t: T) {}
+  | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
diff --git a/embassy-executor/tests/ui/impl_trait.rs b/embassy-executor/tests/ui/impl_trait.rs
new file mode 100644
index 000000000..a21402aa0
--- /dev/null
+++ b/embassy-executor/tests/ui/impl_trait.rs
@@ -0,0 +1,6 @@
+#![cfg_attr(feature = "nightly", feature(impl_trait_in_assoc_type))]
+
+#[embassy_executor::task]
+async fn foo(_x: impl Sized) {}
+
+fn main() {}
diff --git a/embassy-executor/tests/ui/impl_trait.stderr b/embassy-executor/tests/ui/impl_trait.stderr
new file mode 100644
index 000000000..099b1828f
--- /dev/null
+++ b/embassy-executor/tests/ui/impl_trait.stderr
@@ -0,0 +1,5 @@
+error: `impl Trait` is not allowed in task arguments. It is syntax sugar for generics, and tasks can't be generic.
+ --> tests/ui/impl_trait.rs:4:18
+  |
+4 | async fn foo(_x: impl Sized) {}
+  |                  ^^^^^^^^^^
diff --git a/embassy-executor/tests/ui/impl_trait_nested.rs b/embassy-executor/tests/ui/impl_trait_nested.rs
new file mode 100644
index 000000000..07442b8fa
--- /dev/null
+++ b/embassy-executor/tests/ui/impl_trait_nested.rs
@@ -0,0 +1,8 @@
+#![cfg_attr(feature = "nightly", feature(impl_trait_in_assoc_type))]
+
+struct Foo<T>(T);
+
+#[embassy_executor::task]
+async fn foo(_x: Foo<impl Sized + 'static>) {}
+
+fn main() {}
diff --git a/embassy-executor/tests/ui/impl_trait_nested.stderr b/embassy-executor/tests/ui/impl_trait_nested.stderr
new file mode 100644
index 000000000..39592f958
--- /dev/null
+++ b/embassy-executor/tests/ui/impl_trait_nested.stderr
@@ -0,0 +1,5 @@
+error: `impl Trait` is not allowed in task arguments. It is syntax sugar for generics, and tasks can't be generic.
+ --> tests/ui/impl_trait_nested.rs:6:22
+  |
+6 | async fn foo(_x: Foo<impl Sized + 'static>) {}
+  |                      ^^^^^^^^^^^^^^^^^^^^
diff --git a/embassy-executor/tests/ui/impl_trait_static.rs b/embassy-executor/tests/ui/impl_trait_static.rs
new file mode 100644
index 000000000..272470f98
--- /dev/null
+++ b/embassy-executor/tests/ui/impl_trait_static.rs
@@ -0,0 +1,6 @@
+#![cfg_attr(feature = "nightly", feature(impl_trait_in_assoc_type))]
+
+#[embassy_executor::task]
+async fn foo(_x: impl Sized + 'static) {}
+
+fn main() {}
diff --git a/embassy-executor/tests/ui/impl_trait_static.stderr b/embassy-executor/tests/ui/impl_trait_static.stderr
new file mode 100644
index 000000000..0032a20c9
--- /dev/null
+++ b/embassy-executor/tests/ui/impl_trait_static.stderr
@@ -0,0 +1,5 @@
+error: `impl Trait` is not allowed in task arguments. It is syntax sugar for generics, and tasks can't be generic.
+ --> tests/ui/impl_trait_static.rs:4:18
+  |
+4 | async fn foo(_x: impl Sized + 'static) {}
+  |                  ^^^^^^^^^^^^^^^^^^^^
diff --git a/embassy-executor/tests/ui/nonstatic_ref_anon.rs b/embassy-executor/tests/ui/nonstatic_ref_anon.rs
new file mode 100644
index 000000000..417c360a1
--- /dev/null
+++ b/embassy-executor/tests/ui/nonstatic_ref_anon.rs
@@ -0,0 +1,6 @@
+#![cfg_attr(feature = "nightly", feature(impl_trait_in_assoc_type))]
+
+#[embassy_executor::task]
+async fn foo(_x: &'_ u32) {}
+
+fn main() {}
diff --git a/embassy-executor/tests/ui/nonstatic_ref_anon.stderr b/embassy-executor/tests/ui/nonstatic_ref_anon.stderr
new file mode 100644
index 000000000..0544de843
--- /dev/null
+++ b/embassy-executor/tests/ui/nonstatic_ref_anon.stderr
@@ -0,0 +1,5 @@
+error: Arguments for tasks must live forever. Try using the `'static` lifetime.
+ --> tests/ui/nonstatic_ref_anon.rs:4:19
+  |
+4 | async fn foo(_x: &'_ u32) {}
+  |                   ^^
diff --git a/embassy-executor/tests/ui/nonstatic_ref_anon_nested.rs b/embassy-executor/tests/ui/nonstatic_ref_anon_nested.rs
new file mode 100644
index 000000000..175ebccc1
--- /dev/null
+++ b/embassy-executor/tests/ui/nonstatic_ref_anon_nested.rs
@@ -0,0 +1,6 @@
+#![cfg_attr(feature = "nightly", feature(impl_trait_in_assoc_type))]
+
+#[embassy_executor::task]
+async fn foo(_x: &'static &'_ u32) {}
+
+fn main() {}
diff --git a/embassy-executor/tests/ui/nonstatic_ref_anon_nested.stderr b/embassy-executor/tests/ui/nonstatic_ref_anon_nested.stderr
new file mode 100644
index 000000000..79f262e6b
--- /dev/null
+++ b/embassy-executor/tests/ui/nonstatic_ref_anon_nested.stderr
@@ -0,0 +1,5 @@
+error: Arguments for tasks must live forever. Try using the `'static` lifetime.
+ --> tests/ui/nonstatic_ref_anon_nested.rs:4:28
+  |
+4 | async fn foo(_x: &'static &'_ u32) {}
+  |                            ^^
diff --git a/embassy-executor/tests/ui/nonstatic_ref_elided.rs b/embassy-executor/tests/ui/nonstatic_ref_elided.rs
new file mode 100644
index 000000000..cf49ad709
--- /dev/null
+++ b/embassy-executor/tests/ui/nonstatic_ref_elided.rs
@@ -0,0 +1,6 @@
+#![cfg_attr(feature = "nightly", feature(impl_trait_in_assoc_type))]
+
+#[embassy_executor::task]
+async fn foo(_x: &u32) {}
+
+fn main() {}
diff --git a/embassy-executor/tests/ui/nonstatic_ref_elided.stderr b/embassy-executor/tests/ui/nonstatic_ref_elided.stderr
new file mode 100644
index 000000000..7e2b9eb7c
--- /dev/null
+++ b/embassy-executor/tests/ui/nonstatic_ref_elided.stderr
@@ -0,0 +1,5 @@
+error: Arguments for tasks must live forever. Try using the `'static` lifetime.
+ --> tests/ui/nonstatic_ref_elided.rs:4:18
+  |
+4 | async fn foo(_x: &u32) {}
+  |                  ^
diff --git a/embassy-executor/tests/ui/nonstatic_ref_generic.rs b/embassy-executor/tests/ui/nonstatic_ref_generic.rs
new file mode 100644
index 000000000..3f8a26cf8
--- /dev/null
+++ b/embassy-executor/tests/ui/nonstatic_ref_generic.rs
@@ -0,0 +1,6 @@
+#![cfg_attr(feature = "nightly", feature(impl_trait_in_assoc_type))]
+
+#[embassy_executor::task]
+async fn foo<'a>(_x: &'a u32) {}
+
+fn main() {}
diff --git a/embassy-executor/tests/ui/nonstatic_ref_generic.stderr b/embassy-executor/tests/ui/nonstatic_ref_generic.stderr
new file mode 100644
index 000000000..af8491ad7
--- /dev/null
+++ b/embassy-executor/tests/ui/nonstatic_ref_generic.stderr
@@ -0,0 +1,11 @@
+error: task functions must not be generic
+ --> tests/ui/nonstatic_ref_generic.rs:4:1
+  |
+4 | async fn foo<'a>(_x: &'a u32) {}
+  | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+error: Arguments for tasks must live forever. Try using the `'static` lifetime.
+ --> tests/ui/nonstatic_ref_generic.rs:4:23
+  |
+4 | async fn foo<'a>(_x: &'a u32) {}
+  |                       ^^
diff --git a/embassy-executor/tests/ui/nonstatic_struct_anon.rs b/embassy-executor/tests/ui/nonstatic_struct_anon.rs
new file mode 100644
index 000000000..ba95d1459
--- /dev/null
+++ b/embassy-executor/tests/ui/nonstatic_struct_anon.rs
@@ -0,0 +1,8 @@
+#![cfg_attr(feature = "nightly", feature(impl_trait_in_assoc_type))]
+
+struct Foo<'a>(&'a ());
+
+#[embassy_executor::task]
+async fn task(_x: Foo<'_>) {}
+
+fn main() {}
diff --git a/embassy-executor/tests/ui/nonstatic_struct_anon.stderr b/embassy-executor/tests/ui/nonstatic_struct_anon.stderr
new file mode 100644
index 000000000..5df2a6e06
--- /dev/null
+++ b/embassy-executor/tests/ui/nonstatic_struct_anon.stderr
@@ -0,0 +1,5 @@
+error: Arguments for tasks must live forever. Try using the `'static` lifetime.
+ --> tests/ui/nonstatic_struct_anon.rs:6:23
+  |
+6 | async fn task(_x: Foo<'_>) {}
+  |                       ^^
diff --git a/embassy-executor/tests/ui/nonstatic_struct_elided.rs b/embassy-executor/tests/ui/nonstatic_struct_elided.rs
new file mode 100644
index 000000000..4cfe2966a
--- /dev/null
+++ b/embassy-executor/tests/ui/nonstatic_struct_elided.rs
@@ -0,0 +1,8 @@
+#![cfg_attr(feature = "nightly", feature(impl_trait_in_assoc_type))]
+
+struct Foo<'a>(&'a ());
+
+#[embassy_executor::task]
+async fn task(_x: Foo) {}
+
+fn main() {}
diff --git a/embassy-executor/tests/ui/nonstatic_struct_elided.stderr b/embassy-executor/tests/ui/nonstatic_struct_elided.stderr
new file mode 100644
index 000000000..099ef8b4e
--- /dev/null
+++ b/embassy-executor/tests/ui/nonstatic_struct_elided.stderr
@@ -0,0 +1,10 @@
+error[E0726]: implicit elided lifetime not allowed here
+ --> tests/ui/nonstatic_struct_elided.rs:6:19
+  |
+6 | async fn task(_x: Foo) {}
+  |                   ^^^ expected lifetime parameter
+  |
+help: indicate the anonymous lifetime
+  |
+6 | async fn task(_x: Foo<'_>) {}
+  |                      ++++
diff --git a/embassy-executor/tests/ui/nonstatic_struct_generic.rs b/embassy-executor/tests/ui/nonstatic_struct_generic.rs
new file mode 100644
index 000000000..ec3d908f6
--- /dev/null
+++ b/embassy-executor/tests/ui/nonstatic_struct_generic.rs
@@ -0,0 +1,8 @@
+#![cfg_attr(feature = "nightly", feature(impl_trait_in_assoc_type))]
+
+struct Foo<'a>(&'a ());
+
+#[embassy_executor::task]
+async fn task<'a>(_x: Foo<'a>) {}
+
+fn main() {}
diff --git a/embassy-executor/tests/ui/nonstatic_struct_generic.stderr b/embassy-executor/tests/ui/nonstatic_struct_generic.stderr
new file mode 100644
index 000000000..61d5231bc
--- /dev/null
+++ b/embassy-executor/tests/ui/nonstatic_struct_generic.stderr
@@ -0,0 +1,11 @@
+error: task functions must not be generic
+ --> tests/ui/nonstatic_struct_generic.rs:6:1
+  |
+6 | async fn task<'a>(_x: Foo<'a>) {}
+  | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+error: Arguments for tasks must live forever. Try using the `'static` lifetime.
+ --> tests/ui/nonstatic_struct_generic.rs:6:27
+  |
+6 | async fn task<'a>(_x: Foo<'a>) {}
+  |                           ^^
diff --git a/embassy-executor/tests/ui/not_async.rs b/embassy-executor/tests/ui/not_async.rs
new file mode 100644
index 000000000..f3f7e9bd2
--- /dev/null
+++ b/embassy-executor/tests/ui/not_async.rs
@@ -0,0 +1,8 @@
+#![cfg_attr(feature = "nightly", feature(impl_trait_in_assoc_type))]
+
+struct Foo<'a>(&'a ());
+
+#[embassy_executor::task]
+fn task() {}
+
+fn main() {}
diff --git a/embassy-executor/tests/ui/not_async.stderr b/embassy-executor/tests/ui/not_async.stderr
new file mode 100644
index 000000000..27f040d9c
--- /dev/null
+++ b/embassy-executor/tests/ui/not_async.stderr
@@ -0,0 +1,5 @@
+error: task functions must be async
+ --> tests/ui/not_async.rs:6:1
+  |
+6 | fn task() {}
+  | ^^^^^^^^^
diff --git a/embassy-executor/tests/ui/self.rs b/embassy-executor/tests/ui/self.rs
new file mode 100644
index 000000000..f83a962d1
--- /dev/null
+++ b/embassy-executor/tests/ui/self.rs
@@ -0,0 +1,8 @@
+#![cfg_attr(feature = "nightly", feature(impl_trait_in_assoc_type))]
+
+struct Foo<'a>(&'a ());
+
+#[embassy_executor::task]
+async fn task(self) {}
+
+fn main() {}
diff --git a/embassy-executor/tests/ui/self.stderr b/embassy-executor/tests/ui/self.stderr
new file mode 100644
index 000000000..aaf031573
--- /dev/null
+++ b/embassy-executor/tests/ui/self.stderr
@@ -0,0 +1,13 @@
+error: task functions must not have `self` arguments
+ --> tests/ui/self.rs:6:15
+  |
+6 | async fn task(self) {}
+  |               ^^^^
+
+error: `self` parameter is only allowed in associated functions
+ --> tests/ui/self.rs:6:15
+  |
+6 | async fn task(self) {}
+  |               ^^^^ not semantically valid as function parameter
+  |
+  = note: associated functions are those in `impl` or `trait` definitions
diff --git a/embassy-executor/tests/ui/self_ref.rs b/embassy-executor/tests/ui/self_ref.rs
new file mode 100644
index 000000000..5e49bba5e
--- /dev/null
+++ b/embassy-executor/tests/ui/self_ref.rs
@@ -0,0 +1,8 @@
+#![cfg_attr(feature = "nightly", feature(impl_trait_in_assoc_type))]
+
+struct Foo<'a>(&'a ());
+
+#[embassy_executor::task]
+async fn task(&mut self) {}
+
+fn main() {}
diff --git a/embassy-executor/tests/ui/self_ref.stderr b/embassy-executor/tests/ui/self_ref.stderr
new file mode 100644
index 000000000..dd2052977
--- /dev/null
+++ b/embassy-executor/tests/ui/self_ref.stderr
@@ -0,0 +1,13 @@
+error: task functions must not have `self` arguments
+ --> tests/ui/self_ref.rs:6:15
+  |
+6 | async fn task(&mut self) {}
+  |               ^^^^^^^^^
+
+error: `self` parameter is only allowed in associated functions
+ --> tests/ui/self_ref.rs:6:15
+  |
+6 | async fn task(&mut self) {}
+  |               ^^^^^^^^^ not semantically valid as function parameter
+  |
+  = note: associated functions are those in `impl` or `trait` definitions
diff --git a/embassy-executor/tests/ui/where_clause.rs b/embassy-executor/tests/ui/where_clause.rs
new file mode 100644
index 000000000..848d78149
--- /dev/null
+++ b/embassy-executor/tests/ui/where_clause.rs
@@ -0,0 +1,12 @@
+#![cfg_attr(feature = "nightly", feature(impl_trait_in_assoc_type))]
+
+struct Foo<'a>(&'a ());
+
+#[embassy_executor::task]
+async fn task()
+where
+    (): Sized,
+{
+}
+
+fn main() {}
diff --git a/embassy-executor/tests/ui/where_clause.stderr b/embassy-executor/tests/ui/where_clause.stderr
new file mode 100644
index 000000000..eba45af40
--- /dev/null
+++ b/embassy-executor/tests/ui/where_clause.stderr
@@ -0,0 +1,7 @@
+error: task functions must not have `where` clauses
+ --> tests/ui/where_clause.rs:6:1
+  |
+6 | / async fn task()
+7 | | where
+8 | |     (): Sized,
+  | |______________^