Skip to main content

typeshift_derive/
lib.rs

1//! Derive macro implementation for `typeshift`.
2//!
3//! This crate is usually consumed via `typeshift::TypeShift` from the facade
4//! crate. It is split out so proc-macro code can live separately.
5
6use proc_macro::TokenStream;
7use quote::quote;
8use syn::{
9    Data, DataEnum, DataStruct, DeriveInput, Expr, Field, Fields, Variant, parse_macro_input,
10};
11
12#[proc_macro_derive(TypeShift, attributes(validate, serde, schemars))]
13/// Derives serde, validator, and schemars support required by `typeshift`.
14///
15/// Supported inputs:
16/// - structs (named, tuple, unit)
17/// - enums (unit and data-carrying variants)
18/// - generic types
19///
20/// Unions are intentionally rejected.
21///
22/// Generated behavior is equivalent to deriving:
23/// - `serde::Serialize`
24/// - `serde::Deserialize`
25/// - `validator::Validate`
26/// - `schemars::JsonSchema`
27pub fn derive_typeshift(input: TokenStream) -> TokenStream {
28    let input = parse_macro_input!(input as DeriveInput);
29
30    let ident = input.ident;
31    let generics = input.generics;
32    let (_, ty_generics, _) = generics.split_for_impl();
33
34    let ser_generics =
35        generics_with_type_bound(&generics, syn::parse_quote!(::typeshift::serde::Serialize));
36    let (ser_impl_generics, _, ser_where_clause) = ser_generics.split_for_impl();
37
38    let mut de_generics = generics_with_type_bound(
39        &generics,
40        syn::parse_quote!(::typeshift::serde::Deserialize<'__typeshift_de>),
41    );
42    de_generics
43        .params
44        .insert(0, syn::parse_quote!('__typeshift_de));
45    let (de_impl_generics, _, de_where_clause) = de_generics.split_for_impl();
46
47    let schema_generics = generics_with_type_bound(
48        &generics,
49        syn::parse_quote!(::typeshift::schemars::JsonSchema),
50    );
51    let (schema_impl_generics, _, schema_where_clause) = schema_generics.split_for_impl();
52
53    let validate_generics = generics_with_type_bound(
54        &generics,
55        syn::parse_quote!(::typeshift::validator::Validate),
56    );
57    let (validate_impl_generics, _, validate_where_clause) = validate_generics.split_for_impl();
58
59    let serde_container_attrs = filter_attrs(&input.attrs, &["serde"]);
60    let validate_container_attrs = filter_attrs(&input.attrs, &["validate"]);
61    let schema_container_attrs = filter_attrs(&input.attrs, &["serde", "schemars"]);
62
63    let (ser_helper_def, ser_helper_init) = match &input.data {
64        Data::Struct(data) => serialize_struct_helpers(data, &generics),
65        Data::Enum(data) => serialize_enum_helpers(data, &generics),
66        Data::Union(_) => {
67            return syn::Error::new_spanned(ident, "TypeShift derive does not support union")
68                .to_compile_error()
69                .into();
70        }
71    };
72
73    let (de_helper_def, de_construct) = match &input.data {
74        Data::Struct(data) => deserialize_struct_helpers(data, &generics),
75        Data::Enum(data) => deserialize_enum_helpers(data, &generics),
76        Data::Union(_) => {
77            return syn::Error::new_spanned(ident, "TypeShift derive does not support union")
78                .to_compile_error()
79                .into();
80        }
81    };
82
83    let schema_helper_def = match &input.data {
84        Data::Struct(data) => schema_struct_helper(data, &generics),
85        Data::Enum(data) => schema_enum_helper(data, &generics),
86        Data::Union(_) => {
87            return syn::Error::new_spanned(ident, "TypeShift derive does not support union")
88                .to_compile_error()
89                .into();
90        }
91    };
92
93    let ser_ref_lt = match &input.data {
94        Data::Struct(data) => (!matches!(data.fields, Fields::Unit)).then_some("'__typeshift_ser"),
95        Data::Enum(data) => enum_has_payload(data).then_some("'__typeshift_ser"),
96        Data::Union(_) => None,
97    };
98    let ser_helper_use_generics = helper_use_generics(&generics, ser_ref_lt.map(|_| "'_"));
99    let de_helper_use_generics = helper_use_generics(&generics, None);
100    let schema_helper_use_generics = helper_use_generics(&generics, None);
101
102    let validate_impl = match &input.data {
103        Data::Struct(data) => {
104            let (val_helper_def, val_helper_init) = validate_struct_helpers(data, &generics);
105            let val_ref_lt = (!matches!(data.fields, Fields::Unit)).then_some("'__typeshift_val");
106            let val_helper_use_generics = helper_use_generics(&generics, val_ref_lt.map(|_| "'_"));
107            quote! {
108                impl #validate_impl_generics ::typeshift::validator::Validate for #ident #ty_generics #validate_where_clause {
109                    fn validate(&self) -> ::core::result::Result<(), ::typeshift::validator::ValidationErrors> {
110                        #[allow(dead_code)]
111                        #[derive(::typeshift::validator::Validate)]
112                        #[validate(crate = "typeshift::validator")]
113                        #(#validate_container_attrs)*
114                        #val_helper_def
115
116                        let helper: __TypeShiftValidateHelper #val_helper_use_generics = #val_helper_init;
117                        ::typeshift::validator::Validate::validate(&helper)
118                    }
119                }
120            }
121        }
122        Data::Enum(_) => {
123            quote! {
124                impl #validate_impl_generics ::typeshift::validator::Validate for #ident #ty_generics #validate_where_clause {
125                    fn validate(&self) -> ::core::result::Result<(), ::typeshift::validator::ValidationErrors> {
126                        ::core::result::Result::Ok(())
127                    }
128                }
129            }
130        }
131        Data::Union(_) => {
132            return syn::Error::new_spanned(ident, "TypeShift derive does not support union")
133                .to_compile_error()
134                .into();
135        }
136    };
137
138    let expanded = quote! {
139        impl #ser_impl_generics ::typeshift::serde::Serialize for #ident #ty_generics #ser_where_clause {
140            fn serialize<S>(&self, serializer: S) -> ::core::result::Result<S::Ok, S::Error>
141            where
142                S: ::typeshift::serde::Serializer,
143            {
144                #[allow(dead_code)]
145                #[derive(::typeshift::serde::Serialize)]
146                #[serde(crate = "typeshift::serde")]
147                #(#serde_container_attrs)*
148                #ser_helper_def
149
150                let helper: __TypeShiftSerializeHelper #ser_helper_use_generics = #ser_helper_init;
151                ::typeshift::serde::Serialize::serialize(&helper, serializer)
152            }
153        }
154
155        impl #de_impl_generics ::typeshift::serde::Deserialize<'__typeshift_de> for #ident #ty_generics #de_where_clause {
156            fn deserialize<D>(deserializer: D) -> ::core::result::Result<Self, D::Error>
157            where
158                D: ::typeshift::serde::Deserializer<'__typeshift_de>,
159            {
160                #[allow(dead_code)]
161                #[derive(::typeshift::serde::Deserialize)]
162                #[serde(crate = "typeshift::serde")]
163                #(#serde_container_attrs)*
164                #de_helper_def
165
166                let helper = <__TypeShiftDeserializeHelper #de_helper_use_generics as ::typeshift::serde::Deserialize>::deserialize(deserializer)?;
167                ::core::result::Result::Ok(#de_construct)
168            }
169        }
170
171        #validate_impl
172
173        impl #schema_impl_generics ::typeshift::schemars::JsonSchema for #ident #ty_generics #schema_where_clause {
174            fn schema_name() -> ::std::string::String {
175                #[allow(dead_code)]
176                #[derive(::typeshift::schemars::JsonSchema)]
177                #[schemars(crate = "typeshift::schemars")]
178                #(#schema_container_attrs)*
179                #schema_helper_def
180
181                <__TypeShiftSchemaHelper #schema_helper_use_generics as ::typeshift::schemars::JsonSchema>::schema_name()
182            }
183
184            fn json_schema(
185                schema_generator: &mut ::typeshift::schemars::r#gen::SchemaGenerator,
186            ) -> ::typeshift::schemars::schema::Schema {
187                #[allow(dead_code)]
188                #[derive(::typeshift::schemars::JsonSchema)]
189                #[schemars(crate = "typeshift::schemars")]
190                #(#schema_container_attrs)*
191                #schema_helper_def
192
193                <__TypeShiftSchemaHelper #schema_helper_use_generics as ::typeshift::schemars::JsonSchema>::json_schema(schema_generator)
194            }
195
196            fn is_referenceable() -> bool {
197                #[allow(dead_code)]
198                #[derive(::typeshift::schemars::JsonSchema)]
199                #[schemars(crate = "typeshift::schemars")]
200                #(#schema_container_attrs)*
201                #schema_helper_def
202
203                <__TypeShiftSchemaHelper #schema_helper_use_generics as ::typeshift::schemars::JsonSchema>::is_referenceable()
204            }
205        }
206    };
207
208    expanded.into()
209}
210
211fn filter_attrs(attrs: &[syn::Attribute], allowed: &[&str]) -> Vec<syn::Attribute> {
212    attrs
213        .iter()
214        .filter(|attr| {
215            attr.path()
216                .segments
217                .first()
218                .map(|seg| allowed.iter().any(|name| seg.ident == name))
219                .unwrap_or(false)
220        })
221        .cloned()
222        .collect()
223}
224
225fn serialize_struct_helpers(
226    data: &DataStruct,
227    generics: &syn::Generics,
228) -> (proc_macro2::TokenStream, proc_macro2::TokenStream) {
229    let ref_lt = (!matches!(data.fields, Fields::Unit)).then_some("'__typeshift_ser");
230    let helper_def_generics = helper_def_generics(generics, ref_lt);
231    let helper_where_clause = helper_where_clause(generics);
232
233    let helper_def = match &data.fields {
234        Fields::Named(named) => {
235            let defs = named.named.iter().map(|field| {
236                let attrs = filter_attrs(&field.attrs, &["serde"]);
237                let name = named_field_ident(field);
238                let ty = &field.ty;
239                quote! { #(#attrs)* #name: &'__typeshift_ser #ty }
240            });
241            quote! { struct __TypeShiftSerializeHelper #helper_def_generics { #(#defs,)* } #helper_where_clause }
242        }
243        Fields::Unnamed(unnamed) => {
244            let defs = unnamed.unnamed.iter().map(|field| {
245                let attrs = filter_attrs(&field.attrs, &["serde"]);
246                let ty = &field.ty;
247                quote! { #(#attrs)* &'__typeshift_ser #ty }
248            });
249            quote! { struct __TypeShiftSerializeHelper #helper_def_generics ( #(#defs,)* ) #helper_where_clause ; }
250        }
251        Fields::Unit => quote! { struct __TypeShiftSerializeHelper; },
252    };
253
254    let helper_init = match &data.fields {
255        Fields::Named(named) => {
256            let inits = named.named.iter().map(|field| {
257                let name = named_field_ident(field);
258                quote! { #name: &self.#name }
259            });
260            quote! { __TypeShiftSerializeHelper { #(#inits,)* } }
261        }
262        Fields::Unnamed(unnamed) => {
263            let inits = unnamed.unnamed.iter().enumerate().map(|(idx, _)| {
264                let idx = syn::Index::from(idx);
265                quote! { &self.#idx }
266            });
267            quote! { __TypeShiftSerializeHelper( #(#inits,)* ) }
268        }
269        Fields::Unit => quote! { __TypeShiftSerializeHelper },
270    };
271
272    (helper_def, helper_init)
273}
274
275fn deserialize_struct_helpers(
276    data: &DataStruct,
277    generics: &syn::Generics,
278) -> (proc_macro2::TokenStream, proc_macro2::TokenStream) {
279    let helper_def_generics = helper_def_generics(generics, None);
280    let helper_where_clause = helper_where_clause(generics);
281
282    let helper_def = match &data.fields {
283        Fields::Named(named) => {
284            let defs = named.named.iter().map(|field| {
285                let attrs = filter_attrs(&field.attrs, &["serde"]);
286                let name = named_field_ident(field);
287                let ty = &field.ty;
288                quote! { #(#attrs)* #name: #ty }
289            });
290            quote! { struct __TypeShiftDeserializeHelper #helper_def_generics { #(#defs,)* } #helper_where_clause }
291        }
292        Fields::Unnamed(unnamed) => {
293            let defs = unnamed.unnamed.iter().map(|field| {
294                let attrs = filter_attrs(&field.attrs, &["serde"]);
295                let ty = &field.ty;
296                quote! { #(#attrs)* #ty }
297            });
298            quote! { struct __TypeShiftDeserializeHelper #helper_def_generics ( #(#defs,)* ) #helper_where_clause ; }
299        }
300        Fields::Unit => quote! { struct __TypeShiftDeserializeHelper; },
301    };
302
303    let construct = match &data.fields {
304        Fields::Named(named) => {
305            let builds = named.named.iter().map(|field| {
306                let name = named_field_ident(field);
307                quote! { #name: helper.#name }
308            });
309            quote! { Self { #(#builds,)* } }
310        }
311        Fields::Unnamed(unnamed) => {
312            let builds = unnamed.unnamed.iter().enumerate().map(|(idx, _)| {
313                let idx = syn::Index::from(idx);
314                quote! { helper.#idx }
315            });
316            quote! { Self( #(#builds,)* ) }
317        }
318        Fields::Unit => quote! { Self },
319    };
320
321    (helper_def, construct)
322}
323
324fn validate_struct_helpers(
325    data: &DataStruct,
326    generics: &syn::Generics,
327) -> (proc_macro2::TokenStream, proc_macro2::TokenStream) {
328    let ref_lt = (!matches!(data.fields, Fields::Unit)).then_some("'__typeshift_val");
329    let helper_def_generics = helper_def_generics(generics, ref_lt);
330    let helper_where_clause = helper_where_clause(generics);
331
332    let helper_def = match &data.fields {
333        Fields::Named(named) => {
334            let defs = named.named.iter().map(|field| {
335                let attrs = filter_attrs(&field.attrs, &["validate"]);
336                let name = named_field_ident(field);
337                let ty = &field.ty;
338                quote! { #(#attrs)* #name: &'__typeshift_val #ty }
339            });
340            quote! { struct __TypeShiftValidateHelper #helper_def_generics { #(#defs,)* } #helper_where_clause }
341        }
342        Fields::Unnamed(unnamed) => {
343            let defs = unnamed.unnamed.iter().map(|field| {
344                let attrs = filter_attrs(&field.attrs, &["validate"]);
345                let ty = &field.ty;
346                quote! { #(#attrs)* &'__typeshift_val #ty }
347            });
348            quote! { struct __TypeShiftValidateHelper #helper_def_generics ( #(#defs,)* ) #helper_where_clause ; }
349        }
350        Fields::Unit => quote! { struct __TypeShiftValidateHelper; },
351    };
352
353    let helper_init = match &data.fields {
354        Fields::Named(named) => {
355            let inits = named.named.iter().map(|field| {
356                let name = named_field_ident(field);
357                quote! { #name: &self.#name }
358            });
359            quote! { __TypeShiftValidateHelper { #(#inits,)* } }
360        }
361        Fields::Unnamed(unnamed) => {
362            let inits = unnamed.unnamed.iter().enumerate().map(|(idx, _)| {
363                let idx = syn::Index::from(idx);
364                quote! { &self.#idx }
365            });
366            quote! { __TypeShiftValidateHelper( #(#inits,)* ) }
367        }
368        Fields::Unit => quote! { __TypeShiftValidateHelper },
369    };
370
371    (helper_def, helper_init)
372}
373
374fn schema_struct_helper(data: &DataStruct, generics: &syn::Generics) -> proc_macro2::TokenStream {
375    let helper_def_generics = helper_def_generics(generics, None);
376    let helper_where_clause = helper_where_clause(generics);
377
378    match &data.fields {
379        Fields::Named(named) => {
380            let defs = named.named.iter().map(|field| {
381                let attrs = filter_attrs(&field.attrs, &["serde", "schemars"]);
382                let name = named_field_ident(field);
383                let ty = &field.ty;
384                quote! { #(#attrs)* #name: #ty }
385            });
386            quote! { struct __TypeShiftSchemaHelper #helper_def_generics { #(#defs,)* } #helper_where_clause }
387        }
388        Fields::Unnamed(unnamed) => {
389            let defs = unnamed.unnamed.iter().map(|field| {
390                let attrs = filter_attrs(&field.attrs, &["serde", "schemars"]);
391                let ty = &field.ty;
392                quote! { #(#attrs)* #ty }
393            });
394            quote! { struct __TypeShiftSchemaHelper #helper_def_generics ( #(#defs,)* ) #helper_where_clause ; }
395        }
396        Fields::Unit => quote! { struct __TypeShiftSchemaHelper; },
397    }
398}
399
400fn serialize_enum_helpers(
401    data: &DataEnum,
402    generics: &syn::Generics,
403) -> (proc_macro2::TokenStream, proc_macro2::TokenStream) {
404    let ref_lt = enum_has_payload(data).then_some("'__typeshift_ser");
405    let helper_def_generics = helper_def_generics(generics, ref_lt);
406    let helper_where_clause = helper_where_clause(generics);
407
408    let variants = data
409        .variants
410        .iter()
411        .map(|variant| enum_variant_definition(variant, &["serde"], ref_lt, true));
412
413    let helper_def = quote! {
414        enum __TypeShiftSerializeHelper #helper_def_generics #helper_where_clause {
415            #(#variants,)*
416        }
417    };
418
419    let arms = data
420        .variants
421        .iter()
422        .map(|variant| enum_match_arm_self_to_helper(variant, "__TypeShiftSerializeHelper"));
423    let helper_init = quote! {
424        match self {
425            #(#arms,)*
426        }
427    };
428
429    (helper_def, helper_init)
430}
431
432fn deserialize_enum_helpers(
433    data: &DataEnum,
434    generics: &syn::Generics,
435) -> (proc_macro2::TokenStream, proc_macro2::TokenStream) {
436    let helper_def_generics = helper_def_generics(generics, None);
437    let helper_where_clause = helper_where_clause(generics);
438
439    let variants = data
440        .variants
441        .iter()
442        .map(|variant| enum_variant_definition(variant, &["serde"], None, false));
443
444    let helper_def = quote! {
445        enum __TypeShiftDeserializeHelper #helper_def_generics #helper_where_clause {
446            #(#variants,)*
447        }
448    };
449
450    let arms = data
451        .variants
452        .iter()
453        .map(|variant| enum_match_arm_helper_to_self(variant, "__TypeShiftDeserializeHelper"));
454
455    let construct = quote! {
456        match helper {
457            #(#arms,)*
458        }
459    };
460
461    (helper_def, construct)
462}
463
464fn schema_enum_helper(data: &DataEnum, generics: &syn::Generics) -> proc_macro2::TokenStream {
465    let helper_def_generics = helper_def_generics(generics, None);
466    let helper_where_clause = helper_where_clause(generics);
467
468    let variants = data
469        .variants
470        .iter()
471        .map(|variant| enum_variant_definition(variant, &["serde", "schemars"], None, false));
472
473    quote! {
474        enum __TypeShiftSchemaHelper #helper_def_generics #helper_where_clause {
475            #(#variants,)*
476        }
477    }
478}
479
480fn enum_variant_definition(
481    variant: &Variant,
482    attr_allowlist: &[&str],
483    ref_lifetime: Option<&str>,
484    include_serde_field_attrs_for_refs: bool,
485) -> proc_macro2::TokenStream {
486    let variant_attrs = filter_attrs(&variant.attrs, attr_allowlist);
487    let name = &variant.ident;
488    let discriminant = enum_discriminant(&variant.discriminant);
489
490    match &variant.fields {
491        Fields::Named(named) => {
492            let fields = named.named.iter().map(|field| {
493                let mut allowlist = attr_allowlist.to_vec();
494                if include_serde_field_attrs_for_refs && !allowlist.contains(&"serde") {
495                    allowlist.push("serde");
496                }
497                let attrs = filter_attrs(&field.attrs, &allowlist);
498                let field_name = named_field_ident(field);
499                let ty = &field.ty;
500                if let Some(lt) = ref_lifetime {
501                    let lt_ident = syn::Lifetime::new(lt, proc_macro2::Span::call_site());
502                    quote! { #(#attrs)* #field_name: &#lt_ident #ty }
503                } else {
504                    quote! { #(#attrs)* #field_name: #ty }
505                }
506            });
507            quote! { #(#variant_attrs)* #name { #(#fields,)* } #discriminant }
508        }
509        Fields::Unnamed(unnamed) => {
510            let fields = unnamed.unnamed.iter().map(|field| {
511                let mut allowlist = attr_allowlist.to_vec();
512                if include_serde_field_attrs_for_refs && !allowlist.contains(&"serde") {
513                    allowlist.push("serde");
514                }
515                let attrs = filter_attrs(&field.attrs, &allowlist);
516                let ty = &field.ty;
517                if let Some(lt) = ref_lifetime {
518                    let lt_ident = syn::Lifetime::new(lt, proc_macro2::Span::call_site());
519                    quote! { #(#attrs)* &#lt_ident #ty }
520                } else {
521                    quote! { #(#attrs)* #ty }
522                }
523            });
524            quote! { #(#variant_attrs)* #name( #(#fields,)* ) #discriminant }
525        }
526        Fields::Unit => quote! { #(#variant_attrs)* #name #discriminant },
527    }
528}
529
530fn enum_match_arm_self_to_helper(variant: &Variant, helper_name: &str) -> proc_macro2::TokenStream {
531    let helper_ident = syn::Ident::new(helper_name, proc_macro2::Span::call_site());
532    let var_ident = &variant.ident;
533
534    match &variant.fields {
535        Fields::Named(named) => {
536            let names: Vec<_> = named.named.iter().map(named_field_ident).cloned().collect();
537            quote! {
538                Self::#var_ident { #(#names,)* } => #helper_ident::#var_ident { #(#names,)* }
539            }
540        }
541        Fields::Unnamed(unnamed) => {
542            let binds: Vec<_> = unnamed
543                .unnamed
544                .iter()
545                .enumerate()
546                .map(|(idx, _)| {
547                    syn::Ident::new(&format!("__f{idx}"), proc_macro2::Span::call_site())
548                })
549                .collect();
550            quote! {
551                Self::#var_ident( #(#binds,)* ) => #helper_ident::#var_ident( #(#binds,)* )
552            }
553        }
554        Fields::Unit => quote! { Self::#var_ident => #helper_ident::#var_ident },
555    }
556}
557
558fn enum_match_arm_helper_to_self(variant: &Variant, helper_name: &str) -> proc_macro2::TokenStream {
559    let helper_ident = syn::Ident::new(helper_name, proc_macro2::Span::call_site());
560    let var_ident = &variant.ident;
561
562    match &variant.fields {
563        Fields::Named(named) => {
564            let names: Vec<_> = named.named.iter().map(named_field_ident).cloned().collect();
565            quote! {
566                #helper_ident::#var_ident { #(#names,)* } => Self::#var_ident { #(#names,)* }
567            }
568        }
569        Fields::Unnamed(unnamed) => {
570            let binds: Vec<_> = unnamed
571                .unnamed
572                .iter()
573                .enumerate()
574                .map(|(idx, _)| {
575                    syn::Ident::new(&format!("__f{idx}"), proc_macro2::Span::call_site())
576                })
577                .collect();
578            quote! {
579                #helper_ident::#var_ident( #(#binds,)* ) => Self::#var_ident( #(#binds,)* )
580            }
581        }
582        Fields::Unit => quote! { #helper_ident::#var_ident => Self::#var_ident },
583    }
584}
585
586fn enum_discriminant(discriminant: &Option<(syn::token::Eq, Expr)>) -> proc_macro2::TokenStream {
587    match discriminant {
588        Some((_, expr)) => quote! { = #expr },
589        None => quote! {},
590    }
591}
592
593fn generics_with_type_bound(generics: &syn::Generics, bound: syn::TypeParamBound) -> syn::Generics {
594    let mut output = generics.clone();
595    for param in &mut output.params {
596        if let syn::GenericParam::Type(type_param) = param {
597            type_param.bounds.push(bound.clone());
598        }
599    }
600    output
601}
602
603fn helper_def_generics(
604    generics: &syn::Generics,
605    extra_lifetime: Option<&str>,
606) -> proc_macro2::TokenStream {
607    let params = &generics.params;
608    match (extra_lifetime, params.is_empty()) {
609        (Some(lt), true) => {
610            let lt = syn::Lifetime::new(lt, proc_macro2::Span::call_site());
611            quote! { <#lt> }
612        }
613        (Some(lt), false) => {
614            let lt = syn::Lifetime::new(lt, proc_macro2::Span::call_site());
615            quote! { <#lt, #params> }
616        }
617        (None, true) => quote! {},
618        (None, false) => quote! { <#params> },
619    }
620}
621
622fn helper_use_generics(
623    generics: &syn::Generics,
624    extra_lifetime: Option<&str>,
625) -> proc_macro2::TokenStream {
626    let args: Vec<proc_macro2::TokenStream> = generics
627        .params
628        .iter()
629        .map(|param| match param {
630            syn::GenericParam::Type(ty) => {
631                let ident = &ty.ident;
632                quote! { #ident }
633            }
634            syn::GenericParam::Lifetime(lt) => {
635                let lifetime = &lt.lifetime;
636                quote! { #lifetime }
637            }
638            syn::GenericParam::Const(konst) => {
639                let ident = &konst.ident;
640                quote! { #ident }
641            }
642        })
643        .collect();
644
645    match (extra_lifetime, args.is_empty()) {
646        (Some(lt), true) => {
647            let lt = syn::Lifetime::new(lt, proc_macro2::Span::call_site());
648            quote! { <#lt> }
649        }
650        (Some(lt), false) => {
651            let lt = syn::Lifetime::new(lt, proc_macro2::Span::call_site());
652            quote! { <#lt, #(#args,)*> }
653        }
654        (None, true) => quote! {},
655        (None, false) => quote! { <#(#args,)*> },
656    }
657}
658
659fn helper_where_clause(generics: &syn::Generics) -> proc_macro2::TokenStream {
660    match &generics.where_clause {
661        Some(where_clause) => quote! { #where_clause },
662        None => quote! {},
663    }
664}
665
666fn enum_has_payload(data: &DataEnum) -> bool {
667    data.variants
668        .iter()
669        .any(|variant| !matches!(variant.fields, Fields::Unit))
670}
671
672fn named_field_ident(field: &Field) -> &syn::Ident {
673    match &field.ident {
674        Some(name) => name,
675        None => unreachable!("named field must have ident"),
676    }
677}