function_compose_proc_macros/
lib.rs1use 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 }
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_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 ) = {
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 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 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 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 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 #underlying_lift_fn_name_ident(f)
330 }
331
332 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 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 }
450 }
451 })
452 .collect()
453}