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}