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