openraft_macros/
lib.rs

1#![doc = include_str!("lib_readme.md")]
2
3mod expand;
4mod since;
5pub(crate) mod utils;
6
7use proc_macro::TokenStream;
8use quote::quote;
9use since::Since;
10use syn::parse2;
11use syn::parse_macro_input;
12use syn::parse_str;
13use syn::token::RArrow;
14use syn::Item;
15use syn::ReturnType;
16use syn::TraitItem;
17use syn::Type;
18
19/// This proc macro attribute optionally adds `Send` bounds to a trait.
20///
21/// By default, `Send` bounds will be added to the trait and to the return bounds of any async
22/// functions defined within the trait.
23///
24/// If the `singlethreaded` feature is enabled, the trait definition remains the same without any
25/// added `Send` bounds.
26///
27/// # Example
28///
29/// ```
30/// use openraft_macros::add_async_trait;
31///
32/// #[add_async_trait]
33/// trait MyTrait {
34///     async fn my_method(&self) -> Result<(), String>;
35/// }
36/// ```
37///
38/// The above code will be transformed into:
39///
40/// ```ignore
41/// trait MyTrait {
42///     fn my_method(&self) -> impl Future<Output=Result<(), String>> + Send;
43/// }
44/// ```
45///
46/// Note: This proc macro can only be used with traits.
47///
48/// # Panics
49///
50/// This proc macro will panic if used on anything other than trait definitions.
51#[proc_macro_attribute]
52pub fn add_async_trait(_attr: TokenStream, item: TokenStream) -> TokenStream {
53    if cfg!(feature = "singlethreaded") {
54        allow_non_send_bounds(item)
55    } else {
56        add_send_bounds(item)
57    }
58}
59
60fn allow_non_send_bounds(item: TokenStream) -> TokenStream {
61    // `async_fn_in_trait` requires the user to explicitly specify the `Send` bound for public
62    // trait methods, however the `singlethreaded` feature renders the requirement irrelevant.
63    let item: proc_macro2::TokenStream = item.into();
64    quote! {
65        #[allow(async_fn_in_trait)]
66        #item
67    }
68    .into()
69}
70
71fn add_send_bounds(item: TokenStream) -> TokenStream {
72    let send_bound = parse_str("Send").unwrap();
73    let default_return_type: Box<Type> = parse_str("impl std::future::Future<Output = ()> + Send").unwrap();
74
75    match parse_macro_input!(item) {
76        Item::Trait(mut input) => {
77            // add `Send` bound to the trait
78            input.supertraits.push(send_bound);
79
80            for item in input.items.iter_mut() {
81                // for each async function definition
82                let TraitItem::Fn(function) = item else { continue };
83                if function.sig.asyncness.is_none() {
84                    continue;
85                };
86
87                // remove async from signature
88                function.sig.asyncness = None;
89
90                // wrap the return type in a `Future`
91                function.sig.output = match &function.sig.output {
92                    ReturnType::Default => ReturnType::Type(RArrow::default(), default_return_type.clone()),
93                    ReturnType::Type(arrow, t) => {
94                        let tokens = quote!(impl std::future::Future<Output = #t> + Send);
95                        ReturnType::Type(*arrow, parse2(tokens).unwrap())
96                    }
97                };
98
99                // if a body is defined, wrap it in an async block
100                let Some(body) = &function.default else { continue };
101                let body = parse2(quote!({ async move #body })).unwrap();
102                function.default = Some(body);
103            }
104
105            quote!(#input).into()
106        }
107
108        _ => panic!("add_async_trait can only be used with traits"),
109    }
110}
111
112/// Add a `Since` line of doc, such as `/// Since: 1.0.0`.
113///
114/// `#[since(version = "1.0.0")]` generates:
115/// ```rust,ignore
116/// /// Since: 1.0.0
117/// ```
118///
119/// `#[since(version = "1.0.0", date = "2021-01-01")]` generates:
120/// ```rust,ignore
121/// /// Since: 1.0.0, Date(2021-01-01)
122/// ```
123///
124/// - The `version` must be a valid semver string.
125/// - The `date` must be a valid date string in the format `yyyy-mm-dd`.
126///
127/// ### Example
128///
129/// ```rust,ignore
130/// /// Foo function
131/// ///
132/// /// Does something.
133/// #[since(version = "1.0.0")]
134/// fn foo() {}
135/// ```
136///
137/// The above code will be transformed into:
138///
139/// ```rust,ignore
140/// /// Foo function
141/// ///
142/// /// Does something.
143/// ///
144/// /// Since: 1.0.0
145/// fn foo() {}
146/// ```
147#[proc_macro_attribute]
148pub fn since(args: TokenStream, item: TokenStream) -> TokenStream {
149    let tokens = do_since(args, item.clone());
150    match tokens {
151        Ok(x) => x,
152        Err(e) => utils::token_stream_with_error(item, e),
153    }
154}
155
156fn do_since(args: TokenStream, item: TokenStream) -> Result<TokenStream, syn::Error> {
157    let since = Since::new(args)?;
158    let tokens = since.append_since_doc(item)?;
159    Ok(tokens)
160}
161
162/// Render a template with arguments multiple times.
163///
164/// The template to expand is defined as `(K,V) => { ... }`, where `K` and `V` are template
165/// variables.
166///
167/// - The template must contain at least 1 variable.
168/// - If the first macro argument is `KEYED`, the first variable serve as the key for deduplication.
169///   Otherwise, the first macro argument should be `!KEYED`, and no deduplication will be
170///   performed.
171///
172/// # Example: `KEYED` for deduplication
173///
174/// The following code builds a series of let statements:
175/// ```
176/// # use openraft_macros::expand;
177/// # fn foo () {
178/// expand!(
179///     KEYED,
180///     // Template with variables K and V, and template body, excluding the braces.
181///     (K, T, V) => {let K: T = V;},
182///     // Arguments for rendering the template
183///     (a, u64, 1),
184///     (b, String, "foo".to_string()),
185///     (a, u32, 2), // duplicate a will be ignored
186///     (c, Vec<u8>, vec![1,2])
187/// );
188/// # }
189/// ```
190///
191/// The above code will be transformed into:
192///
193/// ```
194/// # fn foo () {
195/// let a: u64 = 1;
196/// let b: String = "foo".to_string();
197/// let c: Vec<u8> = vec![1, 2];
198/// # }
199/// ```
200///
201/// # Example: `!KEYED` for no deduplication
202///
203/// ```
204/// # use openraft_macros::expand;
205/// # fn foo () {
206/// expand!(!KEYED, (K, T, V) => {let K: T = V;},
207///                 (c, u8, 8),
208///                 (c, u16, 16),
209/// );
210/// # }
211/// ```
212///
213/// The above code will be transformed into:
214///
215/// ```
216/// # fn foo () {
217/// let c: u8 = 8;
218/// let c: u16 = 16;
219/// # }
220/// ```
221#[proc_macro]
222pub fn expand(item: TokenStream) -> TokenStream {
223    let repeat = parse_macro_input!(item as expand::Expand);
224    repeat.render().into()
225}