From 44f8a2cc6dfa5d2c32972cdf0700c618e3e5fd38 Mon Sep 17 00:00:00 2001 From: rsdy Date: Tue, 27 Sep 2022 17:23:28 +0100 Subject: [PATCH] Reduce code redundancy (#28) --- instant-xml-macros/src/de.rs | 70 +++++++++++--------------------- instant-xml-macros/src/lib.rs | 76 +++++++++++++++++++++++++++-------- instant-xml-macros/src/ser.rs | 41 ++++++------------- 3 files changed, 96 insertions(+), 91 deletions(-) diff --git a/instant-xml-macros/src/de.rs b/instant-xml-macros/src/de.rs index 47358cf..25edf49 100644 --- a/instant-xml-macros/src/de.rs +++ b/instant-xml-macros/src/de.rs @@ -1,36 +1,25 @@ use proc_macro2::{Ident, Span, TokenStream}; -use quote::{quote, ToTokens}; -use syn::{spanned::Spanned, ImplGenerics}; +use quote::quote; +use syn::spanned::Spanned; use super::{discard_lifetimes, ContainerMeta, FieldMeta, Namespace, VariantMeta}; pub(crate) fn from_xml(input: &syn::DeriveInput) -> TokenStream { - let ident = &input.ident; let meta = match ContainerMeta::from_derive(input) { Ok(meta) => meta, Err(e) => return e.to_compile_error(), }; - let mut xml_generics = input.generics.clone(); - let mut xml = syn::LifetimeDef::new(syn::Lifetime::new("'xml", Span::call_site())); - xml.bounds - .extend(xml_generics.lifetimes().map(|lt| lt.lifetime.clone())); - xml_generics.params.push(xml.into()); - - let (xml_impl_generics, _, _) = xml_generics.split_for_impl(); - match &input.data { syn::Data::Struct(_) if meta.scalar => { syn::Error::new(input.span(), "scalar structs are unsupported!").to_compile_error() } - syn::Data::Struct(ref data) => { - deserialize_struct(input, data, meta, ident, xml_impl_generics) - } + syn::Data::Struct(ref data) => deserialize_struct(input, data, meta), syn::Data::Enum(_) if !meta.scalar => { syn::Error::new(input.span(), "non-scalar enums are currently unsupported!") .to_compile_error() } - syn::Data::Enum(ref data) => deserialize_enum(input, data, meta, xml_impl_generics), + syn::Data::Enum(ref data) => deserialize_enum(input, data, meta), _ => todo!(), } } @@ -40,7 +29,6 @@ fn deserialize_enum( input: &syn::DeriveInput, data: &syn::DataEnum, meta: ContainerMeta, - xml_impl_generics: ImplGenerics ) -> TokenStream { let ident = &input.ident; let mut variants = TokenStream::new(); @@ -56,10 +44,12 @@ fn deserialize_enum( variants.extend(quote!(Ok(#serialize_as) => Ok(#ident::#v_ident),)); } + let generics = meta.xml_generics(); + let (impl_generics, _, _) = generics.split_for_impl(); let (_, ty_generics, where_clause) = input.generics.split_for_impl(); quote!( - impl #xml_impl_generics FromXml<'xml> for #ident #ty_generics #where_clause { + impl #impl_generics FromXml<'xml> for #ident #ty_generics #where_clause { fn deserialize<'cx>(deserializer: &'cx mut ::instant_xml::Deserializer<'cx, 'xml>) -> Result { match deserializer.take_str() { #variants @@ -76,16 +66,7 @@ fn deserialize_struct( input: &syn::DeriveInput, data: &syn::DataStruct, container_meta: ContainerMeta, - ident: &Ident, - xml_impl_generics: ImplGenerics, ) -> TokenStream { - let default_namespace = match &container_meta.ns.uri { - Some(ns) => quote!(#ns), - None => quote!(""), - }; - - let (_, ty_generics, where_clause) = input.generics.split_for_impl(); - let mut namespaces_map = quote!(let mut namespaces_map = std::collections::HashMap::new();); for (k, v) in container_meta.ns.prefixes.iter() { namespaces_map.extend(quote!( @@ -104,7 +85,7 @@ fn deserialize_struct( match data.fields { syn::Fields::Named(ref fields) => { fields.named.iter().enumerate().for_each(|(index, field)| { - let field_meta = match FieldMeta::from_field(field) { + let field_meta = match FieldMeta::from_field(field, &container_meta) { Ok(meta) => meta, Err(err) => { return_val.extend(err.into_compile_error()); @@ -144,10 +125,13 @@ fn deserialize_struct( let attributes_names = attributes_tokens.names; let attr_type_match = attributes_tokens.r#match; - let name = match &container_meta.rename { - Some(name) => quote!(#name), - None => ident.to_string().into_token_stream(), - }; + let ident = &input.ident; + let name = container_meta.tag(); + let default_namespace = container_meta.default_namespace(); + let generics = container_meta.xml_generics(); + + let (xml_impl_generics, _, _) = generics.split_for_impl(); + let (_, ty_generics, where_clause) = input.generics.split_for_impl(); quote!( impl #xml_impl_generics FromXml<'xml> for #ident #ty_generics #where_clause { @@ -233,22 +217,7 @@ fn process_field( container_meta: &ContainerMeta, ) { let field_name = field.ident.as_ref().unwrap(); - - let field_tag = match &field_meta.rename { - Some(rename) => quote!(#rename), - None => container_meta - .rename_all - .apply_to_field(field_name) - .into_token_stream(), - }; - - let const_field_var_str = Ident::new(&field_name.to_string().to_uppercase(), Span::call_site()); - let mut no_lifetime_type = field.ty.clone(); - discard_lifetimes(&mut no_lifetime_type); - - let enum_name = Ident::new(&format!("__Value{index}"), Span::call_site()); - tokens.r#enum.extend(quote!(#enum_name,)); - + let field_tag = field_meta.tag; let default_ns = match &field_meta.ns.uri { None => &container_meta.ns.uri, _ => &field_meta.ns.uri, @@ -260,6 +229,13 @@ fn process_field( None => quote!(""), }; + let const_field_var_str = Ident::new(&field_name.to_string().to_uppercase(), Span::call_site()); + let mut no_lifetime_type = field.ty.clone(); + discard_lifetimes(&mut no_lifetime_type); + + let enum_name = Ident::new(&format!("__Value{index}"), Span::call_site()); + tokens.r#enum.extend(quote!(#enum_name,)); + tokens.consts.extend(quote!( const #const_field_var_str: Id<'static> = <#no_lifetime_type as FromXml<'_>>::KIND.name( Id { ns: #ns, name: #field_tag } diff --git a/instant-xml-macros/src/lib.rs b/instant-xml-macros/src/lib.rs index 5899b04..2d5721f 100644 --- a/instant-xml-macros/src/lib.rs +++ b/instant-xml-macros/src/lib.rs @@ -8,11 +8,11 @@ use std::collections::BTreeMap; use std::fmt; use proc_macro2::{Delimiter, Group, Ident, Literal, Punct, Span, TokenStream, TokenTree}; -use quote::ToTokens; -use syn::parse_macro_input; +use quote::{quote, ToTokens}; use syn::punctuated::Punctuated; use syn::spanned::Spanned; use syn::token::Colon2; +use syn::{parse_macro_input, DeriveInput, Generics}; use case::RenameRule; @@ -28,17 +28,22 @@ pub fn from_xml(input: proc_macro::TokenStream) -> proc_macro::TokenStream { proc_macro::TokenStream::from(de::from_xml(&ast)) } -#[derive(Debug, Default)] -struct ContainerMeta { +struct ContainerMeta<'input> { + input: &'input DeriveInput, + ns: NamespaceMeta, rename: Option, rename_all: RenameRule, scalar: bool, } -impl ContainerMeta { - fn from_derive(input: &syn::DeriveInput) -> Result { - let mut meta = ContainerMeta::default(); +impl<'input> ContainerMeta<'input> { + fn from_derive(input: &'input syn::DeriveInput) -> Result { + let mut ns = NamespaceMeta::default(); + let mut rename = Default::default(); + let mut rename_all = Default::default(); + let mut scalar = Default::default(); + for (item, span) in meta_items(&input.attrs) { match item { MetaItem::Attribute => { @@ -47,18 +52,49 @@ impl ContainerMeta { "attribute key invalid in container xml attribute", )) } - MetaItem::Ns(ns) => meta.ns = ns, - MetaItem::Rename(lit) => meta.rename = Some(lit), + MetaItem::Ns(namespace) => ns = namespace, + MetaItem::Rename(lit) => rename = Some(lit), MetaItem::RenameAll(lit) => { - meta.rename_all = match RenameRule::from_str(&lit.to_string()) { + rename_all = match RenameRule::from_str(&lit.to_string()) { Ok(rule) => rule, Err(err) => return Err(syn::Error::new(span, err)), }; } - MetaItem::Scalar => meta.scalar = true, + MetaItem::Scalar => scalar = true, } } - Ok(meta) + + Ok(Self { + input, + ns, + rename, + rename_all, + scalar, + }) + } + + fn xml_generics(&self) -> Generics { + let mut xml_generics = self.input.generics.clone(); + let mut xml = syn::LifetimeDef::new(syn::Lifetime::new("'xml", Span::call_site())); + xml.bounds + .extend(xml_generics.lifetimes().map(|lt| lt.lifetime.clone())); + xml_generics.params.push(xml.into()); + + xml_generics + } + + fn tag(&self) -> TokenStream { + match &self.rename { + Some(name) => quote!(#name), + None => self.input.ident.to_string().into_token_stream(), + } + } + + fn default_namespace(&self) -> TokenStream { + match &self.ns.uri { + Some(ns) => quote!(#ns), + None => quote!(""), + } } } @@ -66,17 +102,25 @@ impl ContainerMeta { struct FieldMeta { attribute: bool, ns: NamespaceMeta, - rename: Option, + tag: TokenStream, } impl FieldMeta { - fn from_field(input: &syn::Field) -> Result { - let mut meta = FieldMeta::default(); + fn from_field(input: &syn::Field, container: &ContainerMeta) -> Result { + let field_name = input.ident.as_ref().unwrap(); + let mut meta = FieldMeta { + tag: container + .rename_all + .apply_to_field(field_name) + .into_token_stream(), + ..Default::default() + }; + for (item, span) in meta_items(&input.attrs) { match item { MetaItem::Attribute => meta.attribute = true, MetaItem::Ns(ns) => meta.ns = ns, - MetaItem::Rename(lit) => meta.rename = Some(lit), + MetaItem::Rename(lit) => meta.tag = quote!(#lit), MetaItem::RenameAll(_) => { return Err(syn::Error::new( span, diff --git a/instant-xml-macros/src/ser.rs b/instant-xml-macros/src/ser.rs index eb2a6c7..84313b2 100644 --- a/instant-xml-macros/src/ser.rs +++ b/instant-xml-macros/src/ser.rs @@ -1,5 +1,5 @@ use proc_macro2::TokenStream; -use quote::{quote, ToTokens}; +use quote::quote; use syn::spanned::Spanned; use crate::Namespace; @@ -80,28 +80,22 @@ fn serialize_struct( syn::Fields::Unit => {} }; - let default_namespace = match &meta.ns.uri { - Some(ns) => quote!(#ns), - None => quote!(""), - }; - + let default_namespace = meta.default_namespace(); let cx_len = meta.ns.prefixes.len(); let mut context = quote!( let mut new = ::instant_xml::ser::Context::<#cx_len>::default(); new.default_ns = #default_namespace; ); + for (i, (prefix, ns)) in meta.ns.prefixes.iter().enumerate() { context.extend(quote!( new.prefixes[#i] = ::instant_xml::ser::Prefix { ns: #ns, prefix: #prefix }; )); } - let ident = &input.ident; let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl(); - let tag = match &meta.rename { - Some(rename) => quote!(#rename), - None => ident.to_string().into_token_stream(), - }; + let tag = meta.tag(); + let ident = &input.ident; quote!( impl #impl_generics ToXml for #ident #ty_generics #where_clause { @@ -145,7 +139,7 @@ fn process_named_field( meta: &ContainerMeta, ) { let field_name = field.ident.as_ref().unwrap(); - let field_meta = match FieldMeta::from_field(field) { + let field_meta = match FieldMeta::from_field(field, meta) { Ok(meta) => meta, Err(err) => { body.extend(err.into_compile_error()); @@ -153,15 +147,12 @@ fn process_named_field( } }; - let tag = match &field_meta.rename { - Some(rename) => quote!(#rename), - None => meta - .rename_all - .apply_to_field(field_name) - .into_token_stream(), + let tag = field_meta.tag; + let default_ns = match &meta.ns.uri { + Some(ns) => quote!(#ns), + None => quote!(""), }; - let default_ns = &meta.ns.uri; if field_meta.attribute { let (ns, error) = match &field_meta.ns.uri { Some(Namespace::Path(path)) => match path.get_ident() { @@ -193,10 +184,7 @@ fn process_named_field( ) .into_compile_error(), ), - None => (match default_ns { - Some(ns) => quote!(#ns), - None => quote!(""), - }, quote!()), + None => (default_ns, quote!()), }; attributes.extend(quote!( @@ -207,11 +195,8 @@ fn process_named_field( } let ns = match field_meta.ns.uri { - Some(ns) => quote!(#ns), - None => match &meta.ns.uri { - Some(ns) => quote!(#ns), - None => quote!(""), - }, + Some(ref ns) => quote!(#ns), + None => default_ns, }; let mut no_lifetime_type = field.ty.clone();