function_compose_proc_macros/
lib.rs

1//! # function-compose-proc-macros
2//! This crate contains proc macro to mark a function as composeable. It must be used along with [function_compose](https://crates.io/crates/function-compose) crate
3//! 
4//! check the docs of [function-compose](https://docs.rs/function-compose)
5
6use proc_macro::TokenStream;
7
8use std::fmt::Formatter;
9use std::{fmt::Display, ops::Deref};
10
11use quote::{quote, ToTokens, TokenStreamExt};
12use syn::parse::ParseStream;
13use syn::{parse::Parse, Expr, FnArg, ItemFn, ReturnType, Token, Type};
14
15use crate::OptionalRetry::SomeRetry;
16
17fn generate_return_type_param(index: u8) -> String {
18    format!("T{index}")
19}
20
21mod keyword {
22    syn::custom_keyword!(retry);
23}
24
25fn generate_generics_parameters(count: u8) -> String {
26    let mut result: String = "".to_owned();
27    for i in 1..=count {
28        result.push_str(format!("T{},", i.to_string().as_str()).as_str());
29    }
30    result
31}
32
33#[proc_macro_attribute]
34pub fn retry(_attr: TokenStream, _item: TokenStream) -> TokenStream {
35    panic!()
36}
37
38struct Retry {
39    strategy: Expr,
40}
41
42struct FunctionArgs<'a> {
43    args: Vec<&'a FnArg>,
44}
45
46struct FunctionMutArgs<'a> {
47    args: Vec<&'a FnArg>,
48}
49
50impl<'a> ToTokens for FunctionArgs<'a> {
51    fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
52        self.args.iter().for_each(|arg| {
53            match *arg {
54                FnArg::Receiver(_) => {}
55                FnArg::Typed(t) => {
56                    let ident = &t.pat;
57                    let ty = t.ty.as_ref();
58                    let token_stream = match ty {
59                        Type::Reference(reference) => {
60                            if reference.mutability.is_some() {
61                                quote! {
62                                   #[allow(unused)] &mut #ident,
63                                }
64                            } else {
65                                quote! {
66                                    #[allow(unused)] & #ident,
67                                }
68                            }
69                        }
70                        _ => {
71                            quote! {
72                                 #[allow(unused)] #ident,
73                            }
74                        }
75                    };
76
77                    tokens.append_all(token_stream.into_iter());
78                    /*let mut toeknStream: proc_macro::TokenStream = tokeStream.into();
79                    toeknStream.extend(tokens.into_iter())*/
80                }
81            }
82        });
83    }
84}
85
86impl<'a> ToTokens for FunctionMutArgs<'a> {
87    fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
88        self.args.iter().for_each(|arg| {
89            let token_stream = quote! {
90                       #[allow(unused)] mut #arg,
91            };
92            tokens.append_all(token_stream.into_iter());
93        });
94    }
95}
96
97enum OptionalRetry {
98    SomeRetry(Retry),
99    NoRetry,
100}
101
102impl Display for Retry {
103    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
104        write!(f, "{}", "test")
105    }
106}
107
108impl Parse for OptionalRetry {
109    fn parse(input: ParseStream) -> syn::Result<Self> {
110        let lookahead = input.lookahead1();
111        if lookahead.peek(keyword::retry) {
112            let _ = input.parse::<keyword::retry>();
113            let _ = input.parse::<Token![=]>();
114            let expr: Expr = input.parse()?;
115            println!("{}", expr.to_token_stream());
116            Ok(OptionalRetry::SomeRetry(Retry { strategy: expr }))
117        } else {
118            Ok(OptionalRetry::NoRetry)
119        }
120    }
121}
122
123
124fn generate_ident_with_prefix(ident: &str) -> String{
125    format!("fn_composer__{}", ident)
126}
127
128
129#[proc_macro_attribute]
130pub fn composeable(attr: TokenStream, item: TokenStream) -> TokenStream {
131    
132
133    let token_stream_clone = item.clone();
134    let item_fn: ItemFn = syn::parse_macro_input!(token_stream_clone);
135
136    let fn_gen = item_fn.sig.generics;
137    let mut async_fn = item_fn.sig.asyncness.is_some();
138    let input_args = item_fn.sig.inputs;
139    let arg_tokens: Vec<_> = input_args.iter().collect();
140    let mut_arg_tokens: Vec<_> = input_args.iter().collect();
141    let arg_length = input_args.len();
142    let fn_ident = &item_fn.sig.ident;
143    let fn_name = item_fn.sig.ident.to_string();
144    let fn_return_type = &item_fn.sig.output;
145
146    let return_type_without_token = match fn_return_type {
147        ReturnType::Default => None,
148        ReturnType::Type(_, return_type) => Some(return_type),
149    };
150
151    let retry = syn::parse_macro_input!(attr as OptionalRetry);
152
153    if !async_fn {
154        match fn_return_type {
155            syn::ReturnType::Default => {}
156            syn::ReturnType::Type(_, t) => {
157                let x = t.deref();
158                async_fn = x.to_token_stream().to_string().starts_with("BoxFuture");
159            }
160        }
161    }
162    let lifted_fn_name = "lifted_fn_".to_owned() + &fn_name;
163    let prefixed_lifted_fn_name = &generate_ident_with_prefix(&lifted_fn_name);
164    //let lift_retry_fn_name = &generate_ident_with_prefix(&("retry_".to_owned() + &lifted_fn_name));
165    let lift_fn_ident = syn::Ident::new(prefixed_lifted_fn_name, proc_macro2::Span::call_site());
166    let is_retry_fn_name = &generate_ident_with_prefix(&("is_retryable_".to_owned() + &fn_name));
167    let is_retry_fn_ident = syn::Ident::new(&is_retry_fn_name, proc_macro2::Span::call_site());
168    let retry_fn_ident = syn::Ident::new(
169        &generate_ident_with_prefix(&("retry_".to_owned() + &fn_name)),
170        proc_macro2::Span::call_site(),
171    );
172
173    let async_fn_name = &generate_ident_with_prefix(&("is_async_".to_owned() + fn_name.deref()));
174    let async_fn_ident = syn::Ident::new(
175        async_fn_name,
176        proc_macro2::Span::call_site(),
177    );
178    let (
179        _return_type,
180        _underlying_lift_fn_name,
181        fun_gen,
182        return_type_ident,
183        ret_gen,
184        underlying_lift_fn_name_ident,
185    ) = /*if (asyncFn)*/ {
186        let return_type = if async_fn {
187            "BoxedAsyncFn".to_owned() + arg_length.to_string().as_str()
188        } else {
189            "BoxedFn".to_owned() + arg_length.to_string().as_str()
190        };
191        let underlying_lift_fn_name = if async_fn {
192            "lift_async_fn".to_owned() + arg_length.to_string().as_str()
193        } else {
194            "lift_sync_fn".to_owned() + arg_length.to_string().as_str()
195        };
196        let gen_type_params = generate_generics_parameters((arg_length + 1) as u8);
197        let fun_arg_params = generate_generics_parameters((arg_length) as u8);
198        let return_type_param = generate_return_type_param((arg_length + 1) as u8);
199        let fun_gen = if async_fn {
200            let gen_type = format!("<'a, {gen_type_params} E1, F:Fn({fun_arg_params})->BoxFuture<'a,Result<{return_type_param}, E1>> + 'a + Send +Sync>", );            
201            syn::parse_str::<syn::Generics>(
202                gen_type.as_str()
203            ).ok()
204                .unwrap()
205        } else {
206            let gen_type  =format!("<'a, {gen_type_params} E1, F:Fn({fun_arg_params})->Result<{return_type_param}, E1> + Send +Sync + 'a>");            
207            syn::parse_str::<syn::Generics>(
208                gen_type.as_str()                
209            ).ok().unwrap()
210        };
211
212        let return_type_ident = syn::Ident::new(return_type.as_str(), proc_macro2::Span::call_site());
213        let underlying_lift_fn_name_ident =
214            syn::Ident::new(underlying_lift_fn_name.as_str(), proc_macro2::Span::call_site());
215        let ret_gen = syn::parse_str::<syn::Generics>(format!("<'a,{gen_type_params} E1>").as_str()).ok().unwrap();
216
217        (
218            return_type,
219            underlying_lift_fn_name,
220            fun_gen,
221            return_type_ident,
222            ret_gen,
223            underlying_lift_fn_name_ident,
224        )
225    };
226
227    match retry {
228        OptionalRetry::NoRetry => {
229            let function_mut_args = FunctionMutArgs {
230                args: mut_arg_tokens,
231            };
232            let tokens: proc_macro2::TokenStream = quote! {
233                use function_compose::*;
234
235                pub fn #lift_fn_ident #fun_gen(f: F)  -> #return_type_ident #ret_gen{
236                    #underlying_lift_fn_name_ident(f)
237                }
238
239                pub fn #async_fn_ident ()  -> bool{
240                    #async_fn
241                }
242
243                 pub fn #is_retry_fn_ident ()  -> bool{
244                         false
245                    }
246
247                /**
248                * It is only added to keep the compiler happy for non retryable functions
249                */
250                pub fn #retry_fn_ident #fn_gen ( #function_mut_args)  #fn_return_type {
251                    panic!("Function not to be called");
252                }
253            };
254            let mut token_stream: proc_macro::TokenStream = tokens.into();
255            token_stream.extend(item.into_iter());
256            //println!("{}", toekn_stream.to_string());
257            token_stream
258        }
259        SomeRetry(strategy) => {
260            let function_args = FunctionArgs { args: arg_tokens };
261
262            let function_mut_args = FunctionMutArgs {
263                args: mut_arg_tokens,
264            };
265
266            let mutable_args: Vec<_> = filter_mutable_args(&function_args);
267
268            let mutex_tokens: Vec<_> = convert_to_create_mutex_tokens(&mutable_args);
269
270            let mutex_unlock_tokens: Vec<_> = convert_to_mutex_unlock_tokens(mutable_args);
271
272            let deref_mut_tokens: Vec<_> = convert_to_deref_tokens(&function_args);
273
274            let strategy_expr = strategy.strategy;
275            let retry_tokens: proc_macro2::TokenStream = if async_fn {
276                quote! {
277
278                    pub fn #retry_fn_ident #fn_gen(#function_mut_args)  #fn_return_type {
279                        use function_compose::*;
280                        use retry::*;
281                        use tokio_retry::Retry as AsyncRetry;
282                        use tokio::sync::Mutex;
283                        use std::ops::{Deref, DerefMut};
284                        async{
285                            #( #mutex_tokens )*
286                            let result = AsyncRetry::spawn(#strategy_expr, || async{
287                                #( #mutex_unlock_tokens )*;
288                                let r = #fn_ident(#( #deref_mut_tokens )*);
289                                //OperationResult::from()
290                                r.await
291                            });
292
293                            let result = match result.await{
294                                    Ok(result) => Ok(result),
295                                    Err(e) => Err(e)
296                            };
297                            result
298                        }.boxed()
299                    }
300                }
301            } else {
302                quote! {
303
304                    pub fn #retry_fn_ident #fn_gen (#function_mut_args)  #fn_return_type {
305                        use function_compose::*;
306                        use retry::*;
307
308                        let result = retry(#strategy_expr, ||{
309                            let r:#return_type_without_token = #fn_ident(#function_args).into();
310                            r
311                        });
312                        match result{
313                            Ok(result) => Ok(result),
314                            Err(e) => Err(e.error)
315                        }
316                    }
317                }
318            };
319
320            /*println!("#############################");
321            println!("{}", retry_tokens);
322            println!("#############################");*/
323
324            let tokens: proc_macro2::TokenStream = quote! {
325
326                use function_compose::*;
327                pub fn #lift_fn_ident #fun_gen(f: F)  -> #return_type_ident #ret_gen{
328                    //#lift_retry_fn_ident(#retryFnIdent)
329                    #underlying_lift_fn_name_ident(f)
330                }
331
332                /*pub fn #lift_retry_fn_ident #fun_gen(f: F)  -> #return_type_ident #ret_gen{
333                    #underlying_lift_fn_name_ident(#retryFnIdent)
334                }*/
335
336                 pub fn #is_retry_fn_ident ()  -> bool{
337                     true
338                }
339
340
341                pub fn #async_fn_ident ()  -> bool{
342                    #async_fn
343                }
344            };
345            let retry_token_stream: TokenStream = retry_tokens.into();
346
347            let mut token_stream: proc_macro::TokenStream = tokens.into();
348
349            token_stream.extend(item.into_iter());
350            token_stream.extend(retry_token_stream.into_iter());
351            /*println!("{}", toekn_stream.to_string());*/
352            token_stream
353        }
354    }
355}
356
357fn filter_mutable_args<'a>(function_args: &'a FunctionArgs) -> Vec<&'a &'a FnArg> {
358    function_args
359        .args
360        .iter()
361        .filter(|fn_arg| {
362            match fn_arg {
363                FnArg::Receiver(_) => return false,
364                FnArg::Typed(pat) => {
365                    let ty = pat.ty.deref();
366                    match ty {
367                        Type::Reference(ty_ref) => return ty_ref.mutability.is_some(),
368
369                        _ => {
370                            return false;
371                        }
372                    }
373                }
374            }
375        })
376        .collect()
377}
378
379fn convert_to_create_mutex_tokens(mutable_args: &Vec<&&FnArg>) -> Vec<proc_macro2::TokenStream> {
380    mutable_args
381        .iter()
382        .map(|i| match i {
383            FnArg::Receiver(_pat_type) => {
384                panic!();
385            }
386            FnArg::Typed(pat_type) => {
387                let pat = &pat_type.pat;
388                quote! {
389                    let mut #pat =Mutex::new(#pat);
390                }
391            }
392        })
393        .collect()
394}
395
396fn convert_to_mutex_unlock_tokens(mutable_args: Vec<&&FnArg>) -> Vec<proc_macro2::TokenStream> {
397    mutable_args
398        .iter()
399        .map(|i| match i {
400            FnArg::Receiver(_pat_type) => {
401                panic!();
402            }
403            FnArg::Typed(pat_type) => {
404                let pat = &pat_type.pat;
405                quote! {
406                    let mut #pat = #pat.lock().await;
407
408                }
409            }
410        })
411        .collect()
412}
413
414fn convert_to_deref_tokens(function_args: &FunctionArgs) -> Vec<proc_macro2::TokenStream> {
415    function_args
416        .args
417        .iter()
418        .map(|i| {
419            match i {
420                FnArg::Receiver(_pat_type) => {
421                    return quote! {};
422                }
423                FnArg::Typed(pat_type) => {
424                    let pat = &pat_type.pat;
425                    let ty = pat_type.ty.deref();
426                    match ty {
427                        Type::Reference(ty_ref) => {
428                            if ty_ref.mutability.is_some() {
429                                return quote! {
430                                    #pat.deref_mut(),
431                                };
432                            } else {
433                                return quote! {
434                                    #pat,
435                                };
436                            }
437                        }
438
439                        _ => {
440                            return quote! {
441                                #pat,
442                            };
443                        }
444                    }
445                    /*quote!{
446                        let mut #pat = #pat.lock().await;
447
448                    }*/
449                }
450            }
451        })
452        .collect()
453}