Skip to main content

serde_shape_derive/
lib.rs

1// Copyright 2026 FastLabs Developers
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15//! Procedural macros for `serde-shape`.
16//!
17//! Most users should enable the main crate's `derive` feature, which re-exports
18//! [`SerializeShape`](derive@SerializeShape) and [`DeserializeShape`](derive@DeserializeShape)
19//! with the corresponding traits and graph types.
20
21use std::collections::BTreeSet;
22
23use proc_macro::TokenStream;
24use proc_macro_crate::FoundCrate;
25use proc_macro_crate::crate_name;
26use proc_macro2::Ident;
27use proc_macro2::Span;
28use proc_macro2::TokenStream as TokenStream2;
29use quote::ToTokens;
30use quote::quote;
31use serde_derive_internals::Ctxt;
32use serde_derive_internals::Derive;
33use serde_derive_internals::ast;
34use serde_derive_internals::attr;
35use serde_derive_internals::name::Name;
36use serde_derive_internals::ungroup;
37use syn::DeriveInput;
38use syn::GenericArgument;
39use syn::LitStr;
40use syn::Member;
41use syn::PathArguments;
42use syn::ReturnType;
43use syn::Type;
44use syn::TypeParamBound;
45use syn::parse_macro_input;
46use syn::parse_quote;
47
48mod shape_attr;
49
50use shape_attr::ShapeAttrs;
51use shape_attr::description;
52
53/// Derives `serde_shape::SerializeShape` from Serde serialization metadata.
54#[proc_macro_derive(SerializeShape, attributes(serde, serde_shape))]
55pub fn derive_serialize_shape(input: TokenStream) -> TokenStream {
56    let input = parse_macro_input!(input as DeriveInput);
57
58    match expand_serialize_shape(&input) {
59        Ok(tokens) => tokens.into(),
60        Err(err) => err.to_compile_error().into(),
61    }
62}
63
64/// Derives `serde_shape::DeserializeShape` from Serde deserialization metadata.
65#[proc_macro_derive(DeserializeShape, attributes(serde, serde_shape))]
66pub fn derive_deserialize_shape(input: TokenStream) -> TokenStream {
67    let input = parse_macro_input!(input as DeriveInput);
68
69    match expand_deserialize_shape(&input) {
70        Ok(tokens) => tokens.into(),
71        Err(err) => err.to_compile_error().into(),
72    }
73}
74
75fn expand_serialize_shape(input: &DeriveInput) -> syn::Result<TokenStream2> {
76    let serde_shape = serde_shape_crate()?;
77    let container = parse_container(input, Derive::Serialize)?;
78    let shape_attrs = ShapeAttrs::parse(&input.attrs)?;
79    validate_shape_attrs(&container)?;
80    let ident = &input.ident;
81    let mut generics = input.generics.clone();
82    add_serialize_shape_bounds(&mut generics, &container, &shape_attrs)?;
83    let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
84    let body = serialize_shape_body(&container, &shape_attrs)?;
85
86    Ok(quote! {
87        const _: () = {
88            use #serde_shape as __serde_shape;
89
90            impl #impl_generics __serde_shape::SerializeShape for #ident #ty_generics #where_clause {
91                fn serialize_shape_in(
92                    context: &mut __serde_shape::SerializeShapeContext,
93                ) -> __serde_shape::ShapeRef {
94                    #body
95                }
96            }
97        };
98    })
99}
100
101fn expand_deserialize_shape(input: &DeriveInput) -> syn::Result<TokenStream2> {
102    let serde_shape = serde_shape_crate()?;
103    let container = parse_container(input, Derive::Deserialize)?;
104    let shape_attrs = ShapeAttrs::parse(&input.attrs)?;
105    validate_shape_attrs(&container)?;
106    let ident = &input.ident;
107    let mut generics = input.generics.clone();
108    add_deserialize_shape_bounds(&mut generics, &container, &shape_attrs)?;
109    let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
110    let body = deserialize_shape_body(&container, &shape_attrs)?;
111
112    Ok(quote! {
113        const _: () = {
114            use #serde_shape as __serde_shape;
115
116            impl #impl_generics __serde_shape::DeserializeShape for #ident #ty_generics #where_clause {
117                fn deserialize_shape_in(
118                    context: &mut __serde_shape::DeserializeShapeContext,
119                ) -> __serde_shape::ShapeRef {
120                    #body
121                }
122            }
123        };
124    })
125}
126
127fn serde_shape_crate() -> syn::Result<TokenStream2> {
128    match crate_name("serde-shape") {
129        Ok(FoundCrate::Itself) => Ok(quote!(::serde_shape)),
130        Ok(FoundCrate::Name(name)) => {
131            let ident = Ident::new(&name.replace('-', "_"), Span::call_site());
132            Ok(quote!(::#ident))
133        }
134        Err(err) => Err(syn::Error::new(
135            Span::call_site(),
136            format!("serde-shape derive could not resolve the serde-shape crate: {err}"),
137        )),
138    }
139}
140
141fn parse_container<'a>(input: &'a DeriveInput, derive: Derive) -> syn::Result<ast::Container<'a>> {
142    let cx = Ctxt::new();
143    let private = Ident::new("__private", Span::call_site());
144    let Some(container) = ast::Container::from_ast(&cx, input, derive, &private) else {
145        cx.check()?;
146        return Err(syn::Error::new_spanned(
147            input,
148            "serde-shape could not parse this item",
149        ));
150    };
151    cx.check()?;
152
153    if matches!(derive, Derive::Serialize) {
154        let message = match container.attrs.identifier() {
155            attr::Identifier::No => None,
156            attr::Identifier::Field => Some("field identifiers cannot be serialized"),
157            attr::Identifier::Variant => Some("variant identifiers cannot be serialized"),
158        };
159        if let Some(message) = message {
160            return Err(syn::Error::new_spanned(input, message));
161        }
162    }
163
164    Ok(container)
165}
166
167fn validate_shape_attrs(container: &ast::Container<'_>) -> syn::Result<()> {
168    match &container.data {
169        ast::Data::Enum(variants) => {
170            for variant in variants {
171                validate_variant_shape_attrs(variant)?;
172                for field in &variant.fields {
173                    validate_field_shape_attrs(field)?;
174                }
175            }
176        }
177        ast::Data::Struct(_, fields) => {
178            for field in fields {
179                validate_field_shape_attrs(field)?;
180            }
181        }
182    }
183    Ok(())
184}
185
186fn validate_variant_shape_attrs(variant: &ast::Variant<'_>) -> syn::Result<()> {
187    let attrs = ShapeAttrs::parse(&variant.original.attrs)?;
188    if attrs.has_bound() {
189        return Err(syn::Error::new_spanned(
190            variant.original,
191            "serde_shape bounds are supported on containers, not variants",
192        ));
193    }
194    Ok(())
195}
196
197fn validate_field_shape_attrs(field: &ast::Field<'_>) -> syn::Result<()> {
198    let attrs = ShapeAttrs::parse(&field.original.attrs)?;
199    if attrs.has_bound() {
200        return Err(syn::Error::new_spanned(
201            field.original,
202            "serde_shape bounds are supported on containers, not fields",
203        ));
204    }
205    Ok(())
206}
207
208fn add_serialize_shape_bounds(
209    generics: &mut syn::Generics,
210    container: &ast::Container<'_>,
211    shape_attrs: &ShapeAttrs,
212) -> syn::Result<()> {
213    if let Some(predicates) = shape_attrs.serialize_bound() {
214        generics
215            .make_where_clause()
216            .predicates
217            .extend(predicates.iter().cloned());
218        return Ok(());
219    }
220    let type_params: BTreeSet<_> = generics
221        .type_params()
222        .map(|param| param.ident.to_string())
223        .collect();
224    if shape_attrs.serialize_with().is_some() {
225        return Ok(());
226    }
227    if let Some(ty) = container.attrs.type_into() {
228        if type_uses_params(ty, &type_params) {
229            generics
230                .make_where_clause()
231                .predicates
232                .push(parse_quote!(#ty: __serde_shape::SerializeShape));
233        }
234        return Ok(());
235    }
236
237    let mut field_bound_types = Vec::new();
238
239    match &container.data {
240        ast::Data::Struct(_, fields) => {
241            collect_serialize_field_bound_types(fields, &type_params, &mut field_bound_types)?;
242        }
243        ast::Data::Enum(variants) => {
244            for variant in variants {
245                let variant_shape_attrs = ShapeAttrs::parse(&variant.original.attrs)?;
246                if variant.attrs.skip_serializing()
247                    || variant.attrs.serialize_with().is_some()
248                    || variant_shape_attrs.serialize_with().is_some()
249                {
250                    continue;
251                }
252                collect_serialize_field_bound_types(
253                    &variant.fields,
254                    &type_params,
255                    &mut field_bound_types,
256                )?;
257            }
258        }
259    }
260
261    for ty in field_bound_types {
262        generics
263            .make_where_clause()
264            .predicates
265            .push(parse_quote!(#ty: __serde_shape::SerializeShape));
266    }
267    Ok(())
268}
269
270fn add_deserialize_shape_bounds(
271    generics: &mut syn::Generics,
272    container: &ast::Container<'_>,
273    shape_attrs: &ShapeAttrs,
274) -> syn::Result<()> {
275    if let Some(predicates) = shape_attrs.deserialize_bound() {
276        generics
277            .make_where_clause()
278            .predicates
279            .extend(predicates.iter().cloned());
280        return Ok(());
281    }
282    let type_params: BTreeSet<_> = generics
283        .type_params()
284        .map(|param| param.ident.to_string())
285        .collect();
286    if shape_attrs.deserialize_with().is_some() {
287        return Ok(());
288    }
289    if let Some(ty) = container
290        .attrs
291        .type_from()
292        .or_else(|| container.attrs.type_try_from())
293    {
294        if type_uses_params(ty, &type_params) {
295            generics
296                .make_where_clause()
297                .predicates
298                .push(parse_quote!(#ty: __serde_shape::DeserializeShape));
299        }
300        return Ok(());
301    }
302
303    let mut field_bound_types = Vec::new();
304
305    match &container.data {
306        ast::Data::Struct(_, fields) => {
307            collect_deserialize_field_bound_types(fields, &type_params, &mut field_bound_types)?;
308        }
309        ast::Data::Enum(variants) => {
310            for variant in variants {
311                let variant_shape_attrs = ShapeAttrs::parse(&variant.original.attrs)?;
312                if variant.attrs.skip_deserializing()
313                    || variant.attrs.deserialize_with().is_some()
314                    || variant_shape_attrs.deserialize_with().is_some()
315                {
316                    continue;
317                }
318                collect_deserialize_field_bound_types(
319                    &variant.fields,
320                    &type_params,
321                    &mut field_bound_types,
322                )?;
323            }
324        }
325    }
326
327    for ty in field_bound_types {
328        generics
329            .make_where_clause()
330            .predicates
331            .push(parse_quote!(#ty: __serde_shape::DeserializeShape));
332    }
333    Ok(())
334}
335
336fn collect_serialize_field_bound_types(
337    fields: &[ast::Field<'_>],
338    type_params: &BTreeSet<String>,
339    field_bound_types: &mut Vec<Type>,
340) -> syn::Result<()> {
341    for field in fields {
342        let shape_attrs = ShapeAttrs::parse(&field.original.attrs)?;
343        if field.attrs.skip_serializing() {
344            continue;
345        }
346        if shape_attrs.serialize_with().is_none() && field.attrs.serialize_with().is_none() {
347            collect_shape_bound_types(field.ty, type_params, field_bound_types);
348        }
349    }
350    Ok(())
351}
352
353fn collect_deserialize_field_bound_types(
354    fields: &[ast::Field<'_>],
355    type_params: &BTreeSet<String>,
356    field_bound_types: &mut Vec<Type>,
357) -> syn::Result<()> {
358    for field in fields {
359        let shape_attrs = ShapeAttrs::parse(&field.original.attrs)?;
360        if field.attrs.skip_deserializing() {
361            continue;
362        }
363        if shape_attrs.deserialize_with().is_none() && field.attrs.deserialize_with().is_none() {
364            collect_shape_bound_types(field.ty, type_params, field_bound_types);
365        }
366    }
367    Ok(())
368}
369
370fn collect_shape_bound_types(
371    ty: &Type,
372    type_params: &BTreeSet<String>,
373    field_bound_types: &mut Vec<Type>,
374) {
375    // Keep this selective traversal aligned with serde_derive::bound::with_bound. A general
376    // syn::visit::Visit would also enter macros and const expressions, where mentioning a type
377    // parameter does not imply that the field needs a Shape bound.
378    match ty {
379        Type::Array(ty) => {
380            if !is_zero_length(&ty.len) {
381                collect_shape_bound_types(&ty.elem, type_params, field_bound_types);
382            }
383        }
384        Type::FnPtr(ty) => {
385            for input in &ty.inputs {
386                collect_shape_bound_types(&input.ty, type_params, field_bound_types);
387            }
388            collect_return_type_params(&ty.output, type_params, field_bound_types);
389        }
390        Type::Group(ty) => collect_shape_bound_types(&ty.elem, type_params, field_bound_types),
391        Type::ImplTrait(ty) => {
392            collect_type_param_bounds(&ty.bounds, type_params, field_bound_types);
393        }
394        Type::Paren(ty) => collect_shape_bound_types(&ty.elem, type_params, field_bound_types),
395        Type::Path(ty) => {
396            if ty
397                .path
398                .segments
399                .last()
400                .is_some_and(|segment| segment.ident == "PhantomData")
401            {
402                return;
403            }
404
405            let is_associated_type = ty.qself.as_ref().is_some_and(|qself| {
406                let mut qself_bounds = Vec::new();
407                collect_shape_bound_types(&qself.ty, type_params, &mut qself_bounds);
408                !qself_bounds.is_empty()
409            }) || (ty.path.leading_colon.is_none()
410                && ty.path.segments.len() > 1
411                && ty
412                    .path
413                    .segments
414                    .first()
415                    .is_some_and(|segment| type_params.contains(&segment.ident.to_string())));
416
417            if is_associated_type {
418                push_bound_type(field_bound_types, Type::Path(ty.clone()));
419                return;
420            }
421
422            if ty.qself.is_none()
423                && ty.path.leading_colon.is_none()
424                && ty.path.segments.len() == 1
425                && ty
426                    .path
427                    .segments
428                    .first()
429                    .is_some_and(|segment| type_params.contains(&segment.ident.to_string()))
430            {
431                push_bound_type(field_bound_types, Type::Path(ty.clone()));
432                return;
433            }
434
435            if let Some(qself) = &ty.qself {
436                collect_shape_bound_types(&qself.ty, type_params, field_bound_types);
437            }
438
439            for segment in &ty.path.segments {
440                collect_path_arguments(&segment.arguments, type_params, field_bound_types);
441            }
442        }
443        Type::Ptr(ty) => collect_shape_bound_types(&ty.elem, type_params, field_bound_types),
444        Type::Reference(ty) => collect_shape_bound_types(&ty.elem, type_params, field_bound_types),
445        Type::Slice(ty) => collect_shape_bound_types(&ty.elem, type_params, field_bound_types),
446        Type::TraitObject(ty) => {
447            collect_type_param_bounds(&ty.bounds, type_params, field_bound_types);
448        }
449        Type::Tuple(ty) => {
450            for elem in &ty.elems {
451                collect_shape_bound_types(elem, type_params, field_bound_types);
452            }
453        }
454        Type::Infer(_) | Type::Macro(_) | Type::Never(_) | Type::Verbatim(_) => {}
455        _ => {}
456    }
457}
458
459fn is_zero_length(expr: &syn::Expr) -> bool {
460    match expr {
461        syn::Expr::Group(expr) => is_zero_length(&expr.expr),
462        syn::Expr::Lit(expr) => {
463            matches!(&expr.lit, syn::Lit::Int(value) if value
464                .base10_parse::<usize>()
465                .is_ok_and(|value| value == 0))
466        }
467        syn::Expr::Paren(expr) => is_zero_length(&expr.expr),
468        _ => false,
469    }
470}
471
472fn type_uses_params(ty: &Type, type_params: &BTreeSet<String>) -> bool {
473    let mut bound_types = Vec::new();
474    collect_shape_bound_types(ty, type_params, &mut bound_types);
475    !bound_types.is_empty()
476}
477
478fn collect_path_arguments(
479    arguments: &PathArguments,
480    type_params: &BTreeSet<String>,
481    field_bound_types: &mut Vec<Type>,
482) {
483    match arguments {
484        PathArguments::None => {}
485        PathArguments::AngleBracketed(arguments) => {
486            for argument in &arguments.args {
487                match argument {
488                    GenericArgument::Type(ty) => {
489                        collect_shape_bound_types(ty, type_params, field_bound_types);
490                    }
491                    GenericArgument::AssocType(assoc) => {
492                        collect_shape_bound_types(&assoc.ty, type_params, field_bound_types);
493                    }
494                    GenericArgument::Constraint(constraint) => {
495                        collect_type_param_bounds(
496                            &constraint.bounds,
497                            type_params,
498                            field_bound_types,
499                        );
500                    }
501                    GenericArgument::Lifetime(_)
502                    | GenericArgument::Const(_)
503                    | GenericArgument::AssocConst(_) => {}
504                    _ => {}
505                }
506            }
507        }
508        PathArguments::Parenthesized(arguments) => {
509            for input in &arguments.inputs {
510                collect_shape_bound_types(&input.ty, type_params, field_bound_types);
511            }
512            collect_return_type_params(&arguments.output, type_params, field_bound_types);
513        }
514    }
515}
516
517fn collect_type_param_bounds(
518    bounds: &syn::punctuated::Punctuated<TypeParamBound, syn::Token![+]>,
519    type_params: &BTreeSet<String>,
520    field_bound_types: &mut Vec<Type>,
521) {
522    for bound in bounds {
523        if let TypeParamBound::Trait(bound) = bound {
524            for segment in &bound.path.segments {
525                collect_path_arguments(&segment.arguments, type_params, field_bound_types);
526            }
527        }
528    }
529}
530
531fn collect_return_type_params(
532    return_type: &ReturnType,
533    type_params: &BTreeSet<String>,
534    field_bound_types: &mut Vec<Type>,
535) {
536    if let ReturnType::Type(_, ty) = return_type {
537        collect_shape_bound_types(ty, type_params, field_bound_types);
538    }
539}
540
541fn push_bound_type(field_bound_types: &mut Vec<Type>, ty: Type) {
542    let tokens = ty.to_token_stream().to_string();
543    if field_bound_types
544        .iter()
545        .all(|existing| existing.to_token_stream().to_string() != tokens)
546    {
547        field_bound_types.push(ty);
548    }
549}
550
551fn serialize_shape_body(
552    container: &ast::Container<'_>,
553    shape_attrs: &ShapeAttrs,
554) -> syn::Result<TokenStream2> {
555    if let Some(function) = shape_attrs.serialize_with() {
556        return Ok(quote!(#function(context)));
557    }
558    if let Some(ty) = container.attrs.type_into() {
559        return Ok(quote!(<#ty as __serde_shape::SerializeShape>::serialize_shape_in(context)));
560    }
561
562    let name = lit_name(container.attrs.name().serialize_name());
563    let description = description(&container.original.attrs);
564    let description = option_lit(description.as_deref());
565    let kind = serialize_definition_kind(container)?;
566
567    Ok(quote! {
568        context.define_named_type_with_description(
569            __serde_shape::TypeName::of::<Self>(#name),
570            #description,
571            |context| {
572                #kind
573            },
574        )
575    })
576}
577
578fn deserialize_shape_body(
579    container: &ast::Container<'_>,
580    shape_attrs: &ShapeAttrs,
581) -> syn::Result<TokenStream2> {
582    if let Some(function) = shape_attrs.deserialize_with() {
583        return Ok(quote!(#function(context)));
584    }
585    if let Some(ty) = container
586        .attrs
587        .type_from()
588        .or_else(|| container.attrs.type_try_from())
589    {
590        return Ok(quote!(<#ty as __serde_shape::DeserializeShape>::deserialize_shape_in(context)));
591    }
592
593    let name = lit_name(container.attrs.name().deserialize_name());
594    let description = description(&container.original.attrs);
595    let description = option_lit(description.as_deref());
596    let kind = deserialize_definition_kind(container)?;
597
598    Ok(quote! {
599        context.define_named_type_with_description(
600            __serde_shape::TypeName::of::<Self>(#name),
601            #description,
602            |context| {
603                #kind
604            },
605        )
606    })
607}
608
609fn serialize_definition_kind(container: &ast::Container<'_>) -> syn::Result<TokenStream2> {
610    let attributes = serialize_container_attributes(&container.attrs);
611    Ok(match &container.data {
612        ast::Data::Struct(style, fields) => {
613            let style = fields_style(*style);
614            let fields = fields
615                .iter()
616                .map(serialize_field_shape)
617                .collect::<syn::Result<Vec<_>>>()?;
618            quote! {
619                __serde_shape::SerializeDefinitionKind::Struct(__serde_shape::SerializeStructShape {
620                    style: #style,
621                    fields: __serde_shape::__private::vec![#(#fields),*],
622                    attributes: #attributes,
623                })
624            }
625        }
626        ast::Data::Enum(variants) => {
627            let repr = tagging(container.attrs.tag());
628            let variants = variants
629                .iter()
630                .map(serialize_variant_shape)
631                .collect::<syn::Result<Vec<_>>>()?;
632            quote! {
633                __serde_shape::SerializeDefinitionKind::Enum(__serde_shape::SerializeEnumShape {
634                    repr: #repr,
635                    variants: __serde_shape::__private::vec![#(#variants),*],
636                    attributes: #attributes,
637                })
638            }
639        }
640    })
641}
642
643fn deserialize_definition_kind(container: &ast::Container<'_>) -> syn::Result<TokenStream2> {
644    let attributes = deserialize_container_attributes(&container.attrs);
645    Ok(match &container.data {
646        ast::Data::Struct(style, fields) => {
647            let style = fields_style(*style);
648            let fields = fields
649                .iter()
650                .map(deserialize_field_shape)
651                .collect::<syn::Result<Vec<_>>>()?;
652            quote! {
653                __serde_shape::DeserializeDefinitionKind::Struct(__serde_shape::DeserializeStructShape {
654                    style: #style,
655                    fields: __serde_shape::__private::vec![#(#fields),*],
656                    attributes: #attributes,
657                })
658            }
659        }
660        ast::Data::Enum(variants) => {
661            let repr = deserialize_tagging(&container.attrs);
662            let variants = variants
663                .iter()
664                .map(deserialize_variant_shape)
665                .collect::<syn::Result<Vec<_>>>()?;
666            quote! {
667                __serde_shape::DeserializeDefinitionKind::Enum(__serde_shape::DeserializeEnumShape {
668                    repr: #repr,
669                    variants: __serde_shape::__private::vec![#(#variants),*],
670                    attributes: #attributes,
671                })
672            }
673        }
674    })
675}
676
677fn serialize_container_attributes(attrs: &attr::Container) -> TokenStream2 {
678    let non_exhaustive = attrs.non_exhaustive();
679
680    quote! {
681        __serde_shape::SerializeContainerAttributes {
682            non_exhaustive: #non_exhaustive,
683        }
684    }
685}
686
687fn deserialize_container_attributes(attrs: &attr::Container) -> TokenStream2 {
688    let deny_unknown_fields = attrs.deny_unknown_fields();
689    let default = default_shape(attrs.default());
690    let expecting = option_lit(attrs.expecting());
691    let non_exhaustive = attrs.non_exhaustive();
692
693    quote! {
694        __serde_shape::DeserializeContainerAttributes {
695            deny_unknown_fields: #deny_unknown_fields,
696            default: #default,
697            expecting: #expecting,
698            non_exhaustive: #non_exhaustive,
699        }
700    }
701}
702
703fn serialize_variant_shape(variant: &ast::Variant<'_>) -> syn::Result<TokenStream2> {
704    let shape_attrs = ShapeAttrs::parse(&variant.original.attrs)?;
705    let rust_name = lit(variant.ident.to_string());
706    let name = lit_name(variant.attrs.name().serialize_name());
707    let description = description(&variant.original.attrs);
708    let description = option_lit(description.as_deref());
709    let style = fields_style(variant.style);
710    let skip = variant.attrs.skip_serializing();
711    let untagged = variant.attrs.untagged();
712    let content = if skip {
713        quote!(__serde_shape::SerializeVariantContent::Omitted)
714    } else if let Some(function) = shape_attrs.serialize_with() {
715        quote!(__serde_shape::SerializeVariantContent::Shape(#function(context)))
716    } else if let Some(custom_serializer) = variant.attrs.serialize_with() {
717        let detail = option_path(Some(custom_serializer));
718        quote! {
719            __serde_shape::SerializeVariantContent::Custom(__serde_shape::OpaqueShape {
720                type_name: ::core::any::type_name::<Self>(),
721                reason: __serde_shape::OpaqueReason::CustomSerializer,
722                detail: #detail,
723            })
724        }
725    } else {
726        let fields = variant
727            .fields
728            .iter()
729            .map(serialize_field_shape)
730            .collect::<syn::Result<Vec<_>>>()?;
731        quote! {
732            __serde_shape::SerializeVariantContent::Fields(
733                __serde_shape::__private::vec![#(#fields),*],
734            )
735        }
736    };
737
738    Ok(quote! {
739        __serde_shape::SerializeVariantShape {
740            rust_name: #rust_name,
741            name: #name,
742            description: #description,
743            style: #style,
744            content: #content,
745            untagged: #untagged,
746        }
747    })
748}
749
750fn deserialize_variant_shape(variant: &ast::Variant<'_>) -> syn::Result<TokenStream2> {
751    let shape_attrs = ShapeAttrs::parse(&variant.original.attrs)?;
752    let rust_name = lit(variant.ident.to_string());
753    let name = lit_name(variant.attrs.name().deserialize_name());
754    let aliases = aliases(variant.attrs.aliases());
755    let description = description(&variant.original.attrs);
756    let description = option_lit(description.as_deref());
757    let style = fields_style(variant.style);
758    let skip = variant.attrs.skip_deserializing();
759    let other = variant.attrs.other();
760    let untagged = variant.attrs.untagged();
761    let content = if skip {
762        quote!(__serde_shape::DeserializeVariantContent::Omitted)
763    } else if let Some(function) = shape_attrs.deserialize_with() {
764        quote!(__serde_shape::DeserializeVariantContent::Shape(#function(context)))
765    } else if let Some(custom_deserializer) = variant.attrs.deserialize_with() {
766        let detail = option_path(Some(custom_deserializer));
767        quote! {
768            __serde_shape::DeserializeVariantContent::Custom(__serde_shape::OpaqueShape {
769                type_name: ::core::any::type_name::<Self>(),
770                reason: __serde_shape::OpaqueReason::CustomDeserializer,
771                detail: #detail,
772            })
773        }
774    } else {
775        let fields = variant
776            .fields
777            .iter()
778            .map(deserialize_field_shape)
779            .collect::<syn::Result<Vec<_>>>()?;
780        quote! {
781            __serde_shape::DeserializeVariantContent::Fields(
782                __serde_shape::__private::vec![#(#fields),*],
783            )
784        }
785    };
786
787    Ok(quote! {
788        __serde_shape::DeserializeVariantShape {
789            rust_name: #rust_name,
790            name: #name,
791            aliases: #aliases,
792            description: #description,
793            style: #style,
794            content: #content,
795            other: #other,
796            untagged: #untagged,
797        }
798    })
799}
800
801fn serialize_field_shape(field: &ast::Field<'_>) -> syn::Result<TokenStream2> {
802    let shape_attrs = ShapeAttrs::parse(&field.original.attrs)?;
803    let member = field_member(&field.member);
804    let name = lit_name(field.attrs.name().serialize_name());
805    let description = description(&field.original.attrs);
806    let description = option_lit(description.as_deref());
807    let skip = field.attrs.skip_serializing();
808    let skip_if = option_path(field.attrs.skip_serializing_if());
809    let flatten = field.attrs.flatten();
810    let transparent = field.attrs.transparent();
811    let ty = field.ty;
812    let wire_shape = if skip {
813        quote!(__serde_shape::FieldWireShape::Omitted)
814    } else {
815        let value_shape = if let Some(function) = shape_attrs.serialize_with() {
816            quote!(#function(context))
817        } else if let Some(custom_serializer) = field.attrs.serialize_with() {
818            let detail = option_path(Some(custom_serializer));
819            quote! {
820                __serde_shape::ShapeRef::Opaque(__serde_shape::OpaqueShape {
821                    type_name: ::core::any::type_name::<#ty>(),
822                    reason: __serde_shape::OpaqueReason::CustomSerializer,
823                    detail: #detail,
824                })
825            }
826        } else {
827            quote!(<#ty as __serde_shape::SerializeShape>::serialize_shape_in(context))
828        };
829
830        if transparent {
831            quote!(__serde_shape::FieldWireShape::Inline(#value_shape))
832        } else if flatten {
833            quote!(__serde_shape::FieldWireShape::Flatten(#value_shape))
834        } else {
835            quote!(__serde_shape::FieldWireShape::Value(#value_shape))
836        }
837    };
838
839    Ok(quote! {
840        __serde_shape::SerializeFieldShape {
841            member: #member,
842            name: #name,
843            description: #description,
844            wire_shape: #wire_shape,
845            skip_if: #skip_if,
846        }
847    })
848}
849
850fn deserialize_field_shape(field: &ast::Field<'_>) -> syn::Result<TokenStream2> {
851    let shape_attrs = ShapeAttrs::parse(&field.original.attrs)?;
852    let borrowed_cow_shape = borrowed_cow_shape(field)?;
853    let member = field_member(&field.member);
854    let name = lit_name(field.attrs.name().deserialize_name());
855    let aliases = aliases(field.attrs.aliases());
856    let description = description(&field.original.attrs);
857    let description = option_lit(description.as_deref());
858    let skip = field.attrs.skip_deserializing();
859    let default = default_shape(field.attrs.default());
860    let flatten = field.attrs.flatten();
861    let transparent = field.attrs.transparent();
862    let ty = field.ty;
863    let wire_shape = if skip {
864        quote!(__serde_shape::FieldWireShape::Omitted)
865    } else {
866        let value_shape = if let Some(function) = shape_attrs.deserialize_with() {
867            quote!(#function(context))
868        } else if let Some(shape) = borrowed_cow_shape {
869            shape
870        } else if let Some(custom_deserializer) = field.attrs.deserialize_with() {
871            let detail = option_path(Some(custom_deserializer));
872            quote! {
873                __serde_shape::ShapeRef::Opaque(__serde_shape::OpaqueShape {
874                    type_name: ::core::any::type_name::<#ty>(),
875                    reason: __serde_shape::OpaqueReason::CustomDeserializer,
876                    detail: #detail,
877                })
878            }
879        } else {
880            quote!(<#ty as __serde_shape::DeserializeShape>::deserialize_shape_in(context))
881        };
882
883        if transparent {
884            quote!(__serde_shape::FieldWireShape::Inline(#value_shape))
885        } else if flatten {
886            quote!(__serde_shape::FieldWireShape::Flatten(#value_shape))
887        } else {
888            quote!(__serde_shape::FieldWireShape::Value(#value_shape))
889        }
890    };
891
892    Ok(quote! {
893        __serde_shape::DeserializeFieldShape {
894            member: #member,
895            name: #name,
896            aliases: #aliases,
897            description: #description,
898            wire_shape: #wire_shape,
899            default: #default,
900        }
901    })
902}
903
904fn field_member(member: &Member) -> TokenStream2 {
905    match member {
906        Member::Named(ident) => {
907            let ident = lit(ident.to_string());
908            quote!(__serde_shape::FieldMember::Named(#ident))
909        }
910        Member::Unnamed(index) => {
911            let index = index.index as usize;
912            quote!(__serde_shape::FieldMember::Unnamed(#index))
913        }
914    }
915}
916
917fn fields_style(style: ast::Style) -> TokenStream2 {
918    match style {
919        ast::Style::Struct => quote!(__serde_shape::FieldsStyle::Struct),
920        ast::Style::Tuple => quote!(__serde_shape::FieldsStyle::Tuple),
921        ast::Style::Newtype => quote!(__serde_shape::FieldsStyle::Newtype),
922        ast::Style::Unit => quote!(__serde_shape::FieldsStyle::Unit),
923    }
924}
925
926fn tagging(tag: &attr::TagType) -> TokenStream2 {
927    match tag {
928        attr::TagType::External => quote!(__serde_shape::Tagging::External),
929        attr::TagType::Internal { tag } => {
930            let tag = lit(tag);
931            quote!(__serde_shape::Tagging::Internal { tag: #tag })
932        }
933        attr::TagType::Adjacent { tag, content } => {
934            let tag = lit(tag);
935            let content = lit(content);
936            quote!(__serde_shape::Tagging::Adjacent {
937                tag: #tag,
938                content: #content,
939            })
940        }
941        attr::TagType::None => quote!(__serde_shape::Tagging::Untagged),
942    }
943}
944
945fn deserialize_tagging(attrs: &attr::Container) -> TokenStream2 {
946    match attrs.identifier() {
947        attr::Identifier::No => tagging(attrs.tag()),
948        attr::Identifier::Field => quote!(__serde_shape::Tagging::FieldIdentifier),
949        attr::Identifier::Variant => quote!(__serde_shape::Tagging::VariantIdentifier),
950    }
951}
952
953fn default_shape(default: &attr::Default) -> TokenStream2 {
954    match default {
955        attr::Default::None => quote!(__serde_shape::DefaultShape::None),
956        attr::Default::Default => quote!(__serde_shape::DefaultShape::Default),
957        attr::Default::Path(path) => {
958            let path = lit(path.to_token_stream().to_string());
959            quote!(__serde_shape::DefaultShape::Path(#path))
960        }
961    }
962}
963
964fn aliases(aliases: &BTreeSet<Name>) -> TokenStream2 {
965    let aliases = aliases.iter().map(lit_name);
966    quote!(__serde_shape::__private::vec![#(#aliases),*])
967}
968
969fn borrowed_cow_shape(field: &ast::Field<'_>) -> syn::Result<Option<TokenStream2>> {
970    // serde_derive_internals models borrowed Cow fields as custom deserializers internally. Read
971    // the source-level contract instead, so this derive does not depend on Serde's private helper
972    // path. An explicit user deserializer still takes precedence over the built-in Cow behavior.
973    if field.attrs.borrowed_lifetimes().is_empty()
974        || has_explicit_serde_deserializer(&field.original.attrs)?
975    {
976        return Ok(None);
977    }
978
979    let Some(element) = cow_element_type(field.ty) else {
980        return Ok(None);
981    };
982    if is_primitive_type(element, "str") {
983        Ok(Some(quote!(__serde_shape::ShapeRef::String)))
984    } else if is_byte_slice(element) {
985        Ok(Some(quote!(__serde_shape::ShapeRef::Bytes)))
986    } else {
987        Ok(None)
988    }
989}
990
991fn has_explicit_serde_deserializer(attrs: &[syn::Attribute]) -> syn::Result<bool> {
992    for attr in attrs.iter().filter(|attr| attr.path().is_ident("serde")) {
993        let metas = attr.parse_args_with(
994            syn::punctuated::Punctuated::<syn::Meta, syn::Token![,]>::parse_terminated,
995        )?;
996        if metas
997            .iter()
998            .any(|meta| meta.path().is_ident("deserialize_with") || meta.path().is_ident("with"))
999        {
1000            return Ok(true);
1001        }
1002    }
1003    Ok(false)
1004}
1005
1006fn cow_element_type(ty: &Type) -> Option<&Type> {
1007    let Type::Path(ty) = ungroup(ty) else {
1008        return None;
1009    };
1010    let segment = ty.path.segments.last()?;
1011    let PathArguments::AngleBracketed(arguments) = &segment.arguments else {
1012        return None;
1013    };
1014    let mut arguments = arguments.args.iter();
1015    match (arguments.next(), arguments.next(), arguments.next()) {
1016        (Some(GenericArgument::Lifetime(_)), Some(GenericArgument::Type(element)), None)
1017            if segment.ident == "Cow" =>
1018        {
1019            Some(element)
1020        }
1021        _ => None,
1022    }
1023}
1024
1025fn is_byte_slice(ty: &Type) -> bool {
1026    match ungroup(ty) {
1027        Type::Slice(slice) => is_primitive_type(&slice.elem, "u8"),
1028        _ => false,
1029    }
1030}
1031
1032fn is_primitive_type(ty: &Type, name: &str) -> bool {
1033    let Type::Path(ty) = ungroup(ty) else {
1034        return false;
1035    };
1036    ty.qself.is_none()
1037        && ty.path.leading_colon.is_none()
1038        && ty.path.segments.len() == 1
1039        && ty.path.segments[0].ident == name
1040        && ty.path.segments[0].arguments.is_empty()
1041}
1042
1043fn lit_name(value: &Name) -> LitStr {
1044    LitStr::new(&value.value, value.span)
1045}
1046
1047fn option_lit(value: Option<&str>) -> TokenStream2 {
1048    match value {
1049        Some(value) => {
1050            let value = lit(value);
1051            quote!(::core::option::Option::Some(#value))
1052        }
1053        None => quote!(::core::option::Option::None),
1054    }
1055}
1056
1057fn option_path(value: Option<&syn::ExprPath>) -> TokenStream2 {
1058    match value {
1059        Some(value) => {
1060            let value = lit(value.to_token_stream().to_string());
1061            quote!(::core::option::Option::Some(#value))
1062        }
1063        None => quote!(::core::option::Option::None),
1064    }
1065}
1066
1067fn lit(value: impl AsRef<str>) -> LitStr {
1068    LitStr::new(value.as_ref(), proc_macro2::Span::call_site())
1069}