aboutsummaryrefslogtreecommitdiff
path: root/embassy-executor-macros
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
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')
-rw-r--r--embassy-executor-macros/Cargo.toml2
-rw-r--r--embassy-executor-macros/src/lib.rs50
-rw-r--r--embassy-executor-macros/src/macros/main.rs240
-rw-r--r--embassy-executor-macros/src/macros/task.rs151
-rw-r--r--embassy-executor-macros/src/util.rs74
-rw-r--r--embassy-executor-macros/src/util/ctxt.rs72
-rw-r--r--embassy-executor-macros/src/util/mod.rs1
7 files changed, 303 insertions, 287 deletions
diff --git a/embassy-executor-macros/Cargo.toml b/embassy-executor-macros/Cargo.toml
index 218e820ce..ef509c3f9 100644
--- a/embassy-executor-macros/Cargo.toml
+++ b/embassy-executor-macros/Cargo.toml
@@ -13,7 +13,7 @@ categories = [
13] 13]
14 14
15[dependencies] 15[dependencies]
16syn = { version = "2.0.15", features = ["full", "extra-traits"] } 16syn = { version = "2.0.15", features = ["full", "visit"] }
17quote = "1.0.9" 17quote = "1.0.9"
18darling = "0.20.1" 18darling = "0.20.1"
19proc-macro2 = "1.0.29" 19proc-macro2 = "1.0.29"
diff --git a/embassy-executor-macros/src/lib.rs b/embassy-executor-macros/src/lib.rs
index 61d388b9e..5f2182f10 100644
--- a/embassy-executor-macros/src/lib.rs
+++ b/embassy-executor-macros/src/lib.rs
@@ -1,28 +1,11 @@
1#![doc = include_str!("../README.md")] 1#![doc = include_str!("../README.md")]
2extern crate proc_macro; 2extern crate proc_macro;
3 3
4use darling::ast::NestedMeta;
5use proc_macro::TokenStream; 4use proc_macro::TokenStream;
6 5
7mod macros; 6mod macros;
8mod util; 7mod util;
9use macros::*; 8use macros::*;
10use syn::parse::{Parse, ParseBuffer};
11use syn::punctuated::Punctuated;
12use syn::Token;
13
14struct Args {
15 meta: Vec<NestedMeta>,
16}
17
18impl Parse for Args {
19 fn parse(input: &ParseBuffer) -> syn::Result<Self> {
20 let meta = Punctuated::<NestedMeta, Token![,]>::parse_terminated(input)?;
21 Ok(Args {
22 meta: meta.into_iter().collect(),
23 })
24 }
25}
26 9
27/// Declares an async task that can be run by `embassy-executor`. The optional `pool_size` parameter can be used to specify how 10/// Declares an async task that can be run by `embassy-executor`. The optional `pool_size` parameter can be used to specify how
28/// many concurrent tasks can be spawned (default is 1) for the function. 11/// many concurrent tasks can be spawned (default is 1) for the function.
@@ -56,17 +39,12 @@ impl Parse for Args {
56/// ``` 39/// ```
57#[proc_macro_attribute] 40#[proc_macro_attribute]
58pub fn task(args: TokenStream, item: TokenStream) -> TokenStream { 41pub fn task(args: TokenStream, item: TokenStream) -> TokenStream {
59 let args = syn::parse_macro_input!(args as Args); 42 task::run(args.into(), item.into()).into()
60 let f = syn::parse_macro_input!(item as syn::ItemFn);
61
62 task::run(&args.meta, f).unwrap_or_else(|x| x).into()
63} 43}
64 44
65#[proc_macro_attribute] 45#[proc_macro_attribute]
66pub fn main_avr(args: TokenStream, item: TokenStream) -> TokenStream { 46pub fn main_avr(args: TokenStream, item: TokenStream) -> TokenStream {
67 let args = syn::parse_macro_input!(args as Args); 47 main::run(args.into(), item.into(), &main::ARCH_AVR).into()
68 let f = syn::parse_macro_input!(item as syn::ItemFn);
69 main::run(&args.meta, f, main::avr()).unwrap_or_else(|x| x).into()
70} 48}
71 49
72/// Creates a new `executor` instance and declares an application entry point for Cortex-M spawning the corresponding function body as an async task. 50/// Creates a new `executor` instance and declares an application entry point for Cortex-M spawning the corresponding function body as an async task.
@@ -89,9 +67,7 @@ pub fn main_avr(args: TokenStream, item: TokenStream) -> TokenStream {
89/// ``` 67/// ```
90#[proc_macro_attribute] 68#[proc_macro_attribute]
91pub fn main_cortex_m(args: TokenStream, item: TokenStream) -> TokenStream { 69pub fn main_cortex_m(args: TokenStream, item: TokenStream) -> TokenStream {
92 let args = syn::parse_macro_input!(args as Args); 70 main::run(args.into(), item.into(), &main::ARCH_CORTEX_M).into()
93 let f = syn::parse_macro_input!(item as syn::ItemFn);
94 main::run(&args.meta, f, main::cortex_m()).unwrap_or_else(|x| x).into()
95} 71}
96 72
97/// Creates a new `executor` instance and declares an architecture agnostic application entry point spawning 73/// Creates a new `executor` instance and declares an architecture agnostic application entry point spawning
@@ -116,11 +92,7 @@ pub fn main_cortex_m(args: TokenStream, item: TokenStream) -> TokenStream {
116/// ``` 92/// ```
117#[proc_macro_attribute] 93#[proc_macro_attribute]
118pub fn main_spin(args: TokenStream, item: TokenStream) -> TokenStream { 94pub fn main_spin(args: TokenStream, item: TokenStream) -> TokenStream {
119 let args = syn::parse_macro_input!(args as Args); 95 main::run(args.into(), item.into(), &main::ARCH_SPIN).into()
120 let f = syn::parse_macro_input!(item as syn::ItemFn);
121 main::run(&args.meta, f, main::spin(&args.meta))
122 .unwrap_or_else(|x| x)
123 .into()
124} 96}
125 97
126/// Creates a new `executor` instance and declares an application entry point for RISC-V spawning the corresponding function body as an async task. 98/// Creates a new `executor` instance and declares an application entry point for RISC-V spawning the corresponding function body as an async task.
@@ -153,11 +125,7 @@ pub fn main_spin(args: TokenStream, item: TokenStream) -> TokenStream {
153/// ``` 125/// ```
154#[proc_macro_attribute] 126#[proc_macro_attribute]
155pub fn main_riscv(args: TokenStream, item: TokenStream) -> TokenStream { 127pub fn main_riscv(args: TokenStream, item: TokenStream) -> TokenStream {
156 let args = syn::parse_macro_input!(args as Args); 128 main::run(args.into(), item.into(), &main::ARCH_RISCV).into()
157 let f = syn::parse_macro_input!(item as syn::ItemFn);
158 main::run(&args.meta, f, main::riscv(&args.meta))
159 .unwrap_or_else(|x| x)
160 .into()
161} 129}
162 130
163/// Creates a new `executor` instance and declares an application entry point for STD spawning the corresponding function body as an async task. 131/// Creates a new `executor` instance and declares an application entry point for STD spawning the corresponding function body as an async task.
@@ -180,9 +148,7 @@ pub fn main_riscv(args: TokenStream, item: TokenStream) -> TokenStream {
180/// ``` 148/// ```
181#[proc_macro_attribute] 149#[proc_macro_attribute]
182pub fn main_std(args: TokenStream, item: TokenStream) -> TokenStream { 150pub fn main_std(args: TokenStream, item: TokenStream) -> TokenStream {
183 let args = syn::parse_macro_input!(args as Args); 151 main::run(args.into(), item.into(), &main::ARCH_STD).into()
184 let f = syn::parse_macro_input!(item as syn::ItemFn);
185 main::run(&args.meta, f, main::std()).unwrap_or_else(|x| x).into()
186} 152}
187 153
188/// Creates a new `executor` instance and declares an application entry point for WASM spawning the corresponding function body as an async task. 154/// Creates a new `executor` instance and declares an application entry point for WASM spawning the corresponding function body as an async task.
@@ -205,7 +171,5 @@ pub fn main_std(args: TokenStream, item: TokenStream) -> TokenStream {
205/// ``` 171/// ```
206#[proc_macro_attribute] 172#[proc_macro_attribute]
207pub fn main_wasm(args: TokenStream, item: TokenStream) -> TokenStream { 173pub fn main_wasm(args: TokenStream, item: TokenStream) -> TokenStream {
208 let args = syn::parse_macro_input!(args as Args); 174 main::run(args.into(), item.into(), &main::ARCH_WASM).into()
209 let f = syn::parse_macro_input!(item as syn::ItemFn);
210 main::run(&args.meta, f, main::wasm()).unwrap_or_else(|x| x).into()
211} 175}
diff --git a/embassy-executor-macros/src/macros/main.rs b/embassy-executor-macros/src/macros/main.rs
index 66a3965d0..14b1d9de2 100644
--- a/embassy-executor-macros/src/macros/main.rs
+++ b/embassy-executor-macros/src/macros/main.rs
@@ -1,152 +1,107 @@
1use std::str::FromStr;
2
1use darling::export::NestedMeta; 3use darling::export::NestedMeta;
2use darling::{Error, FromMeta}; 4use darling::FromMeta;
3use proc_macro2::TokenStream; 5use proc_macro2::TokenStream;
4use quote::quote; 6use quote::quote;
5use syn::{Expr, ReturnType, Type}; 7use syn::{ReturnType, Type};
8
9use crate::util::*;
10
11enum Flavor {
12 Standard,
13 Wasm,
14}
6 15
7use crate::util::ctxt::Ctxt; 16pub(crate) struct Arch {
17 default_entry: Option<&'static str>,
18 flavor: Flavor,
19}
8 20
9#[derive(Debug, FromMeta)] 21pub static ARCH_AVR: Arch = Arch {
22 default_entry: Some("avr_device::entry"),
23 flavor: Flavor::Standard,
24};
25
26pub static ARCH_RISCV: Arch = Arch {
27 default_entry: Some("riscv_rt::entry"),
28 flavor: Flavor::Standard,
29};
30
31pub static ARCH_CORTEX_M: Arch = Arch {
32 default_entry: Some("cortex_m_rt::entry"),
33 flavor: Flavor::Standard,
34};
35
36pub static ARCH_SPIN: Arch = Arch {
37 default_entry: None,
38 flavor: Flavor::Standard,
39};
40
41pub static ARCH_STD: Arch = Arch {
42 default_entry: None,
43 flavor: Flavor::Standard,
44};
45
46pub static ARCH_WASM: Arch = Arch {
47 default_entry: Some("wasm_bindgen::prelude::wasm_bindgen(start)"),
48 flavor: Flavor::Wasm,
49};
50
51#[derive(Debug, FromMeta, Default)]
10struct Args { 52struct Args {
11 #[darling(default)] 53 #[darling(default)]
12 entry: Option<String>, 54 entry: Option<String>,
13} 55}
14 56
15pub fn avr() -> TokenStream { 57pub fn run(args: TokenStream, item: TokenStream, arch: &Arch) -> TokenStream {
16 quote! { 58 let mut errors = TokenStream::new();
17 #[avr_device::entry]
18 fn main() -> ! {
19 let mut executor = ::embassy_executor::Executor::new();
20 let executor = unsafe { __make_static(&mut executor) };
21 59
22 executor.run(|spawner| { 60 // If any of the steps for this macro fail, we still want to expand to an item that is as close
23 spawner.must_spawn(__embassy_main(spawner)); 61 // to the expected output as possible. This helps out IDEs such that completions and other
24 }) 62 // related features keep working.
25 } 63 let f: ItemFn = match syn::parse2(item.clone()) {
26 } 64 Ok(x) => x,
27} 65 Err(e) => return token_stream_with_error(item, e),
28
29pub fn riscv(args: &[NestedMeta]) -> TokenStream {
30 let maybe_entry = match Args::from_list(args) {
31 Ok(args) => args.entry,
32 Err(e) => return e.write_errors(),
33 }; 66 };
34 67
35 let entry = maybe_entry.unwrap_or("riscv_rt::entry".into()); 68 let args = match NestedMeta::parse_meta_list(args) {
36 let entry = match Expr::from_string(&entry) { 69 Ok(x) => x,
37 Ok(expr) => expr, 70 Err(e) => return token_stream_with_error(item, e),
38 Err(e) => return e.write_errors(),
39 }; 71 };
40 72
41 quote! { 73 let args = match Args::from_list(&args) {
42 #[#entry] 74 Ok(x) => x,
43 fn main() -> ! { 75 Err(e) => {
44 let mut executor = ::embassy_executor::Executor::new(); 76 errors.extend(e.write_errors());
45 let executor = unsafe { __make_static(&mut executor) }; 77 Args::default()
46 executor.run(|spawner| {
47 spawner.must_spawn(__embassy_main(spawner));
48 })
49 } 78 }
50 }
51}
52
53pub fn spin(args: &[NestedMeta]) -> TokenStream {
54 let maybe_entry = match Args::from_list(args) {
55 Ok(args) => args.entry,
56 Err(e) => return e.write_errors(),
57 };
58
59 let entry = match maybe_entry {
60 Some(str) => str,
61 None => return Error::missing_field("entry").write_errors(),
62 }; 79 };
63 let entry = match Expr::from_string(&entry) {
64 Ok(expr) => expr,
65 Err(e) => return e.write_errors(),
66 };
67
68 quote! {
69 #[#entry]
70 fn main() -> ! {
71 let mut executor = ::embassy_executor::Executor::new();
72 let executor = unsafe { __make_static(&mut executor) };
73 executor.run(|spawner| {
74 spawner.must_spawn(__embassy_main(spawner));
75 })
76 }
77 }
78}
79
80pub fn cortex_m() -> TokenStream {
81 quote! {
82 #[cortex_m_rt::entry]
83 fn main() -> ! {
84 let mut executor = ::embassy_executor::Executor::new();
85 let executor = unsafe { __make_static(&mut executor) };
86 executor.run(|spawner| {
87 spawner.must_spawn(__embassy_main(spawner));
88 })
89 }
90 }
91}
92
93pub fn wasm() -> TokenStream {
94 quote! {
95 #[wasm_bindgen::prelude::wasm_bindgen(start)]
96 pub fn main() -> Result<(), wasm_bindgen::JsValue> {
97 let executor = ::std::boxed::Box::leak(::std::boxed::Box::new(::embassy_executor::Executor::new()));
98
99 executor.start(|spawner| {
100 spawner.must_spawn(__embassy_main(spawner));
101 });
102
103 Ok(())
104 }
105 }
106}
107
108pub fn std() -> TokenStream {
109 quote! {
110 fn main() -> ! {
111 let mut executor = ::embassy_executor::Executor::new();
112 let executor = unsafe { __make_static(&mut executor) };
113
114 executor.run(|spawner| {
115 spawner.must_spawn(__embassy_main(spawner));
116 })
117 }
118 }
119}
120
121pub fn run(args: &[NestedMeta], f: syn::ItemFn, main: TokenStream) -> Result<TokenStream, TokenStream> {
122 #[allow(unused_variables)]
123 let args = Args::from_list(args).map_err(|e| e.write_errors())?;
124 80
125 let fargs = f.sig.inputs.clone(); 81 let fargs = f.sig.inputs.clone();
126 82
127 let ctxt = Ctxt::new();
128
129 if f.sig.asyncness.is_none() { 83 if f.sig.asyncness.is_none() {
130 ctxt.error_spanned_by(&f.sig, "main function must be async"); 84 error(&mut errors, &f.sig, "main function must be async");
131 } 85 }
132 if !f.sig.generics.params.is_empty() { 86 if !f.sig.generics.params.is_empty() {
133 ctxt.error_spanned_by(&f.sig, "main function must not be generic"); 87 error(&mut errors, &f.sig, "main function must not be generic");
134 } 88 }
135 if !f.sig.generics.where_clause.is_none() { 89 if !f.sig.generics.where_clause.is_none() {
136 ctxt.error_spanned_by(&f.sig, "main function must not have `where` clauses"); 90 error(&mut errors, &f.sig, "main function must not have `where` clauses");
137 } 91 }
138 if !f.sig.abi.is_none() { 92 if !f.sig.abi.is_none() {
139 ctxt.error_spanned_by(&f.sig, "main function must not have an ABI qualifier"); 93 error(&mut errors, &f.sig, "main function must not have an ABI qualifier");
140 } 94 }
141 if !f.sig.variadic.is_none() { 95 if !f.sig.variadic.is_none() {
142 ctxt.error_spanned_by(&f.sig, "main function must not be variadic"); 96 error(&mut errors, &f.sig, "main function must not be variadic");
143 } 97 }
144 match &f.sig.output { 98 match &f.sig.output {
145 ReturnType::Default => {} 99 ReturnType::Default => {}
146 ReturnType::Type(_, ty) => match &**ty { 100 ReturnType::Type(_, ty) => match &**ty {
147 Type::Tuple(tuple) if tuple.elems.is_empty() => {} 101 Type::Tuple(tuple) if tuple.elems.is_empty() => {}
148 Type::Never(_) => {} 102 Type::Never(_) => {}
149 _ => ctxt.error_spanned_by( 103 _ => error(
104 &mut errors,
150 &f.sig, 105 &f.sig,
151 "main function must either not return a value, return `()` or return `!`", 106 "main function must either not return a value, return `()` or return `!`",
152 ), 107 ),
@@ -154,26 +109,69 @@ pub fn run(args: &[NestedMeta], f: syn::ItemFn, main: TokenStream) -> Result<Tok
154 } 109 }
155 110
156 if fargs.len() != 1 { 111 if fargs.len() != 1 {
157 ctxt.error_spanned_by(&f.sig, "main function must have 1 argument: the spawner."); 112 error(&mut errors, &f.sig, "main function must have 1 argument: the spawner.");
158 } 113 }
159 114
160 ctxt.check()?; 115 let entry = match args.entry.as_deref().or(arch.default_entry) {
116 None => TokenStream::new(),
117 Some(x) => match TokenStream::from_str(x) {
118 Ok(x) => quote!(#[#x]),
119 Err(e) => {
120 error(&mut errors, &f.sig, e);
121 TokenStream::new()
122 }
123 },
124 };
161 125
162 let f_body = f.block; 126 let f_body = f.body;
163 let out = &f.sig.output; 127 let out = &f.sig.output;
164 128
129 let (main_ret, mut main_body) = match arch.flavor {
130 Flavor::Standard => (
131 quote!(!),
132 quote! {
133 unsafe fn __make_static<T>(t: &mut T) -> &'static mut T {
134 ::core::mem::transmute(t)
135 }
136
137 let mut executor = ::embassy_executor::Executor::new();
138 let executor = unsafe { __make_static(&mut executor) };
139 executor.run(|spawner| {
140 spawner.must_spawn(__embassy_main(spawner));
141 })
142 },
143 ),
144 Flavor::Wasm => (
145 quote!(Result<(), wasm_bindgen::JsValue>),
146 quote! {
147 let executor = ::std::boxed::Box::leak(::std::boxed::Box::new(::embassy_executor::Executor::new()));
148
149 executor.start(|spawner| {
150 spawner.must_spawn(__embassy_main(spawner));
151 });
152
153 Ok(())
154 },
155 ),
156 };
157
158 if !errors.is_empty() {
159 main_body = quote! {loop{}};
160 }
161
165 let result = quote! { 162 let result = quote! {
166 #[::embassy_executor::task()] 163 #[::embassy_executor::task()]
167 async fn __embassy_main(#fargs) #out { 164 async fn __embassy_main(#fargs) #out {
168 #f_body 165 #f_body
169 } 166 }
170 167
171 unsafe fn __make_static<T>(t: &mut T) -> &'static mut T { 168 #entry
172 ::core::mem::transmute(t) 169 fn main() -> #main_ret {
170 #main_body
173 } 171 }
174 172
175 #main 173 #errors
176 }; 174 };
177 175
178 Ok(result) 176 result
179} 177}
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}
diff --git a/embassy-executor-macros/src/util.rs b/embassy-executor-macros/src/util.rs
new file mode 100644
index 000000000..ebd032a62
--- /dev/null
+++ b/embassy-executor-macros/src/util.rs
@@ -0,0 +1,74 @@
1use std::fmt::Display;
2
3use proc_macro2::{TokenStream, TokenTree};
4use quote::{ToTokens, TokenStreamExt};
5use syn::parse::{Parse, ParseStream};
6use syn::{braced, bracketed, token, AttrStyle, Attribute, Signature, Token, Visibility};
7
8pub fn token_stream_with_error(mut tokens: TokenStream, error: syn::Error) -> TokenStream {
9 tokens.extend(error.into_compile_error());
10 tokens
11}
12
13pub fn error<A: ToTokens, T: Display>(s: &mut TokenStream, obj: A, msg: T) {
14 s.extend(syn::Error::new_spanned(obj.into_token_stream(), msg).into_compile_error())
15}
16
17/// Function signature and body.
18///
19/// Same as `syn`'s `ItemFn` except we keep the body as a TokenStream instead of
20/// parsing it. This makes the macro not error if there's a syntax error in the body,
21/// which helps IDE autocomplete work better.
22#[derive(Debug, Clone)]
23pub struct ItemFn {
24 pub attrs: Vec<Attribute>,
25 pub vis: Visibility,
26 pub sig: Signature,
27 pub brace_token: token::Brace,
28 pub body: TokenStream,
29}
30
31impl Parse for ItemFn {
32 fn parse(input: ParseStream) -> syn::Result<Self> {
33 let mut attrs = input.call(Attribute::parse_outer)?;
34 let vis: Visibility = input.parse()?;
35 let sig: Signature = input.parse()?;
36
37 let content;
38 let brace_token = braced!(content in input);
39 while content.peek(Token![#]) && content.peek2(Token![!]) {
40 let content2;
41 attrs.push(Attribute {
42 pound_token: content.parse()?,
43 style: AttrStyle::Inner(content.parse()?),
44 bracket_token: bracketed!(content2 in content),
45 meta: content2.parse()?,
46 });
47 }
48
49 let mut body = Vec::new();
50 while !content.is_empty() {
51 body.push(content.parse::<TokenTree>()?);
52 }
53 let body = body.into_iter().collect();
54
55 Ok(ItemFn {
56 attrs,
57 vis,
58 sig,
59 brace_token,
60 body,
61 })
62 }
63}
64
65impl ToTokens for ItemFn {
66 fn to_tokens(&self, tokens: &mut TokenStream) {
67 tokens.append_all(self.attrs.iter().filter(|a| matches!(a.style, AttrStyle::Outer)));
68 self.vis.to_tokens(tokens);
69 self.sig.to_tokens(tokens);
70 self.brace_token.surround(tokens, |tokens| {
71 tokens.append_all(self.body.clone());
72 });
73 }
74}
diff --git a/embassy-executor-macros/src/util/ctxt.rs b/embassy-executor-macros/src/util/ctxt.rs
deleted file mode 100644
index 9c78cda01..000000000
--- a/embassy-executor-macros/src/util/ctxt.rs
+++ /dev/null
@@ -1,72 +0,0 @@
1// nifty utility borrowed from serde :)
2// https://github.com/serde-rs/serde/blob/master/serde_derive/src/internals/ctxt.rs
3
4use std::cell::RefCell;
5use std::fmt::Display;
6use std::thread;
7
8use proc_macro2::TokenStream;
9use quote::{quote, ToTokens};
10
11/// A type to collect errors together and format them.
12///
13/// Dropping this object will cause a panic. It must be consumed using `check`.
14///
15/// References can be shared since this type uses run-time exclusive mut checking.
16#[derive(Default)]
17pub struct Ctxt {
18 // The contents will be set to `None` during checking. This is so that checking can be
19 // enforced.
20 errors: RefCell<Option<Vec<syn::Error>>>,
21}
22
23impl Ctxt {
24 /// Create a new context object.
25 ///
26 /// This object contains no errors, but will still trigger a panic if it is not `check`ed.
27 pub fn new() -> Self {
28 Ctxt {
29 errors: RefCell::new(Some(Vec::new())),
30 }
31 }
32
33 /// Add an error to the context object with a tokenenizable object.
34 ///
35 /// The object is used for spanning in error messages.
36 pub fn error_spanned_by<A: ToTokens, T: Display>(&self, obj: A, msg: T) {
37 self.errors
38 .borrow_mut()
39 .as_mut()
40 .unwrap()
41 // Curb monomorphization from generating too many identical methods.
42 .push(syn::Error::new_spanned(obj.into_token_stream(), msg));
43 }
44
45 /// Add one of Syn's parse errors.
46 #[allow(unused)]
47 pub fn syn_error(&self, err: syn::Error) {
48 self.errors.borrow_mut().as_mut().unwrap().push(err);
49 }
50
51 /// Consume this object, producing a formatted error string if there are errors.
52 pub fn check(self) -> Result<(), TokenStream> {
53 let errors = self.errors.borrow_mut().take().unwrap();
54 match errors.len() {
55 0 => Ok(()),
56 _ => Err(to_compile_errors(errors)),
57 }
58 }
59}
60
61fn to_compile_errors(errors: Vec<syn::Error>) -> proc_macro2::TokenStream {
62 let compile_errors = errors.iter().map(syn::Error::to_compile_error);
63 quote!(#(#compile_errors)*)
64}
65
66impl Drop for Ctxt {
67 fn drop(&mut self) {
68 if !thread::panicking() && self.errors.borrow().is_some() {
69 panic!("forgot to check for errors");
70 }
71 }
72}
diff --git a/embassy-executor-macros/src/util/mod.rs b/embassy-executor-macros/src/util/mod.rs
deleted file mode 100644
index 28702809e..000000000
--- a/embassy-executor-macros/src/util/mod.rs
+++ /dev/null
@@ -1 +0,0 @@
1pub mod ctxt;