glissade_macro/
lib.rs

1use proc_macro::TokenStream;
2use quote::quote;
3use syn::{parse_macro_input, DeriveInput, Fields, GenericParam};
4
5#[derive(Debug)]
6enum Error {
7    CantDeriveForEnum,
8    CantDeriveForUnion,
9}
10
11impl From<Error> for TokenStream {
12    fn from(error: Error) -> TokenStream {
13        match error {
14            Error::CantDeriveForEnum => {
15                quote! {
16                    compile_error!("Mix cannot be derived for enums");
17                }
18            }
19            Error::CantDeriveForUnion => {
20                quote! {
21                    compile_error!("Mix cannot be derived for unions");
22                }
23            }
24        }
25        .into()
26    }
27}
28
29/// Derive the `Mix` trait for a struct.
30/// It interpolates each field of the struct with the `Mix` trait.
31#[proc_macro_derive(Mix)]
32pub fn mix_macro(input: TokenStream) -> TokenStream {
33    let input = parse_macro_input!(input as DeriveInput);
34
35    let name = input.ident;
36
37    let fields = match input.data {
38        syn::Data::Struct(ref data) => match data.fields {
39            Fields::Named(ref fields) => {
40                let fields_mix = fields
41                    .named
42                    .iter()
43                    .map(|field| {
44                        let name = &field.ident.as_ref().unwrap();
45                        quote! {
46                            #name: self.#name.mix(other.#name, t)
47                        }
48                    })
49                    .collect::<Vec<_>>();
50
51                quote! {
52                    {
53                      #(#fields_mix),*
54                    }
55                }
56            }
57            Fields::Unnamed(ref fields) => {
58                let fields_mix = (0..fields.unnamed.len())
59                    .map(syn::Index::from)
60                    .map(|i| {
61                        quote! {
62                            self.#i.mix(other.#i, t)
63                        }
64                    })
65                    .collect::<Vec<_>>();
66
67                quote! {
68                    (
69                        #(#fields_mix),*
70                    )
71                }
72            }
73            Fields::Unit => TokenStream::default().into(),
74        },
75        syn::Data::Enum(_) => {
76            return Error::CantDeriveForEnum.into();
77        }
78        syn::Data::Union(_) => {
79            return Error::CantDeriveForUnion.into();
80        }
81    };
82
83    let generic_params = input.generics.params;
84    let generic_names = if generic_params.is_empty() {
85        quote! {}
86    } else {
87        let names = generic_params
88            .iter()
89            .map(|param| match param {
90                GenericParam::Type(t) => {
91                    let name = t.ident.clone();
92                    quote! { #name }
93                }
94                GenericParam::Lifetime(l) => {
95                    let lifetime = l.lifetime.clone();
96                    quote! { #lifetime }
97                }
98                GenericParam::Const(c) => {
99                    let name = c.ident.clone();
100                    quote! { #name }
101                }
102            })
103            .collect::<Vec<_>>();
104        quote! {
105            <#(#names),*>
106        }
107    };
108
109    let generic_params = if generic_params.is_empty() {
110        quote! {}
111    } else {
112        quote! {
113            <#generic_params>
114        }
115    };
116
117    let where_clause = input.generics.where_clause;
118
119    (quote! {
120        impl #generic_params glissade::Mix for #name #generic_names #where_clause {
121            fn mix(self, other: Self, t: f32) -> Self {
122                Self #fields
123            }
124        }
125    })
126    .into()
127}