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    /// Compared with `!=` and replaced wholesale.
30    Scalar,
31    /// Diffed recursively via the field type's own `Delta` implementation.
32    Delta,
33}
34
35const VALID_FIELD_TYPES: &str = "\"ordered\", \"unordered\", \"delta\", or \"scalar\"";
36
37/// Derives `Delta`, generating a `{Self}Delta` struct that holds only the
38/// changed parts of a value plus the trait implementation that produces and
39/// applies one.
40///
41/// The generated struct takes the visibility and generic parameters of the
42/// type it is derived on, and all of its fields are `pub`. See the
43/// [`delta-struct`](https://docs.rs/delta-struct) crate documentation for the
44/// full picture, including trait bounds, serde usage, and limitations; what
45/// follows is the attribute reference.
46///
47/// # Container attributes
48///
49/// | Attribute | Effect |
50/// | --- | --- |
51/// | `default = "<field type>"` | Field type for fields that don't specify one. Defaults to `"scalar"`. |
52/// | `delta_leader = "<tokens>"` | Tokens emitted directly above the generated struct — derives, doc comments, anything. |
53///
54/// # Field attributes
55///
56/// | Attribute | Effect |
57/// | --- | --- |
58/// | `field_type = "<field type>"` | How this field is diffed. Overrides the container's `default`. |
59/// | `delta_leader = "<tokens>"` | Tokens emitted directly above the generated field. On an `unordered` field they land above both the `_add` and the `_remove` field. |
60///
61/// # Field types
62///
63/// | Value | Delta representation | Requires |
64/// | --- | --- | --- |
65/// | `"scalar"` | `Option<T>` | `T: PartialEq` |
66/// | `"unordered"` | `{field}_add` and `{field}_remove`, both `Vec<Item>` | `T: IntoIterator + FromIterator<Item> + Extend<Item>`, `Item: PartialEq` |
67/// | `"ordered"` | `SeqDelta<Item>`, a Myers edit script | `T: IntoIterator + FromIterator<Item>`, `Item: Hash + Eq` |
68/// | `"delta"` | `Option<<T as Delta>::Output>` | `T: Delta` |
69///
70/// # Example
71///
72/// ```ignore
73/// use delta_struct::Delta;
74///
75/// #[derive(Delta)]
76/// #[delta_struct(delta_leader = "#[derive(Debug)]")]
77/// struct Device {
78///     #[delta_struct(field_type = "unordered")]
79///     services: Vec<String>,
80///     online: bool,
81/// }
82/// ```
83///
84/// The example is not run as a doctest because this crate cannot depend on the
85/// crate that re-exports it; the tested versions live in the `delta-struct`
86/// crate documentation.
87#[proc_macro_derive(Delta, attributes(delta_struct))]
88#[proc_macro_error]
89pub fn derive_delta(input: TokenStream) -> TokenStream {
90    let DeriveInput {
91        attrs,
92        vis,
93        ident,
94        mut generics,
95        data,
96    } = parse_macro_input!(input as DeriveInput);
97    let (default_field_type, delta_leader) =
98        match get_fieldtype_from_attrs(attrs.into_iter(), "default") {
99            Ok((v, delta_leader)) => (v.unwrap_or(FieldType::Scalar), delta_leader),
100            Err(_) => {
101                abort_call_site!(
102                    "delta_struct(default = ...) for {} is not an accepted value, expected {}.",
103                    ident,
104                    VALID_FIELD_TYPES
105                );
106            }
107        };
108
109    let (named, fields) = match data {
110        Data::Struct(strukt) => match strukt.fields {
111            Fields::Named(named) => (
112                true,
113                collect_results(
114                    named.named.into_iter().map(|field| {
115                        (
116                            field.ident.unwrap().to_string(),
117                            field.ty,
118                            get_fieldtype_from_attrs(field.attrs.into_iter(), "field_type"),
119                        )
120                    }),
121                    default_field_type,
122                ),
123            ),
124            Fields::Unnamed(unnamed) => (
125                false,
126                collect_results(
127                    unnamed.unnamed.into_iter().enumerate().map(|(i, field)| {
128                        (
129                            i.to_string(),
130                            field.ty,
131                            get_fieldtype_from_attrs(field.attrs.into_iter(), "field_type"),
132                        )
133                    }),
134                    default_field_type,
135                ),
136            ),
137            Fields::Unit => (false, Ok(vec![])),
138        },
139        _ => {
140            abort_call_site!(
141                "delta_struct::Delta may only be derived for struct types currently. {} is not a struct type."
142            , ident)
143        }
144    };
145    let fields = match fields {
146        Ok(fields) => fields,
147        Err(bad_fields) => {
148            let bad_fields = format!("{:?}", bad_fields);
149            abort_call_site!(
150                "delta_struct(field_type = ...) for fields in {}: {} are not valid values. Expected {}.",
151                ident,
152                bad_fields,
153                VALID_FIELD_TYPES
154            )
155        }
156    };
157    let delta_leader = match proc_macro2::TokenStream::from_str(&delta_leader) {
158        Ok(v) => v,
159        Err(e) => {
160            abort_call_site!("error parsing delta leader as token stream {}", e);
161        }
162    };
163    let delta_ident = format_ident!("{}Delta", ident);
164    let delta_fields = delta_fields(named, fields.iter().cloned());
165    // The delta struct repeats the source type's generics verbatim, bounds and
166    // all, since its fields can project through them — `<T as Delta>::Output`
167    // for a delta field, `<T as IntoIterator>::Item` for an unordered one. Grab
168    // the where clause before the `PartialEq` predicates below are pushed onto
169    // it; those are the impl's business, not the struct's.
170    let og_where_clause = generics.where_clause.clone();
171    let delta_struct = quote! {
172      #delta_leader
173      #vis struct #delta_ident #generics #og_where_clause {
174          #delta_fields
175      }
176    };
177    let (delta_compute_let, delta_compute_fields) =
178        delta_compute_fields(named, fields.iter().cloned());
179    let (delta_apply_let, delta_apply_actions) = delta_apply_fields(named, fields.into_iter());
180    // Scalar and unordered fields compare values with `==`, so every type
181    // parameter picks up a `PartialEq` bound on the impl. This is broader than
182    // strictly necessary — a parameter used only by a `delta` field does not
183    // need it.
184    let partial_eq_types = generics
185        .type_params()
186        .map(|t| t.ident.clone())
187        .collect::<Vec<_>>();
188    let where_clause = generics.make_where_clause();
189    for ty in partial_eq_types {
190        let mut bounds = Punctuated::new();
191        let mut segments = Punctuated::new();
192        segments.push(Ident::new("std", Span::call_site()).into());
193        segments.push(Ident::new("cmp", Span::call_site()).into());
194        segments.push(Ident::new("PartialEq", Span::call_site()).into());
195        bounds.push(TypeParamBound::Trait(TraitBound {
196            paren_token: None,
197            modifier: TraitBoundModifier::None,
198            lifetimes: None,
199            path: Path {
200                leading_colon: Some(Token!(::)(Span::call_site())),
201                segments,
202            },
203        }));
204        where_clause
205            .predicates
206            .push(WherePredicate::Type(PredicateType {
207                lifetimes: None,
208                bounded_ty: Type::Verbatim(<Ident as Into<TokenTree>>::into(ty).into()),
209                colon_token: Token!(:)(Span::call_site()),
210                bounds,
211            }));
212    }
213    let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
214    let delta_impl = quote! {
215      impl #impl_generics Delta for #ident #ty_generics #where_clause  {
216          // `ty_generics` and not `generics`: the latter renders parameter
217          // bounds too, which are not allowed in a type position.
218          type Output = #delta_ident #ty_generics;
219
220          fn delta(old: Self, new: Self) -> Option<Self::Output> {
221           let mut delta_is_some = false;
222           #delta_compute_let
223           if delta_is_some {
224               Some(Self::Output {
225                #delta_compute_fields
226               })
227           } else {
228               None
229           }
230          }
231
232          fn apply_delta(&mut self, delta: Self::Output) {
233            let Self::Output {
234                #delta_apply_let
235            } = delta;
236            #delta_apply_actions
237          }
238      }
239    };
240    let output = quote! {
241        #delta_struct
242
243        #delta_impl
244    };
245    TokenStream::from(output)
246}
247
248/// Emits the field declarations of the generated delta struct.
249///
250/// Fields arrive as `(name, type, field type, delta_leader)`, where `name` is
251/// the source field's name or, for tuple structs, its index. `named` says
252/// which of the two it is: tuple struct fields become `field_0`, `field_1`,
253/// and so on, because a tuple struct has nowhere to put the `_add`/`_remove`
254/// pair an unordered field expands into.
255fn delta_fields(
256    named: bool,
257    iter: impl Iterator<Item = (String, Type, FieldType, String)>,
258) -> proc_macro2::TokenStream {
259    FromIterator::from_iter(iter.map(|(ident, ty, field_ty, field_leader)| {
260        let field_leader = proc_macro2::TokenStream::from_str(&field_leader).unwrap();
261        let ident = if named {
262            format_ident!("{}", ident)
263        } else {
264            format_ident!("field_{}", ident)
265        };
266        match field_ty {
267            FieldType::Ordered => {
268                quote! {
269                    #field_leader
270                    pub #ident: ::delta_struct::SeqDelta<<#ty as ::std::iter::IntoIterator>::Item>,
271                }
272            }
273            FieldType::Unordered => {
274                let add = format_ident!("{}_add", ident);
275                let remove = format_ident!("{}_remove", ident);
276                quote! {
277                 #field_leader
278                 pub #add: Vec<<#ty as ::std::iter::IntoIterator>::Item>,
279                 #field_leader
280                 pub #remove: Vec<<#ty as ::std::iter::IntoIterator>::Item>,
281                }
282            }
283            FieldType::Scalar => {
284                quote! {
285                  #field_leader
286                  pub #ident: ::std::option::Option<#ty>,
287                }
288            }
289            FieldType::Delta => {
290                quote! {
291                    #field_leader
292                    pub #ident: ::std::option::Option<<#ty as Delta>::Output>,
293                }
294            }
295        }
296    }))
297}
298
299/// Emits the body of `Delta::delta`, as `(statements, struct initializer)`.
300///
301/// The statements bind one local per generated field and set `delta_is_some`
302/// whenever they find a real change; the initializer then moves those locals
303/// into the delta struct. Fields arrive in the same shape as in
304/// [`delta_fields`].
305fn delta_compute_fields(
306    named: bool,
307    iter: impl Iterator<Item = (String, Type, FieldType, String)>,
308) -> (proc_macro2::TokenStream, proc_macro2::TokenStream) {
309    iter.map(|(og_ident, _ty, field_ty, _field_leader)| {
310        let ident = if named {
311            format_ident!("{}", og_ident)
312        } else {
313            format_ident!("field_{}", og_ident)
314        };
315        let og_ident: proc_macro2::TokenStream = FromStr::from_str(&og_ident).unwrap();
316        match field_ty {
317            FieldType::Ordered => (
318                quote! {
319                    let #ident = ::delta_struct::seq::diff(old.#og_ident, new.#og_ident);
320                    delta_is_some = delta_is_some || !#ident.is_empty();
321                },
322                quote! {
323                    #ident,
324                },
325            ),
326            FieldType::Unordered => {
327                let add = format_ident!("{}_add", ident);
328                let remove = format_ident!("{}_remove", ident);
329
330                (
331                    quote! {
332                        // Start from every item in `new` and cancel out the
333                        // ones `old` also had, one occurrence at a time, so
334                        // duplicates are counted rather than deduplicated.
335                        // Whatever is left in `old` is what was removed.
336                        let mut #add = new.#og_ident.into_iter().collect::<::std::vec::Vec<_>>();
337                        let #remove = old.#og_ident.into_iter().filter_map(|i| {
338                            if let Some(index) = #add.iter().position(|a| a == &i) {
339                                #add.remove(index);
340                                None
341                            } else {
342                                Some(i)
343                            }
344                        }).collect::<::std::vec::Vec<_>>();
345                        delta_is_some = delta_is_some || !#add.is_empty() || !#remove.is_empty();
346                    },
347                    quote! {
348                        #add,
349                        #remove,
350                    },
351                )
352            }
353            FieldType::Scalar => (
354                quote! {
355                   let #ident = if old.#og_ident != new.#og_ident {
356                       delta_is_some = true;
357                       Some(new.#og_ident)
358                   } else {
359                       None
360                   };
361                },
362                quote! {
363                    #ident,
364                },
365            ),
366            FieldType::Delta => (
367                quote! {
368                    let #ident = Delta::delta(old.#og_ident, new.#og_ident);
369                    delta_is_some = delta_is_some || #ident.is_some();
370
371                },
372                quote! {
373                    #ident,
374                },
375            ),
376        }
377    })
378    .unzip()
379}
380
381/// Emits the body of `Delta::apply_delta`, as `(destructuring pattern,
382/// statements)`.
383///
384/// The pattern takes the delta struct apart into locals — binding the
385/// `_remove` lists as `mut`, since applying them consumes their entries — and
386/// the statements write each change back into `self`. Fields arrive in the
387/// same shape as in [`delta_fields`].
388fn delta_apply_fields(
389    named: bool,
390    iter: impl Iterator<Item = (String, Type, FieldType, String)>,
391) -> (proc_macro2::TokenStream, proc_macro2::TokenStream) {
392    iter.map(|(og_ident, ty, field_ty, _field_leader)| {
393        let ident = if named {
394            format_ident!("{}", og_ident)
395        } else {
396            format_ident!("field_{}", og_ident)
397        };
398        let og_ident: proc_macro2::TokenStream = FromStr::from_str(&og_ident).unwrap();
399        match field_ty {
400            FieldType::Ordered => (
401                quote! {
402                    #ident,
403                },
404                quote! {
405                    ::delta_struct::seq::apply(&mut self.#og_ident, #ident);
406                },
407            ),
408            FieldType::Unordered => {
409                let add = format_ident!("{}_add", ident);
410                let remove = format_ident!("{}_remove", ident);
411                (
412                    quote! {
413                        #add,
414                        mut #remove,
415                    },
416                    quote! {
417                        {
418                            // Take the collection by value so its items can be
419                            // moved through the filter, then rebuild it minus
420                            // the removals and plus the additions. Note that
421                            // this does not preserve position for ordered
422                            // collections — additions land at the end.
423                            let og = ::std::mem::replace(&mut self.#og_ident, ::std::iter::FromIterator::from_iter(vec![]));
424                            let mut #ident: #ty = ::std::iter::FromIterator::from_iter(og.into_iter().filter_map(|i| {
425                               if let Some(index) = #remove.iter().position(|a| a == &i) {
426                                 #remove.remove(index);
427                                 None
428                               } else {
429                                 Some(i)
430                               }
431                            }));
432                            #ident.extend(#add.into_iter());
433                            self.#og_ident = #ident;
434                        }
435                    }
436                )
437            }
438            FieldType::Scalar =>
439            (
440                quote! {
441                    #ident,
442                },
443                quote! {
444                   if let Some(v) = #ident {
445                       self.#og_ident = v;
446                   }
447                }
448            ),
449            FieldType::Delta =>
450            (
451                quote! {
452                    #ident,
453                },
454                quote!{
455                   if let Some(v) = #ident {
456                       self.#og_ident.apply_delta(v);
457                   }
458                }
459            ),
460        }
461    }).unzip()
462}
463
464/// Resolves each field's parsed attributes against the container default,
465/// collecting *every* bad field rather than stopping at the first, so one
466/// compile reports them all.
467fn collect_results(
468    iter: impl Iterator<
469        Item = (
470            String,
471            Type,
472            Result<(Option<FieldType>, String), FieldTypeError>,
473        ),
474    >,
475    default_field_type: FieldType,
476) -> Result<Vec<(String, Type, FieldType, String)>, Vec<String>> {
477    iter.fold(Ok(vec![]), |v, i| match (v, i) {
478        (Ok(mut v), (ident, b, Ok((c, d)))) => {
479            v.push((ident, b, c.unwrap_or(default_field_type), d));
480            Ok(v)
481        }
482        (Ok(_), (ident, _, Err(_))) => Err(vec![ident]),
483        (Err(mut v), (ident, _, Err(_))) => {
484            v.push(ident);
485            Err(v)
486        }
487        (v @ Err(_), _) => v,
488    })
489}
490
491enum FieldTypeError {
492    /// The `delta_struct(...)` attribute contained entries that were not
493    /// `name = "value"` pairs.
494    UnrecognizedJunkFound,
495}
496
497/// Reads a `#[delta_struct(...)]` attribute, returning
498/// `(field type, delta_leader)`.
499///
500/// `attr_name` is the key naming the field type in this position — `"default"`
501/// on a container, `"field_type"` on a field — because the two spellings mean
502/// the same thing at different scopes. The field type is `None` when the
503/// attribute is absent or names no field type, leaving the caller to fill in
504/// the default; `delta_leader` is empty when unspecified.
505fn get_fieldtype_from_attrs(
506    iter: impl Iterator<Item = Attribute>,
507    attr_name: &str,
508) -> Result<(Option<FieldType>, String), FieldTypeError> {
509    for attr in iter {
510        if let Ok(Meta::List(MetaList { path, nested, .. })) = attr.parse_meta() {
511            let Path { segments, .. } = path;
512            if segments
513                .iter()
514                .map(|p| &p.ident)
515                .eq(["delta_struct"].iter().cloned())
516            {
517                let values: Result<Vec<_>, Vec<NestedMeta>> = nested
518                    .iter()
519                    .map(|nested_meta| match nested_meta {
520                        NestedMeta::Meta(Meta::NameValue(MetaNameValue {
521                            path,
522                            lit: Lit::Str(s),
523                            ..
524                        })) => Ok((path.get_ident().map(|i| i.to_string()), s.value())),
525                        e @ _ => Err(e),
526                    })
527                    .fold(Ok(vec![]), |v, i| match (v, i) {
528                        (Ok(mut v), Ok(i)) => {
529                            v.push(i);
530                            Ok(v)
531                        }
532                        (Ok(_), Err(e)) => Err(vec![e.clone()]),
533                        (Err(mut v), Err(e)) => {
534                            v.push(e.clone());
535                            Err(v)
536                        }
537                        (v @ Err(_), _) => v,
538                    });
539                return match values {
540                    Ok(v) => {
541                        let mut field_type = None;
542                        let mut delta_leader = String::new();
543                        for i in v {
544                            match i.0.as_deref() {
545                                Some("delta_leader") => {
546                                    delta_leader = i.1;
547                                }
548                                a @ _ if Some(attr_name) == a => {
549                                    field_type = string_to_fieldtype(&i.1);
550                                }
551                                a @ _ => {
552                                    abort_call_site!("Unrecognized value {:?}", a);
553                                }
554                            }
555                        }
556                        Ok((field_type, delta_leader))
557                    }
558                    Err(_) => Err(FieldTypeError::UnrecognizedJunkFound),
559                };
560            }
561        }
562    }
563    Ok((None, String::new()))
564}
565
566/// Maps the attribute spelling of a field type to its variant, or `None` if it
567/// is not one of the recognized names.
568fn string_to_fieldtype(s: &str) -> Option<FieldType> {
569    match s {
570        "ordered" => Some(FieldType::Ordered),
571        "unordered" => Some(FieldType::Unordered),
572        "scalar" => Some(FieldType::Scalar),
573        "delta" => Some(FieldType::Delta),
574        _ => None,
575    }
576}