xmlity-derive 0.0.9

Derive proc-macros for xmlity.
Documentation
#![allow(clippy::type_complexity)]

use std::borrow::Cow;

use proc_macro2::Span;
use syn::{parse_quote, Ident, Lifetime, LifetimeParam, Stmt, Type};

use crate::{
    common::{non_bound_generics, ExpandedName, FieldIdent, RecordInput, StructTypeWithFields},
    de::builders::{DeserializeBuilder, VisitorBuilder, VisitorBuilderExt},
    options::{
        records::fields::{ChildOpts, ElementOpts, FieldOpts, GroupOpts, ValueOpts},
        AllowUnknown, ElementOrder, Extendable, FieldWithOpts, IgnoreComments, IgnoreWhitespace,
        WithExpandedNameExt,
    },
    DeriveError,
};

use super::elements::RecordDeserializeElementBuilder;

pub struct DeserializeSingleChildElementBuilder<'a> {
    pub ident: &'a syn::Ident,
    pub generics: &'a syn::Generics,
    pub required_expanded_name: Option<ExpandedName<'static>>,
    pub item_type: &'a syn::Type,
    pub extendable: Extendable,
    pub group: bool,
    pub default: bool,
    pub default_with: Option<syn::Path>,
}

impl ElementOpts {
    pub fn to_builder<'a>(
        &'a self,
        field_ident: &'a FieldIdent,
        ident: &'a Ident,
        generics: &'a syn::Generics,
        item_type: &'a Type,
    ) -> DeserializeSingleChildElementBuilder<'a> {
        DeserializeSingleChildElementBuilder {
            ident,
            generics,
            required_expanded_name: Some(
                self.expanded_name(field_ident.to_named_ident().to_string().as_str())
                    .into_owned(),
            ),
            item_type,
            default: self.default,
            default_with: self.default_with.clone(),
            extendable: self.extendable,
            group: self.group,
        }
    }
}

impl DeserializeSingleChildElementBuilder<'_> {
    fn value_access_ident(&self) -> Ident {
        Ident::new("__value", Span::call_site())
    }

    pub fn struct_definition(&self) -> syn::ItemStruct {
        let Self {
            ident, item_type, ..
        } = self;

        let value_access_ident = self.value_access_ident();

        parse_quote! {
            struct #ident {
                #value_access_ident: #item_type,
            }
        }
    }

    pub fn unwrap_expression(&self) -> impl Fn(&syn::Expr) -> syn::Expr + Clone {
        let value_access_ident = self.value_access_ident();

        move |value_expr: &syn::Expr| {
            parse_quote! {
                #value_expr.#value_access_ident
            }
        }
    }
}

impl VisitorBuilder for DeserializeSingleChildElementBuilder<'_> {
    fn visit_element_fn_body(
        &self,
        visitor_lifetime: &Lifetime,
        element_access_ident: &Ident,
        access_type: &Type,
    ) -> Result<Option<Vec<Stmt>>, DeriveError> {
        let ident = self.ident;

        let input = RecordInput {
            impl_for_ident: Cow::Borrowed(self.ident),
            constructor_path: Cow::Owned(parse_quote!(#ident)),
            result_type: Cow::Borrowed(self.item_type),
            generics: Cow::Owned(parse_quote!()),
            wrapper_function: std::convert::identity,
            record_path: Cow::Owned(parse_quote!(self)),
            fields: StructTypeWithFields::Named(vec![FieldWithOpts {
                field_ident: self.value_access_ident(),
                field_type: self.item_type.clone(),
                options: if self.group {
                    FieldOpts::Group(GroupOpts {})
                } else {
                    FieldOpts::Value(ChildOpts::Value(ValueOpts {
                        default: self.default,
                        default_with: self.default_with.clone(),
                        extendable: self.extendable,
                        skip_serializing_if: None,
                    }))
                },
            }]),
            sub_path_ident: None,
            fallable_deconstruction: false,
        };

        let builder = RecordDeserializeElementBuilder {
            input: &input,
            ignore_whitespace: IgnoreWhitespace::default(),
            ignore_comments: IgnoreComments::default(),
            required_expanded_name: self.required_expanded_name.clone(),
            allow_unknown_attributes: AllowUnknown::default(),
            allow_unknown_children: AllowUnknown::default(),
            children_order: ElementOrder::None,
            attribute_order: ElementOrder::None,
        };

        builder.visit_element_fn_body(visitor_lifetime, element_access_ident, access_type)
    }

    fn visitor_definition(&self) -> Result<syn::ItemStruct, DeriveError> {
        let Self {
            ident, generics, ..
        } = &self;
        let non_bound_generics = non_bound_generics(generics);

        let mut deserialize_generics = (*generics).to_owned();

        let visitor_ident = Ident::new("__Visitor", Span::mixed_site());
        let visitor_lifetime = Lifetime::new("'__visitor", Span::mixed_site());

        deserialize_generics.params.insert(
            0,
            syn::GenericParam::Lifetime(LifetimeParam::new(visitor_lifetime.clone())),
        );

        Ok(parse_quote! {
            struct #visitor_ident #deserialize_generics {
                marker: ::core::marker::PhantomData<#ident #non_bound_generics>,
                lifetime: ::core::marker::PhantomData<&#visitor_lifetime ()>,
            }
        })
    }

    fn visitor_ident(&self) -> Cow<'_, Ident> {
        Cow::Borrowed(self.ident)
    }

    fn visitor_generics(&self) -> Cow<'_, syn::Generics> {
        Cow::Borrowed(self.generics)
    }
}

impl DeserializeBuilder for DeserializeSingleChildElementBuilder<'_> {
    fn deserialize_fn_body(
        &self,

        deserializer_ident: &Ident,
        _deserialize_lifetime: &Lifetime,
    ) -> Result<Vec<Stmt>, DeriveError> {
        let formatter_expecting = format!("struct {}", self.ident);

        let visitor_ident = Ident::new("__Visitor", Span::mixed_site());

        let visitor_def = self.visitor_definition()?;
        let visitor_trait_impl = self.visitor_trait_impl(&visitor_ident, &formatter_expecting)?;

        Ok(parse_quote! {
            #visitor_def

            #visitor_trait_impl

            ::xmlity::de::Deserializer::deserialize_any(#deserializer_ident, #visitor_ident {
                lifetime: ::core::marker::PhantomData,
                marker: ::core::marker::PhantomData,
            })
        })
    }

    fn ident(&self) -> Cow<'_, Ident> {
        Cow::Borrowed(self.ident)
    }

    fn generics(&self) -> Cow<'_, syn::Generics> {
        Cow::Borrowed(self.generics)
    }
}