taurpc_macros/
lib.rs

1use generator::ProceduresGenerator;
2use proc::{IpcMethod, Procedures};
3use proc_macro::{self, TokenStream};
4use quote::{format_ident, quote, ToTokens};
5use syn::{
6    parse_macro_input, parse_quote, parse_quote_spanned, spanned::Spanned, Ident, ImplItem,
7    ImplItemFn, ImplItemType, ItemImpl, ItemStruct, ReturnType, Type,
8};
9
10mod args;
11mod attrs;
12mod generator;
13mod proc;
14
15use crate::attrs::ProceduresAttrs;
16
17/// https://github.com/google/tarpc/blob/master/plugins/src/lib.rs#L29
18/// Accumulates multiple errors into a result.
19/// Only use this for recoverable errors, i.e. non-parse errors. Fatal errors should early exit to
20/// avoid further complications.
21macro_rules! extend_errors {
22    ($errors: ident, $e: expr) => {
23        match $errors {
24            Ok(_) => $errors = Err($e),
25            Err(ref mut errors) => errors.extend($e),
26        }
27    };
28}
29pub(crate) use extend_errors;
30
31/// Add this macro to all structs used inside the procedures arguments or return types.
32/// This macro is necessary for serialization and TS type generation.
33#[proc_macro_attribute]
34pub fn ipc_type(_attr: TokenStream, item: TokenStream) -> TokenStream {
35    let input = parse_macro_input!(item as ItemStruct);
36
37    quote! {
38        #[derive(taurpc::serde::Serialize, taurpc::serde::Deserialize, taurpc::specta_macros::Type, Clone)]
39        #input
40    }
41    .into()
42}
43
44/// Generates the necessary structs and enums for handling calls and generating TS-types.
45#[proc_macro_attribute]
46pub fn procedures(attrs: TokenStream, item: TokenStream) -> TokenStream {
47    let procedures_attrs = parse_macro_input!(attrs as ProceduresAttrs);
48
49    let Procedures {
50        ref ident,
51        ref methods,
52        ref vis,
53        ref generics,
54        ref attrs,
55    } = parse_macro_input!(item as Procedures);
56
57    let unit_type: &Type = &parse_quote!(());
58
59    ProceduresGenerator {
60        trait_ident: ident,
61        handler_ident: &format_ident!("TauRpc{}Handler", ident),
62        event_trigger_ident: &procedures_attrs
63            .event_trigger_ident
64            .unwrap_or(format_ident!("TauRpc{}EventTrigger", ident)),
65        export_path: procedures_attrs.export_to,
66        path_prefix: procedures_attrs.path,
67        inputs_ident: &format_ident!("TauRpc{}Inputs", ident),
68        outputs_ident: &format_ident!("TauRpc{}Outputs", ident),
69        output_futures_ident: &format_ident!("TauRpc{}OutputFutures", ident),
70        methods,
71        method_output_types: &methods
72            .iter()
73            .map(|IpcMethod { output, .. }| match output {
74                ReturnType::Type(_, ref ty) => ty,
75                ReturnType::Default => unit_type,
76            })
77            .collect::<Vec<_>>(),
78        alias_method_idents: &methods
79            .iter()
80            .map(|IpcMethod { ident, attrs, .. }| {
81                attrs
82                    .alias
83                    .as_ref()
84                    .map(|alias| Ident::new(alias, ident.span()))
85                    .unwrap_or(ident.clone())
86            })
87            .collect::<Vec<_>>(),
88        vis,
89        generics,
90        attrs,
91    }
92    .into_token_stream()
93    .into()
94}
95
96/// Transforms all methods to return `Pin<Box<Future<Output = ...>>>`, async traits are not supported.
97#[proc_macro_attribute]
98pub fn resolvers(_attr: TokenStream, item: TokenStream) -> TokenStream {
99    let mut item = syn::parse_macro_input!(item as ItemImpl);
100    let mut types: Vec<ImplItemType> = Vec::new();
101
102    for inner in &mut item.items {
103        if let ImplItem::Fn(method) = inner {
104            if method.sig.asyncness.is_some() {
105                types.push(transform_method(method));
106            }
107        }
108    }
109
110    // add the type declarations into the impl block
111    for t in types.into_iter() {
112        item.items.push(syn::ImplItem::Type(t));
113    }
114
115    quote!(#item).into()
116}
117
118/// Transform an async method into a sync one that returns a `Pin<Box<Future<Output = ...  >>`.
119fn transform_method(method: &mut ImplItemFn) -> ImplItemType {
120    method.sig.asyncness = None;
121
122    let ret = match &method.sig.output {
123        ReturnType::Default => quote!(()),
124        ReturnType::Type(_, ret) => quote!(#ret),
125    };
126
127    let fut_ident = method_fut_ident(&method.sig.ident);
128
129    method.sig.output = parse_quote! {
130        -> ::core::pin::Pin<Box<
131                dyn ::core::future::Future<Output = #ret> + ::core::marker::Send
132            >>
133    };
134
135    // transform the body of the method into Box::pin(async move { body }).
136    let block = method.block.clone();
137    method.block = parse_quote_spanned! {method.span()=>{
138        Box::pin(async move #block)
139    }};
140
141    // generate and return type declaration for return type.
142    let t = parse_quote! {
143        type #fut_ident = ::core::pin::Pin<Box<dyn ::core::future::Future<Output = #ret> + ::core::marker::Send>>;
144    };
145
146    t
147}
148
149fn method_fut_ident(ident: &Ident) -> Ident {
150    format_ident!("{}Fut", ident)
151}