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
extern crate proc_macro;

use proc_macro2::{Literal, Span, TokenStream as TokenStream2};
use quote::quote;
use syn::{
    parse_macro_input, Data, DataStruct, DeriveInput, Fields, FieldsNamed, FieldsUnnamed, Ident,
};

#[proc_macro_derive(Struple)]
pub fn derive_struple(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
    let input = parse_macro_input!(input as DeriveInput);
    expand(input)
        .unwrap_or_else(|err| err.to_compile_error())
        .into()
}

fn expand(
    DeriveInput {
        ident: name,
        generics,
        data,
        ..
    }: DeriveInput,
) -> syn::Result<TokenStream2> {
    let fields = match data {
        Data::Struct(DataStruct { fields, .. }) => fields,
        _ => {
            return Err(syn::Error::new(
                Span::call_site(),
                "Struple may only be derived on structs",
            ))
        }
    };

    let field_tys = fields.iter().map(|field| &field.ty);
    let tuple_ty = quote! { ( #( #field_tys, )* ) };

    let (from_tuple_body, into_tuple_body) = make_conversion_impl(&name, fields);

    let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();

    Ok(quote! {
        impl #impl_generics ::struple::Struple for #name #ty_generics #where_clause {
            type Tuple = #tuple_ty;
            fn from_tuple(tuple: Self::Tuple) -> Self {
                #from_tuple_body
            }
            fn into_tuple(self) -> Self::Tuple {
                #into_tuple_body
            }
        }
    })
}

fn make_conversion_impl(name: &Ident, fields: Fields) -> (TokenStream2, TokenStream2) {
    let tuple_indices = |len| (0..len).map(Literal::usize_unsuffixed);
    match fields {
        Fields::Named(FieldsNamed { named, .. }) => (
            {
                let indices = tuple_indices(named.len());
                let fields = named.iter().flat_map(|field| field.ident.as_ref());
                quote! { #name { #( #fields: tuple.#indices, )* } }
            },
            {
                let fields = named.iter().flat_map(|field| field.ident.as_ref());
                quote! { ( #( self.#fields, )* ) }
            },
        ),
        Fields::Unnamed(FieldsUnnamed { unnamed, .. }) => (
            {
                let indices = tuple_indices(unnamed.len());
                quote! { #name( #( tuple.#indices, )* ) }
            },
            {
                let indices = tuple_indices(unnamed.len());
                quote! { ( #( self.#indices, )* ) }
            },
        ),
        Fields::Unit => (quote!(#name), quote!(())),
    }
}