diff --git a/parsers/src/util/macro_tests.rs b/parsers/src/util/macro_tests.rs index 6d8ecde..5958cc7 100644 --- a/parsers/src/util/macro_tests.rs +++ b/parsers/src/util/macro_tests.rs @@ -148,3 +148,24 @@ fn empty_qname_check_has_precedence_over_attr_check() { other => panic!("unexpected result: {:?}", other), } } + +static SOME_NAME: &::xso::exports::rxml::strings::NcNameStr = { + #[allow(unsafe_code)] + unsafe { + ::xso::exports::rxml::strings::NcNameStr::from_str_unchecked("bar") + } +}; + +#[derive(FromXml, IntoXml, PartialEq, Debug, Clone)] +#[xml(namespace = NS1, name = SOME_NAME)] +struct NamePath; + +#[test] +fn name_path_roundtrip() { + #[allow(unused_imports)] + use std::{ + option::Option::{None, Some}, + result::Result::{Err, Ok}, + }; + roundtrip_full::(""); +} diff --git a/xso-proc/src/lib.rs b/xso-proc/src/lib.rs index c075c26..ac7a48d 100644 --- a/xso-proc/src/lib.rs +++ b/xso-proc/src/lib.rs @@ -77,8 +77,11 @@ fn from_xml_impl(input: Item) -> Result { let from_events_builder_ty_name = quote::format_ident!("{}FromEvents", ident); let state_ty_name = quote::format_ident!("{}FromEventsState", ident); - let unknown_attr_err = format!("Unknown attribute in {} element.", xml_name.as_str()); - let unknown_child_err = format!("Unknown child in {} element.", xml_name.as_str()); + let unknown_attr_err = format!( + "Unknown attribute in {} element.", + xml_name.repr_to_string() + ); + let unknown_child_err = format!("Unknown child in {} element.", xml_name.repr_to_string()); let docstr = format!("Build a [`{}`] from XML events", ident); #[cfg_attr(not(feature = "minidom"), allow(unused_mut))] @@ -215,7 +218,7 @@ fn into_xml_impl(input: Item) -> Result { ::xso::exports::rxml::parser::EventMetrics::zero(), ( ::xso::exports::rxml::Namespace::from_str(#xml_namespace), - ::xso::exports::rxml::NcName::from(#xml_name), + #xml_name.to_owned(), ), ::xso::exports::rxml::AttrMap::new(), ))) diff --git a/xso-proc/src/meta.rs b/xso-proc/src/meta.rs index 8e6ff6c..32de9d6 100644 --- a/xso-proc/src/meta.rs +++ b/xso-proc/src/meta.rs @@ -9,6 +9,8 @@ //! This module is concerned with parsing attributes from the Rust "meta" //! annotations on structs, enums, enum variants and fields. +use std::borrow::Cow; + use proc_macro2::{Span, TokenStream}; use quote::{quote, quote_spanned}; use syn::{spanned::Spanned, *}; @@ -23,47 +25,73 @@ pub(crate) type NamespaceRef = Path; /// Value for the `#[xml(name = .. )]` attribute. #[derive(Debug)] -pub(crate) struct NameRef { - value: NcName, - span: Span, +pub(crate) enum NameRef { + /// The XML name is specified as a string literal. + Literal { + /// The validated XML name. + value: NcName, + + /// The span of the original [`syn::LitStr`]. + span: Span, + }, + + /// The XML name is specified as a path. + Path(Path), } impl NameRef { - /// Access the XML name as str. + /// Access a representation of the XML name as str. /// - /// *Note*: This function may vanish in the future if we ever support - /// non-literal XML names. - pub(crate) fn as_str(&self) -> &str { - self.value.as_str() + /// If this name reference is a [`Self::Path`], this will return the name + /// of the rightmost identifier in the path. + /// + /// If this name reference is a [`Self::Literal`], this will return the + /// contents of the literal. + pub(crate) fn repr_to_string(&self) -> Cow<'_, str> { + match self { + Self::Literal { ref value, .. } => Cow::Borrowed(value.as_str()), + Self::Path(ref path) => path.segments.last().unwrap().ident.to_string().into(), + } } } impl syn::parse::Parse for NameRef { fn parse(input: syn::parse::ParseStream<'_>) -> Result { - let s: LitStr = input.parse()?; - let span = s.span(); - match NcName::try_from(s.value()) { - Ok(value) => Ok(Self { value, span }), - Err(e) => Err(Error::new( - span, - format!("not a valid XML element name: {}", e), - )), + if input.peek(syn::LitStr) { + let s: LitStr = input.parse()?; + let span = s.span(); + match NcName::try_from(s.value()) { + Ok(value) => Ok(Self::Literal { value, span }), + Err(e) => Err(Error::new( + span, + format!("not a valid XML element name: {}", e), + )), + } + } else { + let p: Path = input.parse()?; + Ok(Self::Path(p)) } } } impl quote::ToTokens for NameRef { fn to_tokens(&self, tokens: &mut TokenStream) { - let value = self.value.as_str(); - let value = quote_spanned! { self.span=> #value }; - // SAFETY: self.0 is a known-good NcName, so converting it to an - // NcNameStr is known to be safe. - // NOTE: we cannot use `quote_spanned! { self.span=> }` for the unsafe - // block as that would then in fact trip a `#[deny(unsafe_code)]` lint - // at the use site of the macro. - tokens.extend(quote! { - unsafe { ::xso::exports::rxml::NcNameStr::from_str_unchecked(#value) } - }) + match self { + Self::Literal { ref value, span } => { + let span = *span; + let value = value.as_str(); + let value = quote_spanned! { span=> #value }; + // SAFETY: self.0 is a known-good NcName, so converting it to an + // NcNameStr is known to be safe. + // NOTE: we cannot use `quote_spanned! { self.span=> }` for the unsafe + // block as that would then in fact trip a `#[deny(unsafe_code)]` lint + // at the use site of the macro. + tokens.extend(quote! { + unsafe { ::xso::exports::rxml::NcNameStr::from_str_unchecked(#value) } + }) + } + Self::Path(ref path) => path.to_tokens(tokens), + } } } diff --git a/xso/src/from_xml_doc.md b/xso/src/from_xml_doc.md index 3c4fd0e..72ef5e3 100644 --- a/xso/src/from_xml_doc.md +++ b/xso/src/from_xml_doc.md @@ -32,7 +32,7 @@ All key-value pairs interpreted by these derive macros must be wrapped in a | Key | Value type | Description | | --- | --- | --- | | `namespace` | *path* | The path to a `&'static str` which holds the XML namespace to match. | -| `name` | *string literal* | The XML element name to match. | +| `name` | *string literal* or *path* | The XML element name to match. If it is a *path*, it must point at a `&'static NcNameStr`. | Note that the `name` value must be a valid XML element name, without colons. The namespace prefix, if any, is assigned automatically at serialisation time