Skip to main content

taurpc_macros/
lib.rs

1use generator::ProceduresGenerator;
2use proc::{IpcMethod, Procedures};
3use proc_macro::{self, TokenStream};
4use quote::{ToTokens, format_ident, quote};
5use syn::{
6    Ident, ImplItem, ImplItemFn, ImplItemType, ItemImpl, ItemStruct, ReturnType, Type,
7    parse_macro_input, parse_quote, parse_quote_spanned, spanned::Spanned,
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::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        path_prefix: procedures_attrs.path,
66        inputs_ident: &format_ident!("TauRpc{}Inputs", ident),
67        outputs_ident: &format_ident!("TauRpc{}Outputs", ident),
68        output_futures_ident: &format_ident!("TauRpc{}OutputFutures", ident),
69        methods,
70        method_output_types: &methods
71            .iter()
72            .map(|IpcMethod { output, .. }| match output {
73                ReturnType::Type(_, ref ty) => ty,
74                ReturnType::Default => unit_type,
75            })
76            .collect::<Vec<_>>(),
77        alias_method_idents: &methods
78            .iter()
79            .map(|IpcMethod { ident, attrs, .. }| {
80                attrs
81                    .alias
82                    .as_ref()
83                    .map(|alias| Ident::new(alias, ident.span()))
84                    .unwrap_or(ident.clone())
85            })
86            .collect::<Vec<_>>(),
87        vis,
88        generics,
89        attrs,
90    }
91    .into_token_stream()
92    .into()
93}
94
95/// Transforms all methods to return `Pin<Box<Future<Output = ...>>>`, async traits are not supported.
96#[proc_macro_attribute]
97pub fn resolvers(_attr: TokenStream, item: TokenStream) -> TokenStream {
98    let mut item = syn::parse_macro_input!(item as ItemImpl);
99    let mut types: Vec<ImplItemType> = Vec::new();
100
101    for inner in &mut item.items {
102        if let ImplItem::Fn(method) = inner {
103            if method.sig.asyncness.is_some() {
104                types.push(transform_method(method));
105            }
106        }
107    }
108
109    // add the type declarations into the impl block
110    for t in types.into_iter() {
111        item.items.push(syn::ImplItem::Type(t));
112    }
113
114    quote! {
115        #[allow(non_camel_case_types)]
116        #item
117    }
118    .into()
119}
120
121/// Transform an async method into a sync one that returns a `Pin<Box<Future<Output = ...  >>`.
122fn transform_method(method: &mut ImplItemFn) -> ImplItemType {
123    method.sig.asyncness = None;
124
125    let ret = match &method.sig.output {
126        ReturnType::Default => quote!(()),
127        ReturnType::Type(_, ret) => quote!(#ret),
128    };
129
130    let fut_ident = method_fut_ident(&method.sig.ident);
131
132    method.sig.output = parse_quote! {
133        -> ::core::pin::Pin<Box<
134                dyn ::core::future::Future<Output = #ret> + ::core::marker::Send
135            >>
136    };
137
138    // transform the body of the method into Box::pin(async move { body }).
139    let block = method.block.clone();
140    method.block = parse_quote_spanned! {method.span()=>{
141        Box::pin(async move #block)
142    }};
143
144    // generate and return type declaration for return type.
145    let t = parse_quote! {
146        type #fut_ident = ::core::pin::Pin<Box<dyn ::core::future::Future<Output = #ret> + ::core::marker::Send>>;
147    };
148
149    t
150}
151
152fn method_fut_ident(ident: &Ident) -> Ident {
153    format_ident!("{}Fut", ident)
154}