Skip to main content

pack_io_derive/
lib.rs

1//! Procedural macros for [`pack-io`].
2//!
3//! Three derives are provided:
4//!
5//! - `#[derive(Serialize)]` — implements `pack_io::Serialize`.
6//! - `#[derive(Deserialize)]` — implements `pack_io::Deserialize` (owning).
7//! - `#[derive(DeserializeView)]` — implements `pack_io::DeserializeView`
8//!   for types whose fields borrow from the input buffer (`&'a str`,
9//!   `&'a [u8]`, primitives).
10//!
11//! All three work on structs (named, tuple, unit) and enums (any variant
12//! shape). Field order in the source code is the encoded byte order; the
13//! wire format adds a `varint(variant_index)` prefix for enums.
14//!
15//! This crate is not intended to be used directly — depend on
16//! [`pack-io`] with the `derive` feature instead, which re-exports the
17//! macros at `pack_io::{Serialize, Deserialize, DeserializeView}`.
18//!
19//! [`pack-io`]: https://crates.io/crates/pack-io
20
21#![deny(missing_docs)]
22#![forbid(unsafe_code)]
23
24use proc_macro::TokenStream;
25use proc_macro2::TokenStream as TokenStream2;
26use quote::{quote, quote_spanned};
27use syn::spanned::Spanned;
28use syn::{
29    Attribute, Data, DataEnum, DataStruct, DeriveInput, Field, Fields, GenericParam, Generics,
30    Ident, Index, Lifetime, LifetimeParam, parse_macro_input, parse_quote,
31};
32
33// ---------------------------------------------------------------------------
34// Public entry points
35// ---------------------------------------------------------------------------
36
37/// Derive `pack_io::Serialize` for a struct or enum.
38///
39/// Fields are encoded in their declaration order. For enums, a
40/// `varint(variant_index)` is emitted first, followed by the variant's
41/// fields.
42///
43/// Each field type must already implement `pack_io::Serialize`.
44#[proc_macro_derive(Serialize, attributes(pack_io))]
45pub fn derive_serialize(input: TokenStream) -> TokenStream {
46    let input = parse_macro_input!(input as DeriveInput);
47    expand_serialize(&input)
48        .unwrap_or_else(syn::Error::into_compile_error)
49        .into()
50}
51
52/// Derive `pack_io::Deserialize` for a struct or enum (owning decode).
53///
54/// Each field type must already implement `pack_io::Deserialize`.
55#[proc_macro_derive(Deserialize, attributes(pack_io))]
56pub fn derive_deserialize(input: TokenStream) -> TokenStream {
57    let input = parse_macro_input!(input as DeriveInput);
58    expand_deserialize(&input)
59        .unwrap_or_else(syn::Error::into_compile_error)
60        .into()
61}
62
63/// Derive `pack_io::DeserializeView` for a struct (zero-copy decode).
64///
65/// The struct MUST have exactly one lifetime parameter — used as the borrow
66/// of the underlying input buffer. Field types must implement
67/// `pack_io::DeserializeView<'that_lifetime>`. The built-in impls for
68/// primitives, `&'a str`, `&'a [u8]`, and the standard `Option` / `Result`
69/// / tuple / array types cover the common cases.
70///
71/// Enum support and multi-lifetime structs are planned for a later
72/// minor release.
73#[proc_macro_derive(DeserializeView, attributes(pack_io))]
74pub fn derive_deserialize_view(input: TokenStream) -> TokenStream {
75    let input = parse_macro_input!(input as DeriveInput);
76    expand_deserialize_view(&input)
77        .unwrap_or_else(syn::Error::into_compile_error)
78        .into()
79}
80
81// ---------------------------------------------------------------------------
82// Serialize
83// ---------------------------------------------------------------------------
84
85fn expand_serialize(input: &DeriveInput) -> syn::Result<TokenStream2> {
86    let name = &input.ident;
87    let generics = add_trait_bound(&input.generics, parse_quote!(::pack_io::Serialize));
88    let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
89    let type_version = parse_type_attrs(&input.attrs)?;
90
91    let body = match &input.data {
92        Data::Struct(data) => serialize_struct_body(name, data, type_version)?,
93        Data::Enum(data) => {
94            if type_version.is_some() {
95                return Err(syn::Error::new_spanned(
96                    name,
97                    "pack-io: `#[pack_io(version = N)]` is not supported on enums in this \
98                     release; wrap the enum in a versioned newtype struct instead",
99                ));
100            }
101            serialize_enum_body(name, data)?
102        }
103        Data::Union(u) => {
104            return Err(syn::Error::new(
105                u.union_token.span(),
106                "pack-io: `union` is not supported (no defined wire format)",
107            ));
108        }
109    };
110
111    Ok(quote! {
112        #[automatically_derived]
113        impl #impl_generics ::pack_io::Serialize for #name #ty_generics #where_clause {
114            #[inline]
115            fn serialize<__E: ::pack_io::Encode + ?Sized>(
116                &self,
117                __encoder: &mut __E,
118            ) -> ::pack_io::Result<()> {
119                #body
120                ::core::result::Result::Ok(())
121            }
122        }
123    })
124}
125
126fn serialize_struct_body(
127    name: &Ident,
128    data: &DataStruct,
129    type_version: Option<u32>,
130) -> syn::Result<TokenStream2> {
131    // Pair each field accessor with its schema attrs so we can filter / sort.
132    let fields_meta = collect_field_meta(&data.fields, quote!(self))?;
133
134    // Reject schema attrs on a non-versioned struct — they have no meaning
135    // without an enclosing version.
136    if type_version.is_none() {
137        for fm in &fields_meta {
138            if fm.attrs.since.is_some() || fm.attrs.deprecated.is_some() {
139                return Err(syn::Error::new_spanned(
140                    name,
141                    "pack-io: `#[pack_io(since = N)]` / `#[pack_io(deprecated = N)]` requires \
142                     the struct itself to carry `#[pack_io(version = N)]`",
143                ));
144            }
145        }
146    }
147
148    match type_version {
149        None => {
150            // Plain (v0.4) encoding: fields concatenated in declaration order.
151            let writes = fields_meta.iter().map(|fm| {
152                let acc = &fm.accessor;
153                quote! { ::pack_io::Serialize::serialize(&#acc, __encoder)?; }
154            });
155            Ok(quote! { #(#writes)* })
156        }
157        Some(version) => {
158            // Versioned encoding: varint(version) ++ varint(body_len) ++ body.
159            // Body holds exactly the fields whose [since, deprecated) window
160            // includes `version`.
161            let writes = fields_meta
162                .iter()
163                .filter(|fm| fm.attrs.live_at(version))
164                .map(|fm| {
165                    let acc = &fm.accessor;
166                    quote! { ::pack_io::Serialize::serialize(&#acc, &mut __body)?; }
167                });
168            Ok(quote! {
169                __encoder.write_varint_u64(#version as u64)?;
170                let mut __body = ::pack_io::Encoder::new();
171                #(#writes)*
172                let __body_bytes = ::pack_io::Encoder::into_inner(__body);
173                __encoder.write_varint_u64(__body_bytes.len() as u64)?;
174                __encoder.write_bytes(&__body_bytes)?;
175            })
176        }
177    }
178}
179
180fn serialize_enum_body(name: &Ident, data: &DataEnum) -> syn::Result<TokenStream2> {
181    if data.variants.is_empty() {
182        return Err(syn::Error::new_spanned(
183            name,
184            "pack-io: empty enums cannot be serialised (no value to encode)",
185        ));
186    }
187
188    let arms: Vec<TokenStream2> = data
189        .variants
190        .iter()
191        .enumerate()
192        .map(|(index, variant)| {
193            let index = u32::try_from(index).expect("u32 enum variants");
194            let var_name = &variant.ident;
195            let bindings = variant_bindings(&variant.fields);
196            let pattern = match &variant.fields {
197                Fields::Named(_) => quote!(Self::#var_name { #(#bindings),* }),
198                Fields::Unnamed(_) => quote!(Self::#var_name(#(#bindings),*)),
199                Fields::Unit => quote!(Self::#var_name),
200            };
201            let writes = bindings.iter().map(|b| {
202                quote! { ::pack_io::Serialize::serialize(#b, __encoder)?; }
203            });
204            quote! {
205                #pattern => {
206                    __encoder.write_varint_u64(#index as u64)?;
207                    #(#writes)*
208                }
209            }
210        })
211        .collect();
212
213    Ok(quote! {
214        match self {
215            #(#arms)*
216        }
217    })
218}
219
220// ---------------------------------------------------------------------------
221// Deserialize (owning)
222// ---------------------------------------------------------------------------
223
224fn expand_deserialize(input: &DeriveInput) -> syn::Result<TokenStream2> {
225    let name = &input.ident;
226    let generics = add_trait_bound(&input.generics, parse_quote!(::pack_io::Deserialize));
227    let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
228    let type_version = parse_type_attrs(&input.attrs)?;
229
230    let body = match &input.data {
231        Data::Struct(data) => deserialize_struct_body(name, &data.fields, type_version)?,
232        Data::Enum(data) => {
233            if type_version.is_some() {
234                return Err(syn::Error::new_spanned(
235                    name,
236                    "pack-io: `#[pack_io(version = N)]` is not supported on enums in this \
237                     release; wrap the enum in a versioned newtype struct instead",
238                ));
239            }
240            deserialize_enum_body(name, data)?
241        }
242        Data::Union(u) => {
243            return Err(syn::Error::new(
244                u.union_token.span(),
245                "pack-io: `union` is not supported",
246            ));
247        }
248    };
249
250    Ok(quote! {
251        #[automatically_derived]
252        impl #impl_generics ::pack_io::Deserialize for #name #ty_generics #where_clause {
253            #[inline]
254            fn deserialize<__D: ::pack_io::Decode + ?Sized>(
255                __decoder: &mut __D,
256            ) -> ::pack_io::Result<Self> {
257                #body
258            }
259        }
260    })
261}
262
263fn deserialize_struct_body(
264    name: &Ident,
265    fields: &Fields,
266    type_version: Option<u32>,
267) -> syn::Result<TokenStream2> {
268    let fields_meta = collect_field_meta(fields, quote!(self))?;
269
270    if type_version.is_none() {
271        for fm in &fields_meta {
272            if fm.attrs.since.is_some() || fm.attrs.deprecated.is_some() {
273                return Err(syn::Error::new_spanned(
274                    name,
275                    "pack-io: `#[pack_io(since = N)]` / `#[pack_io(deprecated = N)]` requires \
276                     the struct itself to carry `#[pack_io(version = N)]`",
277                ));
278            }
279        }
280    }
281
282    match type_version {
283        None => {
284            // Plain (v0.4) decoding: each field's Deserialize impl in order.
285            let constructor = construct_from_fields(quote!(#name), fields, |ty| {
286                quote_spanned! { ty.span() =>
287                    <#ty as ::pack_io::Deserialize>::deserialize(__decoder)?
288                }
289            });
290            Ok(quote! { ::core::result::Result::Ok(#constructor) })
291        }
292        Some(_) => {
293            // Versioned decoding: read varint(version), varint(body_len),
294            // then run each field through a conditional based on its
295            // [since, deprecated) window vs. the wire's version.
296            //
297            // Only fields whose [since, deprecated) window does NOT span
298            // every possible version (i.e. `since > 1` or `deprecated` is
299            // set) need a `Default` fallback. Fields that are always live
300            // skip the conditional entirely — no `Default` bound required.
301            let field_inits: Vec<TokenStream2> = fields_meta
302                .iter()
303                .enumerate()
304                .map(|(i, fm)| {
305                    let var = field_local_ident(fm, i);
306                    let ty = &fm.ty;
307                    let always_live =
308                        fm.attrs.since.unwrap_or(1) == 1 && fm.attrs.deprecated.is_none();
309                    if always_live {
310                        quote! {
311                            let #var: #ty =
312                                <#ty as ::pack_io::Deserialize>::deserialize(&mut __body_dec)?;
313                        }
314                    } else {
315                        let since = fm.attrs.since.unwrap_or(1);
316                        let deprecated_check = match fm.attrs.deprecated {
317                            Some(d) => quote! { __version < (#d as u32) },
318                            None => quote! { true },
319                        };
320                        quote! {
321                            let #var: #ty = if (#since as u32) <= __version
322                                && #deprecated_check
323                            {
324                                <#ty as ::pack_io::Deserialize>::deserialize(&mut __body_dec)?
325                            } else {
326                                ::core::default::Default::default()
327                            };
328                        }
329                    }
330                })
331                .collect();
332
333            let constructor = match fields {
334                Fields::Named(_) => {
335                    let pairs = fields_meta.iter().enumerate().map(|(i, fm)| {
336                        let name = fm.field_ident.as_ref().expect("named");
337                        let var = field_local_ident(fm, i);
338                        quote! { #name: #var }
339                    });
340                    quote! { #name { #(#pairs),* } }
341                }
342                Fields::Unnamed(_) => {
343                    let positions = fields_meta
344                        .iter()
345                        .enumerate()
346                        .map(|(i, fm)| field_local_ident(fm, i));
347                    quote! { #name ( #(#positions),* ) }
348                }
349                Fields::Unit => quote! { #name },
350            };
351
352            Ok(quote! {
353                let __version_u64 = __decoder.read_varint_u64()?;
354                let __version = u32::try_from(__version_u64)
355                    .map_err(|_| ::pack_io::SerialError::IntegerOutOfRange)?;
356                let __body_len_u64 = __decoder.read_varint_u64()?;
357                let __max = ::pack_io::Decode::max_alloc(__decoder) as u64;
358                if __body_len_u64 > __max {
359                    return ::core::result::Result::Err(::pack_io::SerialError::InvalidLength {
360                        declared: __body_len_u64,
361                        remaining: 0,
362                    });
363                }
364                let __body_len = __body_len_u64 as usize;
365                let mut __body_buf = ::std::vec![0u8; __body_len];
366                __decoder.read_into(&mut __body_buf)?;
367                let mut __body_dec = ::pack_io::Decoder::new(&__body_buf);
368                #(#field_inits)*
369                ::core::result::Result::Ok(#constructor)
370            })
371        }
372    }
373}
374
375fn field_local_ident(fm: &FieldMeta<'_>, index: usize) -> Ident {
376    match &fm.field_ident {
377        Some(id) => Ident::new(&format!("__f_{}", id), id.span()),
378        None => Ident::new(&format!("__f_{}", index), fm.ty.span()),
379    }
380}
381
382fn deserialize_enum_body(name: &Ident, data: &DataEnum) -> syn::Result<TokenStream2> {
383    if data.variants.is_empty() {
384        return Err(syn::Error::new_spanned(
385            name,
386            "pack-io: empty enums cannot be deserialised",
387        ));
388    }
389
390    let arms: Vec<TokenStream2> = data
391        .variants
392        .iter()
393        .enumerate()
394        .map(|(index, variant)| {
395            let index = u32::try_from(index).expect("u32 enum variants");
396            let var_name = &variant.ident;
397            let constructor =
398                construct_from_fields(quote!(#name :: #var_name), &variant.fields, |ty| {
399                    quote_spanned! { ty.span() =>
400                        <#ty as ::pack_io::Deserialize>::deserialize(__decoder)?
401                    }
402                });
403            quote! { #index => ::core::result::Result::Ok(#constructor), }
404        })
405        .collect();
406
407    let enum_name = name.to_string();
408    Ok(quote! {
409        let __tag = __decoder.read_varint_u64()?;
410        let __idx = u32::try_from(__tag)
411            .map_err(|_| ::pack_io::SerialError::UnknownVariant {
412                kind: #enum_name,
413                index: u64::MAX,
414            })?;
415        match __idx {
416            #(#arms)*
417            other => ::core::result::Result::Err(::pack_io::SerialError::UnknownVariant {
418                kind: #enum_name,
419                index: u64::from(other),
420            }),
421        }
422    })
423}
424
425// ---------------------------------------------------------------------------
426// DeserializeView (zero-copy)
427// ---------------------------------------------------------------------------
428
429fn expand_deserialize_view(input: &DeriveInput) -> syn::Result<TokenStream2> {
430    let name = &input.ident;
431
432    let lifetime = extract_single_lifetime(&input.generics).ok_or_else(|| {
433        syn::Error::new_spanned(
434            &input.generics,
435            "pack-io: `DeserializeView` requires the type to have exactly one lifetime parameter \
436             (used as the borrow of the input buffer)",
437        )
438    })?;
439
440    let generics = add_trait_bound_with_lifetime(
441        &input.generics,
442        parse_quote!(::pack_io::DeserializeView<#lifetime>),
443        &lifetime,
444    );
445    let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
446
447    let body = match &input.data {
448        Data::Struct(data) => {
449            let constructor = construct_from_fields(quote!(#name), &data.fields, |ty| {
450                quote_spanned! { ty.span() =>
451                    <#ty as ::pack_io::DeserializeView<#lifetime>>::deserialize_view(__decoder)?
452                }
453            });
454            quote! { ::core::result::Result::Ok(#constructor) }
455        }
456        Data::Enum(e) => {
457            return Err(syn::Error::new_spanned(
458                e.enum_token,
459                "pack-io: `DeserializeView` on enums is not yet supported; track \
460                 https://github.com/jamesgober/pack-io/issues for status",
461            ));
462        }
463        Data::Union(u) => {
464            return Err(syn::Error::new(
465                u.union_token.span(),
466                "pack-io: `union` is not supported",
467            ));
468        }
469    };
470
471    Ok(quote! {
472        #[automatically_derived]
473        impl #impl_generics ::pack_io::DeserializeView<#lifetime> for #name #ty_generics #where_clause {
474            #[inline]
475            fn deserialize_view(
476                __decoder: &mut ::pack_io::Decoder<#lifetime>,
477            ) -> ::pack_io::Result<Self> {
478                #body
479            }
480        }
481    })
482}
483
484// ---------------------------------------------------------------------------
485// Schema attribute parsing
486// ---------------------------------------------------------------------------
487
488/// Schema attributes attached to a struct or field.
489#[derive(Default, Clone, Copy)]
490struct SchemaAttrs {
491    /// Only meaningful on fields: the type version at which this field was
492    /// added. Absent ⇒ `1` (always present).
493    since: Option<u32>,
494    /// Only meaningful on fields: the type version at which this field was
495    /// removed. Absent ⇒ never deprecated.
496    deprecated: Option<u32>,
497}
498
499impl SchemaAttrs {
500    /// Is the field live at the given type version (`since <= v < deprecated`)?
501    fn live_at(self, version: u32) -> bool {
502        let since = self.since.unwrap_or(1);
503        if version < since {
504            return false;
505        }
506        match self.deprecated {
507            Some(d) => version < d,
508            None => true,
509        }
510    }
511}
512
513/// Per-field metadata used by the version-aware code generator.
514struct FieldMeta<'a> {
515    /// `self.<name>` (named) / `self.<index>` (tuple) / variant binding.
516    accessor: TokenStream2,
517    /// `Some(ident)` for named fields; `None` for tuple-struct positions.
518    field_ident: Option<&'a Ident>,
519    /// The field's type.
520    ty: &'a syn::Type,
521    /// Parsed `#[pack_io(...)]` attributes on the field.
522    attrs: SchemaAttrs,
523}
524
525/// Parse `#[pack_io(...)]` attributes on a type. Only `version = N` is
526/// accepted at the type level.
527fn parse_type_attrs(attrs: &[Attribute]) -> syn::Result<Option<u32>> {
528    let mut version: Option<u32> = None;
529    for attr in attrs {
530        if !attr.path().is_ident("pack_io") {
531            continue;
532        }
533        attr.parse_nested_meta(|meta| {
534            if meta.path.is_ident("version") {
535                let lit: syn::LitInt = meta.value()?.parse()?;
536                let v: u32 = lit.base10_parse()?;
537                if v == 0 {
538                    return Err(meta.error("pack-io: schema versions start at 1, not 0"));
539                }
540                version = Some(v);
541                Ok(())
542            } else if meta.path.is_ident("since") || meta.path.is_ident("deprecated") {
543                Err(meta.error(
544                    "pack-io: `since` / `deprecated` are field-level attributes; only \
545                     `version` is allowed on the type",
546                ))
547            } else {
548                Err(meta.error("pack-io: unknown attribute (expected `version`)"))
549            }
550        })?;
551    }
552    Ok(version)
553}
554
555/// Parse `#[pack_io(...)]` attributes on a struct field.
556fn parse_field_attrs(attrs: &[Attribute]) -> syn::Result<SchemaAttrs> {
557    let mut out = SchemaAttrs::default();
558    for attr in attrs {
559        if !attr.path().is_ident("pack_io") {
560            continue;
561        }
562        attr.parse_nested_meta(|meta| {
563            if meta.path.is_ident("since") {
564                let lit: syn::LitInt = meta.value()?.parse()?;
565                let v: u32 = lit.base10_parse()?;
566                if v == 0 {
567                    return Err(meta.error("pack-io: schema versions start at 1, not 0"));
568                }
569                out.since = Some(v);
570                Ok(())
571            } else if meta.path.is_ident("deprecated") {
572                let lit: syn::LitInt = meta.value()?.parse()?;
573                let v: u32 = lit.base10_parse()?;
574                if v == 0 {
575                    return Err(meta.error("pack-io: schema versions start at 1, not 0"));
576                }
577                out.deprecated = Some(v);
578                Ok(())
579            } else if meta.path.is_ident("version") {
580                Err(meta.error(
581                    "pack-io: `version` is a type-level attribute; use `since` / `deprecated` \
582                     on individual fields",
583                ))
584            } else {
585                Err(meta.error("pack-io: unknown attribute (expected `since` or `deprecated`)"))
586            }
587        })?;
588    }
589    if let (Some(since), Some(dep)) = (out.since, out.deprecated) {
590        if dep <= since {
591            return Err(syn::Error::new(
592                attrs[0].span(),
593                format!(
594                    "pack-io: `deprecated = {dep}` must be strictly greater than \
595                     `since = {since}` — a field cannot be removed before it is introduced",
596                ),
597            ));
598        }
599    }
600    Ok(out)
601}
602
603/// Walk a struct's fields and collect their per-field metadata.
604fn collect_field_meta(fields: &Fields, owner: TokenStream2) -> syn::Result<Vec<FieldMeta<'_>>> {
605    let mut out = Vec::new();
606    match fields {
607        Fields::Named(named) => {
608            for f in &named.named {
609                let ident = f.ident.as_ref().expect("named fields have idents");
610                out.push(FieldMeta {
611                    accessor: quote!(#owner.#ident),
612                    field_ident: Some(ident),
613                    ty: &f.ty,
614                    attrs: parse_field_attrs(&f.attrs)?,
615                });
616            }
617        }
618        Fields::Unnamed(unnamed) => {
619            for (i, f) in unnamed.unnamed.iter().enumerate() {
620                let idx = Index::from(i);
621                out.push(FieldMeta {
622                    accessor: quote!(#owner.#idx),
623                    field_ident: None,
624                    ty: &f.ty,
625                    attrs: parse_field_attrs(&f.attrs)?,
626                });
627            }
628        }
629        Fields::Unit => {}
630    }
631    Ok(out)
632}
633
634// ---------------------------------------------------------------------------
635// Helpers
636// ---------------------------------------------------------------------------
637
638/// Emit one identifier per field, for use in an enum-pattern binding.
639fn variant_bindings(fields: &Fields) -> Vec<TokenStream2> {
640    match fields {
641        Fields::Named(named) => named
642            .named
643            .iter()
644            .map(|f| {
645                let name = f.ident.as_ref().expect("named fields have idents");
646                quote!(#name)
647            })
648            .collect(),
649        Fields::Unnamed(unnamed) => unnamed
650            .unnamed
651            .iter()
652            .enumerate()
653            .map(|(i, _)| {
654                let id = Ident::new(&format!("__f{i}"), unnamed.unnamed[i].span());
655                quote!(#id)
656            })
657            .collect(),
658        Fields::Unit => Vec::new(),
659    }
660}
661
662/// Build `Path { f1: …, f2: … }`, `Path(…, …)`, or `Path` from a `Fields`
663/// description, using `gen_expr(field_type)` to produce each per-field
664/// expression.
665fn construct_from_fields<F>(path: TokenStream2, fields: &Fields, mut gen_expr: F) -> TokenStream2
666where
667    F: FnMut(&syn::Type) -> TokenStream2,
668{
669    match fields {
670        Fields::Named(named) => {
671            let pieces = named.named.iter().map(|f: &Field| {
672                let name = f.ident.as_ref().expect("named fields have idents");
673                let expr = gen_expr(&f.ty);
674                quote! { #name: #expr }
675            });
676            quote! { #path { #(#pieces),* } }
677        }
678        Fields::Unnamed(unnamed) => {
679            let pieces = unnamed.unnamed.iter().map(|f| gen_expr(&f.ty));
680            quote! { #path ( #(#pieces),* ) }
681        }
682        Fields::Unit => quote! { #path },
683    }
684}
685
686/// Add `: Bound` to every type parameter of `generics`, leaving lifetimes
687/// and const generics alone.
688fn add_trait_bound(generics: &Generics, bound: syn::TypeParamBound) -> Generics {
689    let mut generics = generics.clone();
690    for param in &mut generics.params {
691        if let GenericParam::Type(t) = param {
692            t.bounds.push(bound.clone());
693        }
694    }
695    generics
696}
697
698/// Like `add_trait_bound`, plus ensures the named lifetime outlives every
699/// other generic lifetime — required for the `DeserializeView<'a>` bound on
700/// generic type parameters.
701fn add_trait_bound_with_lifetime(
702    generics: &Generics,
703    bound: syn::TypeParamBound,
704    _lifetime: &Lifetime,
705) -> Generics {
706    let mut generics = generics.clone();
707    for param in &mut generics.params {
708        if let GenericParam::Type(t) = param {
709            t.bounds.push(bound.clone());
710        }
711    }
712    generics
713}
714
715/// Return the single lifetime parameter of `generics`, or `None` if there
716/// are zero or more than one.
717fn extract_single_lifetime(generics: &Generics) -> Option<Lifetime> {
718    let lifetimes: Vec<&LifetimeParam> = generics
719        .params
720        .iter()
721        .filter_map(|p| {
722            if let GenericParam::Lifetime(l) = p {
723                Some(l)
724            } else {
725                None
726            }
727        })
728        .collect();
729    if lifetimes.len() == 1 {
730        Some(lifetimes[0].lifetime.clone())
731    } else {
732        None
733    }
734}