Skip to main content

delta_struct_macros/
lib.rs

1//! Procedural macro implementation behind the `delta-struct` crate.
2//!
3//! Use [`delta-struct`](https://docs.rs/delta-struct) rather than depending on
4//! this crate directly; it re-exports the [`Delta`] derive alongside the trait
5//! the derive generates an implementation of, and carries the user-facing
6//! documentation.
7
8extern crate proc_macro;
9
10use proc_macro::TokenStream;
11use proc_macro2::{Span, TokenStream as TokenStream2, TokenTree};
12use proc_macro_error::{abort_call_site, proc_macro_error};
13use quote::{format_ident, quote, ToTokens};
14use std::{
15    fmt::{self, Display},
16    iter::FromIterator,
17    str::FromStr,
18};
19use syn::{
20    parse_macro_input, punctuated::Punctuated, Attribute, Data, DeriveInput, Expr, ExprLit, Fields,
21    Ident, Lit, Meta, MetaList, MetaNameValue, Path, PredicateType, Token, TraitBound,
22    TraitBoundModifiers, Type, TypeParamBound, WherePredicate,
23};
24
25/// How a single field is diffed, and therefore how it is represented on the
26/// generated delta struct.
27#[derive(Copy, Clone, Debug, Eq, PartialEq)]
28enum FieldType {
29    /// A positional diff: the delta is a Myers edit script over the sequence.
30    Ordered,
31    /// A bag of items: the delta records additions and removals, not order.
32    /// Its shape is the collection's to choose, through `Unordered`, since a
33    /// map can name a departing entry by key where a set cannot.
34    Unordered,
35    /// A bag of key/value entries: like [`FieldType::Unordered`], except that
36    /// entries sharing a key are diffed with the value's own `Delta` rather
37    /// than recorded as a removal plus an addition.
38    UnorderedDelta,
39    /// Compared with `!=` and replaced wholesale.
40    Scalar,
41    /// Diffed recursively via the field type's own `Delta` implementation.
42    Delta,
43}
44
45const VALID_FIELD_TYPES: &str =
46    "\"ordered\", \"unordered\", \"unordered-delta\", \"delta\", or \"scalar\"";
47
48/// One field of the source struct, as the code generators want it: its name
49/// (or, for a tuple struct, its index), its declared type, how it is diffed,
50/// and the tokens to emit above the field it turns into.
51type Field = (String, Type, FieldType, String);
52
53/// One field as it comes back from attribute parsing, before the container's
54/// `default` has been used to fill in a missing `field_type`.
55type ParsedField = (String, Type, ParsedAttrs);
56
57/// The `(field type, delta_leader)` pair a single `#[delta_struct(...)]`
58/// yields, or the reason it could not be read.
59type ParsedAttrs = Result<(Option<FieldType>, String), FieldTypeError>;
60
61/// Derives `Delta`, generating a `{Self}Delta` struct that holds only the
62/// changed parts of a value plus the trait implementation that produces and
63/// applies one.
64///
65/// The generated type takes the visibility and generic parameters of the type
66/// it is derived on, and mirrors its shape: a tuple struct's delta is a tuple
67/// struct with its fields in the same positions, and an enum's is an enum with
68/// one variant per *diffable* source variant. A struct's delta fields are all
69/// `pub`.
70///
71/// For an enum, `Output` is `EnumDelta<Self, {Self}Delta>` rather than the
72/// bare companion, because a value can change variant as well as change within
73/// one — and changing variant is a replacement rather than a difference.
74///
75/// See the [`delta-struct`](https://docs.rs/delta-struct) crate documentation
76/// for the full picture, including trait bounds, serde usage, and limitations;
77/// what follows is the attribute reference.
78///
79/// # Container attributes
80///
81/// | Attribute | Effect |
82/// | --- | --- |
83/// | `default = "<field type>"` | Field type for fields that don't specify one. Defaults to `"scalar"`. |
84/// | `delta_leader = "<tokens>"` | Tokens emitted directly above the generated struct — derives, doc comments, anything. |
85///
86/// # Field attributes
87///
88/// | Attribute | Effect |
89/// | --- | --- |
90/// | `field_type = "<field type>"` | How this field is diffed. Overrides the container's `default`. |
91/// | `delta_leader = "<tokens>"` | Tokens emitted directly above the generated field. |
92///
93/// # Field types
94///
95/// Each maps one source field onto exactly one delta field.
96///
97/// | Value | Delta representation | Requires |
98/// | --- | --- | --- |
99/// | `"scalar"` | `delta_struct::ScalarDelta<T>` | `T: PartialEq` |
100/// | `"unordered"` | `<T as Unordered>::Delta` — `BagDelta<Item>` for a set, `EntryDelta<Key, Value>` for a map | `T: Unordered` |
101/// | `"unordered-delta"` | `MapDelta<Key, Value, <Value as Delta>::Output>`, an `add`, a `remove`, and a `change` | `T: IntoIterator + Extend<Item> + TryIndexMut<Key, Output = Value> Item: MapEntry` (so `(K, V)`), `Value: Delta` |
102/// | `"ordered"` | `SeqDelta<Item>`, a Myers edit script | `T: IntoIterator + FromIterator<Item>`, `Item: Hash + Eq` |
103/// | `"delta"` | `Option<<T as Delta>::Output>` | `T: Delta` |
104///
105/// # Example
106///
107/// ```ignore
108/// use delta_struct::Delta;
109///
110/// #[derive(Delta)]
111/// #[delta_struct(delta_leader = "#[derive(Debug)]")]
112/// struct Device {
113///     #[delta_struct(field_type = "unordered")]
114///     services: std::collections::HashSet<String>,
115///     online: bool,
116/// }
117/// ```
118#[proc_macro_derive(Delta, attributes(delta_struct))]
119#[proc_macro_error]
120pub fn derive_delta(input: TokenStream) -> TokenStream {
121    let DeriveInput {
122        attrs,
123        vis,
124        ident,
125        mut generics,
126        data,
127    } = parse_macro_input!(input as DeriveInput);
128    let (default_field_type, delta_leader) =
129        match get_fieldtype_from_attrs(attrs.into_iter(), "default") {
130            Ok((v, delta_leader)) => (v.unwrap_or(FieldType::Scalar), delta_leader),
131            Err(e) => {
132                abort_call_site!(
133                    "delta_struct(default = ...) for {} is not an accepted value, expected {}. {}",
134                    ident,
135                    VALID_FIELD_TYPES,
136                    e,
137                );
138            }
139        };
140
141    let delta_leader = match proc_macro2::TokenStream::from_str(&delta_leader) {
142        Ok(v) => v,
143        Err(e) => {
144            abort_call_site!("error parsing delta leader as token stream {}", e);
145        }
146    };
147    let delta_ident = format_ident!("{}Delta", ident);
148    // The delta type repeats the source type's generics verbatim, bounds and
149    // all, since its fields can project through them — `<T as Delta>::Output`
150    // for a delta field, `<T as IntoIterator>::Item` for an unordered one. Grab
151    // the where clause before the `PartialEq` predicates below are pushed onto
152    // it; those are the impl's business, not the type's.
153    let og_where_clause = generics.where_clause.clone();
154    let ty_generics_only = generics.split_for_impl().1.to_token_stream();
155
156    let Generated {
157        delta_type,
158        output_ty,
159        delta_body,
160        apply_body,
161    } = match data {
162        Data::Struct(strukt) => struct_impl(
163            &ident,
164            &vis,
165            &delta_ident,
166            &delta_leader,
167            &generics,
168            &og_where_clause,
169            &ty_generics_only,
170            strukt.fields,
171            default_field_type,
172        ),
173        Data::Enum(enom) => enum_impl(
174            &ident,
175            &vis,
176            &delta_ident,
177            &delta_leader,
178            &generics,
179            &og_where_clause,
180            &ty_generics_only,
181            enom.variants.into_iter().collect(),
182            default_field_type,
183        ),
184        Data::Union(_) => {
185            abort_call_site!(
186                "delta_struct::Delta may only be derived for struct and enum types. {} is a union.",
187                ident
188            )
189        }
190    };
191    // Scalar and unordered fields compare values with `==`, so every type
192    // parameter picks up a `PartialEq` bound on the impl. This is broader than
193    // strictly necessary — a parameter used only by a `delta` field does not
194    // need it.
195    let partial_eq_types = generics
196        .type_params()
197        .map(|t| t.ident.clone())
198        .collect::<Vec<_>>();
199    let where_clause = generics.make_where_clause();
200    for ty in partial_eq_types {
201        let mut bounds = Punctuated::new();
202        let mut segments = Punctuated::new();
203        segments.push(Ident::new("std", Span::call_site()).into());
204        segments.push(Ident::new("cmp", Span::call_site()).into());
205        segments.push(Ident::new("PartialEq", Span::call_site()).into());
206        bounds.push(TypeParamBound::Trait(TraitBound {
207            paren_token: None,
208            modifiers: TraitBoundModifiers::default(),
209            lifetimes: None,
210            maybe: None,
211            path: Path {
212                leading_colon: Some(Token!(::)(Span::call_site())),
213                segments,
214            },
215        }));
216        where_clause
217            .predicates
218            .push(WherePredicate::Type(PredicateType {
219                attrs: Vec::new(),
220                lifetimes: None,
221                bounded_ty: Type::Verbatim(<Ident as Into<TokenTree>>::into(ty).into()),
222                colon_token: Token!(:)(Span::call_site()),
223                bounds,
224            }));
225    }
226    let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
227    let delta_impl = quote! {
228      impl #impl_generics Delta for #ident #ty_generics #where_clause  {
229          // `ty_generics` and not `generics`: the latter renders parameter
230          // bounds too, which are not allowed in a type position.
231          type Output = #output_ty;
232
233          fn delta(old: Self, new: Self) -> Option<Self::Output> {
234              #delta_body
235          }
236
237          // A one-variant enum has no mismatch to catch, and an enum of only
238          // unit variants has an uninhabited delta, which makes the tail of
239          // this unreachable. Both are fine; neither should warn the caller.
240          #[allow(unreachable_patterns, unreachable_code)]
241          fn apply_delta(
242              &mut self,
243              delta: Self::Output,
244          ) -> ::std::result::Result<(), ::delta_struct::Mismatch> {
245              #apply_body
246          }
247      }
248    };
249    let output = quote! {
250        #delta_type
251
252        #delta_impl
253    };
254    TokenStream::from(output)
255}
256
257/// The four pieces the struct and enum paths each produce: the delta type's
258/// declaration, the `Output` it becomes, and the two method bodies.
259struct Generated {
260    delta_type: TokenStream2,
261    output_ty: TokenStream2,
262    delta_body: TokenStream2,
263    apply_body: TokenStream2,
264}
265
266/// Reads one group of source fields into the shape the code generators want,
267/// resolving each against the container's default field type.
268///
269/// Returns `(named, fields)`, where `named` says whether the group is written
270/// with braces or with parentheses.
271fn read_fields(owner: &Ident, fields: Fields, default_field_type: FieldType) -> (bool, Vec<Field>) {
272    let (named, collected) = match fields {
273        Fields::Named(named) => (
274            true,
275            collect_results(
276                named.named.into_iter().map(|field| {
277                    (
278                        field.ident.unwrap().to_string(),
279                        field.ty,
280                        get_fieldtype_from_attrs(field.attrs.into_iter(), "field_type"),
281                    )
282                }),
283                default_field_type,
284            ),
285        ),
286        Fields::Unnamed(unnamed) => (
287            false,
288            collect_results(
289                unnamed.unnamed.into_iter().enumerate().map(|(i, field)| {
290                    (
291                        i.to_string(),
292                        field.ty,
293                        get_fieldtype_from_attrs(field.attrs.into_iter(), "field_type"),
294                    )
295                }),
296                default_field_type,
297            ),
298        ),
299        Fields::Unit => (false, Ok(vec![])),
300    };
301    match collected {
302        Ok(fields) => (named, fields),
303        Err(bad_fields) => {
304            let bad_fields = format!("{:?}", bad_fields);
305            abort_call_site!(
306                "delta_struct(field_type = ...) for fields in {}: {} are not valid values. Expected {}.",
307                owner,
308                bad_fields,
309                VALID_FIELD_TYPES
310            )
311        }
312    }
313}
314
315/// Generates the delta of a struct: a companion struct of the same shape, and
316/// two method bodies that walk its fields.
317#[allow(clippy::too_many_arguments)] // All of it is one type's description.
318fn struct_impl(
319    ident: &Ident,
320    vis: &syn::Visibility,
321    delta_ident: &Ident,
322    delta_leader: &TokenStream2,
323    generics: &syn::Generics,
324    og_where_clause: &Option<syn::WhereClause>,
325    ty_generics: &TokenStream2,
326    fields: Fields,
327    default_field_type: FieldType,
328) -> Generated {
329    let (named, fields) = read_fields(ident, fields, default_field_type);
330    let delta_fields = delta_fields(named, fields.iter().cloned());
331    let (compute_let, compute_fields) =
332        delta_compute_fields(named, Source::Whole, fields.iter().cloned());
333    let (apply_let, apply_actions) = delta_apply_fields(named, Source::Whole, fields.into_iter());
334
335    // A tuple struct's delta is a tuple struct too, which means the
336    // declaration, the initializer, and the destructuring pattern all have to
337    // switch from braces to parentheses together. Two things differ beyond the
338    // brackets: a tuple struct puts its `where` clause *after* the fields and
339    // ends in a semicolon, and its constructor lives in the value namespace,
340    // which `Self::Output` — an associated type — cannot reach, so the
341    // initializer and pattern name the struct itself and let inference supply
342    // its generics.
343    let (delta_type, compute_init, apply_pattern) = if named {
344        (
345            quote! {
346                #delta_leader
347                #vis struct #delta_ident #generics #og_where_clause {
348                    #delta_fields
349                }
350            },
351            quote!(Self::Output { #compute_fields }),
352            quote!(Self::Output { #apply_let }),
353        )
354    } else {
355        (
356            quote! {
357                #delta_leader
358                #vis struct #delta_ident #generics (#delta_fields) #og_where_clause;
359            },
360            quote!(#delta_ident(#compute_fields)),
361            quote!(#delta_ident(#apply_let)),
362        )
363    };
364
365    Generated {
366        delta_type,
367        output_ty: quote!(#delta_ident #ty_generics),
368        delta_body: quote! {
369            let mut delta_is_some = false;
370            #compute_let
371            if delta_is_some {
372                Some(#compute_init)
373            } else {
374                None
375            }
376        },
377        // A struct's delta always fits, so this is the arm of `apply_delta`
378        // that can only ever be `Ok` — the `?`s inside come from fields whose
379        // own types are enums.
380        apply_body: quote! {
381            let #apply_pattern = delta;
382            #apply_actions
383            Ok(())
384        },
385    }
386}
387
388/// Generates the delta of an enum.
389///
390/// The companion enum carries one variant per *diffable* source variant — a
391/// field-less variant can never differ from itself, so giving it an arm would
392/// only create one nothing could construct. Changing variant is not a
393/// difference at all but a replacement, and that case lives in
394/// [`EnumDelta::Became`](::delta_struct::EnumDelta), a type in the runtime
395/// crate rather than an arm here, so it cannot collide with a variant the user
396/// wrote.
397#[allow(clippy::too_many_arguments)] // All of it is one type's description.
398fn enum_impl(
399    ident: &Ident,
400    vis: &syn::Visibility,
401    delta_ident: &Ident,
402    delta_leader: &TokenStream2,
403    generics: &syn::Generics,
404    og_where_clause: &Option<syn::WhereClause>,
405    ty_generics: &TokenStream2,
406    variants: Vec<syn::Variant>,
407    default_field_type: FieldType,
408) -> Generated {
409    if variants.is_empty() {
410        abort_call_site!(
411            "delta_struct::Delta cannot be derived for {}, which has no variants: an \
412             uninhabited type has no two values to differ.",
413            ident
414        )
415    }
416
417    let read: Vec<(Ident, bool, Vec<Field>)> = variants
418        .into_iter()
419        .map(|variant| {
420            let (named, fields) = read_fields(ident, variant.fields, default_field_type);
421            (variant.ident, named, fields)
422        })
423        .collect();
424
425    let mut delta_variants = Vec::new();
426    let mut diff_arms = Vec::new();
427    let mut apply_arms = Vec::new();
428
429    for (variant, named, fields) in &read {
430        if fields.is_empty() {
431            // Nothing to diff, and nothing to apply: two of these are equal by
432            // being the same variant.
433            let pattern = variant_pattern(&quote!(Self), variant, *named, fields, Some("old"));
434            diff_arms.push(quote!((#pattern, Self::#variant) => None,));
435            continue;
436        }
437
438        // Enum variant fields carry the enum's visibility, so unlike a struct's
439        // they must not be written `pub`.
440        let declared = delta_fields_inner(*named, false, fields.iter().cloned());
441        delta_variants.push(if *named {
442            quote!(#variant { #declared })
443        } else {
444            quote!(#variant(#declared))
445        });
446
447        let (compute_let, compute_fields) =
448            delta_compute_fields(*named, Source::Bound, fields.iter().cloned());
449        let old = variant_pattern(&quote!(Self), variant, *named, fields, Some("old"));
450        let new = variant_pattern(&quote!(Self), variant, *named, fields, Some("new"));
451        let init = if *named {
452            quote!(#delta_ident::#variant { #compute_fields })
453        } else {
454            quote!(#delta_ident::#variant(#compute_fields))
455        };
456        diff_arms.push(quote! {
457            (#old, #new) => {
458                let mut delta_is_some = false;
459                #compute_let
460                if delta_is_some {
461                    Some(::delta_struct::EnumDelta::Delta(#init))
462                } else {
463                    None
464                }
465            }
466        });
467
468        let (_, apply_actions) = delta_apply_fields(*named, Source::Bound, fields.iter().cloned());
469        let target = variant_pattern(&quote!(Self), variant, *named, fields, Some("self"));
470        let carried = variant_pattern(&quote!(#delta_ident), variant, *named, fields, None);
471        apply_arms.push(quote! {
472            (#target, #carried) => { #apply_actions }
473        });
474    }
475
476    // Both halves of a mismatch report a name, and both are found by matching
477    // — `{ .. }` fits every variant shape, so one arm per variant does it.
478    let source_names = read
479        .iter()
480        .map(|(variant, ..)| quote!(Self::#variant { .. } => stringify!(#variant),));
481    let delta_names = read
482        .iter()
483        .filter(|(_, _, fields)| !fields.is_empty())
484        .map(|(variant, ..)| quote!(#delta_ident::#variant { .. } => stringify!(#variant),));
485
486    Generated {
487        delta_type: quote! {
488            #delta_leader
489            #vis enum #delta_ident #generics #og_where_clause {
490                #(#delta_variants,)*
491            }
492        },
493        output_ty: quote! {
494            ::delta_struct::EnumDelta<#ident #ty_generics, #delta_ident #ty_generics>
495        },
496        delta_body: quote! {
497            #[allow(unreachable_patterns)] // A one-variant enum never `Became`.
498            match (old, new) {
499                #(#diff_arms)*
500                // Different variants: there is no difference to describe, only
501                // a replacement.
502                (_, new) => Some(::delta_struct::EnumDelta::Became(new)),
503            }
504        },
505        apply_body: quote! {
506            let delta = match delta {
507                ::delta_struct::EnumDelta::Became(new) => {
508                    *self = new;
509                    return Ok(());
510                }
511                ::delta_struct::EnumDelta::Delta(delta) => delta,
512            };
513            match (&mut *self, delta) {
514                #(#apply_arms)*
515                (found, mismatched) => {
516                    return Err(::delta_struct::Mismatch {
517                        type_name: stringify!(#ident),
518                        expected: match mismatched { #(#delta_names)* },
519                        found: match found { #(#source_names)* },
520                    });
521                }
522            }
523            Ok(())
524        },
525    }
526}
527
528/// The pattern that takes one variant apart, binding each field to a local.
529///
530/// `prefix` distinguishes the several copies of a variant that appear in one
531/// match — `old_`, `new_`, `self_` — or is [`None`] for the delta being
532/// consumed, whose fields bind to the bare local names the generated field
533/// code already refers to.
534fn variant_pattern(
535    path: &TokenStream2,
536    variant: &Ident,
537    named: bool,
538    fields: &[Field],
539    prefix: Option<&str>,
540) -> TokenStream2 {
541    if fields.is_empty() {
542        return quote!(#path::#variant);
543    }
544    let bindings = fields
545        .iter()
546        .map(|(og_ident, ..)| {
547            let local = local_ident(named, og_ident);
548            match prefix {
549                Some(prefix) => format_ident!("{}_{}", prefix, local),
550                None => local,
551            }
552        })
553        .collect::<Vec<_>>();
554    if named {
555        let names = fields
556            .iter()
557            .map(|(og_ident, ..)| format_ident!("{}", og_ident));
558        quote!(#path::#variant { #(#names: #bindings),* })
559    } else {
560        quote!(#path::#variant( #(#bindings),* ))
561    }
562}
563
564/// Emits the field declarations of the generated delta struct.
565///
566/// Fields arrive as `(name, type, field type, delta_leader)`, where `name` is
567/// the source field's name or, for tuple structs, its index. `named` says
568/// which of the two it is, and so whether these declarations are about to be
569/// wrapped in braces or in parentheses: a tuple struct's delta is a tuple
570/// struct too, and its fields are positional rather than named.
571fn delta_fields(named: bool, iter: impl Iterator<Item = Field>) -> proc_macro2::TokenStream {
572    delta_fields_inner(named, true, iter)
573}
574
575/// The body of [`delta_fields`], with a say over `pub`.
576///
577/// A struct's delta fields are all `pub`; an enum variant's take the enum's
578/// visibility and may not be written `pub` at all.
579fn delta_fields_inner(
580    named: bool,
581    public: bool,
582    iter: impl Iterator<Item = Field>,
583) -> proc_macro2::TokenStream {
584    let vis = public.then(|| quote!(pub));
585    FromIterator::from_iter(iter.map(|(ident, ty, field_ty, field_leader)| {
586        let field_leader = proc_macro2::TokenStream::from_str(&field_leader).unwrap();
587        let declared_ty = match field_ty {
588            FieldType::Ordered => {
589                quote!(::delta_struct::SeqDelta<<#ty as ::std::iter::IntoIterator>::Item>)
590            }
591            FieldType::Unordered => {
592                // Unlike the other collection field types this does not name a
593                // delta type directly: a set's membership diff and a map's are
594                // different shapes, and `Unordered` is what picks between them.
595                quote!(<#ty as ::delta_struct::Unordered>::Delta)
596            }
597            FieldType::UnorderedDelta => {
598                // The field's own type names the collection, not its key and
599                // value; `MapEntry` is what projects those back out of the
600                // item type so the delta field can be spelled at all.
601                let entry = quote!(<#ty as ::std::iter::IntoIterator>::Item);
602                let key = quote!(<#entry as ::delta_struct::MapEntry>::Key);
603                let value = quote!(<#entry as ::delta_struct::MapEntry>::Value);
604                quote!(::delta_struct::MapDelta<#key, #value, <#value as Delta>::Output>)
605            }
606            FieldType::Scalar => quote!(::delta_struct::ScalarDelta<#ty>),
607            FieldType::Delta => quote!(::std::option::Option<<#ty as Delta>::Output>),
608        };
609        if named {
610            let ident = format_ident!("{}", ident);
611            quote! {
612                #field_leader
613                #vis #ident: #declared_ty,
614            }
615        } else {
616            quote! {
617                #field_leader
618                #vis #declared_ty,
619            }
620        }
621    }))
622}
623
624/// Emits the body of `Delta::delta`, as `(statements, struct initializer)`.
625///
626/// The statements bind one local per generated field and set `delta_is_some`
627/// whenever they find a real change; the initializer then moves those locals
628/// into the delta struct. Fields arrive in the same shape as in
629/// [`delta_fields`].
630fn delta_compute_fields(
631    named: bool,
632    source: Source,
633    iter: impl Iterator<Item = Field>,
634) -> (proc_macro2::TokenStream, proc_macro2::TokenStream) {
635    iter.map(|(og_ident, ty, field_ty, _field_leader)| {
636        let ident = local_ident(named, &og_ident);
637        let (old, new) = source.sides(&og_ident, &ident);
638        let statements = match field_ty {
639            FieldType::Ordered | FieldType::UnorderedDelta => {
640                let module = collection_module(field_ty);
641                quote! {
642                    let #ident = ::delta_struct::#module::diff(#old, #new);
643                    delta_is_some = delta_is_some || !#ident.is_empty();
644                }
645            }
646            // `Unordered::diff` reports "nothing changed" as `None` rather than
647            // as an empty delta, so this reads like the `Scalar` arm below
648            // rather than like the two collection modules above. The field
649            // still holds an empty delta, which is what `Default` supplies.
650            FieldType::Unordered => quote! {
651                let #ident = match <#ty as ::delta_struct::Unordered>::diff(#old, #new) {
652                    Some(v) => {
653                        delta_is_some = true;
654                        v
655                    }
656                    None => ::std::default::Default::default(),
657                };
658            },
659            FieldType::Scalar => quote! {
660                let #ident = if #old != #new {
661                    delta_is_some = true;
662                    ::delta_struct::ScalarDelta::Changed(#new)
663                } else {
664                    ::delta_struct::ScalarDelta::Unchanged
665                };
666            },
667            FieldType::Delta => quote! {
668                let #ident = Delta::delta(#old, #new);
669                delta_is_some = delta_is_some || #ident.is_some();
670            },
671        };
672        // The locals are listed in declaration order, so this reads as a field
673        // shorthand inside braces and as a positional argument inside parens —
674        // whichever bracket the caller wraps it in.
675        (statements, quote!(#ident,))
676    })
677    .unzip()
678}
679
680/// Emits the body of `Delta::apply_delta`, as `(destructuring pattern,
681/// statements)`.
682///
683/// The pattern takes the delta struct apart into locals and the statements
684/// write each change back into `self`. Fields arrive in the same shape as in
685/// [`delta_fields`].
686fn delta_apply_fields(
687    named: bool,
688    source: Source,
689    iter: impl Iterator<Item = Field>,
690) -> (proc_macro2::TokenStream, proc_macro2::TokenStream) {
691    iter.map(|(og_ident, ty, field_ty, _field_leader)| {
692        let ident = local_ident(named, &og_ident);
693        let target = source.target(&og_ident, &ident);
694        let statements = match field_ty {
695            // `map::apply` is the one collection helper that can fail, because
696            // it is the one that recurses into `apply_delta`.
697            FieldType::Ordered | FieldType::UnorderedDelta => {
698                let module = collection_module(field_ty);
699                let question = (field_ty == FieldType::UnorderedDelta).then(|| quote!(?));
700                quote! {
701                    ::delta_struct::#module::apply(&mut #target, #ident)#question;
702                }
703            }
704            FieldType::Unordered => quote! {
705                <#ty as ::delta_struct::Unordered>::apply(&mut #target, #ident);
706            },
707            FieldType::Scalar => quote! {
708                if let ::delta_struct::ScalarDelta::Changed(v) = #ident {
709                    #target = v;
710                }
711            },
712            FieldType::Delta => quote! {
713                if let Some(v) = #ident {
714                    #target.apply_delta(v)?;
715                }
716            },
717        };
718        // Binds one local per field, in declaration order — see the matching
719        // note in `delta_compute_fields` about braces versus parens.
720        (quote!(#ident,), statements)
721    })
722    .unzip()
723}
724
725/// How generated code reaches the two sides of a field.
726///
727/// A struct's impl holds whole values and reads through them. An enum's has
728/// taken its values apart in a match pattern, so the fields are already locals
729/// by the time the per-field code runs — and for `apply_delta` they are `&mut`
730/// locals, which is why [`Source::target`] dereferences them.
731#[derive(Copy, Clone, Debug, Eq, PartialEq)]
732enum Source {
733    /// Through the values themselves: `old.foo`, `new.foo`, `self.foo`.
734    Whole,
735    /// Through pattern bindings: `old_foo`, `new_foo`, `*self_foo`.
736    Bound,
737}
738
739impl Source {
740    /// The expressions naming a field's old and new values in `Delta::delta`.
741    fn sides(self, og_ident: &str, ident: &Ident) -> (TokenStream2, TokenStream2) {
742        match self {
743            Source::Whole => {
744                let og_ident = field_accessor(og_ident);
745                (quote!(old.#og_ident), quote!(new.#og_ident))
746            }
747            Source::Bound => {
748                let old = format_ident!("old_{}", ident);
749                let new = format_ident!("new_{}", ident);
750                (quote!(#old), quote!(#new))
751            }
752        }
753    }
754
755    /// The place expression a field is written back to in `apply_delta`.
756    fn target(self, og_ident: &str, ident: &Ident) -> TokenStream2 {
757        match self {
758            Source::Whole => {
759                let og_ident = field_accessor(og_ident);
760                quote!(self.#og_ident)
761            }
762            Source::Bound => {
763                let binding = format_ident!("self_{}", ident);
764                quote!((*#binding))
765            }
766        }
767    }
768}
769
770/// A source field's name as it is written after a `.` — its identifier, or the
771/// bare index of a tuple field.
772fn field_accessor(og_ident: &str) -> TokenStream2 {
773    FromStr::from_str(og_ident).unwrap()
774}
775
776/// The local a generated field binds to: its own name, or `field_0`,
777/// `field_1`, … where the source field is positional.
778fn local_ident(named: bool, og_ident: &str) -> Ident {
779    if named {
780        format_ident!("{}", og_ident)
781    } else {
782        format_ident!("field_{}", og_ident)
783    }
784}
785
786/// The runtime module backing a collection field type whose delta type is
787/// fixed by the field type alone.
788///
789/// These two differ in what their delta looks like but not in how the derive
790/// drives one: each module pairs a `diff` and an `apply` over a delta type
791/// that reports whether it is empty. `unordered` is not among them — its shape
792/// depends on the collection rather than the field type, so it goes through
793/// the `Unordered` trait instead. Panics for every field type the callers
794/// never pass.
795fn collection_module(field_ty: FieldType) -> Ident {
796    match field_ty {
797        FieldType::Ordered => format_ident!("seq"),
798        FieldType::UnorderedDelta => format_ident!("map"),
799        FieldType::Unordered | FieldType::Scalar | FieldType::Delta => {
800            unreachable!("{:?} does not have a fixed collection module", field_ty)
801        }
802    }
803}
804
805/// Resolves each field's parsed attributes against the container default,
806/// collecting *every* bad field rather than stopping at the first, so one
807/// compile reports them all.
808#[allow(clippy::manual_try_fold)] // Collects errors too
809fn collect_results(
810    iter: impl Iterator<Item = ParsedField>,
811    default_field_type: FieldType,
812) -> Result<Vec<Field>, Vec<String>> {
813    iter.fold(Ok(vec![]), |v, i| match (v, i) {
814        (Ok(mut v), (ident, b, Ok((c, d)))) => {
815            v.push((ident, b, c.unwrap_or(default_field_type), d));
816            Ok(v)
817        }
818        (Ok(_), (ident, _, Err(_))) => Err(vec![ident]),
819        (Err(mut v), (ident, _, Err(_))) => {
820            v.push(ident);
821            Err(v)
822        }
823        (v @ Err(_), _) => v,
824    })
825}
826
827enum FieldTypeError {
828    Syn(syn::Error),
829    /// The `delta_struct(...)` attribute contained entries that were not
830    /// `name = "value"` pairs.
831    UnrecognizedJunkFound(Vec<Meta>),
832}
833
834impl From<syn::Error> for FieldTypeError {
835    fn from(value: syn::Error) -> Self {
836        FieldTypeError::Syn(value)
837    }
838}
839
840impl Display for FieldTypeError {
841    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
842        match self {
843            FieldTypeError::Syn(error) => write!(f, "{error}"),
844            FieldTypeError::UnrecognizedJunkFound(metas) => {
845                let metas = metas
846                    .iter()
847                    .map(|m| m.into_token_stream().to_string())
848                    .collect::<Vec<_>>()
849                    .join(" ");
850                write!(
851                    f,
852                    "expected a comma separated list of named values, got {metas}"
853                )
854            }
855        }
856    }
857}
858
859/// Reads a `#[delta_struct(...)]` attribute, returning
860/// `(field type, delta_leader)`.
861///
862/// `attr_name` is the key naming the field type in this position — `"default"`
863/// on a container, `"field_type"` on a field — because the two spellings mean
864/// the same thing at different scopes. The field type is `None` when the
865/// attribute is absent or names no field type, leaving the caller to fill in
866/// the default; `delta_leader` is empty when unspecified.
867#[allow(clippy::manual_try_fold)] // Collects errors too
868fn get_fieldtype_from_attrs(iter: impl Iterator<Item = Attribute>, attr_name: &str) -> ParsedAttrs {
869    for attr in iter {
870        if let Meta::List(MetaList { path, .. }) = &attr.meta {
871            let Path { segments, .. } = path;
872            if segments
873                .iter()
874                .map(|p| &p.ident)
875                .eq(["delta_struct"].iter().cloned())
876            {
877                let nested =
878                    attr.parse_args_with(Punctuated::<Meta, Token!(,)>::parse_terminated)?;
879                let values: Result<Vec<_>, Vec<Meta>> = nested
880                    .iter()
881                    .map(|meta| match meta {
882                        Meta::NameValue(MetaNameValue {
883                            path,
884                            value:
885                                Expr::Lit(ExprLit {
886                                    lit: Lit::Str(s), ..
887                                }),
888                            ..
889                        }) => Ok((path.get_ident().map(|i| i.to_string()), s.value())),
890                        e => Err(e),
891                    })
892                    .fold(Ok(vec![]), |v, i| match (v, i) {
893                        (Ok(mut v), Ok(i)) => {
894                            v.push(i);
895                            Ok(v)
896                        }
897                        (Ok(_), Err(e)) => Err(vec![e.clone()]),
898                        (Err(mut v), Err(e)) => {
899                            v.push(e.clone());
900                            Err(v)
901                        }
902                        (v @ Err(_), _) => v,
903                    });
904                let v = values.map_err(FieldTypeError::UnrecognizedJunkFound)?;
905                let mut field_type = None;
906                let mut delta_leader = String::new();
907                for i in v {
908                    match i.0.as_deref() {
909                        Some("delta_leader") => {
910                            delta_leader = i.1;
911                        }
912                        a if Some(attr_name) == a => {
913                            field_type = string_to_fieldtype(&i.1);
914                        }
915                        a => {
916                            abort_call_site!("Unrecognized value {:?}", a);
917                        }
918                    }
919                }
920                return Ok((field_type, delta_leader));
921            }
922        }
923    }
924    Ok((None, String::new()))
925}
926
927/// Maps the attribute spelling of a field type to its variant, or `None` if it
928/// is not one of the recognized names.
929fn string_to_fieldtype(s: &str) -> Option<FieldType> {
930    match s {
931        "ordered" => Some(FieldType::Ordered),
932        "unordered" => Some(FieldType::Unordered),
933        "unordered-delta" => Some(FieldType::UnorderedDelta),
934        "scalar" => Some(FieldType::Scalar),
935        "delta" => Some(FieldType::Delta),
936        _ => None,
937    }
938}
939
940/// Derives `Fingerprint`, a stable content hash used to check that a delta is
941/// being applied to the state it was computed against.
942///
943/// Walks a struct's fields in declaration order, or an enum's variant index
944/// followed by that variant's fields. Every field type has to implement
945/// `Fingerprint` too, and every type parameter picks up a `Fingerprint` bound.
946///
947/// Unlike the `Delta` derive this needs nothing in scope — the generated code
948/// names `::delta_struct::Fingerprint` in full — and it accepts enums, which
949/// have a perfectly good content hash even though they have no obvious delta.
950///
951/// ```ignore
952/// use delta_struct::Fingerprint;
953///
954/// #[derive(Fingerprint)]
955/// struct Device {
956///     services: std::collections::HashSet<String>,
957///     online: bool,
958/// }
959/// ```
960#[proc_macro_derive(Fingerprint)]
961#[proc_macro_error]
962pub fn derive_fingerprint(input: TokenStream) -> TokenStream {
963    let DeriveInput {
964        ident,
965        mut generics,
966        data,
967        ..
968    } = parse_macro_input!(input as DeriveInput);
969
970    let body = match data {
971        Data::Struct(strukt) => {
972            // A struct's fields are reached through `self`, by name or by
973            // position.
974            fingerprint_calls(strukt.fields.iter().enumerate().map(|(i, field)| {
975                match &field.ident {
976                    Some(ident) => quote!(self.#ident),
977                    None => {
978                        let index = syn::Index::from(i);
979                        quote!(self.#index)
980                    }
981                }
982            }))
983        }
984        Data::Enum(enom) => {
985            // A variant's fields are reached through the locals its pattern
986            // binds. The variant's index is folded in first, so two variants
987            // holding equal payloads still fingerprint differently.
988            let arms = enom.variants.into_iter().enumerate().map(|(index, variant)| {
989                let variant_ident = variant.ident;
990                let bindings = binding_idents(&variant.fields);
991                let pattern = match &variant.fields {
992                    Fields::Named(_) => quote!(Self::#variant_ident { #(#bindings),* }),
993                    Fields::Unnamed(_) => quote!(Self::#variant_ident( #(#bindings),* )),
994                    Fields::Unit => quote!(Self::#variant_ident),
995                };
996                let fields = fingerprint_calls(bindings.iter().map(|b| quote!(#b)));
997                let index = index as u32;
998                quote! {
999                    #pattern => {
1000                        ::delta_struct::Fingerprint::fingerprint(&#index, hasher);
1001                        #fields
1002                    }
1003                }
1004            });
1005            quote! {
1006                match self {
1007                    #(#arms)*
1008                }
1009            }
1010        }
1011        _ => abort_call_site!(
1012            "delta_struct::Fingerprint may only be derived for struct and enum types. {} is neither.",
1013            ident
1014        ),
1015    };
1016
1017    let fingerprint_types = generics
1018        .type_params()
1019        .map(|t| t.ident.clone())
1020        .collect::<Vec<_>>();
1021    let where_clause = generics.make_where_clause();
1022    for ty in fingerprint_types {
1023        where_clause
1024            .predicates
1025            .push(syn::parse_quote!(#ty: ::delta_struct::Fingerprint));
1026    }
1027    let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
1028
1029    TokenStream::from(quote! {
1030        impl #impl_generics ::delta_struct::Fingerprint for #ident #ty_generics #where_clause {
1031            fn fingerprint(&self, hasher: &mut ::delta_struct::fingerprint::Hasher) {
1032                #body
1033            }
1034        }
1035    })
1036}
1037
1038/// The locals an enum variant's fields bind to in a match pattern: the field's
1039/// own name where it has one, and `field_0`, `field_1`, … where it does not.
1040fn binding_idents(fields: &Fields) -> Vec<Ident> {
1041    fields
1042        .iter()
1043        .enumerate()
1044        .map(|(i, field)| match &field.ident {
1045            Some(ident) => ident.clone(),
1046            None => format_ident!("field_{}", i),
1047        })
1048        .collect()
1049}
1050
1051/// Emits one `Fingerprint::fingerprint` call per expression, in order.
1052///
1053/// The expressions name the fields however the caller can reach them —
1054/// `self.foo` inside a struct, a pattern binding inside a match arm.
1055fn fingerprint_calls(
1056    exprs: impl Iterator<Item = proc_macro2::TokenStream>,
1057) -> proc_macro2::TokenStream {
1058    let calls = exprs.map(|expr| quote!(::delta_struct::Fingerprint::fingerprint(&#expr, hasher);));
1059    quote!(#(#calls)*)
1060}