difficient_macros/
lib.rs

1extern crate proc_macro;
2
3use std::str::FromStr;
4
5use darling::{
6    FromDeriveInput, FromField, FromMeta, FromVariant,
7    ast::{Data, Fields, Style},
8};
9use heck::{ToKebabCase, ToLowerCamelCase, ToSnakeCase};
10use proc_macro2::TokenStream;
11use quote::{ToTokens, format_ident, quote};
12use syn::{DeriveInput, Generics, Ident};
13
14#[derive(Debug, FromField)]
15#[darling(attributes(diffable, serde), allow_unknown_fields)]
16struct StructLike {
17    ident: Option<syn::Ident>,
18    ty: syn::Type,
19    #[darling(default)]
20    atomic: bool,
21    #[darling(default)]
22    skip: bool,
23    rename: Option<String>,
24}
25
26#[derive(Debug, FromVariant)]
27#[darling(attributes(serde), allow_unknown_fields)]
28struct EnumData {
29    ident: syn::Ident,
30    fields: Fields<StructLike>,
31    rename: Option<String>, // TODO
32    rename_all: Option<String>,
33}
34
35/// Used for attributes on structs or enums
36#[derive(FromMeta, Debug, Default)]
37#[darling(allow_unknown_fields)]
38struct ContainerSerdeAttrs {
39    rename_all: Option<String>,
40    tag: Option<String>,
41    content: Option<String>,
42    #[darling(default)]
43    untagged: bool,
44    #[darling(default)]
45    transparent: bool,
46}
47
48impl ContainerSerdeAttrs {
49    fn variant_tag(&self) -> SerdeVariantTag {
50        if let Some(tag) = self.tag.as_ref() {
51            if let Some(c) = self.content.as_ref() {
52                SerdeVariantTag::Adjacent {
53                    tag: tag.clone(),
54                    content: c.clone(),
55                }
56            } else {
57                SerdeVariantTag::Internal { tag: tag.clone() }
58            }
59        } else if self.untagged {
60            SerdeVariantTag::Untagged
61        } else {
62            SerdeVariantTag::External
63        }
64    }
65}
66
67#[derive(Debug, Clone, PartialEq)]
68enum SerdeVariantTag {
69    External,
70    Internal { tag: String },
71    Adjacent { tag: String, content: String },
72    Untagged,
73}
74
75enum SerdeRenameAllCase {
76    Lower,
77    Upper,
78    Snake,
79    Camel,
80    Pascal,
81    ScreamingSnake,
82    Kebab,
83    ScreamingKebab,
84}
85
86impl FromStr for SerdeRenameAllCase {
87    type Err = String;
88
89    fn from_str(s: &str) -> Result<Self, Self::Err> {
90        use SerdeRenameAllCase as S;
91        let ok = match s {
92            "lowercase" => S::Lower,
93            "UPPERCASE" => S::Upper,
94            "PascalCase" => S::Pascal,
95            "camelCase" => S::Camel,
96            "snake_case" => S::Snake,
97            "SCREAMING_SNAKE_CASE" => S::ScreamingSnake,
98            "kebab-case" => S::Kebab,
99            "SCREAMING-KEBAB-CASE" => S::ScreamingKebab,
100            other => return Err(format!("bad rename: {other}")),
101        };
102        Ok(ok)
103    }
104}
105
106impl SerdeRenameAllCase {
107    fn do_rename(&self, ident: &Ident) -> String {
108        use SerdeRenameAllCase as S;
109        match self {
110            S::Snake => ident.to_string().to_snake_case(),
111            S::Camel => ident.to_string().to_lower_camel_case(),
112            S::Kebab => ident.to_string().to_kebab_case(),
113            S::Lower => ident.to_string().to_lowercase(),
114            S::Upper | S::Pascal | S::ScreamingSnake | S::ScreamingKebab => {
115                todo!("Difficient does not support case: {{self:?}}")
116            }
117        }
118    }
119}
120
121impl ToTokens for SerdeVariantTag {
122    fn to_tokens(&self, tokens: &mut TokenStream) {
123        let tok = match self {
124            SerdeVariantTag::External => quote! { difficient::SerdeVariantTag::External },
125            SerdeVariantTag::Internal { tag } => {
126                quote! { difficient::SerdeVariantTag::Internal { tag: #tag.to_string() } }
127            }
128            SerdeVariantTag::Adjacent { tag, content } => {
129                quote! { difficient::SerdeVariantTag::Adjacent {
130                    tag: #tag.to_string(), content: #content.to_string()
131                } }
132            }
133            SerdeVariantTag::Untagged => {
134                quote! { difficient::SerdeVariantTag::Untagged }
135            }
136        };
137        tokens.extend(tok);
138    }
139}
140
141#[derive(Debug, FromDeriveInput)]
142#[darling(attributes(diffable, serde))]
143struct DeriveDiffable {
144    ident: syn::Ident,
145    vis: syn::Visibility,
146    data: Data<EnumData, StructLike>,
147    generics: Generics,
148    #[darling(default)]
149    visit_transparent: bool,
150    #[darling(default)]
151    atomic: bool,
152    #[darling(flatten)]
153    serde: ContainerSerdeAttrs,
154}
155
156impl DeriveDiffable {
157    fn derive(&self, serde_feature: bool, derive_visitor: bool) -> TokenStream {
158        assert!(
159            self.generics.params.is_empty(),
160            "derive(Diffable) does not support generic parameters"
161        );
162
163        let name = &self.ident;
164
165        // short-circuit branch for top-level atomic
166        if self.atomic {
167            let has_any_skipped_fields = match &self.data {
168                Data::Enum(variants) => variants.iter().any(|ed| ed.fields.iter().any(|f| f.skip)),
169                Data::Struct(fields) => fields.iter().any(|f| f.skip),
170            };
171            assert!(!has_any_skipped_fields, "cannot skip fields in atomic diff");
172            return quote! {
173                impl<'a> difficient::Diffable<'a> for #name {
174                    type Diff = difficient::AtomicDiff<'a, Self>;
175
176                    fn diff(&self, other: &'a Self) -> Self::Diff {
177                        Self::Diff::new(self, other)
178                    }
179                }
180            };
181        }
182
183        let diff_ty = format_ident!("{}Diff", self.ident);
184        let vis = &self.vis;
185        let serde_derive = serde_feature.then(|| quote! { #[derive(serde::Serialize)] });
186        let serde_container_rename_all: Option<SerdeRenameAllCase> = self
187            .serde
188            .rename_all
189            .as_deref()
190            .and_then(|s| s.parse::<SerdeRenameAllCase>().ok());
191
192        match &self.data {
193            Data::Enum(variants) => {
194                let tag = self.serde.variant_tag();
195                enum_impl(
196                    variants,
197                    &tag,
198                    name,
199                    &diff_ty,
200                    vis,
201                    serde_derive.as_ref(),
202                    serde_container_rename_all.as_ref(),
203                    derive_visitor,
204                    self.visit_transparent || self.serde.transparent,
205                )
206            }
207            Data::Struct(fields) => struct_impl(
208                fields,
209                name,
210                &diff_ty,
211                vis,
212                serde_derive.as_ref(),
213                serde_container_rename_all.as_ref(),
214                derive_visitor,
215                self.visit_transparent || self.serde.transparent,
216            ),
217        }
218    }
219}
220
221impl ToTokens for DeriveDiffable {
222    fn to_tokens(&self, tokens: &mut TokenStream) {
223        let serde_feature = cfg!(feature = "serde_impl");
224        let derive_visitor = cfg!(feature = "visitor_impl");
225        tokens.extend(self.derive(serde_feature, derive_visitor));
226    }
227}
228
229#[expect(
230    clippy::too_many_arguments,
231    reason = "TODO Consider refactoring or configuring the lint"
232)]
233#[expect(
234    clippy::too_many_lines,
235    reason = "TODO Consider refactoring or configuring the lint"
236)]
237fn enum_impl(
238    variants: &[EnumData],
239    variant_tag: &SerdeVariantTag,
240    name: &Ident,
241    diff_ty: &Ident,
242    vis: &syn::Visibility,
243    serde_derive: Option<&TokenStream>,
244    serde_container_rename_all: Option<&SerdeRenameAllCase>,
245    derive_visitor: bool,
246    transparent: bool,
247) -> TokenStream {
248    let var_name: Vec<&Ident> = variants.iter().map(|ed| &ed.ident).collect();
249    let is_fieldless = variants.iter().all(|ed| ed.fields.is_empty());
250    let lifetime = if is_fieldless {
251        quote! {}
252    } else {
253        quote! { <'a> }
254    };
255    let var_diff_def = variants.iter().map(|var| {
256        let ty: Vec<_> = var.fields.iter().map(|data| &data.ty).collect();
257        let field_diff_ty: Vec<_> = var
258            .fields
259            .iter()
260            .zip(ty.iter())
261            // totally ignore fields that are annotated with `#[diffable(skip)]`
262            .filter(|(f, _)| !f.skip)
263            .map(|(f, ty)| {
264                if f.atomic {
265                    quote! {
266                        difficient::AtomicDiff<'a, #ty>
267                    }
268                } else {
269                    quote! {
270                        <#ty as difficient::Diffable<'a>>::Diff
271                    }
272                }
273            })
274            .collect();
275        match var.fields.style {
276            Style::Unit => quote! {},
277            Style::Tuple => {
278                quote! {
279                    (
280                        #(  #field_diff_ty, )*
281                    )
282                }
283            }
284            Style::Struct => {
285                let field = var
286                    .fields
287                    .iter()
288                    .filter(|f| !f.skip)
289                    .map(|data| &data.ident)
290                    .collect::<Vec<_>>();
291                quote! {
292                    {
293                        #( #field: #field_diff_ty, )*
294                    }
295                }
296            }
297        }
298    });
299
300    let enum_definition = quote! {
301        #[derive(Debug, Clone, PartialEq)]
302        #[allow(non_camel_case_types, non_snake_case, dead_code, reason = "Macro")]
303        #[automatically_derived]
304        #serde_derive
305        #vis enum #diff_ty #lifetime {
306            #(
307                #var_name #var_diff_def,
308            )*
309        }
310    };
311
312    let variant_diff_impl = variants.iter().zip(var_name.iter()).map(|(var, var_name)| {
313        let pattern_match_left = pattern_match(&var.fields, "left", true);
314        let pattern_match_right = pattern_match(&var.fields, "right", true);
315        let diff_impl = variant_diff_body(diff_ty, var_name, &var.fields);
316        quote! {
317            (Self::#var_name #pattern_match_left, Self::#var_name #pattern_match_right)  => {
318                #diff_impl
319            }
320        }
321    });
322
323    let diffable_impl = quote! {
324        impl<'a> difficient::Diffable<'a> for #name {
325            type Diff = difficient::DeepDiff<'a, Self, #diff_ty #lifetime>;
326
327            #[allow(non_snake_case, reason = "Macro")]
328            fn diff(&self, other: &'a Self) -> Self::Diff {
329                use difficient::Replace as _;
330                match (self, other) {
331                    #(
332                        #variant_diff_impl
333                    ),*
334                    _ => difficient::DeepDiff::Replaced(other)
335                }
336            }
337        }
338    };
339
340    let apply_body = variants
341        .iter()
342        .zip(var_name.iter())
343        .map(|(var, var_name)| {
344            let pat_l = prefixed_idents(&var.fields, "left", false);
345            let pat_r = prefixed_idents(&var.fields, "right", false);
346            let pattern_match_left = pattern_match(&var.fields, "left", false);
347            let pattern_match_right = pattern_match(&var.fields, "right", true);
348            quote! {
349                (Self::#var_name #pattern_match_left, #name::#var_name #pattern_match_right)  => {
350                    #( #pat_l.apply_to_base(#pat_r, errs); )*
351                }
352            }
353        })
354        .collect::<Vec<_>>();
355
356    let apply_impl = quote! {
357        impl #lifetime difficient::Apply for #diff_ty #lifetime {
358            type Parent = #name;
359            fn apply_to_base(&self, source: &mut Self::Parent, errs: &mut Vec<difficient::ApplyError>) {
360                match (self, source) {
361                    #( #apply_body )*
362                    _ => errs.push(difficient::ApplyError::MismatchingEnum),
363                }
364            }
365        }
366    };
367
368    let visit_enum_variant_impl = variants.iter().zip(var_name.iter()).map(|(var, var_name)| {
369        let ident = get_idents(&var.fields);
370        let num_non_skipped_fields = var.fields.iter().filter(|f| !f.skip).count();
371        #[expect(clippy::map_unwrap_or, reason = "more readable this way")]
372        let serde_var_rename = var.rename.as_ref().map(|r| quote! { Some(#r) }).unwrap_or_else(||
373            if let Some(r) = serde_container_rename_all.as_ref().map(|r| r.do_rename(var_name)) {
374                quote! { Some(#r) } }
375            else {
376                quote! { None }
377            });
378        match var.fields.style {
379            Style::Tuple => {
380                if num_non_skipped_fields == 0 {
381                    return quote! {
382                        Self:: #var_name () => {}
383                    }
384                }
385                let position = 0..(var.fields.len());
386                let var_name_str = var_name.to_string();
387                if transparent {
388                    // if tagged with `diffable(visit_transparent)`, we do not emit any
389                    // enter/exit events information and just continue inwards
390                    quote! {
391                        Self:: #var_name ( #( #ident, )* ) => {
392                            #(
393                             if !#ident.is_unchanged() {
394                                 #ident.accept(visitor);
395                             }
396                            )*
397                        }
398                    }
399                } else {
400                    quote! {
401                        Self:: #var_name ( #( #ident, )* ) => {
402                            visitor.enter(difficient::Enter::Variant{
403                                name: #var_name_str, serde_rename: #serde_var_rename, serde_tag: #variant_tag
404                            });
405                            #(
406                             if !#ident.is_unchanged() {
407                                 visitor.enter(difficient::Enter::PositionalField(#position));
408                                 #ident.accept(visitor);
409                                 visitor.exit();
410                             }
411                            )*
412                            visitor.exit();
413                        }
414                    }
415                }
416            }
417            Style::Struct => {
418                if num_non_skipped_fields == 0 {
419                    return quote! {
420                        Self:: #var_name {} => {}
421                    }
422                }
423                let var_name_str = var_name.to_string();
424                let ident_str = ident.iter().map(std::string::ToString::to_string);
425                let var_rename_all: Option<SerdeRenameAllCase> = var.rename_all.as_ref().map(|r| r.parse().unwrap());
426                let serde_field_rename: Vec<_> = var.fields
427                    .iter()
428                    .map(|f| {
429                        if let Some(n) = f.rename.as_ref() {
430                            quote! { Some(#n) }
431                        } else if let Some(r) = var_rename_all.as_ref() {
432                            let re = r.do_rename(f.ident.as_ref().unwrap());
433                            quote! { Some(#re) }
434                        } else {
435                            quote! { None }
436                        }
437                    })
438                    .collect();
439                quote! {
440                    Self:: #var_name { #( #ident, )* } => {
441                        visitor.enter(difficient::Enter::Variant{
442                            name: #var_name_str, serde_rename: #serde_var_rename, serde_tag: #variant_tag
443                        });
444                        #(
445                         if !#ident.is_unchanged() {
446                             visitor.enter(difficient::Enter::NamedField {
447                                 name: #ident_str, serde_rename: #serde_field_rename
448                             });
449                             #ident.accept(visitor);
450                             visitor.exit();
451                         }
452                        )*
453                        visitor.exit();
454                    }
455                }
456            }
457            Style::Unit => quote! {
458                Self:: #var_name => {}
459            }
460        }
461    });
462
463    let visitor_impl = derive_visitor.then(|| {
464        quote! {
465            impl #lifetime difficient::AcceptVisitor for #diff_ty #lifetime {
466                fn accept<V: difficient::Visitor>(&self, visitor: &mut V) {
467                    use difficient::Replace as _;
468                    match self {
469                        #( #visit_enum_variant_impl ),*
470                    }
471                }
472            }
473        }
474    });
475
476    quote! {
477        #enum_definition
478
479        #diffable_impl
480
481        #apply_impl
482
483        #visitor_impl
484    }
485}
486
487#[expect(
488    clippy::too_many_arguments,
489    reason = "TODO Consider refactoring or configuring the lint"
490)]
491#[expect(
492    clippy::too_many_lines,
493    reason = "TODO Consider refactoring or configuring the lint"
494)]
495fn struct_impl(
496    fields: &Fields<StructLike>,
497    name: &Ident,
498    diff_ty: &Ident,
499    vis: &syn::Visibility,
500    serde_derive: Option<&TokenStream>,
501    serde_rename_all: Option<&SerdeRenameAllCase>,
502    derive_visitor: bool,
503    transparent: bool,
504) -> TokenStream {
505    let ty = fields.iter().map(|data| &data.ty).collect::<Vec<_>>();
506    let num_skipped_fields = fields.iter().filter(|f| f.skip).count();
507    let num_non_skipped_fields = fields.iter().filter(|f| !f.skip).count();
508
509    assert!(
510        !(transparent && num_non_skipped_fields != 1),
511        "visit_transparent only makes sense when applied to newtypes"
512    );
513
514    if matches!(fields.style, Style::Unit) || num_non_skipped_fields == 0 {
515        // short-circuit return
516        return quote! {
517            impl<'a> difficient::Diffable<'a> for #name {
518                type Diff = difficient::Id<Self>;
519
520                fn diff(&self, other: &'a Self) -> Self::Diff {
521                    difficient::Id::new()
522                }
523            }
524        };
525    }
526
527    let field = get_idents(fields);
528    let field_diff_ty: Vec<_> = fields
529        .iter()
530        .zip(ty.iter())
531        // totally ignore fields that are annotated with `#[diffable(skip)]`
532        .filter(|(f, _)| !f.skip)
533        .map(|(f, ty)| {
534            if f.atomic {
535                quote! {
536                    difficient::AtomicDiff<'a, #ty>
537                }
538            } else {
539                quote! {
540                    <#ty as difficient::Diffable<'a>>::Diff
541                }
542            }
543        })
544        .collect();
545    let source_accessor = get_accessors(fields, false);
546    let diff_accessor = get_accessors(fields, true);
547    let diff_ty_def = match fields.style {
548        Style::Tuple => {
549            quote! {
550                #vis struct #diff_ty<'a>(
551                    #(
552                        #field_diff_ty,
553                    )*
554                );
555            }
556        }
557        Style::Struct => {
558            quote! {
559                #vis struct #diff_ty<'a> {
560                    #(
561                        #field: #field_diff_ty,
562                    )*
563                }
564            }
565        }
566        Style::Unit => unreachable!(),
567    };
568    let patch_ctor = match fields.style {
569        Style::Tuple => quote! {
570            #diff_ty( #( #field, )* )
571        },
572        Style::Struct => quote! {
573            #diff_ty{ #( #field ),* }
574        },
575        Style::Unit => unreachable!(),
576    };
577
578    let field_diff_impl: Vec<_> = fields
579        .iter()
580        .zip(source_accessor.iter())
581        .map(|(f, accessor)| {
582            if f.atomic {
583                quote! {
584                    difficient::AtomicDiff::new(&self.#accessor, &other.#accessor)
585                }
586            } else {
587                quote! {
588                    self.#accessor.diff(&other.#accessor)
589                }
590            }
591        })
592        .collect();
593
594    // if there is only one field on the struct, we do not allow replacement, only
595    // patching. This is to make the patches as targeted as possible
596    let (struct_diff_type, replaced_impl) = if num_skipped_fields > 0 || num_non_skipped_fields == 1
597    {
598        (
599            quote! {
600                difficient::PatchOnlyDiff<#diff_ty<'a>>
601            },
602            quote! {
603                        Self::Diff::Patched(#patch_ctor)
604            },
605        )
606    } else {
607        (
608            quote! {
609                difficient::DeepDiff<'a, Self, #diff_ty<'a>>
610            },
611            quote! {
612                        Self::Diff::Replaced(other)
613            },
614        )
615    };
616
617    let diffable_impl = quote! {
618        impl<'a> difficient::Diffable<'a> for #name {
619            type Diff = #struct_diff_type;
620
621            #[allow(non_snake_case, reason = "Macro")]
622            fn diff(&self, other: &'a Self) -> Self::Diff {
623                use difficient::Replace as _;
624                #(
625                    let #field = #field_diff_impl;
626                )*
627                if #( #field.is_unchanged() && )* true {
628                    Self::Diff::Unchanged
629                } else if #( #field.is_replaced() && )* true {
630                    #replaced_impl
631                } else {
632                    Self::Diff::Patched(#patch_ctor)
633                }
634            }
635        }
636    };
637
638    let apply_impl = quote! {
639        impl<'a> difficient::Apply for #diff_ty<'a> {
640            type Parent = #name;
641            #[allow(non_snake_case, reason = "Macro")]
642            fn apply_to_base(&self, source: &mut Self::Parent, errs: &mut Vec<difficient::ApplyError>) {
643                #( self.#diff_accessor.apply_to_base(&mut source.#source_accessor, errs); )*
644            }
645        }
646    };
647
648    let struct_field_visit_impl = {
649        let ident = get_idents(fields);
650        match fields.style {
651            Style::Tuple => {
652                let position = 0..(fields.len());
653                if transparent {
654                    // if tagged with `diffable(visit_transparent)`, we do not emit any
655                    // enter/exit events information and just continue inwards
656                    quote! {
657                        let Self ( #( #ident, )* ) = self;
658                        #(
659                             if !#ident.is_unchanged() {
660                                 #ident.accept(visitor);
661                             }
662                        )*
663
664                    }
665                } else {
666                    quote! {
667                        let Self ( #( #ident, )* ) = self;
668                        #(
669                             if !#ident.is_unchanged() {
670                                 visitor.enter(difficient::Enter::PositionalField(#position));
671                                 #ident.accept(visitor);
672                                 visitor.exit();
673                             }
674                        )*
675
676                    }
677                }
678            }
679            Style::Struct => {
680                let ident_str = ident.iter().map(std::string::ToString::to_string);
681                let serde_rename: Vec<_> = fields
682                    .iter()
683                    .filter(|f| !f.skip)
684                    .map(|f| {
685                        if let Some(n) = f.rename.as_ref() {
686                            quote! { Some(#n) }
687                        } else if let Some(r) = serde_rename_all.as_ref() {
688                            let re = r.do_rename(f.ident.as_ref().unwrap());
689                            quote! { Some(#re) }
690                        } else {
691                            quote! { None }
692                        }
693                    })
694                    .collect();
695                assert_eq!(ident_str.len(), serde_rename.len());
696                if transparent {
697                    // if tagged with `diffable(visit_transparent)`, we do not emit any
698                    // enter/exit events information and just continue inwards
699                    quote! {
700                        let Self { #( #ident, )* } = self;
701                        #(
702                             if !#ident.is_unchanged() {
703                                 #ident.accept(visitor);
704                             }
705                        )*
706
707                    }
708                } else {
709                    quote! {
710                        let Self { #( #ident, )* } = self;
711                        #(
712                             if !#ident.is_unchanged() {
713                                 visitor.enter(difficient::Enter::NamedField{
714                                     name: #ident_str, serde_rename: #serde_rename
715                                 });
716                                 #ident.accept(visitor);
717                                 visitor.exit();
718                             }
719                        )*
720
721                    }
722                }
723            }
724            Style::Unit => quote! {},
725        }
726    };
727
728    let visitor_impl = derive_visitor.then(|| {
729        quote! {
730            impl<'a> difficient::AcceptVisitor for #diff_ty <'a> {
731                fn accept<V: difficient::Visitor>(&self, visitor: &mut V) {
732                    use difficient::Replace as _;
733                    #struct_field_visit_impl
734                }
735            }
736        }
737    });
738
739    quote! {
740        #[derive(Debug, Clone, PartialEq)]
741        #[allow(non_camel_case_types, non_snake_case, dead_code, reason = "Macro")]
742        #[automatically_derived]
743        #serde_derive
744        #diff_ty_def
745
746        #diffable_impl
747
748        #apply_impl
749
750        #visitor_impl
751    }
752}
753
754fn variant_diff_body(
755    diff_ty: &Ident,
756    variant_name: &Ident,
757    fields: &Fields<StructLike>,
758) -> TokenStream {
759    let num_non_skipped_fields = fields.iter().filter(|f| !f.skip).count();
760    let ident = get_idents(fields);
761
762    let patch_ctor = match fields.style {
763        Style::Tuple => quote! {
764            // round brackets
765            #diff_ty::#variant_name(
766                # ( #ident, )*
767            )
768        },
769        Style::Struct => quote! {
770            // curly brackets
771            #diff_ty::#variant_name {
772                # ( #ident, )*
773            }
774        },
775        Style::Unit => quote! {},
776    };
777
778    if num_non_skipped_fields == 0 {
779        return quote! {
780            // no fields -> by definition they are unchanged
781            difficient::DeepDiff::Unchanged
782        };
783    }
784
785    match fields.style {
786        Style::Unit => unreachable!(), // dealt with above, unit types have zero fields
787        Style::Tuple | Style::Struct => {
788            let left_ident = prefixed_idents(fields, "left", false);
789            let right_ident = prefixed_idents(fields, "right", false);
790
791            let field_diff_impl: Vec<_> = fields
792                .iter()
793                // totally ignore fields that are annotated with `#[diffable(skip)]`
794                .filter(|f| !f.skip)
795                .zip(ident.iter())
796                .zip(left_ident.iter())
797                .zip(right_ident.iter())
798                .map(|(((f, ident), left), right)| {
799                    if f.atomic {
800                        quote! {
801                            let #ident = difficient::AtomicDiff::new(#left, #right);
802                        }
803                    } else {
804                        quote! {
805                            let #ident = #left.diff(#right);
806                        }
807                    }
808                })
809                .collect();
810
811            // We have a decision to make here. Suppose we have an enum where
812            // the enum variant has *not* changed but every field of the variant has.
813            // Should we send a Patch or a Replaced? Our reasoning is that replacing
814            // the enum variant is a more destructive operation than necessary (the
815            // variant and the fields of the variant are separate bits of data) and
816            // therefore we decide that we should always send a Patch not a Replaced
817            // in such cases
818
819            quote! {
820                #(
821                    #field_diff_impl
822                )*
823                if #( #ident.is_unchanged() && )* true {
824                    difficient::DeepDiff::Unchanged
825                } else {
826                    difficient::DeepDiff::Patched(#patch_ctor)
827                }
828            }
829        }
830    }
831}
832
833fn pattern_match(fields: &Fields<StructLike>, prefix: &str, include_skipped: bool) -> TokenStream {
834    let pattern = prefixed_idents(fields, prefix, include_skipped);
835    match fields.style {
836        Style::Unit => quote! {},
837        Style::Tuple => {
838            quote! {
839                (
840                    #(  #pattern, )*
841                )
842            }
843        }
844        Style::Struct => {
845            let id = fields
846                .iter()
847                .filter(|f| !f.skip || include_skipped)
848                .map(|data| &data.ident)
849                .collect::<Vec<_>>();
850            quote! {
851                {
852                    #( #id: #pattern, )*
853                }
854            }
855        }
856    }
857}
858
859// tokens for field idents
860fn get_idents(fields: &Fields<StructLike>) -> Vec<Ident> {
861    fields
862        .iter()
863        .enumerate()
864        .filter(|(_, f)| !f.skip)
865        .map(|(ix, struct_like)| {
866            if let Some(field_name) = &struct_like.ident {
867                field_name.clone()
868            } else {
869                // if the field has no name (tuple-like), fall back to 'f0', 'f1' etc
870                format_ident!("f{ix}")
871            }
872        })
873        .collect()
874}
875
876// as above but with a given prefix, useful for pattern matching
877fn prefixed_idents(fields: &Fields<StructLike>, prefix: &str, include_skipped: bool) -> Vec<Ident> {
878    fields
879        .iter()
880        .enumerate()
881        .filter(|(_, f)| !f.skip || include_skipped)
882        .map(|(ix, struct_like)| {
883            if struct_like.skip {
884                format_ident!("_")
885            } else if let Some(field_name) = &struct_like.ident {
886                format_ident!("{prefix}_{field_name}")
887            } else {
888                format_ident!("{prefix}_{ix}")
889            }
890        })
891        .collect()
892}
893
894// the tokens to access fields from parent struct ('self.my_field', or 'self.0')
895// `monotonic_index` will force the indexes to increment monotonically even if
896// some of the fields are skipped (relevant for tuple structs)
897fn get_accessors(fields: &Fields<StructLike>, monotonic_index: bool) -> Vec<TokenStream> {
898    let mut monotonic_count = monotonic_index.then_some(0);
899    fields
900        .iter()
901        .enumerate()
902        .filter(|(_, f)| !f.skip)
903        .map(|(ix, struct_like)| {
904            if let Some(field_name) = &struct_like.ident {
905                quote! { #field_name }
906            } else {
907                let ix = monotonic_count.unwrap_or(ix);
908                monotonic_count = monotonic_count.map(|ix| ix + 1);
909                let ix = syn::Index::from(ix);
910                quote! { #ix }
911            }
912        })
913        .collect()
914}
915
916#[expect(clippy::missing_panics_doc, reason = "Macro implementation")]
917#[proc_macro_derive(Diffable, attributes(diffable, serde))]
918pub fn derive_diffable(tokens: proc_macro::TokenStream) -> proc_macro::TokenStream {
919    let ast: DeriveInput = syn::parse(tokens).unwrap();
920    let diff = DeriveDiffable::from_derive_input(&ast).unwrap();
921    quote! { #diff }.into()
922}
923
924#[cfg(test)]
925mod tests {
926    #![expect(clippy::too_many_lines, reason = "tests")]
927    use super::*;
928
929    #[test]
930    fn test_derive_struct() {
931        let input = r#"
932        #[derive(Diffable)]
933        #[serde(rename_all = "camelCase", some_fake_field)]
934        struct SimpleStruct {
935            x: i32,
936            #[serde(rename = "yyy")]
937            y: String,
938            #[diffable(atomic)]
939            z_for_zelda: Vec<Fake>,
940        }
941        "#;
942
943        let parsed = syn::parse_str(input).unwrap();
944        let diff = DeriveDiffable::from_derive_input(&parsed).unwrap();
945
946        let expect = quote! {
947        #[derive(Debug, Clone, PartialEq)]
948        #[allow(non_camel_case_types, non_snake_case, dead_code, reason = "Macro")]
949        #[automatically_derived]
950        #[derive(serde::Serialize)]
951        struct SimpleStructDiff<'a> {
952            x: <i32 as difficient::Diffable<'a>>::Diff,
953            y: <String as difficient::Diffable<'a>>::Diff,
954            z_for_zelda: difficient::AtomicDiff<'a, Vec<Fake>>,
955        }
956        impl<'a> difficient::Diffable<'a> for SimpleStruct {
957            type Diff = difficient::DeepDiff<'a, Self, SimpleStructDiff<'a>>;
958            #[allow(non_snake_case, reason = "Macro")]
959            fn diff(&self, other: &'a Self) -> Self::Diff {
960                use difficient::Replace as _;
961                let x = self.x.diff(&other.x);
962                let y = self.y.diff(&other.y);
963                let z_for_zelda = difficient::AtomicDiff::new(&self.z_for_zelda, &other.z_for_zelda);
964                if x.is_unchanged() && y.is_unchanged() && z_for_zelda.is_unchanged() && true {
965                    Self::Diff::Unchanged
966                } else if x.is_replaced() && y.is_replaced() && z_for_zelda.is_replaced() && true {
967                    Self::Diff::Replaced(other)
968                } else {
969                    Self::Diff::Patched(SimpleStructDiff { x, y, z_for_zelda })
970                }
971            }
972        }
973        impl<'a> difficient::Apply for SimpleStructDiff<'a> {
974            type Parent = SimpleStruct;
975            #[allow(non_snake_case, reason = "Macro")]
976            fn apply_to_base(
977                &self,
978                source: &mut Self::Parent,
979                errs: &mut Vec<difficient::ApplyError>,
980            ) {
981                self.x.apply_to_base(&mut source.x, errs);
982                self.y.apply_to_base(&mut source.y, errs);
983                self.z_for_zelda.apply_to_base(&mut source.z_for_zelda, errs);
984            }
985        }
986        impl<'a> difficient::AcceptVisitor for SimpleStructDiff<'a> {
987            fn accept<V: difficient::Visitor>(&self, visitor: &mut V) {
988                use difficient::Replace as _;
989                let Self { x, y, z_for_zelda } = self;
990                if !x.is_unchanged() {
991                    visitor
992                        .enter(difficient::Enter::NamedField {
993                            name: "x",
994                            serde_rename: Some("x"),
995                        });
996                    x.accept(visitor);
997                    visitor.exit();
998                }
999                if !y.is_unchanged() {
1000                    visitor
1001                        .enter(difficient::Enter::NamedField {
1002                            name: "y",
1003                            serde_rename: Some("yyy"),
1004                        });
1005                    y.accept(visitor);
1006                    visitor.exit();
1007                }
1008                if !z_for_zelda.is_unchanged() {
1009                    visitor
1010                        .enter(difficient::Enter::NamedField {
1011                            name: "z_for_zelda",
1012                            serde_rename: Some("zForZelda"),
1013                        });
1014                    z_for_zelda.accept(visitor);
1015                    visitor.exit();
1016                }
1017            }
1018        }
1019        };
1020
1021        let pretty = {
1022            let derived = diff.derive(true, true);
1023            let f: syn::File = syn::parse2(derived).unwrap();
1024            prettyplease::unparse(&f)
1025        };
1026        let expect = {
1027            let f: syn::File = syn::parse2(expect).unwrap();
1028            prettyplease::unparse(&f)
1029        };
1030        pretty_assertions::assert_eq!(pretty.to_string(), expect.to_string());
1031    }
1032
1033    #[test]
1034    fn test_derive_skipped_field() {
1035        let input = r"
1036        #[derive(Diffable)]
1037        struct SkipStruct {
1038            x: i32,
1039            #[diffable(skip)]
1040            y: String,
1041            z: u64,
1042        }
1043        ";
1044        let parsed = syn::parse_str(input).unwrap();
1045        let diff = DeriveDiffable::from_derive_input(&parsed).unwrap();
1046
1047        let expect = quote! {
1048        #[derive(Debug, Clone, PartialEq)]
1049        #[allow(non_camel_case_types, non_snake_case, dead_code, reason = "Macro")]
1050        #[automatically_derived]
1051        #[derive(serde::Serialize)]
1052        struct SkipStructDiff<'a> {
1053            x: <i32 as difficient::Diffable<'a>>::Diff,
1054            z: <u64 as difficient::Diffable<'a>>::Diff,
1055        }
1056        impl<'a> difficient::Diffable<'a> for SkipStruct {
1057            type Diff = difficient::PatchOnlyDiff<SkipStructDiff<'a>>;
1058            #[allow(non_snake_case, reason = "Macro")]
1059            fn diff(&self, other: &'a Self) -> Self::Diff {
1060                use difficient::Replace as _;
1061                let x = self.x.diff(&other.x);
1062                let z = self.z.diff(&other.z);
1063                if x.is_unchanged() && z.is_unchanged() && true {
1064                    Self::Diff::Unchanged
1065                } else if x.is_replaced() && z.is_replaced() && true {
1066                    Self::Diff::Patched(SkipStructDiff { x, z })
1067                } else {
1068                    Self::Diff::Patched(SkipStructDiff { x, z })
1069                }
1070            }
1071        }
1072        impl<'a> difficient::Apply for SkipStructDiff<'a> {
1073            type Parent = SkipStruct;
1074            #[allow(non_snake_case, reason = "Macro")]
1075            fn apply_to_base(
1076                &self,
1077                source: &mut Self::Parent,
1078                errs: &mut Vec<difficient::ApplyError>,
1079            ) {
1080                self.x.apply_to_base(&mut source.x, errs);
1081                self.z.apply_to_base(&mut source.z, errs);
1082            }
1083        }
1084        impl<'a> difficient::AcceptVisitor for SkipStructDiff<'a> {
1085            fn accept<V: difficient::Visitor>(&self, visitor: &mut V) {
1086                use difficient::Replace as _;
1087                let Self { x, z } = self;
1088                if !x.is_unchanged() {
1089                    visitor
1090                        .enter(difficient::Enter::NamedField {
1091                            name: "x",
1092                            serde_rename: None,
1093                        });
1094                    x.accept(visitor);
1095                    visitor.exit();
1096                }
1097                if !z.is_unchanged() {
1098                    visitor
1099                        .enter(difficient::Enter::NamedField {
1100                            name: "z",
1101                            serde_rename: None,
1102                        });
1103                    z.accept(visitor);
1104                    visitor.exit();
1105                }
1106            }
1107        }
1108        };
1109
1110        let pretty = {
1111            let derived = diff.derive(true, true);
1112            let f: syn::File = syn::parse2(derived).unwrap();
1113            prettyplease::unparse(&f)
1114        };
1115        let expect = {
1116            let f: syn::File = syn::parse2(expect).unwrap();
1117            prettyplease::unparse(&f)
1118        };
1119        pretty_assertions::assert_eq!(pretty.to_string(), expect.to_string());
1120    }
1121
1122    #[test]
1123    fn test_derive_tuple_struct() {
1124        let input = r"
1125        #[derive(Diffable)]
1126        struct TupleStruct(i32, #[diffable(skip)] String, i64, #[diffable(skip)] F);
1127        ";
1128        let parsed = syn::parse_str(input).unwrap();
1129        let diff = DeriveDiffable::from_derive_input(&parsed).unwrap();
1130
1131        let expect = quote! {
1132            #[derive(Debug, Clone, PartialEq)]
1133            #[allow(non_camel_case_types, non_snake_case, dead_code,  reason = "Macro")]
1134            #[automatically_derived]
1135            #[derive(serde::Serialize)]
1136            struct TupleStructDiff<'a>(
1137                <i32 as difficient::Diffable<'a>>::Diff,
1138                <i64 as difficient::Diffable<'a>>::Diff,
1139            );
1140            impl<'a> difficient::Diffable<'a> for TupleStruct {
1141                type Diff = difficient::PatchOnlyDiff<TupleStructDiff<'a>>;
1142                #[allow(non_snake_case, reason = "Macro")]
1143                fn diff(&self, other: &'a Self) -> Self::Diff {
1144                    use difficient::Replace as _;
1145                    let f0 = self.0.diff(&other.0);
1146                    let f2 = self.2.diff(&other.2);
1147                    if f0.is_unchanged() && f2.is_unchanged() && true {
1148                        Self::Diff::Unchanged
1149                    } else if f0.is_replaced() && f2.is_replaced() && true {
1150                        Self::Diff::Patched(TupleStructDiff(f0, f2))
1151                    } else {
1152                        Self::Diff::Patched(TupleStructDiff(f0, f2))
1153                    }
1154                }
1155            }
1156            impl<'a> difficient::Apply for TupleStructDiff<'a> {
1157                type Parent = TupleStruct;
1158                #[allow(non_snake_case, reason = "Macro")]
1159                fn apply_to_base(
1160                    &self,
1161                    source: &mut Self::Parent,
1162                    errs: &mut Vec<difficient::ApplyError>,
1163                ) {
1164                    self.0.apply_to_base(&mut source.0, errs);
1165                    self.1.apply_to_base(&mut source.2, errs);
1166                }
1167            }
1168            impl<'a> difficient::AcceptVisitor for TupleStructDiff<'a> {
1169                fn accept<V: difficient::Visitor>(&self, visitor: &mut V) {
1170                    use difficient::Replace as _;
1171                    let Self(f0, f2) = self;
1172                    if !f0.is_unchanged() {
1173                        visitor.enter(difficient::Enter::PositionalField(0usize));
1174                        f0.accept(visitor);
1175                        visitor.exit();
1176                    }
1177                    if !f2.is_unchanged() {
1178                        visitor.enter(difficient::Enter::PositionalField(1usize));
1179                        f2.accept(visitor);
1180                        visitor.exit();
1181                    }
1182                }
1183            }
1184        };
1185        let pretty = {
1186            let derived = diff.derive(true, true);
1187            let f: syn::File = syn::parse2(derived).unwrap();
1188            prettyplease::unparse(&f)
1189        };
1190        let expect = {
1191            let f: syn::File = syn::parse2(expect).unwrap();
1192            prettyplease::unparse(&f)
1193        };
1194        pretty_assertions::assert_eq!(pretty.to_string(), expect.to_string());
1195    }
1196
1197    #[test]
1198    fn test_derive_enum() {
1199        let input = r#"
1200        #[derive(Diffable)]
1201        #[serde(tag = "my_tag", rename_all = "kebab-case")]
1202        enum SimpleEnum {
1203            First,
1204            #[serde(rename = "SecondTheBest")]
1205            Second(i32, String),
1206            #[serde(rename_all = "camelCase")]
1207            ThirdThing {
1208                #[serde(rename = "x-the-unknown")]
1209                x: i32,
1210                #[diffable(atomic)]
1211                y_y: Vec<Fake>,
1212            }
1213        }
1214        "#;
1215
1216        let parsed = syn::parse_str(input).unwrap();
1217        let diff = DeriveDiffable::from_derive_input(&parsed).unwrap();
1218
1219        let expect = quote! {
1220        #[derive(Debug, Clone, PartialEq)]
1221        #[allow(non_camel_case_types, non_snake_case, dead_code, reason = "Macro")]
1222        #[automatically_derived]
1223        #[derive(serde::Serialize)]
1224        enum SimpleEnumDiff<'a> {
1225            First,
1226            Second(
1227                <i32 as difficient::Diffable<'a>>::Diff,
1228                <String as difficient::Diffable<'a>>::Diff,
1229            ),
1230            ThirdThing {
1231                x: <i32 as difficient::Diffable<'a>>::Diff,
1232                y_y: difficient::AtomicDiff<'a, Vec<Fake>>,
1233            },
1234        }
1235        impl<'a> difficient::Diffable<'a> for SimpleEnum {
1236            type Diff = difficient::DeepDiff<'a, Self, SimpleEnumDiff<'a>>;
1237            #[allow(non_snake_case, reason = "Macro")]
1238            fn diff(&self, other: &'a Self) -> Self::Diff {
1239                use difficient::Replace as _;
1240                match (self, other) {
1241                    (Self::First, Self::First) => difficient::DeepDiff::Unchanged,
1242                    (Self::Second(left_0, left_1), Self::Second(right_0, right_1)) => {
1243                        let f0 = left_0.diff(right_0);
1244                        let f1 = left_1.diff(right_1);
1245                        if f0.is_unchanged() && f1.is_unchanged() && true {
1246                            difficient::DeepDiff::Unchanged
1247                        } else {
1248                            difficient::DeepDiff::Patched(SimpleEnumDiff::Second(f0, f1))
1249                        }
1250                    }
1251                    (
1252                        Self::ThirdThing {
1253                            x: left_x,
1254                            y_y: left_y_y,
1255                        },
1256                        Self::ThirdThing {
1257                            x: right_x,
1258                            y_y: right_y_y,
1259                        },
1260                    ) => {
1261                        let x = left_x.diff(right_x);
1262                        let y_y = difficient::AtomicDiff::new(left_y_y, right_y_y);
1263                        if x.is_unchanged() && y_y.is_unchanged() && true {
1264                            difficient::DeepDiff::Unchanged
1265                        } else {
1266                            difficient::DeepDiff::Patched(SimpleEnumDiff::ThirdThing { x, y_y })
1267                        }
1268                    }
1269                    _ => difficient::DeepDiff::Replaced(other),
1270                }
1271            }
1272        }
1273        impl<'a> difficient::Apply for SimpleEnumDiff<'a> {
1274            type Parent = SimpleEnum;
1275            fn apply_to_base(
1276                &self,
1277                source: &mut Self::Parent,
1278                errs: &mut Vec<difficient::ApplyError>,
1279            ) {
1280                match (self, source) {
1281                    (Self::First, SimpleEnum::First) => {}
1282                    (Self::Second(left_0, left_1), SimpleEnum::Second(right_0, right_1)) => {
1283                        left_0.apply_to_base(right_0, errs);
1284                        left_1.apply_to_base(right_1, errs);
1285                    }
1286                    (
1287                        Self::ThirdThing {
1288                            x: left_x,
1289                            y_y: left_y_y,
1290                        },
1291                        SimpleEnum::ThirdThing {
1292                            x: right_x,
1293                            y_y: right_y_y,
1294                        },
1295                    ) => {
1296                        left_x.apply_to_base(right_x, errs);
1297                        left_y_y.apply_to_base(right_y_y, errs);
1298                    }
1299                    _ => errs.push(difficient::ApplyError::MismatchingEnum),
1300                }
1301            }
1302        }
1303        impl<'a> difficient::AcceptVisitor for SimpleEnumDiff<'a> {
1304            fn accept<V: difficient::Visitor>(&self, visitor: &mut V) {
1305                use difficient::Replace as _;
1306                match self {
1307                    Self::First => {}
1308                    Self::Second(f0, f1) => {
1309                        visitor
1310                            .enter(difficient::Enter::Variant {
1311                                name: "Second",
1312                                serde_rename: Some("SecondTheBest"),
1313                                serde_tag: difficient::SerdeVariantTag::Internal {
1314                                    tag: "my_tag".to_string()
1315                                },
1316                            });
1317                        if !f0.is_unchanged() {
1318                            visitor.enter(difficient::Enter::PositionalField(0usize));
1319                            f0.accept(visitor);
1320                            visitor.exit();
1321                        }
1322                        if !f1.is_unchanged() {
1323                            visitor.enter(difficient::Enter::PositionalField(1usize));
1324                            f1.accept(visitor);
1325                            visitor.exit();
1326                        }
1327                        visitor.exit();
1328                    }
1329                    Self::ThirdThing { x, y_y } => {
1330                        visitor
1331                            .enter(difficient::Enter::Variant {
1332                                name: "ThirdThing",
1333                                serde_rename: Some("third-thing"),
1334                                serde_tag: difficient::SerdeVariantTag::Internal {
1335                                    tag: "my_tag".to_string()
1336                                },
1337                            });
1338                        if !x.is_unchanged() {
1339                            visitor
1340                                .enter(difficient::Enter::NamedField {
1341                                    name: "x",
1342                                    serde_rename: Some("x-the-unknown"),
1343                                });
1344                            x.accept(visitor);
1345                            visitor.exit();
1346                        }
1347                        if !y_y.is_unchanged() {
1348                            visitor
1349                                .enter(difficient::Enter::NamedField {
1350                                    name: "y_y",
1351                                    serde_rename: Some("yY"),
1352                                });
1353                            y_y.accept(visitor);
1354                            visitor.exit();
1355                        }
1356                        visitor.exit();
1357                    }
1358                }
1359            }
1360        }
1361        };
1362
1363        let pretty = {
1364            let derived = diff.derive(true, true);
1365            let f: syn::File = syn::parse2(derived).unwrap();
1366            prettyplease::unparse(&f)
1367        };
1368        let expect = {
1369            let f: syn::File = syn::parse2(expect).unwrap();
1370            prettyplease::unparse(&f)
1371        };
1372        pretty_assertions::assert_eq!(pretty.to_string(), expect.to_string());
1373    }
1374
1375    #[test]
1376    fn test_derive_skippable_enum() {
1377        let input = r"
1378        #[derive(Diffable)]
1379        enum SkipEnum {
1380            First(#[diffable(skip)] i32, u64),
1381            Second {
1382                x: i32,
1383                #[diffable(skip)]
1384                y: i32,
1385                z: String
1386            }
1387        }
1388        ";
1389
1390        let parsed = syn::parse_str(input).unwrap();
1391        let diff = DeriveDiffable::from_derive_input(&parsed).unwrap();
1392
1393        let expect = quote! {
1394        #[derive(Debug, Clone, PartialEq)]
1395        #[allow(non_camel_case_types, non_snake_case, dead_code,  reason = "Macro")]
1396        #[automatically_derived]
1397        #[derive(serde::Serialize)]
1398        enum SkipEnumDiff<'a> {
1399            First(<u64 as difficient::Diffable<'a>>::Diff),
1400            Second {
1401                x: <i32 as difficient::Diffable<'a>>::Diff,
1402                z: <String as difficient::Diffable<'a>>::Diff,
1403            },
1404        }
1405        impl<'a> difficient::Diffable<'a> for SkipEnum {
1406            type Diff = difficient::DeepDiff<'a, Self, SkipEnumDiff<'a>>;
1407            #[allow(non_snake_case, reason = "Macro")]
1408            fn diff(&self, other: &'a Self) -> Self::Diff {
1409                use difficient::Replace as _;
1410                match (self, other) {
1411                    (Self::First(_, left_1), Self::First(_, right_1)) => {
1412                        let f1 = left_1.diff(right_1);
1413                        if f1.is_unchanged() && true {
1414                            difficient::DeepDiff::Unchanged
1415                        } else {
1416                            difficient::DeepDiff::Patched(SkipEnumDiff::First(f1))
1417                        }
1418                    }
1419                    (
1420                        Self::Second {
1421                            x: left_x, y: _, z: left_z
1422                        },
1423                        Self::Second {
1424                            x: right_x, y: _, z: right_z
1425                        },
1426                    ) => {
1427                        let x = left_x.diff(right_x);
1428                        let z = left_z.diff(right_z);
1429                        if x.is_unchanged() && z.is_unchanged() && true {
1430                            difficient::DeepDiff::Unchanged
1431                        } else {
1432                            difficient::DeepDiff::Patched(SkipEnumDiff::Second { x, z })
1433                        }
1434                    }
1435                    _ => difficient::DeepDiff::Replaced(other),
1436                }
1437            }
1438        }
1439        impl<'a> difficient::Apply for SkipEnumDiff<'a> {
1440            type Parent = SkipEnum;
1441            fn apply_to_base(
1442                &self,
1443                source: &mut Self::Parent,
1444                errs: &mut Vec<difficient::ApplyError>,
1445            ) {
1446                match (self, source) {
1447                    (Self::First(left_1), SkipEnum::First(_, right_1)) => {
1448                        left_1.apply_to_base(right_1, errs);
1449                    }
1450                    (
1451                        Self::Second {
1452                            x: left_x,
1453                            z: left_z,
1454                        },
1455                        SkipEnum::Second {
1456                            x: right_x,
1457                            y: _,
1458                            z: right_z
1459                        },
1460                    ) => {
1461                        left_x.apply_to_base(right_x, errs);
1462                        left_z.apply_to_base(right_z, errs);
1463                    }
1464                    _ => errs.push(difficient::ApplyError::MismatchingEnum),
1465                }
1466            }
1467        }
1468        impl<'a> difficient::AcceptVisitor for SkipEnumDiff<'a> {
1469            fn accept<V: difficient::Visitor>(&self, visitor: &mut V) {
1470                use difficient::Replace as _;
1471                match self {
1472                    Self::First(f1) => {
1473                        visitor
1474                            .enter(difficient::Enter::Variant {
1475                                name: "First",
1476                                serde_rename: None,
1477                                serde_tag: difficient::SerdeVariantTag::External,
1478                            });
1479                        if !f1.is_unchanged() {
1480                            visitor.enter(difficient::Enter::PositionalField(0usize));
1481                            f1.accept(visitor);
1482                            visitor.exit();
1483                        }
1484                        visitor.exit();
1485                    }
1486                    Self::Second{ x, z } => {
1487                        visitor
1488                            .enter(difficient::Enter::Variant {
1489                                name: "Second",
1490                                serde_rename: None,
1491                                serde_tag: difficient::SerdeVariantTag::External
1492                            });
1493                        if !x.is_unchanged() {
1494                            visitor.enter(difficient::Enter::NamedField{
1495                                name: "x",
1496                                serde_rename: None
1497                            });
1498                            x.accept(visitor);
1499                            visitor.exit();
1500                        }
1501                        if !z.is_unchanged() {
1502                            visitor.enter(difficient::Enter::NamedField{
1503                                name: "z",
1504                                serde_rename: None
1505                            });
1506                            z.accept(visitor);
1507                            visitor.exit();
1508                        }
1509                        visitor.exit();
1510                    }
1511                }
1512            }
1513        }
1514        };
1515
1516        let pretty = {
1517            let derived = diff.derive(true, true);
1518            let f: syn::File = syn::parse2(derived).unwrap();
1519            prettyplease::unparse(&f)
1520        };
1521        let expect = {
1522            let f: syn::File = syn::parse2(expect).unwrap();
1523            prettyplease::unparse(&f)
1524        };
1525        pretty_assertions::assert_eq!(pretty.to_string(), expect.to_string());
1526    }
1527
1528    #[test]
1529    fn test_derive_atomic() {
1530        let input = r"
1531        #[derive(Diffable)]
1532        #[diffable(atomic)]
1533        enum Atomic {
1534            First(X),
1535            Second { x: Y, y: Z }
1536        }
1537        ";
1538
1539        let parsed = syn::parse_str(input).unwrap();
1540        let diff = DeriveDiffable::from_derive_input(&parsed).unwrap();
1541
1542        let expect = quote! {
1543            impl<'a> difficient::Diffable<'a> for Atomic {
1544                type Diff = difficient::AtomicDiff<'a, Self>;
1545
1546                fn diff(&self, other: &'a Self) -> Self::Diff {
1547                    Self::Diff::new(self, other)
1548                }
1549            }
1550        };
1551        let pretty = {
1552            let derived = diff.derive(true, true);
1553            let f: syn::File = syn::parse2(derived).unwrap();
1554            prettyplease::unparse(&f)
1555        };
1556        let expect = {
1557            let f: syn::File = syn::parse2(expect).unwrap();
1558            prettyplease::unparse(&f)
1559        };
1560        pretty_assertions::assert_eq!(pretty.to_string(), expect.to_string());
1561    }
1562
1563    #[test]
1564    fn test_transparent() {
1565        let input = r"
1566        #[derive(Diffable)]
1567        #[serde(transparent)]
1568        enum SeeThrough {
1569            A(X),
1570        }
1571        ";
1572
1573        let parsed = syn::parse_str(input).unwrap();
1574        let diff = DeriveDiffable::from_derive_input(&parsed).unwrap();
1575
1576        let expect = quote! {
1577            impl<'a> difficient::AcceptVisitor for SeeThroughDiff<'a> {
1578                fn accept<V: difficient::Visitor>(&self, visitor: &mut V) {
1579                    use difficient::Replace as _;
1580                    match self {
1581                        Self::A(f0) => {
1582                            if !f0.is_unchanged() {
1583                                f0.accept(visitor);
1584                            }
1585                        }
1586                    }
1587                }
1588            }
1589        };
1590        let pretty = {
1591            let derived = diff.derive(true, true);
1592            let f: syn::File = syn::parse2(derived).unwrap();
1593            prettyplease::unparse(&f)
1594        };
1595        let expect = {
1596            let f: syn::File = syn::parse2(expect).unwrap();
1597            prettyplease::unparse(&f)
1598        };
1599        // if the below assert fails, uncomment:
1600        // pretty_assertions::assert_eq!(pretty.to_string(), expect.to_string());
1601        assert!(pretty.contains(&expect));
1602    }
1603}