1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
#[proc_macro_derive(Service)]
pub fn service_derive(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
    if std::env::var("MANTLE_BUILD_NO_SERVICE_DERIVE").is_ok() {
        return proc_macro::TokenStream::new();
    }
    let input = parse_macro_input!(input as syn::DeriveInput);
    let service = &input.ident;
    proc_macro::TokenStream::from(match get_serde(&input) {
        Some((ser, de)) => {
            quote! {
                impl mantle::exe::Service for #service {
                    fn coalesce() -> Self {
                        #de
                    }

                    fn sunder(service: Self) {
                        #ser
                    }
                }
            }
        }
        None => quote! {},
    })
}

fn get_serde(
    input: &syn::DeriveInput,
) -> Option<(proc_macro2::TokenStream, proc_macro2::TokenStream)> {
    if input.generics.type_params().count() > 0 {
        // early return because `impl Service` won't have generics which will
        // result in additional, confusing error messages.
        // No error because mantle-build will warn about this.
        return None;
    }

    let (named, fields) = match &input.data {
        syn::Data::Struct(s) => {
            let named = match &s.fields {
                syn::Fields::Named(_) | syn::Fields::Unit => true,
                syn::Fields::Unnamed(_) => false,
            };
            (named, s.fields.iter())
        }
        _ => {
            err!(input: "`#[derive(Service)]` can only be applied to structs.");
            return None;
        }
    };

    let (sers, des): (Vec<proc_macro2::TokenStream>, Vec<proc_macro2::TokenStream>) = fields
        .enumerate()
        .map(|(index, field)| {
            let (struct_idx, key) = match &field.ident {
                Some(ident) => (
                    syn::Member::Named(ident.clone()),
                    proc_macro2::Literal::string(&ident.to_string()),
                ),
                None => (
                    syn::Member::Unnamed(syn::Index {
                        index: index as u32,
                        span: proc_macro2::Span::call_site(),
                    }),
                    proc_macro2::Literal::string(&index.to_string()),
                ),
            };
            let (ser, de) = get_type_serde(&field.ty, struct_idx, key);
            let de = match &field.ident {
                Some(ident) => quote! { #ident: #de },
                None => de,
            };
            (ser, de)
        })
        .unzip();

    let ser = quote! { #(#sers);* };

    let de = if named {
        quote! { Self { #(#des),* } }
    } else {
        quote! { Self(#(#des),*) }
    };

    Some((ser, de))
}

/// Returns the serializer and deserializer for a Type.
fn get_type_serde(
    ty: &syn::Type,
    struct_idx: syn::Member,
    key: proc_macro2::Literal,
) -> (proc_macro2::TokenStream, proc_macro2::TokenStream) {
    use syn::Type::*;
    match ty {
        Group(g) => get_type_serde(&*g.elem, struct_idx, key),
        Paren(p) => get_type_serde(&*p.elem, struct_idx, key),
        Array(_) | Tuple(_) | Path(_) => (
            quote! {
                mantle::backend::write(
                    #key.as_bytes(),
                    &mantle::reexports::serde_cbor::to_vec(&service.#struct_idx).unwrap()
                )
            },
            quote! {
                mantle::reexports::serde_cbor::from_slice(
                    &mantle::backend::read(#key.as_bytes())).unwrap()
            },
        ),
        ty => {
            err!(ty: "Service field must be a POD type.");
            (quote!(unreachable!()), quote!(unreachable!()))
        }
    }
}