1extern crate proc_macro;
2
3use quote::{format_ident, quote};
4
5type Output = proc_macro2::TokenStream;
6
7fn edit_fields(fields: &syn::Fields, lifetime: &syn::Lifetime) -> Output {
8 let edit_fields = fields.iter().map(|field| match field {
9 syn::Field {
10 ident: Some(ident),
11 ty,
12 vis,
13 ..
14 } => quote! {
15 #vis #ident: diffus::edit::Edit<#lifetime, #ty>
16 },
17 syn::Field {
18 ident: None,
19 ty,
20 vis,
21 ..
22 } => quote! {
23 #vis diffus::edit::Edit<#lifetime, #ty>
24 },
25 });
26
27 quote! { #(#edit_fields),* }
28}
29
30fn field_ident(enumerated_field: (usize, &syn::Field), prefix: &str) -> syn::Ident {
31 match enumerated_field {
32 (
33 _,
34 syn::Field {
35 ident: Some(ident), ..
36 },
37 ) => format_ident!("{}{}", prefix, ident),
38 (i, syn::Field { ident: None, .. }) => {
39 format_ident!("{}{}", prefix, unnamed_field_ident(i))
40 }
41 }
42}
43
44fn field_idents(fields: &syn::Fields, prefix: &str) -> Output {
45 let field_idents = fields
46 .iter()
47 .enumerate()
48 .map(|enumerated_field| field_ident(enumerated_field, prefix));
49
50 quote! { #(#field_idents),* }
51}
52
53fn renamed_field_ident(enumerated_field: (usize, &syn::Field), prefix: &str) -> Output {
54 match enumerated_field {
55 (
56 _,
57 syn::Field {
58 ident: Some(ident), ..
59 },
60 ) => {
61 let new_ident = format_ident!("{}{}", prefix, ident);
62
63 quote! { #ident: #new_ident }
64 }
65 (_, syn::Field { ident: None, .. }) => unreachable!(),
66 }
67}
68
69fn renamed_field_idents(fields: &syn::Fields, prefix: &str) -> Output {
70 let field_idents = fields
71 .iter()
72 .enumerate()
73 .map(|enumerated_field| renamed_field_ident(enumerated_field, prefix));
74
75 quote! { #(#field_idents),* }
76}
77
78fn matches_all_copy(fields: &syn::Fields) -> Output {
79 let edit_fields_copy = fields.iter().enumerate().map(|_| {
80 quote! { diffus::edit::Edit::Copy(_) }
81 });
82
83 quote! {
84 ( #(#edit_fields_copy),* ) => diffus::edit::Edit::Copy(self)
85 }
86}
87
88fn field_diffs(fields: &syn::Fields) -> Output {
89 let field_diffs = fields.iter().enumerate().map(|(index, field)| {
90 let field_name = match field {
91 syn::Field {
92 ident: Some(ident), ..
93 } => quote! { #ident },
94 syn::Field { ident: None, .. } => {
95 let ident = unnamed_field_name(index);
96
97 quote! { #ident }
98 }
99 };
100
101 quote! {
102 diffus::Diffable::diff(&self.#field_name, &other.#field_name)
103 }
104 });
105
106 quote! { #(#field_diffs),* }
107}
108
109fn unnamed_field_ident(i: usize) -> syn::Ident {
110 format_ident!("x{}", i as u32)
111}
112fn unnamed_field_name(i: usize) -> syn::Lit {
113 syn::parse_str(&format!("{}", i as u32)).unwrap()
114}
115
116fn input_lifetime(generics: &syn::Generics) -> Option<&syn::Lifetime> {
117 let mut lifetimes = generics.params.iter().filter_map(|generic_param| {
118 if let syn::GenericParam::Lifetime(syn::LifetimeDef { lifetime, .. }) = generic_param {
119 Some(lifetime)
120 } else {
121 None
122 }
123 });
124
125 let lifetime = lifetimes.next();
126
127 assert!(
128 lifetimes.next().is_none(),
129 "Multiple lifetimes not supported yet"
130 );
131
132 lifetime
133}
134
135#[proc_macro_derive(Diffus)]
136pub fn derive_diffus(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
137 let input: syn::DeriveInput = syn::parse2(proc_macro2::TokenStream::from(input)).unwrap();
138
139 let ident = &input.ident;
140 let vis = &input.vis;
141 let where_clause = &input.generics.where_clause;
142 let edited_ident = syn::parse_str::<syn::Path>(&format!("Edited{}", ident)).unwrap();
143
144 let data_lifetime = input_lifetime(&input.generics);
145 let default_lifetime = syn::parse_str::<syn::Lifetime>("'diffus_a").unwrap();
146 let impl_lifetime = data_lifetime.unwrap_or(&default_lifetime);
147
148 #[cfg(feature = "serialize-impl")]
149 let derive_serialize = Some(quote! { #[derive(serde::Serialize)] });
150 #[cfg(not(feature = "serialize-impl"))]
151 let derive_serialize: Option<proc_macro2::TokenStream> = None;
152
153 proc_macro::TokenStream::from(match input.data {
154 syn::Data::Enum(syn::DataEnum { variants, .. }) => {
155 let edit_variants = variants.iter().map(|syn::Variant { ident, fields, .. }| {
156 let edit_fields = edit_fields(&fields, &impl_lifetime);
157
158 match fields {
159 syn::Fields::Named(syn::FieldsNamed { .. }) => {
160 quote! {
161 #ident { #edit_fields }
162 }
163 }
164 syn::Fields::Unnamed(syn::FieldsUnnamed { .. }) => {
165 quote! {
166 #ident ( #edit_fields )
167 }
168 }
169 syn::Fields::Unit => {
170 quote! {
171 #ident
172 }
173 }
174 }
175 });
176
177 let has_non_unit_variant = variants.iter().any(|syn::Variant { fields, .. }| {
178 if let syn::Fields::Unit = fields {
179 false
180 } else {
181 true
182 }
183 });
184
185 let unit_enum_impl_lifetime = if has_non_unit_variant {
186 Some(impl_lifetime.clone())
187 } else {
188 None
189 };
190
191 let variants_matches = variants.iter().map(|syn::Variant { ident: variant_ident, fields, .. }| {
192
193 let field_diffs = fields.iter().enumerate().map(|(i, field)| {
194 let self_field_ident = field_ident((i, field), "self_");
195 let other_field_ident = field_ident((i, field), "other_");
196
197 quote! {
198 #self_field_ident . diff(& #other_field_ident )
199 }
200 });
201 let field_diffs = quote! { #(#field_diffs),* };
202
203 let matches_all_copy = matches_all_copy(&fields);
204 let just_field_idents = field_idents(&fields, "");
205 let self_field_idents = field_idents(&fields, "self_");
206 let other_field_idents = field_idents(&fields, "other_");
207
208 match fields {
209 syn::Fields::Named(syn::FieldsNamed { .. }) => {
210 let self_field_idents = renamed_field_idents(&fields, "self_");
211 let other_field_idents = renamed_field_idents(&fields, "other_");
212
213 quote! {
214 (
215 #ident::#variant_ident { #self_field_idents },
216 #ident::#variant_ident { #other_field_idents }
217 ) => {
218 match ( #field_diffs ) {
219 #matches_all_copy,
220 ( #just_field_idents ) => {
221 diffus::edit::Edit::Change(
222 diffus::edit::enm::Edit::AssociatedChanged(
223 #edited_ident::#variant_ident { #just_field_idents }
224 )
225 )
226 }
227 }
228 }
229 }
230 },
231 syn::Fields::Unnamed(syn::FieldsUnnamed { .. }) => {
232 quote! {
233 (
234 #ident::#variant_ident( #self_field_idents ),
235 #ident::#variant_ident( #other_field_idents )
236 ) => {
237 match ( #field_diffs ) {
238 #matches_all_copy,
239 ( #just_field_idents ) => {
240 diffus::edit::Edit::Change(
241 diffus::edit::enm::Edit::AssociatedChanged(
242 #edited_ident::#variant_ident ( #just_field_idents )
243 )
244 )
245 }
246 }
247 }
248 }
249 },
250 syn::Fields::Unit => {
251 quote! {
252 (
253 #ident::#variant_ident,
254 #ident::#variant_ident
255 ) => {
256 diffus::edit::Edit::Copy(self)
257 }
258 }
259 },
260 }
261 });
262
263 quote! {
264 #derive_serialize
265 #vis enum #edited_ident <#unit_enum_impl_lifetime> where #where_clause {
266 #(#edit_variants),*
267 }
268
269 impl<#impl_lifetime> diffus::Diffable<#impl_lifetime> for #ident <#data_lifetime> where #where_clause {
270 type Diff = diffus::edit::enm::Edit<#impl_lifetime, Self, #edited_ident <#unit_enum_impl_lifetime>>;
271
272 fn diff(&#impl_lifetime self, other: &#impl_lifetime Self) -> diffus::edit::Edit<#impl_lifetime, Self> {
273 match (self, other) {
274 #(#variants_matches,)*
275 (self_variant, other_variant) => diffus::edit::Edit::Change(diffus::edit::enm::Edit::VariantChanged(
276 self_variant, other_variant
277 )),
278 }
279 }
280 }
281 }
282 }
283 syn::Data::Struct(syn::DataStruct { fields, .. }) => {
284 let edit_fields = edit_fields(&fields, &impl_lifetime);
285 let field_diffs = field_diffs(&fields);
286 let field_idents = field_idents(&fields, "");
287 let matches_all_copy = matches_all_copy(&fields);
288
289 match fields {
290 syn::Fields::Named(_) => {
291 quote! {
292 #derive_serialize
293 #vis struct #edited_ident<#impl_lifetime> where #where_clause {
294 #edit_fields
295 }
296
297 impl<#impl_lifetime> diffus::Diffable<#impl_lifetime> for #ident <#data_lifetime> where #where_clause {
298 type Diff = #edited_ident<#impl_lifetime>;
299
300 fn diff(&#impl_lifetime self, other: &#impl_lifetime Self) -> diffus::edit::Edit<#impl_lifetime, Self> {
301 match ( #field_diffs ) {
302 #matches_all_copy,
303 ( #field_idents ) => diffus::edit::Edit::Change(
304 #edited_ident { #field_idents }
305 )
306 }
307 }
308 }
309 }
310 }
311 syn::Fields::Unnamed(_) => {
312 quote! {
313 #derive_serialize
314 #vis struct #edited_ident<#impl_lifetime> ( #edit_fields ) where #where_clause;
315
316 impl<#impl_lifetime> diffus::Diffable<#impl_lifetime> for #ident <#data_lifetime> where #where_clause {
317 type Diff = #edited_ident<#impl_lifetime>;
318
319 fn diff(&#impl_lifetime self, other: &#impl_lifetime Self) -> diffus::edit::Edit<#impl_lifetime, Self> {
320 match ( #field_diffs ) {
321 #matches_all_copy,
322 ( #field_idents ) => diffus::edit::Edit::Change(
323 #edited_ident ( #field_idents )
324 )
325 }
326 }
327 }
328 }
329 }
330 syn::Fields::Unit => {
331 quote! {
332 #derive_serialize
333 #vis struct #edited_ident< > where #where_clause;
334
335 impl<#impl_lifetime> diffus::Diffable<#impl_lifetime> for #ident< > where #where_clause {
336 type Diff = #edited_ident;
337
338 fn diff(&#impl_lifetime self, other: &#impl_lifetime Self) -> diffus::edit::Edit<#impl_lifetime, Self> {
339 diffus::edit::Edit::Copy(self)
340 }
341 }
342 }
343 }
344 }
345 }
346 syn::Data::Union(_) => panic!("union type not supported yet"),
347 })
348}