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}