derive_diff/
lib.rs

1#![recursion_limit = "128"]
2extern crate proc_macro;
3extern crate syn;
4#[macro_use]
5extern crate quote;
6extern crate struct_diff;
7
8use proc_macro::TokenStream;
9
10#[proc_macro_derive(Diff)]
11pub fn generate_diff_impl(input: TokenStream) -> TokenStream {
12    let s = input.to_string();
13    // Parse the string representation
14    let ast = syn::parse_derive_input(&s).unwrap();
15
16    // Build the impl
17    let gen = impl_diff(&ast);
18
19    // Return the generated impl
20    gen.parse().unwrap()
21}
22
23fn impl_diff(ast: &syn::DeriveInput) -> quote::Tokens {
24    let name = &ast.ident;
25    match ast.body {
26        syn::Body::Struct(ref vdata) => impl_diff_struct(name, &vdata),
27        syn::Body::Enum(ref variants) => impl_diff_enum(name, &variants),
28    }
29}
30
31/// Generates Diff for each struct field
32struct StructGenerator<'a> {
33    fields: &'a [syn::Field]
34}
35
36impl<'a> quote::ToTokens for StructGenerator<'a> {
37    fn to_tokens(&self, tokens: &mut quote::Tokens) {
38        for field in self.fields.iter() {
39            if let Some(ref field_name) = field.ident {
40                let field_name_s = field_name.to_string();
41                tokens.append(
42                    quote!{
43                        if let Some(inner_diffs) = self.#field_name.diff(&other.#field_name) {
44                            for diff in inner_diffs {
45                                let mut path = String::from(#field_name_s);
46                                if !diff.field.is_empty() {
47                                    path.push_str(&".");
48                                }
49                                path.push_str(&diff.field);
50                                diffs.push(::struct_diff::Difference {
51                                    field: path,
52                                    left: diff.left,
53                                    right: diff.right,
54                                })
55                            }
56                        }
57                    }
58                )
59            }
60        }
61    }
62}
63
64/// Generates Diff impl for enum fields
65struct FieldGenerator<'a> {
66    name: &'a syn::Ident,
67    fields: &'a [syn::Field],
68}
69
70impl<'a> quote::ToTokens for FieldGenerator<'a> {
71    fn to_tokens(&self, tokens: &mut quote::Tokens) {
72        for (i, field) in self.fields.iter().enumerate() {
73            let field_name = field.ident.clone().unwrap_or(syn::Ident::from(i));
74            let field_name_s = field_name.to_string();
75            let left = {
76                    let mut new_name = String::from("left_");
77                    new_name.push_str(&field_name.to_string());
78                    syn::Ident::from(new_name)
79            };
80            let right = {
81                    let mut new_name = String::from("right_");
82                    new_name.push_str(&field_name.to_string());
83                    syn::Ident::from(new_name)
84            };
85            let name = self.name.to_string();
86            tokens.append(
87                quote!{
88                    if let Some(inner_diffs) = #left.diff(&#right) {
89                        for diff in inner_diffs {
90                            let mut path = String::from(#name);
91                            path.push_str(".");
92                            path.push_str(&#field_name_s);
93                            if !diff.field.is_empty() {
94                                path.push_str(&".");
95                            }
96                            path.push_str(&diff.field);
97                            diffs.push(::struct_diff::Difference {
98                                field: path,
99                                left: diff.left,
100                                right: diff.right,
101                            })
102                        }
103                    }
104                }
105            )
106        }
107    }
108}
109
110struct StructPatField<'a> {
111    name: &'a syn::Ident,
112    prefix: &'static str,
113}
114
115impl<'a> quote::ToTokens for StructPatField<'a> {
116    fn to_tokens(&self, tokens: &mut quote::Tokens) {
117            let ident = self.name;
118            let prefixed_ident = {
119                let mut new_name = String::from(self.prefix);
120                new_name.push_str("_");
121                new_name.push_str(&ident.to_string());
122                syn::Ident::from(new_name)
123            };
124            tokens.append(quote! {
125                #ident: ref #prefixed_ident
126            })
127    }
128}
129
130/// Implements Diff for enum
131fn impl_diff_enum(name: &syn::Ident, variants: &[syn::Variant]) -> quote::Tokens {
132    let mut differs = Vec::new();
133    for variant in variants {
134        let var_name = &variant.ident;
135        let var_data = &variant.data;
136        let diff = match var_data {
137            &syn::VariantData::Tuple(ref fields) => {
138                let gen = FieldGenerator { name: var_name, fields };
139                let names: Vec<syn::Ident> = fields.iter()
140                        .enumerate()
141                        .map(|(id, field)| field.ident.clone().unwrap_or(syn::Ident::from(id)))
142                        .collect();
143                let left = names.iter().map(|ident| {
144                    let mut new_name = String::from("left_");
145                    new_name.push_str(&ident.to_string());
146                    syn::Ident::from(new_name)
147                }).collect::<Vec<_>>();
148                let right = names.iter().map(|ident| {
149                    let mut new_name = String::from("right_");
150                    new_name.push_str(&ident.to_string());
151                    syn::Ident::from(new_name)
152                }).collect::<Vec<_>>();
153                quote! {
154                    (&#name::#var_name(#(ref #left),*), &#name::#var_name(#(ref #right),*)) => {
155                       #gen
156                    }
157                }
158            },
159            &syn::VariantData::Struct(ref fields) => {
160                let gen = FieldGenerator { name: var_name, fields };
161                let names: Vec<syn::Ident> = fields.iter()
162                        .enumerate()
163                        .map(|(id, field)| field.ident.clone().unwrap_or(syn::Ident::from(id)))
164                        .collect();
165                let left = names.iter().map(|ident| {
166                    StructPatField{ name: ident, prefix: "left"}
167                }).collect::<Vec<_>>();
168                let right = names.iter().map(|ident| {
169                    StructPatField{ name: ident, prefix: "right"}
170                }).collect::<Vec<_>>();
171                quote! {
172                    (&#name::#var_name{ #(#left),* }, &#name::#var_name{ #(#right),* }) => {
173                       #gen
174                    }
175                }
176            },
177            &syn::VariantData::Unit => {
178                quote! {
179                    (&#name::#var_name, &#name::#var_name) => {
180                        return None;
181                    }
182                }
183            },
184        };
185        differs.push(diff);
186    }
187    return quote! {
188        impl ::struct_diff::Diff for #name {
189
190            #[allow(unreachable_patterns)]
191            fn diff<'a>(&'a self, other: &'a #name) -> Option<Vec<::struct_diff::Difference<'a>>> {
192                let mut diffs = Vec::with_capacity(1);
193                match (self, other) {
194                    #(#differs),*
195                    _ => {
196                        diffs.push(::struct_diff::Difference { field: "self".into(), left: self, right: other });
197                    }
198                }
199                if diffs.len() > 0 {
200                    return Some(diffs);
201                }
202                None
203            }
204        }
205    }
206}
207
208/// Generates Diff impl for enum fields
209struct TupleFieldsGenerator<'a> {
210    fields: &'a [syn::Field],
211}
212
213impl<'a> quote::ToTokens for TupleFieldsGenerator<'a> {
214    fn to_tokens(&self, tokens: &mut quote::Tokens) {
215        for (field_name, _) in self.fields.iter().enumerate() {
216                let field_name_s = field_name.to_string();
217                use quote::Ident;
218                let field_name = Ident::new(format!("{}", field_name));
219                tokens.append(
220                    quote!{
221                        if let Some(inner_diffs) = self.#field_name.diff(&other.#field_name) {
222                            for diff in inner_diffs {
223                                let mut path = String::from(#field_name_s);
224                                if !diff.field.is_empty() {
225                                    path.push_str(&".");
226                                }
227                                path.push_str(&diff.field);
228                                diffs.push(::struct_diff::Difference {
229                                    field: path,
230                                    left: diff.left,
231                                    right: diff.right,
232                                })
233                            }
234                        }
235                    }
236                );
237        }
238    }
239}
240
241/// Implements Diff for structs
242fn impl_diff_struct(name: &syn::Ident, struct_: &syn::VariantData) -> quote::Tokens {
243    match struct_ {
244        &syn::VariantData::Struct(ref fields) => {
245            let gen = StructGenerator { fields };
246            return quote! {
247                impl ::struct_diff::Diff for #name {
248                    fn diff<'a>(&'a self, other: &'a #name) -> Option<Vec<::struct_diff::Difference<'a>>> {
249                        let mut diffs = Vec::new();
250                        #gen
251                        if diffs.len() > 0 {
252                            return Some(diffs);
253                        }
254                        return None;
255                    }
256                }
257            }
258        },
259        &syn::VariantData::Tuple(ref fields) => {
260            let gen = TupleFieldsGenerator { fields };
261            return quote! {
262                impl ::struct_diff::Diff for #name {
263                    fn diff<'a>(&'a self, other: &'a #name) -> Option<Vec<::struct_diff::Difference<'a>>> {
264                        let mut diffs = Vec::new();
265                        #gen
266                        if diffs.len() > 0 {
267                            return Some(diffs);
268                        }
269                        return None;
270                    }
271                }
272            }
273        },
274        v@_ => {
275            /* only structs and tuples are supported for now */
276            panic!("Support for {:?} not implemented", v);
277        }
278    }
279}