zen-macros 0.55.1

Zen Helper Macros
Documentation
use proc_macro::TokenStream;
use quote::quote;
use serde_derive_internals::attr::TagType;
use syn::parse_macro_input;

pub fn to_variable_impl(input: TokenStream) -> TokenStream {
    let mut input = parse_macro_input!(input as syn::DeriveInput);

    serde_derive_internals::replace_receiver(&mut input);

    let ctxt = serde_derive_internals::Ctxt::new();
    let container = match serde_derive_internals::ast::Container::from_ast(
        &ctxt,
        &input,
        serde_derive_internals::Derive::Serialize,
    ) {
        Some(container) => container,
        None => return ctxt.check().unwrap_err().into_compile_error().into(),
    };

    if let Err(err) = ctxt.check() {
        return err.into_compile_error().into();
    }

    let ident = &container.ident;
    let (impl_generics, ty_generics, where_clause) = container.generics.split_for_impl();

    let body = match &container.data {
        serde_derive_internals::ast::Data::Struct(_, fields) => generate_struct_body(fields),
        serde_derive_internals::ast::Data::Enum(variants) => {
            generate_enum_body(variants, &container)
        }
    };

    let impl_block = quote! {
        #[automatically_derived]
        impl #impl_generics _ToVariable for #ident #ty_generics #where_clause {
            fn to_variable(&self) -> _Variable {
                #body
            }
        }
    };

    quote! {
        #[doc(hidden)]
        #[allow(non_upper_case_globals, unused_attributes, unused_qualifications, clippy::absolute_paths)]
        const _: () = {
            extern crate zen_expression as _zen_expression;

            use _zen_expression::variable::{Variable as _Variable, VariableMap as _VariableMap, VariableMapExt, ToVariable as _ToVariable};
            use ::std::rc::Rc as _Rc;

            #impl_block
        };
    }.into()
}

fn generate_struct_body(fields: &[serde_derive_internals::ast::Field]) -> proc_macro2::TokenStream {
    let active_fields: Vec<_> = fields
        .iter()
        .filter(|field| !field.attrs.skip_serializing())
        .collect();

    let field_count = active_fields.len();

    let field_mappings = active_fields.iter().map(|field| {
        let field_ident = match &field.member {
            syn::Member::Named(ident) => ident,
            syn::Member::Unnamed(_) => panic!("ToVariable only supports named fields"),
        };

        let serialized_name = field.attrs.name().serialize_name();

        quote! {
            map.insert(
                _Rc::from(#serialized_name),
                self.#field_ident.to_variable()
            );
        }
    });

    quote! {
        let mut map = _VariableMap::with_capacity(#field_count);
        #(#field_mappings)*
        _Variable::from_object(map)
    }
}

fn generate_enum_body(
    variants: &[serde_derive_internals::ast::Variant],
    container: &serde_derive_internals::ast::Container,
) -> proc_macro2::TokenStream {
    let enum_ident = &container.ident;

    let active_variants: Vec<_> = variants
        .iter()
        .filter(|variant| !variant.attrs.skip_serializing())
        .collect();

    match container.attrs.tag() {
        TagType::None => {
            let variant_arms = active_variants
                .iter()
                .map(|variant| generate_untagged_variant_arm(enum_ident, variant));

            quote! {
                match self {
                    #(#variant_arms)*
                }
            }
        }
        _ => {
            let variant_arms = active_variants
                .iter()
                .map(|variant| generate_variant_arm(enum_ident, variant, container));

            quote! {
                match self {
                    #(#variant_arms)*
                }
            }
        }
    }
}

fn generate_untagged_variant_arm(
    enum_ident: &syn::Ident,
    variant: &serde_derive_internals::ast::Variant,
) -> proc_macro2::TokenStream {
    let variant_ident = &variant.ident;

    match variant.style {
        serde_derive_internals::ast::Style::Unit => {
            // Unit variants in untagged enums typically serialize as null
            quote! {
                #enum_ident::#variant_ident => {
                    _Variable::Null
                }
            }
        }

        serde_derive_internals::ast::Style::Newtype => {
            // Newtype variants serialize directly as their inner value
            quote! {
                #enum_ident::#variant_ident(value) => {
                    value.to_variable()
                }
            }
        }

        serde_derive_internals::ast::Style::Tuple => {
            let field_count = variant.fields.len();
            let field_patterns: Vec<_> = (0..field_count)
                .map(|i| quote::format_ident!("field_{}", i))
                .collect();

            if field_count == 1 {
                quote! {
                    #enum_ident::#variant_ident(#(#field_patterns),*) => {
                        (#(#field_patterns)*).to_variable()
                    }
                }
            } else {
                quote! {
                    #enum_ident::#variant_ident(#(#field_patterns),*) => {
                        let mut vec = Vec::with_capacity(#field_count);
                        #(vec.push((#field_patterns).to_variable());)*
                        _Variable::from_array(vec)
                    }
                }
            }
        }

        serde_derive_internals::ast::Style::Struct => {
            let active_fields: Vec<_> = variant
                .fields
                .iter()
                .filter(|field| !field.attrs.skip_serializing())
                .collect();

            let field_mappings = active_fields.iter().map(|field| {
                let field_ident = match &field.member {
                    syn::Member::Named(ident) => ident,
                    syn::Member::Unnamed(_) => panic!("Unexpected unnamed field in struct variant"),
                };

                let field_name = field.attrs.name().serialize_name();

                quote! {
                    map.insert(_Rc::from(#field_name), #field_ident.to_variable());
                }
            });

            let field_patterns = active_fields.iter().map(|field| match &field.member {
                syn::Member::Named(ident) => quote! { #ident },
                syn::Member::Unnamed(_) => panic!("Unexpected unnamed field in struct variant"),
            });

            let field_count = active_fields.len();
            quote! {
                #enum_ident::#variant_ident { #(#field_patterns),* } => {
                    let mut map = _VariableMap::with_capacity(#field_count);
                    #(#field_mappings)*
                    _Variable::from_object(map)
                }
            }
        }
    }
}

fn generate_variant_arm(
    enum_ident: &syn::Ident,
    variant: &serde_derive_internals::ast::Variant,
    container: &serde_derive_internals::ast::Container,
) -> proc_macro2::TokenStream {
    let variant_ident = &variant.ident;

    let variant_name = variant.attrs.name().serialize_name();
    let rename_rule = container.attrs.rename_all_rules().serialize;
    let type_key = rename_rule.apply_to_field("type");
    let value_key = rename_rule.apply_to_field("value");

    match variant.style {
        serde_derive_internals::ast::Style::Unit => {
            quote! {
                #enum_ident::#variant_ident => {
                    _Variable::String(_Rc::from(#variant_name))
                }
            }
        }

        serde_derive_internals::ast::Style::Newtype => {
            quote! {
                #enum_ident::#variant_ident(value) => {
                    let mut map = _VariableMap::with_capacity(2);
                    map.insert(_Rc::from(#type_key), _Variable::String(_Rc::from(#variant_name)));
                    map.insert(_Rc::from(#value_key), value.to_variable());
                    _Variable::from_object(map)
                }
            }
        }

        serde_derive_internals::ast::Style::Tuple => {
            let field_count = variant.fields.len();
            let field_patterns: Vec<_> = (0..field_count)
                .map(|i| quote::format_ident!("field_{}", i))
                .collect();

            if field_count == 1 {
                quote! {
                    #enum_ident::#variant_ident(#(#field_patterns),*) => {
                        let mut map = _VariableMap::with_capacity(2);
                        map.insert(_Rc::from(#type_key), _Variable::String(_Rc::from(#variant_name)));
                        map.insert(_Rc::from(#value_key), (#(#field_patterns)*).to_variable());
                        _Variable::from_object(map)
                    }
                }
            } else {
                let field_mappings = field_patterns.iter().enumerate().map(|(i, pattern)| {
                    let field_key = rename_rule.apply_to_field(&format!("field_{}", i));
                    quote! {
                        map.insert(_Rc::from(#field_key), (#pattern).to_variable());
                    }
                });

                quote! {
                    #enum_ident::#variant_ident(#(#field_patterns),*) => {
                        let mut map = _VariableMap::with_capacity(#field_count + 1);
                        map.insert(_Rc::from(#type_key), _Variable::String(_Rc::from(#variant_name)));
                        #(#field_mappings)*
                        _Variable::from_object(map)
                    }
                }
            }
        }

        serde_derive_internals::ast::Style::Struct => {
            let active_fields: Vec<_> = variant
                .fields
                .iter()
                .filter(|field| !field.attrs.skip_serializing())
                .collect();

            let field_mappings = active_fields.iter().map(|field| {
                let field_ident = match &field.member {
                    syn::Member::Named(ident) => ident,
                    syn::Member::Unnamed(_) => panic!("Unexpected unnamed field in struct variant"),
                };

                let field_name = field.attrs.name().serialize_name();

                quote! {
                    map.insert(_Rc::from(#field_name), #field_ident.to_variable());
                }
            });

            let field_patterns = active_fields.iter().map(|field| match &field.member {
                syn::Member::Named(ident) => quote! { #ident },
                syn::Member::Unnamed(_) => panic!("Unexpected unnamed field in struct variant"),
            });

            let field_count = active_fields.len() + 1;
            quote! {
                #enum_ident::#variant_ident { #(#field_patterns),* } => {
                    let mut map = _VariableMap::with_capacity(#field_count);
                    map.insert(_Rc::from(#type_key), _Variable::String(_Rc::from(#variant_name)));
                    #(#field_mappings)*
                    _Variable::from_object(map)
                }
            }
        }
    }
}