xso-proc: provide FromXml::xml_name_matcher on derived impls

Jonas SchΓ€fer created

That way, derived implementations can also make use of the performance
benefits.

Change summary

parsers/src/util/macro_tests.rs | 45 +++++++++++++++++++++++++++
xso-proc/src/common.rs          | 40 ++++++++++++++++++++++++
xso-proc/src/enums.rs           | 58 ++++++++++++++++++++++++++++++++++
xso-proc/src/lib.rs             |  5 +++
xso-proc/src/structs.rs         | 21 +++++++++++-
5 files changed, 166 insertions(+), 3 deletions(-)

Detailed changes

parsers/src/util/macro_tests.rs πŸ”—

@@ -94,6 +94,14 @@ fn empty_roundtrip() {
     roundtrip_full::<Empty>("<foo xmlns='urn:example:ns1'/>");
 }
 
+#[test]
+fn empty_xml_name_matcher_is_specific() {
+    assert_eq!(
+        Empty::xml_name_matcher(),
+        xso::fromxml::XmlNameMatcher::Specific(NS1, "foo")
+    );
+}
+
 #[test]
 fn empty_name_mismatch() {
     #[allow(unused_imports)]
@@ -717,6 +725,14 @@ enum NameSwitchedEnum {
     },
 }
 
+#[test]
+fn name_switched_enum_matcher_is_in_namespace() {
+    assert_eq!(
+        NameSwitchedEnum::xml_name_matcher(),
+        xso::fromxml::XmlNameMatcher::InNamespace(NS1)
+    );
+}
+
 #[test]
 fn name_switched_enum_positive_variant_1() {
     #[allow(unused_imports)]
@@ -1844,6 +1860,14 @@ enum DynamicEnum {
     },
 }
 
+#[test]
+fn dynamic_enum_matcher_is_any() {
+    assert_eq!(
+        DynamicEnum::xml_name_matcher(),
+        xso::fromxml::XmlNameMatcher::Any
+    );
+}
+
 #[test]
 fn dynamic_enum_roundtrip_a() {
     #[allow(unused_imports)]
@@ -1903,6 +1927,27 @@ fn fallible_parse_positive_err() {
     }
 }
 
+#[derive(FromXml, AsXml, PartialEq, Debug, Clone)]
+#[xml()]
+enum DynamicEnumWithSharedNamespace {
+    #[xml(transparent)]
+    A(RequiredAttribute),
+
+    #[xml(namespace = NS1, name = "b")]
+    B {
+        #[xml(text)]
+        contents: String,
+    },
+}
+
+#[test]
+fn dynamic_enum_with_shared_namespace_matcher_is_in_namespace() {
+    assert_eq!(
+        DynamicEnumWithSharedNamespace::xml_name_matcher(),
+        xso::fromxml::XmlNameMatcher::InNamespace(NS1)
+    );
+}
+
 #[derive(FromXml, AsXml, PartialEq, Debug, Clone)]
 #[xml(namespace = NS1, name = "parent")]
 struct ExtractTupleToCollection {

xso-proc/src/common.rs πŸ”—

@@ -7,8 +7,45 @@
 //! Definitions common to both enums and structs
 
 use proc_macro2::TokenStream;
+use quote::{quote, ToTokens};
 use syn::*;
 
+/// Template which renders to a `xso::fromxml::XmlNameMatcher` value.
+pub(crate) enum XmlNameMatcher {
+    /// Renders as `xso::fromxml::XmlNameMatcher::Any`.
+    #[allow(dead_code)] // We keep it for completeness.
+    Any,
+
+    /// Renders as `xso::fromxml::XmlNameMatcher::InNamespace(#0)`.
+    InNamespace(TokenStream),
+
+    /// Renders as `xso::fromxml::XmlNameMatcher::Specific(#0, #1)`.
+    Specific(TokenStream, TokenStream),
+
+    /// Renders as `#0`.
+    ///
+    /// This is an escape hatch for more complicated constructs, e.g. when
+    /// a superset of multiple matchers is required.
+    Custom(TokenStream),
+}
+
+impl ToTokens for XmlNameMatcher {
+    fn to_tokens(&self, tokens: &mut TokenStream) {
+        match self {
+            Self::Any => tokens.extend(quote! {
+                ::xso::fromxml::XmlNameMatcher::<'static>::Any
+            }),
+            Self::InNamespace(ref namespace) => tokens.extend(quote! {
+                ::xso::fromxml::XmlNameMatcher::<'static>::InNamespace(#namespace)
+            }),
+            Self::Specific(ref namespace, ref name) => tokens.extend(quote! {
+                ::xso::fromxml::XmlNameMatcher::<'static>::Specific(#namespace, #name)
+            }),
+            Self::Custom(ref stream) => tokens.extend(stream.clone()),
+        }
+    }
+}
+
 /// Parts necessary to construct a `::xso::FromXml` implementation.
 pub(crate) struct FromXmlParts {
     /// Additional items necessary for the implementation.
@@ -19,6 +56,9 @@ pub(crate) struct FromXmlParts {
 
     /// The name of the type which is the `::xso::FromXml::Builder`.
     pub(crate) builder_ty_ident: Ident,
+
+    /// The `XmlNameMatcher` to pre-select elements for this implementation.
+    pub(crate) name_matcher: XmlNameMatcher,
 }
 
 /// Parts necessary to construct a `::xso::AsXml` implementation.

xso-proc/src/enums.rs πŸ”—

@@ -12,7 +12,7 @@ use proc_macro2::{Span, TokenStream};
 use quote::{quote, ToTokens};
 use syn::*;
 
-use crate::common::{AsXmlParts, FromXmlParts, ItemDef};
+use crate::common::{AsXmlParts, FromXmlParts, ItemDef, XmlNameMatcher};
 use crate::compound::Compound;
 use crate::error_message::ParentRef;
 use crate::meta::{reject_key, Flag, NameRef, NamespaceRef, QNameRef, XmlCompoundMeta};
@@ -203,6 +203,16 @@ impl NameSwitchedEnum {
         })
     }
 
+    /// Provide the `XmlNameMatcher` template for this enum.
+    ///
+    /// Name-switched enums always return a matcher in the namespace of the
+    /// elements they match against.
+    fn xml_name_matcher(&self) -> Result<XmlNameMatcher> {
+        Ok(XmlNameMatcher::InNamespace(
+            self.namespace.to_token_stream(),
+        ))
+    }
+
     /// Build the deserialisation statemachine for the name-switched enum.
     fn make_from_events_statemachine(
         &self,
@@ -461,6 +471,17 @@ impl AttributeSwitchedEnum {
         })
     }
 
+    /// Provide the `XmlNameMatcher` template for this enum.
+    ///
+    /// Attribute-switched enums always return a matcher specific to the
+    /// element they operate on.
+    fn xml_name_matcher(&self) -> Result<XmlNameMatcher> {
+        Ok(XmlNameMatcher::Specific(
+            self.elem_namespace.to_token_stream(),
+            self.elem_name.to_token_stream(),
+        ))
+    }
+
     /// Build the deserialisation statemachine for the attribute-switched enum.
     fn make_from_events_statemachine(
         &self,
@@ -613,6 +634,31 @@ impl DynamicEnum {
         Ok(Self { variants })
     }
 
+    /// Provide the `XmlNameMatcher` template for this dynamic enum.
+    ///
+    /// Dynamic enums return the superset of the matchers of their variants,
+    /// which in many cases will be XmlNameMatcher::Any.
+    fn xml_name_matcher(&self) -> Result<XmlNameMatcher> {
+        let mut iter = self.variants.iter();
+        let Some(first) = iter.next() else {
+            // Since the enum has no variants, it cannot match anything. We
+            // use an empty namespace and an empty name, which will never
+            // match.
+            return Ok(XmlNameMatcher::Custom(quote! {
+                ::xso::fromxml::XmlNameMatcher::<'static>::Specific("", "")
+            }));
+        };
+
+        let first_matcher = first.inner.xml_name_matcher()?;
+        let mut composition = quote! { #first_matcher };
+        for variant in iter {
+            let next_matcher = variant.inner.xml_name_matcher()?;
+            composition.extend(quote! { .superset(#next_matcher) });
+        }
+
+        Ok(XmlNameMatcher::Custom(composition))
+    }
+
     /// Build the deserialisation statemachine for the dynamic enum.
     fn make_from_events_statemachine(
         &self,
@@ -781,6 +827,15 @@ impl EnumInner {
         }
     }
 
+    /// Provide the `XmlNameMatcher` template for this enum.
+    fn xml_name_matcher(&self) -> Result<XmlNameMatcher> {
+        match self {
+            Self::NameSwitched(ref inner) => inner.xml_name_matcher(),
+            Self::AttributeSwitched(ref inner) => inner.xml_name_matcher(),
+            Self::Dynamic(ref inner) => inner.xml_name_matcher(),
+        }
+    }
+
     /// Build the deserialisation statemachine for the enum.
     fn make_from_events_statemachine(
         &self,
@@ -911,6 +966,7 @@ impl ItemDef for EnumDef {
                 #builder_ty_ident::new(#name_ident, #attrs_ident, ctx)
             },
             builder_ty_ident: builder_ty_ident.clone(),
+            name_matcher: self.inner.xml_name_matcher()?,
         })
     }
 

xso-proc/src/lib.rs πŸ”—

@@ -71,6 +71,7 @@ fn from_xml_impl(input: Item) -> Result<TokenStream> {
         defs,
         from_events_body,
         builder_ty_ident,
+        name_matcher,
     } = def.make_from_events_builder(&vis, &name_ident, &attrs_ident)?;
 
     #[cfg_attr(not(feature = "minidom"), allow(unused_mut))]
@@ -87,6 +88,10 @@ fn from_xml_impl(input: Item) -> Result<TokenStream> {
             ) -> ::core::result::Result<Self::Builder, ::xso::error::FromEventsError> {
                 #from_events_body
             }
+
+            fn xml_name_matcher() -> ::xso::fromxml::XmlNameMatcher<'static> {
+                #name_matcher
+            }
         }
     };
 

xso-proc/src/structs.rs πŸ”—

@@ -7,10 +7,10 @@
 //! Handling of structs
 
 use proc_macro2::{Span, TokenStream};
-use quote::quote;
+use quote::{quote, ToTokens};
 use syn::{spanned::Spanned, *};
 
-use crate::common::{AsXmlParts, FromXmlParts, ItemDef};
+use crate::common::{AsXmlParts, FromXmlParts, ItemDef, XmlNameMatcher};
 use crate::compound::Compound;
 use crate::error_message::ParentRef;
 use crate::meta::{reject_key, Flag, NameRef, NamespaceRef, QNameRef, XmlCompoundMeta};
@@ -168,6 +168,22 @@ impl StructInner {
         }
     }
 
+    pub(crate) fn xml_name_matcher(&self) -> Result<XmlNameMatcher> {
+        match self {
+            Self::Transparent { ty, .. } => Ok(XmlNameMatcher::Custom(quote! {
+                <#ty as ::xso::FromXml>::xml_name_matcher()
+            })),
+            Self::Compound {
+                xml_namespace,
+                xml_name,
+                ..
+            } => Ok(XmlNameMatcher::Specific(
+                xml_namespace.to_token_stream(),
+                xml_name.to_token_stream(),
+            )),
+        }
+    }
+
     pub(crate) fn make_from_events_statemachine(
         &self,
         state_ty_ident: &Ident,
@@ -399,6 +415,7 @@ impl ItemDef for StructDef {
                 #builder_ty_ident::new(#name_ident, #attrs_ident, ctx)
             },
             builder_ty_ident: builder_ty_ident.clone(),
+            name_matcher: self.inner.xml_name_matcher()?,
         })
     }