aboutsummaryrefslogtreecommitdiff
path: root/embassy-executor-macros/src
diff options
context:
space:
mode:
authorjrmoulton <[email protected]>2025-06-10 15:47:54 -0600
committerjrmoulton <[email protected]>2025-06-10 15:48:36 -0600
commitcfad9798ff99d4de0571a512d156b5fe1ef1d427 (patch)
treefc3bf670f82d139de19466cddad1e909db7f3d2e /embassy-executor-macros/src
parentfc342915e6155dec7bafa3e135da7f37a9a07f5c (diff)
parent6186d111a5c150946ee5b7e9e68d987a38c1a463 (diff)
merge new embassy changes
Diffstat (limited to 'embassy-executor-macros/src')
-rw-r--r--embassy-executor-macros/src/lib.rs118
-rw-r--r--embassy-executor-macros/src/macros/main.rs264
-rw-r--r--embassy-executor-macros/src/macros/task.rs189
-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
6 files changed, 466 insertions, 252 deletions
diff --git a/embassy-executor-macros/src/lib.rs b/embassy-executor-macros/src/lib.rs
index 5461fe04c..962ce2e75 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,57 @@ 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); 71}
94 main::run(&args.meta, f, main::cortex_m()).unwrap_or_else(|x| x).into() 72
73/// Creates a new `executor` instance and declares an application entry point for Cortex-A/R
74/// spawning the corresponding function body as an async task.
75///
76/// The following restrictions apply:
77///
78/// * The function must accept exactly 1 parameter, an `embassy_executor::Spawner` handle that it
79/// can use to spawn additional tasks.
80/// * The function must be declared `async`.
81/// * The function must not use generics.
82/// * Only a single `main` task may be declared.
83///
84/// ## Examples
85/// Spawning a task:
86///
87/// ``` rust
88/// #[embassy_executor::main]
89/// async fn main(_s: embassy_executor::Spawner) {
90/// // Function body
91/// }
92/// ```
93#[proc_macro_attribute]
94pub fn main_cortex_ar(args: TokenStream, item: TokenStream) -> TokenStream {
95 main::run(args.into(), item.into(), &main::ARCH_CORTEX_AR).into()
96}
97
98/// Creates a new `executor` instance and declares an architecture agnostic application entry point spawning
99/// the corresponding function body as an async task.
100///
101/// The following restrictions apply:
102///
103/// * The function must accept exactly 1 parameter, an `embassy_executor::Spawner` handle that it can use to spawn additional tasks.
104/// * The function must be declared `async`.
105/// * The function must not use generics.
106/// * Only a single `main` task may be declared.
107///
108/// A user-defined entry macro must provided via the `entry` argument
109///
110/// ## Examples
111/// Spawning a task:
112/// ``` rust
113/// #[embassy_executor::main(entry = "qingke_rt::entry")]
114/// async fn main(_s: embassy_executor::Spawner) {
115/// // Function body
116/// }
117/// ```
118#[proc_macro_attribute]
119pub fn main_spin(args: TokenStream, item: TokenStream) -> TokenStream {
120 main::run(args.into(), item.into(), &main::ARCH_SPIN).into()
95} 121}
96 122
97/// Creates a new `executor` instance and declares an application entry point for RISC-V spawning the corresponding function body as an async task. 123/// Creates a new `executor` instance and declares an application entry point for RISC-V spawning the corresponding function body as an async task.
@@ -124,11 +150,7 @@ pub fn main_cortex_m(args: TokenStream, item: TokenStream) -> TokenStream {
124/// ``` 150/// ```
125#[proc_macro_attribute] 151#[proc_macro_attribute]
126pub fn main_riscv(args: TokenStream, item: TokenStream) -> TokenStream { 152pub fn main_riscv(args: TokenStream, item: TokenStream) -> TokenStream {
127 let args = syn::parse_macro_input!(args as Args); 153 main::run(args.into(), item.into(), &main::ARCH_RISCV).into()
128 let f = syn::parse_macro_input!(item as syn::ItemFn);
129 main::run(&args.meta, f, main::riscv(&args.meta))
130 .unwrap_or_else(|x| x)
131 .into()
132} 154}
133 155
134/// Creates a new `executor` instance and declares an application entry point for STD spawning the corresponding function body as an async task. 156/// Creates a new `executor` instance and declares an application entry point for STD spawning the corresponding function body as an async task.
@@ -151,9 +173,7 @@ pub fn main_riscv(args: TokenStream, item: TokenStream) -> TokenStream {
151/// ``` 173/// ```
152#[proc_macro_attribute] 174#[proc_macro_attribute]
153pub fn main_std(args: TokenStream, item: TokenStream) -> TokenStream { 175pub fn main_std(args: TokenStream, item: TokenStream) -> TokenStream {
154 let args = syn::parse_macro_input!(args as Args); 176 main::run(args.into(), item.into(), &main::ARCH_STD).into()
155 let f = syn::parse_macro_input!(item as syn::ItemFn);
156 main::run(&args.meta, f, main::std()).unwrap_or_else(|x| x).into()
157} 177}
158 178
159/// Creates a new `executor` instance and declares an application entry point for WASM spawning the corresponding function body as an async task. 179/// Creates a new `executor` instance and declares an application entry point for WASM spawning the corresponding function body as an async task.
@@ -176,7 +196,29 @@ pub fn main_std(args: TokenStream, item: TokenStream) -> TokenStream {
176/// ``` 196/// ```
177#[proc_macro_attribute] 197#[proc_macro_attribute]
178pub fn main_wasm(args: TokenStream, item: TokenStream) -> TokenStream { 198pub fn main_wasm(args: TokenStream, item: TokenStream) -> TokenStream {
179 let args = syn::parse_macro_input!(args as Args); 199 main::run(args.into(), item.into(), &main::ARCH_WASM).into()
180 let f = syn::parse_macro_input!(item as syn::ItemFn); 200}
181 main::run(&args.meta, f, main::wasm()).unwrap_or_else(|x| x).into() 201
202/// Creates a new `executor` instance and declares an application entry point for an unspecified architecture, spawning the corresponding function body as an async task.
203///
204/// The following restrictions apply:
205///
206/// * The function must accept exactly 1 parameter, an `embassy_executor::Spawner` handle that it can use to spawn additional tasks.
207/// * The function must be declared `async`.
208/// * The function must not use generics.
209/// * Only a single `main` task may be declared.
210///
211/// A user-defined entry macro and executor type must be provided via the `entry` and `executor` arguments of the `main` macro.
212///
213/// ## Examples
214/// Spawning a task:
215/// ``` rust
216/// #[embassy_executor::main(entry = "your_hal::entry", executor = "your_hal::Executor")]
217/// async fn main(_s: embassy_executor::Spawner) {
218/// // Function body
219/// }
220/// ```
221#[proc_macro_attribute]
222pub fn main_unspecified(args: TokenStream, item: TokenStream) -> TokenStream {
223 main::run(args.into(), item.into(), &main::ARCH_UNSPECIFIED).into()
182} 224}
diff --git a/embassy-executor-macros/src/macros/main.rs b/embassy-executor-macros/src/macros/main.rs
index 26dfa2397..a0e7b3401 100644
--- a/embassy-executor-macros/src/macros/main.rs
+++ b/embassy-executor-macros/src/macros/main.rs
@@ -1,125 +1,128 @@
1use std::str::FromStr;
2
1use darling::export::NestedMeta; 3use darling::export::NestedMeta;
2use darling::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 executor_required: bool,
20}
8 21
9#[derive(Debug, FromMeta)] 22pub static ARCH_AVR: Arch = Arch {
23 default_entry: Some("avr_device::entry"),
24 flavor: Flavor::Standard,
25 executor_required: false,
26};
27
28pub static ARCH_RISCV: Arch = Arch {
29 default_entry: Some("riscv_rt::entry"),
30 flavor: Flavor::Standard,
31 executor_required: false,
32};
33
34pub static ARCH_CORTEX_M: Arch = Arch {
35 default_entry: Some("cortex_m_rt::entry"),
36 flavor: Flavor::Standard,
37 executor_required: false,
38};
39
40pub static ARCH_CORTEX_AR: Arch = Arch {
41 default_entry: None,
42 flavor: Flavor::Standard,
43 executor_required: false,
44};
45
46pub static ARCH_SPIN: Arch = Arch {
47 default_entry: None,
48 flavor: Flavor::Standard,
49 executor_required: false,
50};
51
52pub static ARCH_STD: Arch = Arch {
53 default_entry: None,
54 flavor: Flavor::Standard,
55 executor_required: false,
56};
57
58pub static ARCH_WASM: Arch = Arch {
59 default_entry: Some("wasm_bindgen::prelude::wasm_bindgen(start)"),
60 flavor: Flavor::Wasm,
61 executor_required: false,
62};
63
64pub static ARCH_UNSPECIFIED: Arch = Arch {
65 default_entry: None,
66 flavor: Flavor::Standard,
67 executor_required: true,
68};
69
70#[derive(Debug, FromMeta, Default)]
10struct Args { 71struct Args {
11 #[darling(default)] 72 #[darling(default)]
12 entry: Option<String>, 73 entry: Option<String>,
74 #[darling(default)]
75 executor: Option<String>,
13} 76}
14 77
15pub fn avr() -> TokenStream { 78pub fn run(args: TokenStream, item: TokenStream, arch: &Arch) -> TokenStream {
16 quote! { 79 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 80
22 executor.run(|spawner| { 81 // 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)); 82 // to the expected output as possible. This helps out IDEs such that completions and other
24 }) 83 // related features keep working.
25 } 84 let f: ItemFn = match syn::parse2(item.clone()) {
26 } 85 Ok(x) => x,
27} 86 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 }; 87 };
34 88
35 let entry = maybe_entry.unwrap_or("riscv_rt::entry".into()); 89 let args = match NestedMeta::parse_meta_list(args) {
36 let entry = match Expr::from_string(&entry) { 90 Ok(x) => x,
37 Ok(expr) => expr, 91 Err(e) => return token_stream_with_error(item, e),
38 Err(e) => return e.write_errors(),
39 }; 92 };
40 93
41 quote! { 94 let args = match Args::from_list(&args) {
42 #[#entry] 95 Ok(x) => x,
43 fn main() -> ! { 96 Err(e) => {
44 let mut executor = ::embassy_executor::Executor::new(); 97 errors.extend(e.write_errors());
45 let executor = unsafe { __make_static(&mut executor) }; 98 Args::default()
46 executor.run(|spawner| {
47 spawner.must_spawn(__embassy_main(spawner));
48 })
49 }
50 }
51}
52
53pub fn cortex_m() -> TokenStream {
54 quote! {
55 #[cortex_m_rt::entry]
56 fn main() -> ! {
57 let mut executor = ::embassy_executor::Executor::new();
58 let executor = unsafe { __make_static(&mut executor) };
59 executor.run(|spawner| {
60 spawner.must_spawn(__embassy_main(spawner));
61 })
62 }
63 }
64}
65
66pub fn wasm() -> TokenStream {
67 quote! {
68 #[wasm_bindgen::prelude::wasm_bindgen(start)]
69 pub fn main() -> Result<(), wasm_bindgen::JsValue> {
70 let executor = ::std::boxed::Box::leak(::std::boxed::Box::new(::embassy_executor::Executor::new()));
71
72 executor.start(|spawner| {
73 spawner.must_spawn(__embassy_main(spawner));
74 });
75
76 Ok(())
77 }
78 }
79}
80
81pub fn std() -> TokenStream {
82 quote! {
83 fn main() -> ! {
84 let mut executor = ::embassy_executor::Executor::new();
85 let executor = unsafe { __make_static(&mut executor) };
86
87 executor.run(|spawner| {
88 spawner.must_spawn(__embassy_main(spawner));
89 })
90 } 99 }
91 } 100 };
92}
93
94pub fn run(args: &[NestedMeta], f: syn::ItemFn, main: TokenStream) -> Result<TokenStream, TokenStream> {
95 #[allow(unused_variables)]
96 let args = Args::from_list(args).map_err(|e| e.write_errors())?;
97 101
98 let fargs = f.sig.inputs.clone(); 102 let fargs = f.sig.inputs.clone();
99 103
100 let ctxt = Ctxt::new();
101
102 if f.sig.asyncness.is_none() { 104 if f.sig.asyncness.is_none() {
103 ctxt.error_spanned_by(&f.sig, "main function must be async"); 105 error(&mut errors, &f.sig, "main function must be async");
104 } 106 }
105 if !f.sig.generics.params.is_empty() { 107 if !f.sig.generics.params.is_empty() {
106 ctxt.error_spanned_by(&f.sig, "main function must not be generic"); 108 error(&mut errors, &f.sig, "main function must not be generic");
107 } 109 }
108 if !f.sig.generics.where_clause.is_none() { 110 if !f.sig.generics.where_clause.is_none() {
109 ctxt.error_spanned_by(&f.sig, "main function must not have `where` clauses"); 111 error(&mut errors, &f.sig, "main function must not have `where` clauses");
110 } 112 }
111 if !f.sig.abi.is_none() { 113 if !f.sig.abi.is_none() {
112 ctxt.error_spanned_by(&f.sig, "main function must not have an ABI qualifier"); 114 error(&mut errors, &f.sig, "main function must not have an ABI qualifier");
113 } 115 }
114 if !f.sig.variadic.is_none() { 116 if !f.sig.variadic.is_none() {
115 ctxt.error_spanned_by(&f.sig, "main function must not be variadic"); 117 error(&mut errors, &f.sig, "main function must not be variadic");
116 } 118 }
117 match &f.sig.output { 119 match &f.sig.output {
118 ReturnType::Default => {} 120 ReturnType::Default => {}
119 ReturnType::Type(_, ty) => match &**ty { 121 ReturnType::Type(_, ty) => match &**ty {
120 Type::Tuple(tuple) if tuple.elems.is_empty() => {} 122 Type::Tuple(tuple) if tuple.elems.is_empty() => {}
121 Type::Never(_) => {} 123 Type::Never(_) => {}
122 _ => ctxt.error_spanned_by( 124 _ => error(
125 &mut errors,
123 &f.sig, 126 &f.sig,
124 "main function must either not return a value, return `()` or return `!`", 127 "main function must either not return a value, return `()` or return `!`",
125 ), 128 ),
@@ -127,26 +130,99 @@ pub fn run(args: &[NestedMeta], f: syn::ItemFn, main: TokenStream) -> Result<Tok
127 } 130 }
128 131
129 if fargs.len() != 1 { 132 if fargs.len() != 1 {
130 ctxt.error_spanned_by(&f.sig, "main function must have 1 argument: the spawner."); 133 error(&mut errors, &f.sig, "main function must have 1 argument: the spawner.");
131 } 134 }
132 135
133 ctxt.check()?; 136 let entry = match (args.entry.as_deref(), arch.default_entry.as_deref()) {
137 (None, None) => TokenStream::new(),
138 (Some(x), _) | (None, Some(x)) if x == "" => TokenStream::new(),
139 (Some(x), _) | (None, Some(x)) => match TokenStream::from_str(x) {
140 Ok(x) => quote!(#[#x]),
141 Err(e) => {
142 error(&mut errors, &f.sig, e);
143 TokenStream::new()
144 }
145 },
146 };
147
148 let executor = match (args.executor.as_deref(), arch.executor_required) {
149 (None, true) => {
150 error(
151 &mut errors,
152 &f.sig,
153 "\
154No architecture selected for embassy-executor. Make sure you've enabled one of the `arch-*` features in your Cargo.toml.
155
156Alternatively, if you would like to use a custom executor implementation, specify it with the `executor` argument.
157For example: `#[embassy_executor::main(entry = ..., executor = \"some_crate::Executor\")]",
158 );
159 ""
160 }
161 (Some(x), _) => x,
162 (None, _) => "::embassy_executor::Executor",
163 };
164
165 let executor = TokenStream::from_str(executor).unwrap_or_else(|e| {
166 error(&mut errors, &f.sig, e);
167 TokenStream::new()
168 });
134 169
135 let f_body = f.block; 170 let f_body = f.body;
136 let out = &f.sig.output; 171 let out = &f.sig.output;
137 172
173 let (main_ret, mut main_body) = match arch.flavor {
174 Flavor::Standard => (
175 quote!(!),
176 quote! {
177 unsafe fn __make_static<T>(t: &mut T) -> &'static mut T {
178 ::core::mem::transmute(t)
179 }
180
181 let mut executor = #executor::new();
182 let executor = unsafe { __make_static(&mut executor) };
183 executor.run(|spawner| {
184 spawner.must_spawn(__embassy_main(spawner));
185 })
186 },
187 ),
188 Flavor::Wasm => (
189 quote!(Result<(), wasm_bindgen::JsValue>),
190 quote! {
191 let executor = ::std::boxed::Box::leak(::std::boxed::Box::new(#executor::new()));
192
193 executor.start(|spawner| {
194 spawner.must_spawn(__embassy_main(spawner));
195 });
196
197 Ok(())
198 },
199 ),
200 };
201
202 let mut main_attrs = TokenStream::new();
203 for attr in f.attrs {
204 main_attrs.extend(quote!(#attr));
205 }
206
207 if !errors.is_empty() {
208 main_body = quote! {loop{}};
209 }
210
138 let result = quote! { 211 let result = quote! {
139 #[::embassy_executor::task()] 212 #[::embassy_executor::task()]
213 #[allow(clippy::future_not_send)]
140 async fn __embassy_main(#fargs) #out { 214 async fn __embassy_main(#fargs) #out {
141 #f_body 215 #f_body
142 } 216 }
143 217
144 unsafe fn __make_static<T>(t: &mut T) -> &'static mut T { 218 #entry
145 ::core::mem::transmute(t) 219 #main_attrs
220 fn main() -> #main_ret {
221 #main_body
146 } 222 }
147 223
148 #main 224 #errors
149 }; 225 };
150 226
151 Ok(result) 227 result
152} 228}
diff --git a/embassy-executor-macros/src/macros/task.rs b/embassy-executor-macros/src/macros/task.rs
index 96c6267b2..91d6beee8 100644
--- a/embassy-executor-macros/src/macros/task.rs
+++ b/embassy-executor-macros/src/macros/task.rs
@@ -1,48 +1,78 @@
1use std::str::FromStr;
2
1use darling::export::NestedMeta; 3use darling::export::NestedMeta;
2use darling::FromMeta; 4use darling::FromMeta;
3use proc_macro2::{Span, TokenStream}; 5use proc_macro2::{Span, TokenStream};
4use quote::{format_ident, quote}; 6use quote::{format_ident, quote};
5use syn::{parse_quote, Expr, ExprLit, ItemFn, Lit, LitInt, ReturnType, Type}; 7use syn::visit::{self, Visit};
8use syn::{Expr, ExprLit, Lit, LitInt, ReturnType, Type};
6 9
7use crate::util::ctxt::Ctxt; 10use crate::util::*;
8 11
9#[derive(Debug, FromMeta)] 12#[derive(Debug, FromMeta, Default)]
10struct Args { 13struct Args {
11 #[darling(default)] 14 #[darling(default)]
12 pool_size: Option<syn::Expr>, 15 pool_size: Option<syn::Expr>,
16 /// Use this to override the `embassy_executor` crate path. Defaults to `::embassy_executor`.
17 #[darling(default)]
18 embassy_executor: Option<syn::Expr>,
13} 19}
14 20
15pub fn run(args: &[NestedMeta], f: syn::ItemFn) -> Result<TokenStream, TokenStream> { 21pub fn run(args: TokenStream, item: TokenStream) -> TokenStream {
16 let args = Args::from_list(args).map_err(|e| e.write_errors())?; 22 let mut errors = TokenStream::new();
23
24 // If any of the steps for this macro fail, we still want to expand to an item that is as close
25 // to the expected output as possible. This helps out IDEs such that completions and other
26 // related features keep working.
27 let f: ItemFn = match syn::parse2(item.clone()) {
28 Ok(x) => x,
29 Err(e) => return token_stream_with_error(item, e),
30 };
31
32 let args = match NestedMeta::parse_meta_list(args) {
33 Ok(x) => x,
34 Err(e) => return token_stream_with_error(item, e),
35 };
36
37 let args = match Args::from_list(&args) {
38 Ok(x) => x,
39 Err(e) => {
40 errors.extend(e.write_errors());
41 Args::default()
42 }
43 };
17 44
18 let pool_size = args.pool_size.unwrap_or(Expr::Lit(ExprLit { 45 let pool_size = args.pool_size.unwrap_or(Expr::Lit(ExprLit {
19 attrs: vec![], 46 attrs: vec![],
20 lit: Lit::Int(LitInt::new("1", Span::call_site())), 47 lit: Lit::Int(LitInt::new("1", Span::call_site())),
21 })); 48 }));
22 49
23 let ctxt = Ctxt::new(); 50 let embassy_executor = args
51 .embassy_executor
52 .unwrap_or(Expr::Verbatim(TokenStream::from_str("::embassy_executor").unwrap()));
24 53
25 if f.sig.asyncness.is_none() { 54 if f.sig.asyncness.is_none() {
26 ctxt.error_spanned_by(&f.sig, "task functions must be async"); 55 error(&mut errors, &f.sig, "task functions must be async");
27 } 56 }
28 if !f.sig.generics.params.is_empty() { 57 if !f.sig.generics.params.is_empty() {
29 ctxt.error_spanned_by(&f.sig, "task functions must not be generic"); 58 error(&mut errors, &f.sig, "task functions must not be generic");
30 } 59 }
31 if !f.sig.generics.where_clause.is_none() { 60 if !f.sig.generics.where_clause.is_none() {
32 ctxt.error_spanned_by(&f.sig, "task functions must not have `where` clauses"); 61 error(&mut errors, &f.sig, "task functions must not have `where` clauses");
33 } 62 }
34 if !f.sig.abi.is_none() { 63 if !f.sig.abi.is_none() {
35 ctxt.error_spanned_by(&f.sig, "task functions must not have an ABI qualifier"); 64 error(&mut errors, &f.sig, "task functions must not have an ABI qualifier");
36 } 65 }
37 if !f.sig.variadic.is_none() { 66 if !f.sig.variadic.is_none() {
38 ctxt.error_spanned_by(&f.sig, "task functions must not be variadic"); 67 error(&mut errors, &f.sig, "task functions must not be variadic");
39 } 68 }
40 match &f.sig.output { 69 match &f.sig.output {
41 ReturnType::Default => {} 70 ReturnType::Default => {}
42 ReturnType::Type(_, ty) => match &**ty { 71 ReturnType::Type(_, ty) => match &**ty {
43 Type::Tuple(tuple) if tuple.elems.is_empty() => {} 72 Type::Tuple(tuple) if tuple.elems.is_empty() => {}
44 Type::Never(_) => {} 73 Type::Never(_) => {}
45 _ => ctxt.error_spanned_by( 74 _ => error(
75 &mut errors,
46 &f.sig, 76 &f.sig,
47 "task functions must either not return a value, return `()` or return `!`", 77 "task functions must either not return a value, return `()` or return `!`",
48 ), 78 ),
@@ -55,26 +85,31 @@ pub fn run(args: &[NestedMeta], f: syn::ItemFn) -> Result<TokenStream, TokenStre
55 for arg in fargs.iter_mut() { 85 for arg in fargs.iter_mut() {
56 match arg { 86 match arg {
57 syn::FnArg::Receiver(_) => { 87 syn::FnArg::Receiver(_) => {
58 ctxt.error_spanned_by(arg, "task functions must not have receiver arguments"); 88 error(&mut errors, arg, "task functions must not have `self` arguments");
59 } 89 }
60 syn::FnArg::Typed(t) => match t.pat.as_mut() { 90 syn::FnArg::Typed(t) => {
61 syn::Pat::Ident(id) => { 91 check_arg_ty(&mut errors, &t.ty);
62 id.mutability = None; 92 match t.pat.as_mut() {
63 args.push((id.clone(), t.attrs.clone())); 93 syn::Pat::Ident(id) => {
94 id.mutability = None;
95 args.push((id.clone(), t.attrs.clone()));
96 }
97 _ => {
98 error(
99 &mut errors,
100 arg,
101 "pattern matching in task arguments is not yet supported",
102 );
103 }
64 } 104 }
65 _ => { 105 }
66 ctxt.error_spanned_by(arg, "pattern matching in task arguments is not yet supported");
67 }
68 },
69 } 106 }
70 } 107 }
71 108
72 ctxt.check()?;
73
74 let task_ident = f.sig.ident.clone(); 109 let task_ident = f.sig.ident.clone();
75 let task_inner_ident = format_ident!("__{}_task", task_ident); 110 let task_inner_ident = format_ident!("__{}_task", task_ident);
76 111
77 let mut task_inner = f; 112 let mut task_inner = f.clone();
78 let visibility = task_inner.vis.clone(); 113 let visibility = task_inner.vis.clone();
79 task_inner.vis = syn::Visibility::Inherited; 114 task_inner.vis = syn::Visibility::Inherited;
80 task_inner.sig.ident = task_inner_ident.clone(); 115 task_inner.sig.ident = task_inner_ident.clone();
@@ -91,35 +126,54 @@ pub fn run(args: &[NestedMeta], f: syn::ItemFn) -> Result<TokenStream, TokenStre
91 } 126 }
92 127
93 #[cfg(feature = "nightly")] 128 #[cfg(feature = "nightly")]
94 let mut task_outer: ItemFn = parse_quote! { 129 let mut task_outer_body = quote! {
95 #visibility fn #task_ident(#fargs) -> ::embassy_executor::SpawnToken<impl Sized> { 130 trait _EmbassyInternalTaskTrait {
96 trait _EmbassyInternalTaskTrait { 131 type Fut: ::core::future::Future + 'static;
97 type Fut: ::core::future::Future + 'static; 132 fn construct(#fargs) -> Self::Fut;
98 fn construct(#fargs) -> Self::Fut; 133 }
99 }
100 134
101 impl _EmbassyInternalTaskTrait for () { 135 impl _EmbassyInternalTaskTrait for () {
102 type Fut = impl core::future::Future + 'static; 136 type Fut = impl core::future::Future + 'static;
103 fn construct(#fargs) -> Self::Fut { 137 fn construct(#fargs) -> Self::Fut {
104 #task_inner_ident(#(#full_args,)*) 138 #task_inner_ident(#(#full_args,)*)
105 }
106 } 139 }
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 } 140 }
141
142 const POOL_SIZE: usize = #pool_size;
143 static POOL: #embassy_executor::raw::TaskPool<<() as _EmbassyInternalTaskTrait>::Fut, POOL_SIZE> = #embassy_executor::raw::TaskPool::new();
144 unsafe { POOL._spawn_async_fn(move || <() as _EmbassyInternalTaskTrait>::construct(#(#full_args,)*)) }
112 }; 145 };
113 #[cfg(not(feature = "nightly"))] 146 #[cfg(not(feature = "nightly"))]
114 let mut task_outer: ItemFn = parse_quote! { 147 let mut task_outer_body = quote! {
115 #visibility fn #task_ident(#fargs) -> ::embassy_executor::SpawnToken<impl Sized> { 148 const fn __task_pool_get<F, Args, Fut>(_: F) -> &'static #embassy_executor::raw::TaskPool<Fut, POOL_SIZE>
116 const POOL_SIZE: usize = #pool_size; 149 where
117 static POOL: ::embassy_executor::_export::TaskPoolRef = ::embassy_executor::_export::TaskPoolRef::new(); 150 F: #embassy_executor::_export::TaskFn<Args, Fut = Fut>,
118 unsafe { POOL.get::<_, POOL_SIZE>()._spawn_async_fn(move || #task_inner_ident(#(#full_args,)*)) } 151 Fut: ::core::future::Future + 'static,
152 {
153 unsafe { &*POOL.get().cast() }
119 } 154 }
155
156 const POOL_SIZE: usize = #pool_size;
157 static POOL: #embassy_executor::_export::TaskPoolHolder<
158 {#embassy_executor::_export::task_pool_size::<_, _, _, POOL_SIZE>(#task_inner_ident)},
159 {#embassy_executor::_export::task_pool_align::<_, _, _, POOL_SIZE>(#task_inner_ident)},
160 > = unsafe { ::core::mem::transmute(#embassy_executor::_export::task_pool_new::<_, _, _, POOL_SIZE>(#task_inner_ident)) };
161 unsafe { __task_pool_get(#task_inner_ident)._spawn_async_fn(move || #task_inner_ident(#(#full_args,)*)) }
120 }; 162 };
121 163
122 task_outer.attrs.append(&mut task_inner.attrs.clone()); 164 let task_outer_attrs = task_inner.attrs.clone();
165
166 if !errors.is_empty() {
167 task_outer_body = quote! {
168 #![allow(unused_variables, unreachable_code)]
169 let _x: #embassy_executor::SpawnToken<()> = ::core::todo!();
170 _x
171 };
172 }
173
174 // Copy the generics + where clause to avoid more spurious errors.
175 let generics = &f.sig.generics;
176 let where_clause = &f.sig.generics.where_clause;
123 177
124 let result = quote! { 178 let result = quote! {
125 // This is the user's task function, renamed. 179 // This is the user's task function, renamed.
@@ -129,8 +183,49 @@ pub fn run(args: &[NestedMeta], f: syn::ItemFn) -> Result<TokenStream, TokenStre
129 #[doc(hidden)] 183 #[doc(hidden)]
130 #task_inner 184 #task_inner
131 185
132 #task_outer 186 #(#task_outer_attrs)*
187 #visibility fn #task_ident #generics (#fargs) -> #embassy_executor::SpawnToken<impl Sized> #where_clause{
188 #task_outer_body
189 }
190
191 #errors
133 }; 192 };
134 193
135 Ok(result) 194 result
195}
196
197fn check_arg_ty(errors: &mut TokenStream, ty: &Type) {
198 struct Visitor<'a> {
199 errors: &'a mut TokenStream,
200 }
201
202 impl<'a, 'ast> Visit<'ast> for Visitor<'a> {
203 fn visit_type_reference(&mut self, i: &'ast syn::TypeReference) {
204 // only check for elided lifetime here. If not elided, it's checked by `visit_lifetime`.
205 if i.lifetime.is_none() {
206 error(
207 self.errors,
208 i.and_token,
209 "Arguments for tasks must live forever. Try using the `'static` lifetime.",
210 )
211 }
212 visit::visit_type_reference(self, i);
213 }
214
215 fn visit_lifetime(&mut self, i: &'ast syn::Lifetime) {
216 if i.ident.to_string() != "static" {
217 error(
218 self.errors,
219 i,
220 "Arguments for tasks must live forever. Try using the `'static` lifetime.",
221 )
222 }
223 }
224
225 fn visit_type_impl_trait(&mut self, i: &'ast syn::TypeImplTrait) {
226 error(self.errors, i, "`impl Trait` is not allowed in task arguments. It is syntax sugar for generics, and tasks can't be generic.");
227 }
228 }
229
230 Visit::visit_type(&mut Visitor { errors }, ty);
136} 231}
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;