Implement #[xml(flag)] meta

This commit is contained in:
Jonas Schäfer 2025-01-20 17:51:22 +01:00
commit 727e57b756
10 changed files with 423 additions and 2 deletions

View file

@ -102,7 +102,7 @@ impl ParentRef {
/// It implements [`core::fmt::Display`] for that purpose and is otherwise of
/// little use.
#[repr(transparent)]
struct FieldName<'x>(&'x Member);
pub(crate) struct FieldName<'x>(pub &'x Member);
impl fmt::Display for FieldName<'_> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {

137
xso-proc/src/field/flag.rs Normal file
View file

@ -0,0 +1,137 @@
// Copyright (c) 2024 Jonas Schäfer <jonas@zombofant.net>
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
//! This module concerns the processing of flag-style children.
//!
//! In particular, it provides the `#[xml(flag)]` implementation.
use proc_macro2::{Span, TokenStream};
use quote::quote;
use syn::*;
use crate::error_message::{FieldName, ParentRef};
use crate::meta::{NameRef, NamespaceRef};
use crate::scope::{AsItemsScope, FromEventsScope};
use crate::types::{bool_ty, empty_builder_ty, u8_ty};
use super::{Field, FieldBuilderPart, FieldIteratorPart, FieldTempInit, NestedMatcher};
/// The field maps to a child element, the presence of which is represented as boolean.
pub(super) struct FlagField {
/// The XML namespace of the child element.
pub(super) xml_namespace: NamespaceRef,
/// The XML name of the child element.
pub(super) xml_name: NameRef,
}
impl Field for FlagField {
fn make_builder_part(
&self,
scope: &FromEventsScope,
container_name: &ParentRef,
member: &Member,
_ty: &Type,
) -> Result<FieldBuilderPart> {
let field_access = scope.access_field(member);
let unknown_attr_err = format!(
"Unknown attribute in flag child {} in {}.",
FieldName(&member),
container_name
);
let unknown_child_err = format!(
"Unknown child in flag child {} in {}.",
FieldName(&member),
container_name
);
let unknown_text_err = format!(
"Unexpected text in flag child {} in {}.",
FieldName(&member),
container_name
);
let xml_namespace = &self.xml_namespace;
let xml_name = &self.xml_name;
Ok(FieldBuilderPart::Nested {
extra_defs: TokenStream::new(),
value: FieldTempInit {
ty: bool_ty(Span::call_site()),
init: quote! { false },
},
matcher: NestedMatcher::Selective(quote! {
if name.0 == #xml_namespace && name.1 == #xml_name {
::xso::fromxml::Empty {
attributeerr: #unknown_attr_err,
childerr: #unknown_child_err,
texterr: #unknown_text_err,
}.start(attrs).map_err(
::xso::error::FromEventsError::Invalid
)
} else {
::core::result::Result::Err(::xso::error::FromEventsError::Mismatch {
name,
attrs,
})
}
}),
builder: empty_builder_ty(Span::call_site()),
collect: quote! {
#field_access = true;
},
finalize: quote! {
#field_access
},
})
}
fn make_iterator_part(
&self,
_scope: &AsItemsScope,
_container_name: &ParentRef,
bound_name: &Ident,
_member: &Member,
_ty: &Type,
) -> Result<FieldIteratorPart> {
let xml_namespace = &self.xml_namespace;
let xml_name = &self.xml_name;
Ok(FieldIteratorPart::Content {
extra_defs: TokenStream::new(),
value: FieldTempInit {
init: quote! {
if *#bound_name {
3
} else {
1
}
},
ty: u8_ty(Span::call_site()),
},
generator: quote! {
{
// using wrapping_sub will make the match below crash
// with unreachable!() in case we messed up somewhere.
#bound_name = #bound_name.wrapping_sub(1);
match #bound_name {
0 => ::core::result::Result::<_, ::xso::error::Error>::Ok(::core::option::Option::None),
1 => ::core::result::Result::Ok(::core::option::Option::Some(
::xso::Item::ElementFoot
)),
2 => ::core::result::Result::Ok(::core::option::Option::Some(
::xso::Item::ElementHeadStart(
::xso::exports::rxml::Namespace::from(#xml_namespace),
::std::borrow::Cow::Borrowed(#xml_name),
)
)),
_ => unreachable!(),
}
}
},
})
}
}

View file

@ -20,12 +20,14 @@ mod attribute;
mod child;
#[cfg(feature = "minidom")]
mod element;
mod flag;
mod text;
use self::attribute::AttributeField;
use self::child::{ChildField, ExtractDef};
#[cfg(feature = "minidom")]
use self::element::ElementField;
use self::flag::FlagField;
use self::text::TextField;
/// Code slices necessary for declaring and initializing a temporary variable
@ -406,6 +408,18 @@ fn new_field(
"#[xml(element)] requires xso to be built with the \"minidom\" feature.",
))
}
XmlFieldMeta::Flag {
span,
qname: QNameRef { namespace, name },
} => {
let xml_namespace = namespace.unwrap_or_else(|| container_namespace.clone());
let xml_name = default_name(span, name, field_ident)?;
Ok(Box::new(FlagField {
xml_namespace,
xml_name,
}))
}
}
}

View file

@ -755,6 +755,17 @@ pub(crate) enum XmlFieldMeta {
/// The `n` flag.
amount: Option<AmountConstraint>,
},
/// `#[xml(flag)]
Flag {
/// The span of the `#[xml(flag)]` meta from which this was parsed.
///
/// This is useful for error messages.
span: Span,
/// The namespace/name keys.
qname: QNameRef,
},
}
impl XmlFieldMeta {
@ -1017,6 +1028,19 @@ impl XmlFieldMeta {
})
}
/// Parse a `#[xml(flag)]` meta.
fn flag_from_meta(meta: ParseNestedMeta<'_>) -> Result<Self> {
let mut qname = QNameRef::default();
meta.parse_nested_meta(|meta| match qname.parse_incremental_from_meta(meta)? {
None => Ok(()),
Some(meta) => Err(Error::new_spanned(meta.path, "unsupported key")),
})?;
Ok(Self::Flag {
span: meta.path.span(),
qname,
})
}
/// Parse [`Self`] from a nestd meta, switching on the identifier
/// of that nested meta.
fn parse_from_meta(meta: ParseNestedMeta<'_>) -> Result<Self> {
@ -1030,6 +1054,8 @@ impl XmlFieldMeta {
Self::extract_from_meta(meta)
} else if meta.path.is_ident("element") {
Self::element_from_meta(meta)
} else if meta.path.is_ident("flag") {
Self::flag_from_meta(meta)
} else {
Err(Error::new_spanned(meta.path, "unsupported field meta"))
}
@ -1112,6 +1138,7 @@ impl XmlFieldMeta {
Self::Text { ref span, .. } => *span,
Self::Extract { ref span, .. } => *span,
Self::Element { ref span, .. } => *span,
Self::Flag { ref span, .. } => *span,
}
}

View file

@ -890,3 +890,95 @@ pub(crate) fn discard_builder_ty(span: Span) -> Type {
},
})
}
/// Construct a [`syn::Type`] referring to the built-in `bool` type.
///
/// Note that we go through `xso::exports::CoreBool` for that, because there seems
/// to be no way to access built-in types once they have been shadowed in a
/// scope.
pub(crate) fn bool_ty(span: Span) -> Type {
Type::Path(TypePath {
qself: None,
path: Path {
leading_colon: Some(syn::token::PathSep {
spans: [span, span],
}),
segments: [
PathSegment {
ident: Ident::new("xso", span),
arguments: PathArguments::None,
},
PathSegment {
ident: Ident::new("exports", span),
arguments: PathArguments::None,
},
PathSegment {
ident: Ident::new("CoreBool", span),
arguments: PathArguments::None,
},
]
.into_iter()
.collect(),
},
})
}
/// Construct a [`syn::Type`] referring to the built-in `u8` type.
///
/// Note that we go through `xso::exports::CoreU8` for that, because there seems
/// to be no way to access built-in types once they have been shadowed in a
/// scope.
pub(crate) fn u8_ty(span: Span) -> Type {
Type::Path(TypePath {
qself: None,
path: Path {
leading_colon: Some(syn::token::PathSep {
spans: [span, span],
}),
segments: [
PathSegment {
ident: Ident::new("xso", span),
arguments: PathArguments::None,
},
PathSegment {
ident: Ident::new("exports", span),
arguments: PathArguments::None,
},
PathSegment {
ident: Ident::new("CoreU8", span),
arguments: PathArguments::None,
},
]
.into_iter()
.collect(),
},
})
}
/// Construct a [`syn::Type`] referring to `::xso::fromxml::EmptyBuilder`.
pub(crate) fn empty_builder_ty(span: Span) -> Type {
Type::Path(TypePath {
qself: None,
path: Path {
leading_colon: Some(syn::token::PathSep {
spans: [span, span],
}),
segments: [
PathSegment {
ident: Ident::new("xso", span),
arguments: PathArguments::None,
},
PathSegment {
ident: Ident::new("fromxml", span),
arguments: PathArguments::None,
},
PathSegment {
ident: Ident::new("EmptyBuilder", span),
arguments: PathArguments::None,
},
]
.into_iter()
.collect(),
},
})
}