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>, rename_all: Option<String>,
33}
34
35#[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 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 .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 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 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 .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 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 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 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 #diff_ty::#variant_name(
766 # ( #ident, )*
767 )
768 },
769 Style::Struct => quote! {
770 #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 difficient::DeepDiff::Unchanged
782 };
783 }
784
785 match fields.style {
786 Style::Unit => unreachable!(), 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 .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 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
859fn 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 format_ident!("f{ix}")
871 }
872 })
873 .collect()
874}
875
876fn 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
894fn 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 assert!(pretty.contains(&expect));
1602 }
1603}