oasis_macros/
service_derive.rs

1#[proc_macro_derive(Service)]
2pub fn service_derive(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
3    if std::env::var("OASIS_BUILD_NO_SERVICE_DERIVE").is_ok() {
4        return proc_macro::TokenStream::new();
5    }
6    let input = parse_macro_input!(input as syn::DeriveInput);
7    let service = &input.ident;
8    let impl_wrapper_ident = format_ident!("_IMPL_SERVICE_FOR_{}", service);
9    proc_macro::TokenStream::from(match get_serde(&input) {
10        Some((ser, de)) => {
11            quote! {
12                #[allow(non_upper_case_globals)]
13                const #impl_wrapper_ident: () = {
14                    use oasis_std::abi::*;
15
16                    impl oasis_std::exe::Service for #service {
17                        fn coalesce() -> Self {
18                            #de
19                        }
20
21                        fn sunder(service: Self) {
22                            #ser
23                        }
24                    }
25                };
26            }
27        }
28        None => quote! {},
29    })
30}
31
32fn get_serde(
33    input: &syn::DeriveInput,
34) -> Option<(proc_macro2::TokenStream, proc_macro2::TokenStream)> {
35    if input.generics.type_params().count() > 0 {
36        // early return because `impl Service` won't have generics which will
37        // result in additional, confusing error messages.
38        // No error because oasis-build will warn about this.
39        return None;
40    }
41
42    let (named, fields) = match &input.data {
43        syn::Data::Struct(s) => {
44            let named = match &s.fields {
45                syn::Fields::Named(_) | syn::Fields::Unit => true,
46                syn::Fields::Unnamed(_) => false,
47            };
48            (named, s.fields.iter())
49        }
50        _ => {
51            err!(input: "`#[derive(Service)]` can only be applied to structs.");
52            return None;
53        }
54    };
55
56    let (sers, des): (Vec<proc_macro2::TokenStream>, Vec<proc_macro2::TokenStream>) = fields
57        .enumerate()
58        .map(|(index, field)| {
59            let (struct_idx, key) = match &field.ident {
60                Some(ident) => (
61                    syn::Member::Named(ident.clone()),
62                    proc_macro2::Literal::string(&ident.to_string()),
63                ),
64                None => (
65                    syn::Member::Unnamed(syn::Index {
66                        index: index as u32,
67                        span: proc_macro2::Span::call_site(),
68                    }),
69                    proc_macro2::Literal::string(&index.to_string()),
70                ),
71            };
72            let (ser, de) = get_type_serde(&field.ty, struct_idx, key);
73            let de = match &field.ident {
74                Some(ident) => quote! { #ident: #de },
75                None => de,
76            };
77            (ser, de)
78        })
79        .unzip();
80
81    let ser = quote! { #(#sers);* };
82
83    let de = if named {
84        quote! { Self { #(#des),* } }
85    } else {
86        quote! { Self(#(#des),*) }
87    };
88
89    Some((ser, de))
90}
91
92/// Returns the serializer and deserializer for a Type.
93fn get_type_serde(
94    ty: &syn::Type,
95    struct_idx: syn::Member,
96    key: proc_macro2::Literal,
97) -> (proc_macro2::TokenStream, proc_macro2::TokenStream) {
98    use syn::Type::*;
99    match ty {
100        Group(g) => get_type_serde(&*g.elem, struct_idx, key),
101        Paren(p) => get_type_serde(&*p.elem, struct_idx, key),
102        Array(_) | Tuple(_) | Path(_) => (
103            quote! {
104                oasis_std::backend::write(
105                    #key.as_bytes(),
106                    &service.#struct_idx.try_to_vec().unwrap()
107                )
108            },
109            quote! {
110                <_>::try_from_slice(
111                    &oasis_std::backend::read(#key.as_bytes())
112                ).unwrap()
113            },
114        ),
115        ty => {
116            err!(ty: "Service field must be a POD type.");
117            (quote!(unreachable!()), quote!(unreachable!()))
118        }
119    }
120}