Skip to main content

dynamic_proxy_macros/
lib.rs

1use proc_macro::TokenStream;
2use quote::{quote, ToTokens};
3use syn::{Block, ImplItem, ImplItemFn, ItemTrait, Meta, parse_macro_input, Pat, Stmt, TraitItem, Type};
4use syn::punctuated::Punctuated;
5use core::default::Default;
6use std::ops::Deref;
7use syn::token::Brace;
8use syn::Visibility::Inherited;
9use syn::FnArg::Typed;
10
11extern crate proc_macro;
12
13#[proc_macro_attribute]
14// _metadata is argument provided to macro call and _input is code to which attribute like macro attaches
15pub fn dynamic_proxy(_metadata: TokenStream, _input: TokenStream) -> TokenStream {
16    let input_struct =
17        parse_macro_input!(_metadata with Punctuated::<Meta, syn::Token![,]>::parse_terminated);
18    let input_trait = parse_macro_input!(_input as ItemTrait);
19    let inp = &input_trait;
20    let name = &input_trait.ident;
21    let (impl_generics, ty_generics, where_clause) = input_trait.generics.split_for_impl();
22    let imp = input_struct.first();
23
24    // let tr = input_trait.to_token_stream();
25    let body = input_trait.items.iter().filter_map(|item| {
26        match item {
27            TraitItem::Fn(ti) => {
28                let func = ti.to_owned();
29                let signature = func.sig;
30                let is_async = signature.asyncness.is_some();
31                let func_name = signature.ident.to_string();
32                let mut errors = Vec::new();
33                let args: Vec<_> = signature.inputs.iter().filter_map(|a| {
34                    match a {
35                        Typed(t) => match t.clone().pat.deref() {
36                            Pat::Ident(id) => {
37                                if matches!(t.ty.deref(), Type::Reference(_)) {
38                                    errors.push(syn::Error::new_spanned(
39                                        &t.ty,
40                                        "dynamic_proxy does not support borrowed arguments; use owned 'static + Send + Sync arguments",
41                                    ).to_compile_error());
42                                }
43                                Some(id.clone().ident)
44                            },
45                            _ => {
46                                errors.push(syn::Error::new_spanned(
47                                    &t.pat,
48                                    "dynamic_proxy only supports identifier arguments",
49                                ).to_compile_error());
50                                None
51                            }
52                        },
53                        _ => None
54                    }
55                }).collect();
56                let arg_names = args.iter().map(|i| i.to_string());
57                let r = &signature.output;
58                let (return_type, returns_unit) = match r {
59                    syn::ReturnType::Type(_, t) => {
60                        if matches!(t.deref(), Type::Reference(_)) {
61                            errors.push(syn::Error::new_spanned(
62                                t,
63                                "dynamic_proxy does not support borrowed return values; use owned 'static + Send + Sync return values",
64                            ).to_compile_error());
65                        }
66                        (t.deref().to_token_stream(), false)
67                    },
68                    _ => (quote!(()), true)
69                };
70                let invocation_stmt: Vec<Stmt> = syn::parse_quote!(
71                                        let mut invocation_info = ::dynamic_proxy::InvocationInfo {
72                        func_name: #func_name,
73                        arg_names: &[#(#arg_names),*],
74                        arg_values: &[#(::std::boxed::Box::new(#args)),*],
75                        return_type: ::std::any::TypeId::of::<#return_type>(),
76                        return_value: None
77                    };
78                );
79                
80                let call_stmt: Vec<Stmt> =
81                    match (is_async, returns_unit)
82                    {
83                        (true, true) => syn::parse_quote!(
84                    ::dynamic_proxy::AsyncDynamicProxy::call_async(&self, &mut invocation_info).await;
85                        ),
86                        (true, false) => syn::parse_quote!(
87                    ::dynamic_proxy::AsyncDynamicProxy::call_async(&self, &mut invocation_info).await;
88                    return *invocation_info.return_value.unwrap().downcast::<#return_type>().unwrap();
89                        ),
90                        (false, true) => syn::parse_quote!(
91                    ::dynamic_proxy::DynamicProxy::call(&self, &mut invocation_info);
92                        ),
93                        (false, false) => syn::parse_quote!(
94                    ::dynamic_proxy::DynamicProxy::call(&self, &mut invocation_info);
95                    return *invocation_info.return_value.unwrap().downcast::<#return_type>().unwrap();
96                        )
97                    };
98                
99                let error_stmt: Vec<Stmt> = errors.into_iter()
100                    .map(|error| syn::parse_quote!(#error))
101                    .collect();
102                let stmt = [error_stmt.as_slice(), invocation_stmt.as_slice(), call_stmt.as_slice()].concat();
103                
104                Some(ImplItem::Fn(ImplItemFn {
105                    attrs: func.attrs,
106                    vis: Inherited,
107                    defaultness: None,
108                    sig: signature,
109                    block: Block {
110                        brace_token: Brace::default(),
111                        stmts: stmt,
112                    },
113                }))
114            }
115            TraitItem::Const(item) if item.default.is_none() => {
116                Some(syn::parse_quote! {
117                    compile_error!("dynamic_proxy does not support required associated constants");
118                })
119            }
120            TraitItem::Type(item) if item.default.is_none() => {
121                Some(syn::parse_quote! {
122                    compile_error!("dynamic_proxy does not support required associated types");
123                })
124            }
125            &_ => None
126        }
127    });
128
129    TokenStream::from(quote! {
130        #inp
131        impl #impl_generics #name #ty_generics for #imp #where_clause {
132            #(#body)*
133        }
134    })
135}