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, TokenTree};
12use proc_macro_error::{abort_call_site, proc_macro_error};
13use quote::{format_ident, quote};
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    Unordered,
29    /// A bag of key/value entries: like [`FieldType::Unordered`], except that
30    /// entries sharing a key are diffed with the value's own `Delta` rather
31    /// than recorded as a removal plus an addition.
32    UnorderedDelta,
33    /// Compared with `!=` and replaced wholesale.
34    Scalar,
35    /// Diffed recursively via the field type's own `Delta` implementation.
36    Delta,
37}
38
39const VALID_FIELD_TYPES: &str =
40    "\"ordered\", \"unordered\", \"unordered-delta\", \"delta\", or \"scalar\"";
41
42/// One field of the source struct, as the code generators want it: its name
43/// (or, for a tuple struct, its index), its declared type, how it is diffed,
44/// and the tokens to emit above the field it turns into.
45type Field = (String, Type, FieldType, String);
46
47/// One field as it comes back from attribute parsing, before the container's
48/// `default` has been used to fill in a missing `field_type`.
49type ParsedField = (String, Type, ParsedAttrs);
50
51/// The `(field type, delta_leader)` pair a single `#[delta_struct(...)]`
52/// yields, or the reason it could not be read.
53type ParsedAttrs = Result<(Option<FieldType>, String), FieldTypeError>;
54
55/// Derives `Delta`, generating a `{Self}Delta` struct that holds only the
56/// changed parts of a value plus the trait implementation that produces and
57/// applies one.
58///
59/// The generated struct takes the visibility and generic parameters of the
60/// type it is derived on, and all of its fields are `pub`. A tuple struct's
61/// delta is a tuple struct in turn, with its fields in the same positions.
62///
63/// See the [`delta-struct`](https://docs.rs/delta-struct) crate documentation
64/// for the full picture, including trait bounds, serde usage, and limitations;
65/// what follows is the attribute reference.
66///
67/// # Container attributes
68///
69/// | Attribute | Effect |
70/// | --- | --- |
71/// | `default = "<field type>"` | Field type for fields that don't specify one. Defaults to `"scalar"`. |
72/// | `delta_leader = "<tokens>"` | Tokens emitted directly above the generated struct — derives, doc comments, anything. |
73///
74/// # Field attributes
75///
76/// | Attribute | Effect |
77/// | --- | --- |
78/// | `field_type = "<field type>"` | How this field is diffed. Overrides the container's `default`. |
79/// | `delta_leader = "<tokens>"` | Tokens emitted directly above the generated field. |
80///
81/// # Field types
82///
83/// Each maps one source field onto exactly one delta field.
84///
85/// | Value | Delta representation | Requires |
86/// | --- | --- | --- |
87/// | `"scalar"` | `Option<T>` | `T: PartialEq` |
88/// | `"unordered"` | `BagDelta<Item>`, an `add` and a `remove` | `T: IntoIterator + Extend<Item> + TryIndex<Item, Output = Item>` |
89/// | `"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` |
90/// | `"ordered"` | `SeqDelta<Item>`, a Myers edit script | `T: IntoIterator + FromIterator<Item>`, `Item: Hash + Eq` |
91/// | `"delta"` | `Option<<T as Delta>::Output>` | `T: Delta` |
92///
93/// # Example
94///
95/// ```ignore
96/// use delta_struct::Delta;
97///
98/// #[derive(Delta)]
99/// #[delta_struct(delta_leader = "#[derive(Debug)]")]
100/// struct Device {
101///     #[delta_struct(field_type = "unordered")]
102///     services: std::collections::HashSet<String>,
103///     online: bool,
104/// }
105/// ```
106#[proc_macro_derive(Delta, attributes(delta_struct))]
107#[proc_macro_error]
108pub fn derive_delta(input: TokenStream) -> TokenStream {
109    let DeriveInput {
110        attrs,
111        vis,
112        ident,
113        mut generics,
114        data,
115    } = parse_macro_input!(input as DeriveInput);
116    let (default_field_type, delta_leader) =
117        match get_fieldtype_from_attrs(attrs.into_iter(), "default") {
118            Ok((v, delta_leader)) => (v.unwrap_or(FieldType::Scalar), delta_leader),
119            Err(_) => {
120                abort_call_site!(
121                    "delta_struct(default = ...) for {} is not an accepted value, expected {}.",
122                    ident,
123                    VALID_FIELD_TYPES
124                );
125            }
126        };
127
128    let (named, fields) = match data {
129        Data::Struct(strukt) => match strukt.fields {
130            Fields::Named(named) => (
131                true,
132                collect_results(
133                    named.named.into_iter().map(|field| {
134                        (
135                            field.ident.unwrap().to_string(),
136                            field.ty,
137                            get_fieldtype_from_attrs(field.attrs.into_iter(), "field_type"),
138                        )
139                    }),
140                    default_field_type,
141                ),
142            ),
143            Fields::Unnamed(unnamed) => (
144                false,
145                collect_results(
146                    unnamed.unnamed.into_iter().enumerate().map(|(i, field)| {
147                        (
148                            i.to_string(),
149                            field.ty,
150                            get_fieldtype_from_attrs(field.attrs.into_iter(), "field_type"),
151                        )
152                    }),
153                    default_field_type,
154                ),
155            ),
156            Fields::Unit => (false, Ok(vec![])),
157        },
158        _ => {
159            abort_call_site!(
160                "delta_struct::Delta may only be derived for struct types currently. {} is not a struct type."
161            , ident)
162        }
163    };
164    let fields = match fields {
165        Ok(fields) => fields,
166        Err(bad_fields) => {
167            let bad_fields = format!("{:?}", bad_fields);
168            abort_call_site!(
169                "delta_struct(field_type = ...) for fields in {}: {} are not valid values. Expected {}.",
170                ident,
171                bad_fields,
172                VALID_FIELD_TYPES
173            )
174        }
175    };
176    let delta_leader = match proc_macro2::TokenStream::from_str(&delta_leader) {
177        Ok(v) => v,
178        Err(e) => {
179            abort_call_site!("error parsing delta leader as token stream {}", e);
180        }
181    };
182    let delta_ident = format_ident!("{}Delta", ident);
183    let delta_fields = delta_fields(named, fields.iter().cloned());
184    // The delta struct repeats the source type's generics verbatim, bounds and
185    // all, since its fields can project through them — `<T as Delta>::Output`
186    // for a delta field, `<T as IntoIterator>::Item` for an unordered one. Grab
187    // the where clause before the `PartialEq` predicates below are pushed onto
188    // it; those are the impl's business, not the struct's.
189    let og_where_clause = generics.where_clause.clone();
190    let (delta_compute_let, delta_compute_fields) =
191        delta_compute_fields(named, fields.iter().cloned());
192    let (delta_apply_let, delta_apply_actions) = delta_apply_fields(named, fields.into_iter());
193    // A tuple struct's delta is a tuple struct too, which means the
194    // declaration, the initializer, and the destructuring pattern all have to
195    // switch from braces to parentheses together. Two things differ beyond the
196    // brackets: a tuple struct puts its `where` clause *after* the fields and
197    // ends in a semicolon, and its constructor lives in the value namespace,
198    // which `Self::Output` — an associated type — cannot reach, so the
199    // initializer and pattern name the struct itself and let inference supply
200    // its generics.
201    let (delta_struct, delta_compute_init, delta_apply_pattern) = if named {
202        (
203            quote! {
204                #delta_leader
205                #vis struct #delta_ident #generics #og_where_clause {
206                    #delta_fields
207                }
208            },
209            quote!(Self::Output { #delta_compute_fields }),
210            quote!(Self::Output { #delta_apply_let }),
211        )
212    } else {
213        (
214            quote! {
215                #delta_leader
216                #vis struct #delta_ident #generics (#delta_fields) #og_where_clause;
217            },
218            quote!(#delta_ident(#delta_compute_fields)),
219            quote!(#delta_ident(#delta_apply_let)),
220        )
221    };
222    // Scalar and unordered fields compare values with `==`, so every type
223    // parameter picks up a `PartialEq` bound on the impl. This is broader than
224    // strictly necessary — a parameter used only by a `delta` field does not
225    // need it.
226    let partial_eq_types = generics
227        .type_params()
228        .map(|t| t.ident.clone())
229        .collect::<Vec<_>>();
230    let where_clause = generics.make_where_clause();
231    for ty in partial_eq_types {
232        let mut bounds = Punctuated::new();
233        let mut segments = Punctuated::new();
234        segments.push(Ident::new("std", Span::call_site()).into());
235        segments.push(Ident::new("cmp", Span::call_site()).into());
236        segments.push(Ident::new("PartialEq", Span::call_site()).into());
237        bounds.push(TypeParamBound::Trait(TraitBound {
238            paren_token: None,
239            modifier: TraitBoundModifier::None,
240            lifetimes: None,
241            path: Path {
242                leading_colon: Some(Token!(::)(Span::call_site())),
243                segments,
244            },
245        }));
246        where_clause
247            .predicates
248            .push(WherePredicate::Type(PredicateType {
249                lifetimes: None,
250                bounded_ty: Type::Verbatim(<Ident as Into<TokenTree>>::into(ty).into()),
251                colon_token: Token!(:)(Span::call_site()),
252                bounds,
253            }));
254    }
255    let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
256    let delta_impl = quote! {
257      impl #impl_generics Delta for #ident #ty_generics #where_clause  {
258          // `ty_generics` and not `generics`: the latter renders parameter
259          // bounds too, which are not allowed in a type position.
260          type Output = #delta_ident #ty_generics;
261
262          fn delta(old: Self, new: Self) -> Option<Self::Output> {
263           let mut delta_is_some = false;
264           #delta_compute_let
265           if delta_is_some {
266               Some(#delta_compute_init)
267           } else {
268               None
269           }
270          }
271
272          fn apply_delta(&mut self, delta: Self::Output) {
273            let #delta_apply_pattern = delta;
274            #delta_apply_actions
275          }
276      }
277    };
278    let output = quote! {
279        #delta_struct
280
281        #delta_impl
282    };
283    TokenStream::from(output)
284}
285
286/// Emits the field declarations of the generated delta struct.
287///
288/// Fields arrive as `(name, type, field type, delta_leader)`, where `name` is
289/// the source field's name or, for tuple structs, its index. `named` says
290/// which of the two it is, and so whether these declarations are about to be
291/// wrapped in braces or in parentheses: a tuple struct's delta is a tuple
292/// struct too, and its fields are positional rather than named.
293fn delta_fields(named: bool, iter: impl Iterator<Item = Field>) -> proc_macro2::TokenStream {
294    FromIterator::from_iter(iter.map(|(ident, ty, field_ty, field_leader)| {
295        let field_leader = proc_macro2::TokenStream::from_str(&field_leader).unwrap();
296        let declared_ty = match field_ty {
297            FieldType::Ordered => {
298                quote!(::delta_struct::SeqDelta<<#ty as ::std::iter::IntoIterator>::Item>)
299            }
300            FieldType::Unordered => {
301                quote!(::delta_struct::BagDelta<<#ty as ::std::iter::IntoIterator>::Item>)
302            }
303            FieldType::UnorderedDelta => {
304                // The field's own type names the collection, not its key and
305                // value; `MapEntry` is what projects those back out of the
306                // item type so the delta field can be spelled at all.
307                let entry = quote!(<#ty as ::std::iter::IntoIterator>::Item);
308                let key = quote!(<#entry as ::delta_struct::MapEntry>::Key);
309                let value = quote!(<#entry as ::delta_struct::MapEntry>::Value);
310                quote!(::delta_struct::MapDelta<#key, #value, <#value as Delta>::Output>)
311            }
312            FieldType::Scalar => quote!(::std::option::Option<#ty>),
313            FieldType::Delta => quote!(::std::option::Option<<#ty as Delta>::Output>),
314        };
315        if named {
316            let ident = format_ident!("{}", ident);
317            quote! {
318                #field_leader
319                pub #ident: #declared_ty,
320            }
321        } else {
322            quote! {
323                #field_leader
324                pub #declared_ty,
325            }
326        }
327    }))
328}
329
330/// Emits the body of `Delta::delta`, as `(statements, struct initializer)`.
331///
332/// The statements bind one local per generated field and set `delta_is_some`
333/// whenever they find a real change; the initializer then moves those locals
334/// into the delta struct. Fields arrive in the same shape as in
335/// [`delta_fields`].
336fn delta_compute_fields(
337    named: bool,
338    iter: impl Iterator<Item = Field>,
339) -> (proc_macro2::TokenStream, proc_macro2::TokenStream) {
340    iter.map(|(og_ident, _ty, field_ty, _field_leader)| {
341        let ident = if named {
342            format_ident!("{}", og_ident)
343        } else {
344            format_ident!("field_{}", og_ident)
345        };
346        let og_ident: proc_macro2::TokenStream = FromStr::from_str(&og_ident).unwrap();
347        let statements = match field_ty {
348            FieldType::Ordered | FieldType::Unordered | FieldType::UnorderedDelta => {
349                let module = collection_module(field_ty);
350                quote! {
351                    let #ident = ::delta_struct::#module::diff(old.#og_ident, new.#og_ident);
352                    delta_is_some = delta_is_some || !#ident.is_empty();
353                }
354            }
355            FieldType::Scalar => quote! {
356                let #ident = if old.#og_ident != new.#og_ident {
357                    delta_is_some = true;
358                    Some(new.#og_ident)
359                } else {
360                    None
361                };
362            },
363            FieldType::Delta => quote! {
364                let #ident = Delta::delta(old.#og_ident, new.#og_ident);
365                delta_is_some = delta_is_some || #ident.is_some();
366            },
367        };
368        // The locals are listed in declaration order, so this reads as a field
369        // shorthand inside braces and as a positional argument inside parens —
370        // whichever bracket the caller wraps it in.
371        (statements, quote!(#ident,))
372    })
373    .unzip()
374}
375
376/// Emits the body of `Delta::apply_delta`, as `(destructuring pattern,
377/// statements)`.
378///
379/// The pattern takes the delta struct apart into locals and the statements
380/// write each change back into `self`. Fields arrive in the same shape as in
381/// [`delta_fields`].
382fn delta_apply_fields(
383    named: bool,
384    iter: impl Iterator<Item = Field>,
385) -> (proc_macro2::TokenStream, proc_macro2::TokenStream) {
386    iter.map(|(og_ident, _ty, field_ty, _field_leader)| {
387        let ident = if named {
388            format_ident!("{}", og_ident)
389        } else {
390            format_ident!("field_{}", og_ident)
391        };
392        let og_ident: proc_macro2::TokenStream = FromStr::from_str(&og_ident).unwrap();
393        let statements = match field_ty {
394            FieldType::Ordered | FieldType::Unordered | FieldType::UnorderedDelta => {
395                let module = collection_module(field_ty);
396                quote! {
397                    ::delta_struct::#module::apply(&mut self.#og_ident, #ident);
398                }
399            }
400            FieldType::Scalar => quote! {
401                if let Some(v) = #ident {
402                    self.#og_ident = v;
403                }
404            },
405            FieldType::Delta => quote! {
406                if let Some(v) = #ident {
407                    self.#og_ident.apply_delta(v);
408                }
409            },
410        };
411        // Binds one local per field, in declaration order — see the matching
412        // note in `delta_compute_fields` about braces versus parens.
413        (quote!(#ident,), statements)
414    })
415    .unzip()
416}
417
418/// The runtime module backing a collection field type.
419///
420/// The three collection field types differ in what their delta looks like, but
421/// not in how the derive drives one: each module pairs a `diff` and an `apply`
422/// over a delta type that reports whether it is empty. Panics for the two
423/// non-collection field types, which the callers never pass.
424fn collection_module(field_ty: FieldType) -> Ident {
425    match field_ty {
426        FieldType::Ordered => format_ident!("seq"),
427        FieldType::Unordered => format_ident!("bag"),
428        FieldType::UnorderedDelta => format_ident!("map"),
429        FieldType::Scalar | FieldType::Delta => {
430            unreachable!("{:?} is not a collection field type", field_ty)
431        }
432    }
433}
434
435/// Resolves each field's parsed attributes against the container default,
436/// collecting *every* bad field rather than stopping at the first, so one
437/// compile reports them all.
438#[allow(clippy::manual_try_fold)] // Collects errors too
439fn collect_results(
440    iter: impl Iterator<Item = ParsedField>,
441    default_field_type: FieldType,
442) -> Result<Vec<Field>, Vec<String>> {
443    iter.fold(Ok(vec![]), |v, i| match (v, i) {
444        (Ok(mut v), (ident, b, Ok((c, d)))) => {
445            v.push((ident, b, c.unwrap_or(default_field_type), d));
446            Ok(v)
447        }
448        (Ok(_), (ident, _, Err(_))) => Err(vec![ident]),
449        (Err(mut v), (ident, _, Err(_))) => {
450            v.push(ident);
451            Err(v)
452        }
453        (v @ Err(_), _) => v,
454    })
455}
456
457enum FieldTypeError {
458    /// The `delta_struct(...)` attribute contained entries that were not
459    /// `name = "value"` pairs.
460    UnrecognizedJunkFound,
461}
462
463/// Reads a `#[delta_struct(...)]` attribute, returning
464/// `(field type, delta_leader)`.
465///
466/// `attr_name` is the key naming the field type in this position — `"default"`
467/// on a container, `"field_type"` on a field — because the two spellings mean
468/// the same thing at different scopes. The field type is `None` when the
469/// attribute is absent or names no field type, leaving the caller to fill in
470/// the default; `delta_leader` is empty when unspecified.
471#[allow(clippy::manual_try_fold)] // Collects errors too
472fn get_fieldtype_from_attrs(iter: impl Iterator<Item = Attribute>, attr_name: &str) -> ParsedAttrs {
473    for attr in iter {
474        if let Ok(Meta::List(MetaList { path, nested, .. })) = attr.parse_meta() {
475            let Path { segments, .. } = path;
476            if segments
477                .iter()
478                .map(|p| &p.ident)
479                .eq(["delta_struct"].iter().cloned())
480            {
481                let values: Result<Vec<_>, Vec<NestedMeta>> = nested
482                    .iter()
483                    .map(|nested_meta| match nested_meta {
484                        NestedMeta::Meta(Meta::NameValue(MetaNameValue {
485                            path,
486                            lit: Lit::Str(s),
487                            ..
488                        })) => Ok((path.get_ident().map(|i| i.to_string()), s.value())),
489                        e => Err(e),
490                    })
491                    .fold(Ok(vec![]), |v, i| match (v, i) {
492                        (Ok(mut v), Ok(i)) => {
493                            v.push(i);
494                            Ok(v)
495                        }
496                        (Ok(_), Err(e)) => Err(vec![e.clone()]),
497                        (Err(mut v), Err(e)) => {
498                            v.push(e.clone());
499                            Err(v)
500                        }
501                        (v @ Err(_), _) => v,
502                    });
503                return match values {
504                    Ok(v) => {
505                        let mut field_type = None;
506                        let mut delta_leader = String::new();
507                        for i in v {
508                            match i.0.as_deref() {
509                                Some("delta_leader") => {
510                                    delta_leader = i.1;
511                                }
512                                a if Some(attr_name) == a => {
513                                    field_type = string_to_fieldtype(&i.1);
514                                }
515                                a => {
516                                    abort_call_site!("Unrecognized value {:?}", a);
517                                }
518                            }
519                        }
520                        Ok((field_type, delta_leader))
521                    }
522                    Err(_) => Err(FieldTypeError::UnrecognizedJunkFound),
523                };
524            }
525        }
526    }
527    Ok((None, String::new()))
528}
529
530/// Maps the attribute spelling of a field type to its variant, or `None` if it
531/// is not one of the recognized names.
532fn string_to_fieldtype(s: &str) -> Option<FieldType> {
533    match s {
534        "ordered" => Some(FieldType::Ordered),
535        "unordered" => Some(FieldType::Unordered),
536        "unordered-delta" => Some(FieldType::UnorderedDelta),
537        "scalar" => Some(FieldType::Scalar),
538        "delta" => Some(FieldType::Delta),
539        _ => None,
540    }
541}
542
543/// Derives `Fingerprint`, a stable content hash used to check that a delta is
544/// being applied to the state it was computed against.
545///
546/// Walks a struct's fields in declaration order, or an enum's variant index
547/// followed by that variant's fields. Every field type has to implement
548/// `Fingerprint` too, and every type parameter picks up a `Fingerprint` bound.
549///
550/// Unlike the `Delta` derive this needs nothing in scope — the generated code
551/// names `::delta_struct::Fingerprint` in full — and it accepts enums, which
552/// have a perfectly good content hash even though they have no obvious delta.
553///
554/// ```ignore
555/// use delta_struct::Fingerprint;
556///
557/// #[derive(Fingerprint)]
558/// struct Device {
559///     services: std::collections::HashSet<String>,
560///     online: bool,
561/// }
562/// ```
563#[proc_macro_derive(Fingerprint)]
564#[proc_macro_error]
565pub fn derive_fingerprint(input: TokenStream) -> TokenStream {
566    let DeriveInput {
567        ident,
568        mut generics,
569        data,
570        ..
571    } = parse_macro_input!(input as DeriveInput);
572
573    let body = match data {
574        Data::Struct(strukt) => {
575            // A struct's fields are reached through `self`, by name or by
576            // position.
577            fingerprint_calls(strukt.fields.iter().enumerate().map(|(i, field)| {
578                match &field.ident {
579                    Some(ident) => quote!(self.#ident),
580                    None => {
581                        let index = syn::Index::from(i);
582                        quote!(self.#index)
583                    }
584                }
585            }))
586        }
587        Data::Enum(enom) => {
588            // A variant's fields are reached through the locals its pattern
589            // binds. The variant's index is folded in first, so two variants
590            // holding equal payloads still fingerprint differently.
591            let arms = enom.variants.into_iter().enumerate().map(|(index, variant)| {
592                let variant_ident = variant.ident;
593                let bindings = binding_idents(&variant.fields);
594                let pattern = match &variant.fields {
595                    Fields::Named(_) => quote!(Self::#variant_ident { #(#bindings),* }),
596                    Fields::Unnamed(_) => quote!(Self::#variant_ident( #(#bindings),* )),
597                    Fields::Unit => quote!(Self::#variant_ident),
598                };
599                let fields = fingerprint_calls(bindings.iter().map(|b| quote!(#b)));
600                let index = index as u32;
601                quote! {
602                    #pattern => {
603                        ::delta_struct::Fingerprint::fingerprint(&#index, hasher);
604                        #fields
605                    }
606                }
607            });
608            quote! {
609                match self {
610                    #(#arms)*
611                }
612            }
613        }
614        _ => abort_call_site!(
615            "delta_struct::Fingerprint may only be derived for struct and enum types. {} is neither.",
616            ident
617        ),
618    };
619
620    let fingerprint_types = generics
621        .type_params()
622        .map(|t| t.ident.clone())
623        .collect::<Vec<_>>();
624    let where_clause = generics.make_where_clause();
625    for ty in fingerprint_types {
626        where_clause
627            .predicates
628            .push(syn::parse_quote!(#ty: ::delta_struct::Fingerprint));
629    }
630    let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
631
632    TokenStream::from(quote! {
633        impl #impl_generics ::delta_struct::Fingerprint for #ident #ty_generics #where_clause {
634            fn fingerprint(&self, hasher: &mut ::delta_struct::fingerprint::Hasher) {
635                #body
636            }
637        }
638    })
639}
640
641/// The locals an enum variant's fields bind to in a match pattern: the field's
642/// own name where it has one, and `field_0`, `field_1`, … where it does not.
643fn binding_idents(fields: &Fields) -> Vec<Ident> {
644    fields
645        .iter()
646        .enumerate()
647        .map(|(i, field)| match &field.ident {
648            Some(ident) => ident.clone(),
649            None => format_ident!("field_{}", i),
650        })
651        .collect()
652}
653
654/// Emits one `Fingerprint::fingerprint` call per expression, in order.
655///
656/// The expressions name the fields however the caller can reach them —
657/// `self.foo` inside a struct, a pattern binding inside a match arm.
658fn fingerprint_calls(
659    exprs: impl Iterator<Item = proc_macro2::TokenStream>,
660) -> proc_macro2::TokenStream {
661    let calls = exprs.map(|expr| quote!(::delta_struct::Fingerprint::fingerprint(&#expr, hasher);));
662    quote!(#(#calls)*)
663}