aboutsummaryrefslogtreecommitdiff
path: root/embassy-executor-macros/src/macros/task.rs
diff options
context:
space:
mode:
authorDario Nieuwenhuis <[email protected]>2024-10-18 03:18:59 +0200
committerGitHub <[email protected]>2024-10-18 03:18:59 +0200
commit1f58e0efd05e5c96409db301e4d481098038aedf (patch)
tree853857285b5ff14eff9a0486dc3eae645de3ee03 /embassy-executor-macros/src/macros/task.rs
parent3d0c557138e87ce94686e5820337c7495206ae56 (diff)
executor: fix unsoundness due to `impl Trait`, improve macro error handling. (#3425)
* executor-macros: don't parse function bodies. * executor-macros: refactor for better recovery and ide-friendliness on errors. * executor-macros: disallow `impl Trait` in task arguments. Fixes #3420 * Fix example using `impl Trait` in tasks.
Diffstat (limited to 'embassy-executor-macros/src/macros/task.rs')
-rw-r--r--embassy-executor-macros/src/macros/task.rs151
1 files changed, 102 insertions, 49 deletions
diff --git a/embassy-executor-macros/src/macros/task.rs b/embassy-executor-macros/src/macros/task.rs
index 96c6267b2..0404dba64 100644
--- a/embassy-executor-macros/src/macros/task.rs
+++ b/embassy-executor-macros/src/macros/task.rs
@@ -2,47 +2,68 @@ use darling::export::NestedMeta;
2use darling::FromMeta; 2use darling::FromMeta;
3use proc_macro2::{Span, TokenStream}; 3use proc_macro2::{Span, TokenStream};
4use quote::{format_ident, quote}; 4use quote::{format_ident, quote};
5use syn::{parse_quote, Expr, ExprLit, ItemFn, Lit, LitInt, ReturnType, Type}; 5use syn::visit::Visit;
6use syn::{Expr, ExprLit, Lit, LitInt, ReturnType, Type};
6 7
7use crate::util::ctxt::Ctxt; 8use crate::util::*;
8 9
9#[derive(Debug, FromMeta)] 10#[derive(Debug, FromMeta, Default)]
10struct Args { 11struct Args {
11 #[darling(default)] 12 #[darling(default)]
12 pool_size: Option<syn::Expr>, 13 pool_size: Option<syn::Expr>,
13} 14}
14 15
15pub fn run(args: &[NestedMeta], f: syn::ItemFn) -> Result<TokenStream, TokenStream> { 16pub fn run(args: TokenStream, item: TokenStream) -> TokenStream {
16 let args = Args::from_list(args).map_err(|e| e.write_errors())?; 17 let mut errors = TokenStream::new();
18
19 // If any of the steps for this macro fail, we still want to expand to an item that is as close
20 // to the expected output as possible. This helps out IDEs such that completions and other
21 // related features keep working.
22 let f: ItemFn = match syn::parse2(item.clone()) {
23 Ok(x) => x,
24 Err(e) => return token_stream_with_error(item, e),
25 };
26
27 let args = match NestedMeta::parse_meta_list(args) {
28 Ok(x) => x,
29 Err(e) => return token_stream_with_error(item, e),
30 };
31
32 let args = match Args::from_list(&args) {
33 Ok(x) => x,
34 Err(e) => {
35 errors.extend(e.write_errors());
36 Args::default()
37 }
38 };
17 39
18 let pool_size = args.pool_size.unwrap_or(Expr::Lit(ExprLit { 40 let pool_size = args.pool_size.unwrap_or(Expr::Lit(ExprLit {
19 attrs: vec![], 41 attrs: vec![],
20 lit: Lit::Int(LitInt::new("1", Span::call_site())), 42 lit: Lit::Int(LitInt::new("1", Span::call_site())),
21 })); 43 }));
22 44
23 let ctxt = Ctxt::new();
24
25 if f.sig.asyncness.is_none() { 45 if f.sig.asyncness.is_none() {
26 ctxt.error_spanned_by(&f.sig, "task functions must be async"); 46 error(&mut errors, &f.sig, "task functions must be async");
27 } 47 }
28 if !f.sig.generics.params.is_empty() { 48 if !f.sig.generics.params.is_empty() {
29 ctxt.error_spanned_by(&f.sig, "task functions must not be generic"); 49 error(&mut errors, &f.sig, "task functions must not be generic");
30 } 50 }
31 if !f.sig.generics.where_clause.is_none() { 51 if !f.sig.generics.where_clause.is_none() {
32 ctxt.error_spanned_by(&f.sig, "task functions must not have `where` clauses"); 52 error(&mut errors, &f.sig, "task functions must not have `where` clauses");
33 } 53 }
34 if !f.sig.abi.is_none() { 54 if !f.sig.abi.is_none() {
35 ctxt.error_spanned_by(&f.sig, "task functions must not have an ABI qualifier"); 55 error(&mut errors, &f.sig, "task functions must not have an ABI qualifier");
36 } 56 }
37 if !f.sig.variadic.is_none() { 57 if !f.sig.variadic.is_none() {
38 ctxt.error_spanned_by(&f.sig, "task functions must not be variadic"); 58 error(&mut errors, &f.sig, "task functions must not be variadic");
39 } 59 }
40 match &f.sig.output { 60 match &f.sig.output {
41 ReturnType::Default => {} 61 ReturnType::Default => {}
42 ReturnType::Type(_, ty) => match &**ty { 62 ReturnType::Type(_, ty) => match &**ty {
43 Type::Tuple(tuple) if tuple.elems.is_empty() => {} 63 Type::Tuple(tuple) if tuple.elems.is_empty() => {}
44 Type::Never(_) => {} 64 Type::Never(_) => {}
45 _ => ctxt.error_spanned_by( 65 _ => error(
66 &mut errors,
46 &f.sig, 67 &f.sig,
47 "task functions must either not return a value, return `()` or return `!`", 68 "task functions must either not return a value, return `()` or return `!`",
48 ), 69 ),
@@ -55,26 +76,31 @@ pub fn run(args: &[NestedMeta], f: syn::ItemFn) -> Result<TokenStream, TokenStre
55 for arg in fargs.iter_mut() { 76 for arg in fargs.iter_mut() {
56 match arg { 77 match arg {
57 syn::FnArg::Receiver(_) => { 78 syn::FnArg::Receiver(_) => {
58 ctxt.error_spanned_by(arg, "task functions must not have receiver arguments"); 79 error(&mut errors, arg, "task functions must not have receiver arguments");
59 } 80 }
60 syn::FnArg::Typed(t) => match t.pat.as_mut() { 81 syn::FnArg::Typed(t) => {
61 syn::Pat::Ident(id) => { 82 check_arg_ty(&mut errors, &t.ty);
62 id.mutability = None; 83 match t.pat.as_mut() {
63 args.push((id.clone(), t.attrs.clone())); 84 syn::Pat::Ident(id) => {
85 id.mutability = None;
86 args.push((id.clone(), t.attrs.clone()));
87 }
88 _ => {
89 error(
90 &mut errors,
91 arg,
92 "pattern matching in task arguments is not yet supported",
93 );
94 }
64 } 95 }
65 _ => { 96 }
66 ctxt.error_spanned_by(arg, "pattern matching in task arguments is not yet supported");
67 }
68 },
69 } 97 }
70 } 98 }
71 99
72 ctxt.check()?;
73
74 let task_ident = f.sig.ident.clone(); 100 let task_ident = f.sig.ident.clone();
75 let task_inner_ident = format_ident!("__{}_task", task_ident); 101 let task_inner_ident = format_ident!("__{}_task", task_ident);
76 102
77 let mut task_inner = f; 103 let mut task_inner = f.clone();
78 let visibility = task_inner.vis.clone(); 104 let visibility = task_inner.vis.clone();
79 task_inner.vis = syn::Visibility::Inherited; 105 task_inner.vis = syn::Visibility::Inherited;
80 task_inner.sig.ident = task_inner_ident.clone(); 106 task_inner.sig.ident = task_inner_ident.clone();
@@ -91,35 +117,43 @@ pub fn run(args: &[NestedMeta], f: syn::ItemFn) -> Result<TokenStream, TokenStre
91 } 117 }
92 118
93 #[cfg(feature = "nightly")] 119 #[cfg(feature = "nightly")]
94 let mut task_outer: ItemFn = parse_quote! { 120 let mut task_outer_body = quote! {
95 #visibility fn #task_ident(#fargs) -> ::embassy_executor::SpawnToken<impl Sized> { 121 trait _EmbassyInternalTaskTrait {
96 trait _EmbassyInternalTaskTrait { 122 type Fut: ::core::future::Future + 'static;
97 type Fut: ::core::future::Future + 'static; 123 fn construct(#fargs) -> Self::Fut;
98 fn construct(#fargs) -> Self::Fut; 124 }
99 }
100 125
101 impl _EmbassyInternalTaskTrait for () { 126 impl _EmbassyInternalTaskTrait for () {
102 type Fut = impl core::future::Future + 'static; 127 type Fut = impl core::future::Future + 'static;
103 fn construct(#fargs) -> Self::Fut { 128 fn construct(#fargs) -> Self::Fut {
104 #task_inner_ident(#(#full_args,)*) 129 #task_inner_ident(#(#full_args,)*)
105 }
106 } 130 }
107
108 const POOL_SIZE: usize = #pool_size;
109 static POOL: ::embassy_executor::raw::TaskPool<<() as _EmbassyInternalTaskTrait>::Fut, POOL_SIZE> = ::embassy_executor::raw::TaskPool::new();
110 unsafe { POOL._spawn_async_fn(move || <() as _EmbassyInternalTaskTrait>::construct(#(#full_args,)*)) }
111 } 131 }
132
133 const POOL_SIZE: usize = #pool_size;
134 static POOL: ::embassy_executor::raw::TaskPool<<() as _EmbassyInternalTaskTrait>::Fut, POOL_SIZE> = ::embassy_executor::raw::TaskPool::new();
135 unsafe { POOL._spawn_async_fn(move || <() as _EmbassyInternalTaskTrait>::construct(#(#full_args,)*)) }
112 }; 136 };
113 #[cfg(not(feature = "nightly"))] 137 #[cfg(not(feature = "nightly"))]
114 let mut task_outer: ItemFn = parse_quote! { 138 let mut task_outer_body = quote! {
115 #visibility fn #task_ident(#fargs) -> ::embassy_executor::SpawnToken<impl Sized> { 139 const POOL_SIZE: usize = #pool_size;
116 const POOL_SIZE: usize = #pool_size; 140 static POOL: ::embassy_executor::_export::TaskPoolRef = ::embassy_executor::_export::TaskPoolRef::new();
117 static POOL: ::embassy_executor::_export::TaskPoolRef = ::embassy_executor::_export::TaskPoolRef::new(); 141 unsafe { POOL.get::<_, POOL_SIZE>()._spawn_async_fn(move || #task_inner_ident(#(#full_args,)*)) }
118 unsafe { POOL.get::<_, POOL_SIZE>()._spawn_async_fn(move || #task_inner_ident(#(#full_args,)*)) }
119 }
120 }; 142 };
121 143
122 task_outer.attrs.append(&mut task_inner.attrs.clone()); 144 let task_outer_attrs = task_inner.attrs.clone();
145
146 if !errors.is_empty() {
147 task_outer_body = quote! {
148 #![allow(unused_variables, unreachable_code)]
149 let _x: ::embassy_executor::SpawnToken<()> = ::core::todo!();
150 _x
151 };
152 }
153
154 // Copy the generics + where clause to avoid more spurious errors.
155 let generics = &f.sig.generics;
156 let where_clause = &f.sig.generics.where_clause;
123 157
124 let result = quote! { 158 let result = quote! {
125 // This is the user's task function, renamed. 159 // This is the user's task function, renamed.
@@ -129,8 +163,27 @@ pub fn run(args: &[NestedMeta], f: syn::ItemFn) -> Result<TokenStream, TokenStre
129 #[doc(hidden)] 163 #[doc(hidden)]
130 #task_inner 164 #task_inner
131 165
132 #task_outer 166 #(#task_outer_attrs)*
167 #visibility fn #task_ident #generics (#fargs) -> ::embassy_executor::SpawnToken<impl Sized> #where_clause{
168 #task_outer_body
169 }
170
171 #errors
133 }; 172 };
134 173
135 Ok(result) 174 result
175}
176
177fn check_arg_ty(errors: &mut TokenStream, ty: &Type) {
178 struct Visitor<'a> {
179 errors: &'a mut TokenStream,
180 }
181
182 impl<'a, 'ast> Visit<'ast> for Visitor<'a> {
183 fn visit_type_impl_trait(&mut self, i: &'ast syn::TypeImplTrait) {
184 error(self.errors, i, "`impl Trait` is not allowed in task arguments. It is syntax sugar for generics, and tasks can't be generic.");
185 }
186 }
187
188 Visit::visit_type(&mut Visitor { errors }, ty);
136} 189}