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#[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}