diff options
Diffstat (limited to 'embassy-executor-macros/src')
| -rw-r--r-- | embassy-executor-macros/src/lib.rs | 175 | ||||
| -rw-r--r-- | embassy-executor-macros/src/macros/main.rs | 138 | ||||
| -rw-r--r-- | embassy-executor-macros/src/macros/mod.rs | 2 | ||||
| -rw-r--r-- | embassy-executor-macros/src/macros/task.rs | 114 | ||||
| -rw-r--r-- | embassy-executor-macros/src/util/ctxt.rs | 73 | ||||
| -rw-r--r-- | embassy-executor-macros/src/util/mod.rs | 1 |
6 files changed, 503 insertions, 0 deletions
diff --git a/embassy-executor-macros/src/lib.rs b/embassy-executor-macros/src/lib.rs new file mode 100644 index 000000000..c9d58746a --- /dev/null +++ b/embassy-executor-macros/src/lib.rs | |||
| @@ -0,0 +1,175 @@ | |||
| 1 | #![doc = include_str!("../README.md")] | ||
| 2 | extern crate proc_macro; | ||
| 3 | |||
| 4 | use darling::ast::NestedMeta; | ||
| 5 | use proc_macro::TokenStream; | ||
| 6 | |||
| 7 | mod macros; | ||
| 8 | mod util; | ||
| 9 | use macros::*; | ||
| 10 | use syn::parse::{Parse, ParseBuffer}; | ||
| 11 | use syn::punctuated::Punctuated; | ||
| 12 | use syn::Token; | ||
| 13 | |||
| 14 | struct Args { | ||
| 15 | meta: Vec<NestedMeta>, | ||
| 16 | } | ||
| 17 | |||
| 18 | impl 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 | |||
| 27 | /// 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. | ||
| 29 | /// | ||
| 30 | /// | ||
| 31 | /// The following restrictions apply: | ||
| 32 | /// | ||
| 33 | /// * The function must be declared `async`. | ||
| 34 | /// * The function must not use generics. | ||
| 35 | /// * The optional `pool_size` attribute must be 1 or greater. | ||
| 36 | /// | ||
| 37 | /// | ||
| 38 | /// ## Examples | ||
| 39 | /// | ||
| 40 | /// Declaring a task taking no arguments: | ||
| 41 | /// | ||
| 42 | /// ``` rust | ||
| 43 | /// #[embassy_executor::task] | ||
| 44 | /// async fn mytask() { | ||
| 45 | /// // Function body | ||
| 46 | /// } | ||
| 47 | /// ``` | ||
| 48 | /// | ||
| 49 | /// Declaring a task with a given pool size: | ||
| 50 | /// | ||
| 51 | /// ``` rust | ||
| 52 | /// #[embassy_executor::task(pool_size = 4)] | ||
| 53 | /// async fn mytask() { | ||
| 54 | /// // Function body | ||
| 55 | /// } | ||
| 56 | /// ``` | ||
| 57 | #[proc_macro_attribute] | ||
| 58 | pub fn task(args: TokenStream, item: TokenStream) -> TokenStream { | ||
| 59 | let args = syn::parse_macro_input!(args as Args); | ||
| 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 | } | ||
| 64 | |||
| 65 | /// Creates a new `executor` instance and declares an application entry point for Cortex-M spawning the corresponding function body as an async task. | ||
| 66 | /// | ||
| 67 | /// The following restrictions apply: | ||
| 68 | /// | ||
| 69 | /// * The function must accept exactly 1 parameter, an `embassy_executor::Spawner` handle that it can use to spawn additional tasks. | ||
| 70 | /// * The function must be declared `async`. | ||
| 71 | /// * The function must not use generics. | ||
| 72 | /// * Only a single `main` task may be declared. | ||
| 73 | /// | ||
| 74 | /// ## Examples | ||
| 75 | /// Spawning a task: | ||
| 76 | /// | ||
| 77 | /// ``` rust | ||
| 78 | /// #[embassy_executor::main] | ||
| 79 | /// async fn main(_s: embassy_executor::Spawner) { | ||
| 80 | /// // Function body | ||
| 81 | /// } | ||
| 82 | /// ``` | ||
| 83 | #[proc_macro_attribute] | ||
| 84 | pub fn main_cortex_m(args: TokenStream, item: TokenStream) -> TokenStream { | ||
| 85 | let args = syn::parse_macro_input!(args as Args); | ||
| 86 | let f = syn::parse_macro_input!(item as syn::ItemFn); | ||
| 87 | main::run(&args.meta, f, main::cortex_m()).unwrap_or_else(|x| x).into() | ||
| 88 | } | ||
| 89 | |||
| 90 | /// Creates a new `executor` instance and declares an application entry point for RISC-V spawning the corresponding function body as an async task. | ||
| 91 | /// | ||
| 92 | /// The following restrictions apply: | ||
| 93 | /// | ||
| 94 | /// * The function must accept exactly 1 parameter, an `embassy_executor::Spawner` handle that it can use to spawn additional tasks. | ||
| 95 | /// * The function must be declared `async`. | ||
| 96 | /// * The function must not use generics. | ||
| 97 | /// * Only a single `main` task may be declared. | ||
| 98 | /// | ||
| 99 | /// A user-defined entry macro can be optionally provided via the `entry` argument to override the default of `riscv_rt::entry`. | ||
| 100 | /// | ||
| 101 | /// ## Examples | ||
| 102 | /// Spawning a task: | ||
| 103 | /// | ||
| 104 | /// ``` rust | ||
| 105 | /// #[embassy_executor::main] | ||
| 106 | /// async fn main(_s: embassy_executor::Spawner) { | ||
| 107 | /// // Function body | ||
| 108 | /// } | ||
| 109 | /// ``` | ||
| 110 | /// | ||
| 111 | /// Spawning a task using a custom entry macro: | ||
| 112 | /// ``` rust | ||
| 113 | /// #[embassy_executor::main(entry = "esp_riscv_rt::entry")] | ||
| 114 | /// async fn main(_s: embassy_executor::Spawner) { | ||
| 115 | /// // Function body | ||
| 116 | /// } | ||
| 117 | /// ``` | ||
| 118 | #[proc_macro_attribute] | ||
| 119 | pub fn main_riscv(args: TokenStream, item: TokenStream) -> TokenStream { | ||
| 120 | let args = syn::parse_macro_input!(args as Args); | ||
| 121 | let f = syn::parse_macro_input!(item as syn::ItemFn); | ||
| 122 | main::run(&args.meta, f, main::riscv(&args.meta)) | ||
| 123 | .unwrap_or_else(|x| x) | ||
| 124 | .into() | ||
| 125 | } | ||
| 126 | |||
| 127 | /// Creates a new `executor` instance and declares an application entry point for STD spawning the corresponding function body as an async task. | ||
| 128 | /// | ||
| 129 | /// The following restrictions apply: | ||
| 130 | /// | ||
| 131 | /// * The function must accept exactly 1 parameter, an `embassy_executor::Spawner` handle that it can use to spawn additional tasks. | ||
| 132 | /// * The function must be declared `async`. | ||
| 133 | /// * The function must not use generics. | ||
| 134 | /// * Only a single `main` task may be declared. | ||
| 135 | /// | ||
| 136 | /// ## Examples | ||
| 137 | /// Spawning a task: | ||
| 138 | /// | ||
| 139 | /// ``` rust | ||
| 140 | /// #[embassy_executor::main] | ||
| 141 | /// async fn main(_s: embassy_executor::Spawner) { | ||
| 142 | /// // Function body | ||
| 143 | /// } | ||
| 144 | /// ``` | ||
| 145 | #[proc_macro_attribute] | ||
| 146 | pub fn main_std(args: TokenStream, item: TokenStream) -> TokenStream { | ||
| 147 | let args = syn::parse_macro_input!(args as Args); | ||
| 148 | let f = syn::parse_macro_input!(item as syn::ItemFn); | ||
| 149 | main::run(&args.meta, f, main::std()).unwrap_or_else(|x| x).into() | ||
| 150 | } | ||
| 151 | |||
| 152 | /// Creates a new `executor` instance and declares an application entry point for WASM spawning the corresponding function body as an async task. | ||
| 153 | /// | ||
| 154 | /// The following restrictions apply: | ||
| 155 | /// | ||
| 156 | /// * The function must accept exactly 1 parameter, an `embassy_executor::Spawner` handle that it can use to spawn additional tasks. | ||
| 157 | /// * The function must be declared `async`. | ||
| 158 | /// * The function must not use generics. | ||
| 159 | /// * Only a single `main` task may be declared. | ||
| 160 | /// | ||
| 161 | /// ## Examples | ||
| 162 | /// Spawning a task: | ||
| 163 | /// | ||
| 164 | /// ``` rust | ||
| 165 | /// #[embassy_executor::main] | ||
| 166 | /// async fn main(_s: embassy_executor::Spawner) { | ||
| 167 | /// // Function body | ||
| 168 | /// } | ||
| 169 | /// ``` | ||
| 170 | #[proc_macro_attribute] | ||
| 171 | pub fn main_wasm(args: TokenStream, item: TokenStream) -> TokenStream { | ||
| 172 | let args = syn::parse_macro_input!(args as Args); | ||
| 173 | let f = syn::parse_macro_input!(item as syn::ItemFn); | ||
| 174 | main::run(&args.meta, f, main::wasm()).unwrap_or_else(|x| x).into() | ||
| 175 | } | ||
diff --git a/embassy-executor-macros/src/macros/main.rs b/embassy-executor-macros/src/macros/main.rs new file mode 100644 index 000000000..3c0d58567 --- /dev/null +++ b/embassy-executor-macros/src/macros/main.rs | |||
| @@ -0,0 +1,138 @@ | |||
| 1 | use darling::export::NestedMeta; | ||
| 2 | use darling::FromMeta; | ||
| 3 | use proc_macro2::TokenStream; | ||
| 4 | use quote::quote; | ||
| 5 | use syn::{Expr, ReturnType, Type}; | ||
| 6 | |||
| 7 | use crate::util::ctxt::Ctxt; | ||
| 8 | |||
| 9 | #[derive(Debug, FromMeta)] | ||
| 10 | struct Args { | ||
| 11 | #[darling(default)] | ||
| 12 | entry: Option<String>, | ||
| 13 | } | ||
| 14 | |||
| 15 | pub fn riscv(args: &[NestedMeta]) -> TokenStream { | ||
| 16 | let maybe_entry = match Args::from_list(args) { | ||
| 17 | Ok(args) => args.entry, | ||
| 18 | Err(e) => return e.write_errors(), | ||
| 19 | }; | ||
| 20 | |||
| 21 | let entry = maybe_entry.unwrap_or("riscv_rt::entry".into()); | ||
| 22 | let entry = match Expr::from_string(&entry) { | ||
| 23 | Ok(expr) => expr, | ||
| 24 | Err(e) => return e.write_errors(), | ||
| 25 | }; | ||
| 26 | |||
| 27 | quote! { | ||
| 28 | #[#entry] | ||
| 29 | fn main() -> ! { | ||
| 30 | let mut executor = ::embassy_executor::Executor::new(); | ||
| 31 | let executor = unsafe { __make_static(&mut executor) }; | ||
| 32 | executor.run(|spawner| { | ||
| 33 | spawner.must_spawn(__embassy_main(spawner)); | ||
| 34 | }) | ||
| 35 | } | ||
| 36 | } | ||
| 37 | } | ||
| 38 | |||
| 39 | pub fn cortex_m() -> TokenStream { | ||
| 40 | quote! { | ||
| 41 | #[cortex_m_rt::entry] | ||
| 42 | fn main() -> ! { | ||
| 43 | let mut executor = ::embassy_executor::Executor::new(); | ||
| 44 | let executor = unsafe { __make_static(&mut executor) }; | ||
| 45 | executor.run(|spawner| { | ||
| 46 | spawner.must_spawn(__embassy_main(spawner)); | ||
| 47 | }) | ||
| 48 | } | ||
| 49 | } | ||
| 50 | } | ||
| 51 | |||
| 52 | pub fn wasm() -> TokenStream { | ||
| 53 | quote! { | ||
| 54 | #[wasm_bindgen::prelude::wasm_bindgen(start)] | ||
| 55 | pub fn main() -> Result<(), wasm_bindgen::JsValue> { | ||
| 56 | let executor = ::std::boxed::Box::leak(::std::boxed::Box::new(::embassy_executor::Executor::new())); | ||
| 57 | |||
| 58 | executor.start(|spawner| { | ||
| 59 | spawner.spawn(__embassy_main(spawner)).unwrap(); | ||
| 60 | }); | ||
| 61 | |||
| 62 | Ok(()) | ||
| 63 | } | ||
| 64 | } | ||
| 65 | } | ||
| 66 | |||
| 67 | pub fn std() -> TokenStream { | ||
| 68 | quote! { | ||
| 69 | fn main() -> ! { | ||
| 70 | let mut executor = ::embassy_executor::Executor::new(); | ||
| 71 | let executor = unsafe { __make_static(&mut executor) }; | ||
| 72 | |||
| 73 | executor.run(|spawner| { | ||
| 74 | spawner.must_spawn(__embassy_main(spawner)); | ||
| 75 | }) | ||
| 76 | } | ||
| 77 | } | ||
| 78 | } | ||
| 79 | |||
| 80 | pub fn run(args: &[NestedMeta], f: syn::ItemFn, main: TokenStream) -> Result<TokenStream, TokenStream> { | ||
| 81 | #[allow(unused_variables)] | ||
| 82 | let args = Args::from_list(args).map_err(|e| e.write_errors())?; | ||
| 83 | |||
| 84 | let fargs = f.sig.inputs.clone(); | ||
| 85 | |||
| 86 | let ctxt = Ctxt::new(); | ||
| 87 | |||
| 88 | if f.sig.asyncness.is_none() { | ||
| 89 | ctxt.error_spanned_by(&f.sig, "main function must be async"); | ||
| 90 | } | ||
| 91 | if !f.sig.generics.params.is_empty() { | ||
| 92 | ctxt.error_spanned_by(&f.sig, "main function must not be generic"); | ||
| 93 | } | ||
| 94 | if !f.sig.generics.where_clause.is_none() { | ||
| 95 | ctxt.error_spanned_by(&f.sig, "main function must not have `where` clauses"); | ||
| 96 | } | ||
| 97 | if !f.sig.abi.is_none() { | ||
| 98 | ctxt.error_spanned_by(&f.sig, "main function must not have an ABI qualifier"); | ||
| 99 | } | ||
| 100 | if !f.sig.variadic.is_none() { | ||
| 101 | ctxt.error_spanned_by(&f.sig, "main function must not be variadic"); | ||
| 102 | } | ||
| 103 | match &f.sig.output { | ||
| 104 | ReturnType::Default => {} | ||
| 105 | ReturnType::Type(_, ty) => match &**ty { | ||
| 106 | Type::Tuple(tuple) if tuple.elems.is_empty() => {} | ||
| 107 | Type::Never(_) => {} | ||
| 108 | _ => ctxt.error_spanned_by( | ||
| 109 | &f.sig, | ||
| 110 | "main function must either not return a value, return `()` or return `!`", | ||
| 111 | ), | ||
| 112 | }, | ||
| 113 | } | ||
| 114 | |||
| 115 | if fargs.len() != 1 { | ||
| 116 | ctxt.error_spanned_by(&f.sig, "main function must have 1 argument: the spawner."); | ||
| 117 | } | ||
| 118 | |||
| 119 | ctxt.check()?; | ||
| 120 | |||
| 121 | let f_body = f.block; | ||
| 122 | let out = &f.sig.output; | ||
| 123 | |||
| 124 | let result = quote! { | ||
| 125 | #[::embassy_executor::task()] | ||
| 126 | async fn __embassy_main(#fargs) #out { | ||
| 127 | #f_body | ||
| 128 | } | ||
| 129 | |||
| 130 | unsafe fn __make_static<T>(t: &mut T) -> &'static mut T { | ||
| 131 | ::core::mem::transmute(t) | ||
| 132 | } | ||
| 133 | |||
| 134 | #main | ||
| 135 | }; | ||
| 136 | |||
| 137 | Ok(result) | ||
| 138 | } | ||
diff --git a/embassy-executor-macros/src/macros/mod.rs b/embassy-executor-macros/src/macros/mod.rs new file mode 100644 index 000000000..572094ca6 --- /dev/null +++ b/embassy-executor-macros/src/macros/mod.rs | |||
| @@ -0,0 +1,2 @@ | |||
| 1 | pub mod main; | ||
| 2 | pub mod task; | ||
diff --git a/embassy-executor-macros/src/macros/task.rs b/embassy-executor-macros/src/macros/task.rs new file mode 100644 index 000000000..5161e1020 --- /dev/null +++ b/embassy-executor-macros/src/macros/task.rs | |||
| @@ -0,0 +1,114 @@ | |||
| 1 | use darling::export::NestedMeta; | ||
| 2 | use darling::FromMeta; | ||
| 3 | use proc_macro2::{Span, TokenStream}; | ||
| 4 | use quote::{format_ident, quote}; | ||
| 5 | use syn::{parse_quote, Expr, ExprLit, ItemFn, Lit, LitInt, ReturnType, Type}; | ||
| 6 | |||
| 7 | use crate::util::ctxt::Ctxt; | ||
| 8 | |||
| 9 | #[derive(Debug, FromMeta)] | ||
| 10 | struct Args { | ||
| 11 | #[darling(default)] | ||
| 12 | pool_size: Option<syn::Expr>, | ||
| 13 | } | ||
| 14 | |||
| 15 | pub fn run(args: &[NestedMeta], f: syn::ItemFn) -> Result<TokenStream, TokenStream> { | ||
| 16 | let args = Args::from_list(args).map_err(|e| e.write_errors())?; | ||
| 17 | |||
| 18 | let pool_size = args.pool_size.unwrap_or(Expr::Lit(ExprLit { | ||
| 19 | attrs: vec![], | ||
| 20 | lit: Lit::Int(LitInt::new("1", Span::call_site())), | ||
| 21 | })); | ||
| 22 | |||
| 23 | let ctxt = Ctxt::new(); | ||
| 24 | |||
| 25 | if f.sig.asyncness.is_none() { | ||
| 26 | ctxt.error_spanned_by(&f.sig, "task functions must be async"); | ||
| 27 | } | ||
| 28 | if !f.sig.generics.params.is_empty() { | ||
| 29 | ctxt.error_spanned_by(&f.sig, "task functions must not be generic"); | ||
| 30 | } | ||
| 31 | if !f.sig.generics.where_clause.is_none() { | ||
| 32 | ctxt.error_spanned_by(&f.sig, "task functions must not have `where` clauses"); | ||
| 33 | } | ||
| 34 | if !f.sig.abi.is_none() { | ||
| 35 | ctxt.error_spanned_by(&f.sig, "task functions must not have an ABI qualifier"); | ||
| 36 | } | ||
| 37 | if !f.sig.variadic.is_none() { | ||
| 38 | ctxt.error_spanned_by(&f.sig, "task functions must not be variadic"); | ||
| 39 | } | ||
| 40 | match &f.sig.output { | ||
| 41 | ReturnType::Default => {} | ||
| 42 | ReturnType::Type(_, ty) => match &**ty { | ||
| 43 | Type::Tuple(tuple) if tuple.elems.is_empty() => {} | ||
| 44 | Type::Never(_) => {} | ||
| 45 | _ => ctxt.error_spanned_by( | ||
| 46 | &f.sig, | ||
| 47 | "task functions must either not return a value, return `()` or return `!`", | ||
| 48 | ), | ||
| 49 | }, | ||
| 50 | } | ||
| 51 | |||
| 52 | let mut arg_names = Vec::new(); | ||
| 53 | let mut fargs = f.sig.inputs.clone(); | ||
| 54 | |||
| 55 | for arg in fargs.iter_mut() { | ||
| 56 | match arg { | ||
| 57 | syn::FnArg::Receiver(_) => { | ||
| 58 | ctxt.error_spanned_by(arg, "task functions must not have receiver arguments"); | ||
| 59 | } | ||
| 60 | syn::FnArg::Typed(t) => match t.pat.as_mut() { | ||
| 61 | syn::Pat::Ident(id) => { | ||
| 62 | arg_names.push(id.ident.clone()); | ||
| 63 | id.mutability = None; | ||
| 64 | } | ||
| 65 | _ => { | ||
| 66 | ctxt.error_spanned_by(arg, "pattern matching in task arguments is not yet supported"); | ||
| 67 | } | ||
| 68 | }, | ||
| 69 | } | ||
| 70 | } | ||
| 71 | |||
| 72 | ctxt.check()?; | ||
| 73 | |||
| 74 | let task_ident = f.sig.ident.clone(); | ||
| 75 | let task_inner_ident = format_ident!("__{}_task", task_ident); | ||
| 76 | |||
| 77 | let mut task_inner = f; | ||
| 78 | let visibility = task_inner.vis.clone(); | ||
| 79 | task_inner.vis = syn::Visibility::Inherited; | ||
| 80 | task_inner.sig.ident = task_inner_ident.clone(); | ||
| 81 | |||
| 82 | #[cfg(feature = "nightly")] | ||
| 83 | let mut task_outer: ItemFn = parse_quote! { | ||
| 84 | #visibility fn #task_ident(#fargs) -> ::embassy_executor::SpawnToken<impl Sized> { | ||
| 85 | type Fut = impl ::core::future::Future + 'static; | ||
| 86 | const POOL_SIZE: usize = #pool_size; | ||
| 87 | static POOL: ::embassy_executor::raw::TaskPool<Fut, POOL_SIZE> = ::embassy_executor::raw::TaskPool::new(); | ||
| 88 | unsafe { POOL._spawn_async_fn(move || #task_inner_ident(#(#arg_names,)*)) } | ||
| 89 | } | ||
| 90 | }; | ||
| 91 | #[cfg(not(feature = "nightly"))] | ||
| 92 | let mut task_outer: ItemFn = parse_quote! { | ||
| 93 | #visibility fn #task_ident(#fargs) -> ::embassy_executor::SpawnToken<impl Sized> { | ||
| 94 | const POOL_SIZE: usize = #pool_size; | ||
| 95 | static POOL: ::embassy_executor::_export::TaskPoolRef = ::embassy_executor::_export::TaskPoolRef::new(); | ||
| 96 | unsafe { POOL.get::<_, POOL_SIZE>()._spawn_async_fn(move || #task_inner_ident(#(#arg_names,)*)) } | ||
| 97 | } | ||
| 98 | }; | ||
| 99 | |||
| 100 | task_outer.attrs.append(&mut task_inner.attrs.clone()); | ||
| 101 | |||
| 102 | let result = quote! { | ||
| 103 | // This is the user's task function, renamed. | ||
| 104 | // We put it outside the #task_ident fn below, because otherwise | ||
| 105 | // the items defined there (such as POOL) would be in scope | ||
| 106 | // in the user's code. | ||
| 107 | #[doc(hidden)] | ||
| 108 | #task_inner | ||
| 109 | |||
| 110 | #task_outer | ||
| 111 | }; | ||
| 112 | |||
| 113 | Ok(result) | ||
| 114 | } | ||
diff --git a/embassy-executor-macros/src/util/ctxt.rs b/embassy-executor-macros/src/util/ctxt.rs new file mode 100644 index 000000000..74c872c3c --- /dev/null +++ b/embassy-executor-macros/src/util/ctxt.rs | |||
| @@ -0,0 +1,73 @@ | |||
| 1 | // nifty utility borrowed from serde :) | ||
| 2 | // https://github.com/serde-rs/serde/blob/master/serde_derive/src/internals/ctxt.rs | ||
| 3 | |||
| 4 | use std::cell::RefCell; | ||
| 5 | use std::fmt::Display; | ||
| 6 | use std::thread; | ||
| 7 | |||
| 8 | use proc_macro2::TokenStream; | ||
| 9 | use quote::{quote, ToTokens}; | ||
| 10 | use syn; | ||
| 11 | |||
| 12 | /// A type to collect errors together and format them. | ||
| 13 | /// | ||
| 14 | /// Dropping this object will cause a panic. It must be consumed using `check`. | ||
| 15 | /// | ||
| 16 | /// References can be shared since this type uses run-time exclusive mut checking. | ||
| 17 | #[derive(Default)] | ||
| 18 | pub struct Ctxt { | ||
| 19 | // The contents will be set to `None` during checking. This is so that checking can be | ||
| 20 | // enforced. | ||
| 21 | errors: RefCell<Option<Vec<syn::Error>>>, | ||
| 22 | } | ||
| 23 | |||
| 24 | impl Ctxt { | ||
| 25 | /// Create a new context object. | ||
| 26 | /// | ||
| 27 | /// This object contains no errors, but will still trigger a panic if it is not `check`ed. | ||
| 28 | pub fn new() -> Self { | ||
| 29 | Ctxt { | ||
| 30 | errors: RefCell::new(Some(Vec::new())), | ||
| 31 | } | ||
| 32 | } | ||
| 33 | |||
| 34 | /// Add an error to the context object with a tokenenizable object. | ||
| 35 | /// | ||
| 36 | /// The object is used for spanning in error messages. | ||
| 37 | pub fn error_spanned_by<A: ToTokens, T: Display>(&self, obj: A, msg: T) { | ||
| 38 | self.errors | ||
| 39 | .borrow_mut() | ||
| 40 | .as_mut() | ||
| 41 | .unwrap() | ||
| 42 | // Curb monomorphization from generating too many identical methods. | ||
| 43 | .push(syn::Error::new_spanned(obj.into_token_stream(), msg)); | ||
| 44 | } | ||
| 45 | |||
| 46 | /// Add one of Syn's parse errors. | ||
| 47 | #[allow(unused)] | ||
| 48 | pub fn syn_error(&self, err: syn::Error) { | ||
| 49 | self.errors.borrow_mut().as_mut().unwrap().push(err); | ||
| 50 | } | ||
| 51 | |||
| 52 | /// Consume this object, producing a formatted error string if there are errors. | ||
| 53 | pub fn check(self) -> Result<(), TokenStream> { | ||
| 54 | let errors = self.errors.borrow_mut().take().unwrap(); | ||
| 55 | match errors.len() { | ||
| 56 | 0 => Ok(()), | ||
| 57 | _ => Err(to_compile_errors(errors)), | ||
| 58 | } | ||
| 59 | } | ||
| 60 | } | ||
| 61 | |||
| 62 | fn to_compile_errors(errors: Vec<syn::Error>) -> proc_macro2::TokenStream { | ||
| 63 | let compile_errors = errors.iter().map(syn::Error::to_compile_error); | ||
| 64 | quote!(#(#compile_errors)*) | ||
| 65 | } | ||
| 66 | |||
| 67 | impl Drop for Ctxt { | ||
| 68 | fn drop(&mut self) { | ||
| 69 | if !thread::panicking() && self.errors.borrow().is_some() { | ||
| 70 | panic!("forgot to check for errors"); | ||
| 71 | } | ||
| 72 | } | ||
| 73 | } | ||
diff --git a/embassy-executor-macros/src/util/mod.rs b/embassy-executor-macros/src/util/mod.rs new file mode 100644 index 000000000..28702809e --- /dev/null +++ b/embassy-executor-macros/src/util/mod.rs | |||
| @@ -0,0 +1 @@ | |||
| pub mod ctxt; | |||
