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::{
        constructor_expr, non_bound_generics, ExpandedName, FieldIdent, StructType,
        StructTypeWithFields,
    },
    de::{
        builders::{DeserializeBuilder, VisitorBuilder, VisitorBuilderExt},
        components::ElementLoopAccessor,
    },
    options::{AllowUnknown, ElementOrder, IgnoreComments, IgnoreWhitespace},
    DeriveError,
};

use super::RecordInput;

pub struct RecordDeserializeElementBuilder<'a, T: Fn(syn::Expr) -> syn::Expr> {
    pub input: &'a RecordInput<'a, T>,
    pub ignore_whitespace: IgnoreWhitespace,
    pub ignore_comments: IgnoreComments,
    pub required_expanded_name: Option<ExpandedName<'static>>,
    pub allow_unknown_attributes: AllowUnknown,
    pub allow_unknown_children: AllowUnknown,
    pub children_order: ElementOrder,
    pub attribute_order: ElementOrder,
}

impl<T: Fn(syn::Expr) -> syn::Expr> VisitorBuilder for RecordDeserializeElementBuilder<'_, T> {
    fn visit_element_fn_body(
        &self,
        visitor_lifetime: &Lifetime,
        element_access_ident: &Ident,
        access_type: &Type,
    ) -> Result<Option<Vec<Stmt>>, DeriveError> {
        let Self {
            input,
            required_expanded_name,
            ..
        } = self;

        let xml_name_identification = required_expanded_name.as_ref().map::<Stmt, _>(|qname| {
          parse_quote! {
              ::xmlity::de::ElementAccessExt::ensure_name::<<#access_type as ::xmlity::de::AttributesAccess<#visitor_lifetime>>::Error>(&#element_access_ident, &#qname)?;
          }
      });

        let (constructor_type, fields) = match &input.fields {
            StructTypeWithFields::Named(n) => (
                StructType::Named,
                n.iter()
                    .map(|a| a.clone().map_ident(FieldIdent::Named))
                    .collect(),
            ),
            StructTypeWithFields::Unnamed(n) => (
                StructType::Unnamed,
                n.iter()
                    .map(|a| a.clone().map_ident(FieldIdent::Indexed))
                    .collect(),
            ),
            StructTypeWithFields::Unit => (StructType::Unit, Vec::new()),
        };

        let element_loop_accessor = (!fields.is_empty()).then(|| {
            ElementLoopAccessor::new(
                self.allow_unknown_children,
                self.allow_unknown_attributes,
                self.children_order,
                self.attribute_order,
                self.ignore_whitespace,
                self.ignore_comments,
            )
        });

        let getter_declarations = element_loop_accessor
            .as_ref()
            .map(|a| a.field_definitions(fields.clone()))
            .transpose()?
            .unwrap_or_default();

        let attribute_loop = element_loop_accessor
            .as_ref()
            .map(|a| {
                a.attribute_access_loop(fields.clone(), &parse_quote!(&mut #element_access_ident))
            })
            .transpose()?
            .unwrap_or_default();

        let children_access_ident = Ident::new("__children", element_access_ident.span());

        let children_access_ty: syn::Type = parse_quote!(
            <#access_type as ::xmlity::de::ElementAccess<#visitor_lifetime>>::ChildrenAccess
        );

        let children_loop = element_loop_accessor
            .as_ref()
            .map(|a| {
                a.children_access_loop(
                    fields.clone(),
                    &parse_quote!(&mut #children_access_ident),
                    &children_access_ty,
                    visitor_lifetime,
                )
            })
            .transpose()?
            .unwrap_or_default();

        let error_type: syn::Type = parse_quote!(
            <#access_type as ::xmlity::de::AttributesAccess<#visitor_lifetime>>::Error
        );

        let constructor_exprs = element_loop_accessor
            .as_ref()
            .map(|a| a.value_expressions(fields, visitor_lifetime, &error_type))
            .transpose()?
            .unwrap_or_default();

        let constructor = (self.input.wrapper_function)(constructor_expr(
            &self.input.constructor_path,
            constructor_exprs,
            &constructor_type,
        ));

        Ok(Some(parse_quote! {
            #xml_name_identification

            #(#getter_declarations)*

            #(#attribute_loop)*

            let mut #children_access_ident = ::xmlity::de::ElementAccess::children(#element_access_ident)?;

            #(#children_loop)*

            ::core::result::Result::Ok(#constructor)
        }))
    }

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

        let mut deserialize_generics = generics.as_ref().clone();

        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.input.impl_for_ident.as_ref())
    }

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

impl<T: Fn(syn::Expr) -> syn::Expr> DeserializeBuilder for RecordDeserializeElementBuilder<'_, T> {
    fn deserialize_fn_body(
        &self,

        deserializer_ident: &Ident,
        _deserialize_lifetime: &Lifetime,
    ) -> Result<Vec<Stmt>, DeriveError> {
        let formatter_expecting = format!("struct {}", self.input.impl_for_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.input.impl_for_ident.as_ref())
    }

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