esp_hal_procmacros/
embassy.rs

1use darling::ast::NestedMeta;
2use main_mod::*;
3use proc_macro::TokenStream;
4use syn::{
5    parse::{Parse, ParseBuffer},
6    punctuated::Punctuated,
7    Token,
8};
9
10pub struct Args {
11    pub(crate) meta: Vec<NestedMeta>,
12}
13
14impl Parse for Args {
15    fn parse(input: &ParseBuffer) -> syn::Result<Self> {
16        let meta = Punctuated::<NestedMeta, Token![,]>::parse_terminated(input)?;
17        Ok(Args {
18            meta: meta.into_iter().collect(),
19        })
20    }
21}
22
23pub fn main(args: TokenStream, item: TokenStream) -> TokenStream {
24    let args = syn::parse_macro_input!(args as Args);
25    let f = syn::parse_macro_input!(item as syn::ItemFn);
26
27    run(&args.meta, f, main_fn()).unwrap_or_else(|x| x).into()
28}
29
30pub mod main_mod {
31    use std::{cell::RefCell, fmt::Display, thread};
32
33    use darling::{export::NestedMeta, FromMeta};
34    use proc_macro2::TokenStream;
35    use quote::{quote, ToTokens};
36    use syn::{ReturnType, Type};
37
38    #[derive(Debug, FromMeta)]
39    struct Args {}
40
41    pub fn run(
42        args: &[NestedMeta],
43        f: syn::ItemFn,
44        main: TokenStream,
45    ) -> Result<TokenStream, TokenStream> {
46        let _args = Args::from_list(args).map_err(|e| e.write_errors())?;
47
48        let fargs = f.sig.inputs.clone();
49
50        let ctxt = Ctxt::new();
51
52        if f.sig.asyncness.is_none() {
53            ctxt.error_spanned_by(&f.sig, "main function must be async");
54        }
55        if !f.sig.generics.params.is_empty() {
56            ctxt.error_spanned_by(&f.sig, "main function must not be generic");
57        }
58        if f.sig.generics.where_clause.is_some() {
59            ctxt.error_spanned_by(&f.sig, "main function must not have `where` clauses");
60        }
61        if f.sig.abi.is_some() {
62            ctxt.error_spanned_by(&f.sig, "main function must not have an ABI qualifier");
63        }
64        if f.sig.variadic.is_some() {
65            ctxt.error_spanned_by(&f.sig, "main function must not be variadic");
66        }
67        match &f.sig.output {
68            ReturnType::Default => {}
69            ReturnType::Type(_, ty) => match &**ty {
70                Type::Tuple(tuple) if tuple.elems.is_empty() => {}
71                Type::Never(_) => {}
72                _ => ctxt.error_spanned_by(
73                    &f.sig,
74                    "main function must either not return a value, return `()` or return `!`",
75                ),
76            },
77        }
78
79        if fargs.len() != 1 {
80            ctxt.error_spanned_by(&f.sig, "main function must have 1 argument: the spawner.");
81        }
82
83        ctxt.check()?;
84
85        let f_body = f.block;
86        let out = &f.sig.output;
87
88        let result = quote! {
89            #[doc(hidden)]
90            #[::embassy_executor::task()]
91            async fn __embassy_main(#fargs) #out {
92                #f_body
93            }
94
95            #[doc(hidden)]
96            unsafe fn __make_static<T>(t: &mut T) -> &'static mut T {
97                ::core::mem::transmute(t)
98            }
99
100            #main
101        };
102
103        Ok(result)
104    }
105
106    /// A type to collect errors together and format them.
107    ///
108    /// Dropping this object will cause a panic. It must be consumed using
109    /// `check`.
110    ///
111    /// References can be shared since this type uses run-time exclusive mut
112    /// checking.
113    #[derive(Default)]
114    pub struct Ctxt {
115        // The contents will be set to `None` during checking. This is so that checking can be
116        // enforced.
117        errors: RefCell<Option<Vec<syn::Error>>>,
118    }
119
120    impl Ctxt {
121        /// Create a new context object.
122        ///
123        /// This object contains no errors, but will still trigger a panic if it
124        /// is not `check`ed.
125        pub fn new() -> Self {
126            Ctxt {
127                errors: RefCell::new(Some(Vec::new())),
128            }
129        }
130
131        /// Add an error to the context object with a tokenenizable object.
132        ///
133        /// The object is used for spanning in error messages.
134        pub fn error_spanned_by<A: ToTokens, T: Display>(&self, obj: A, msg: T) {
135            self.errors
136                .borrow_mut()
137                .as_mut()
138                .unwrap()
139                // Curb monomorphization from generating too many identical methods.
140                .push(syn::Error::new_spanned(obj.into_token_stream(), msg));
141        }
142
143        /// Consume this object, producing a formatted error string if there are
144        /// errors.
145        pub fn check(self) -> Result<(), TokenStream> {
146            let errors = self.errors.borrow_mut().take().unwrap();
147            match errors.len() {
148                0 => Ok(()),
149                _ => Err(to_compile_errors(errors)),
150            }
151        }
152    }
153
154    fn to_compile_errors(errors: Vec<syn::Error>) -> proc_macro2::TokenStream {
155        let compile_errors = errors.iter().map(syn::Error::to_compile_error);
156        quote!(#(#compile_errors)*)
157    }
158
159    impl Drop for Ctxt {
160        fn drop(&mut self) {
161            if !thread::panicking() && self.errors.borrow().is_some() {
162                panic!("forgot to check for errors");
163            }
164        }
165    }
166
167    pub fn main_fn() -> TokenStream {
168        quote! {
169            #[esp_hal::main]
170            fn main() -> ! {
171                let mut executor = ::esp_hal_embassy::Executor::new();
172                let executor = unsafe { __make_static(&mut executor) };
173                executor.run(|spawner| {
174                    spawner.must_spawn(__embassy_main(spawner));
175                })
176            }
177        }
178    }
179}