xso: refine handling of multiple #[xml(text)] fields

Previously, we only enforced the existence of at most one `#[xml(text)]`
field only at code generation time for `FromXml`. This change enforces
it at parsing time, which is more consistent and allows for a clearer
error message.
This commit is contained in:
Jonas Schäfer 2024-07-01 07:46:02 +02:00
parent ae30221e3c
commit 46584f05f9
3 changed files with 34 additions and 7 deletions

View file

@ -8,7 +8,7 @@
use proc_macro2::{Span, TokenStream}; use proc_macro2::{Span, TokenStream};
use quote::quote; use quote::quote;
use syn::*; use syn::{spanned::Spanned, *};
use crate::error_message::ParentRef; use crate::error_message::ParentRef;
use crate::field::{FieldBuilderPart, FieldDef, FieldIteratorPart, FieldTempInit}; use crate::field::{FieldBuilderPart, FieldDef, FieldIteratorPart, FieldTempInit};
@ -26,6 +26,7 @@ impl Compound {
/// Construct a compound from fields. /// Construct a compound from fields.
pub(crate) fn from_fields(compound_fields: &Fields) -> Result<Self> { pub(crate) fn from_fields(compound_fields: &Fields) -> Result<Self> {
let mut fields = Vec::with_capacity(compound_fields.len()); let mut fields = Vec::with_capacity(compound_fields.len());
let mut text_field = None;
for (i, field) in compound_fields.iter().enumerate() { for (i, field) in compound_fields.iter().enumerate() {
let index = match i.try_into() { let index = match i.try_into() {
Ok(v) => v, Ok(v) => v,
@ -38,7 +39,24 @@ impl Compound {
)) ))
} }
}; };
fields.push(FieldDef::from_field(field, index)?); let field = FieldDef::from_field(field, index)?;
if field.is_text_field() {
if let Some(other_field) = text_field.as_ref() {
let mut err = Error::new_spanned(
field.member(),
"only one `#[xml(text)]` field allowed per compound",
);
err.combine(Error::new(
*other_field,
"the other `#[xml(text)]` field is here",
));
return Err(err);
}
text_field = Some(field.member().span())
}
fields.push(field);
} }
Ok(Self { fields }) Ok(Self { fields })
@ -104,10 +122,9 @@ impl Compound {
finalize, finalize,
} => { } => {
if text_handler.is_some() { if text_handler.is_some() {
return Err(Error::new_spanned( // the existence of only one text handler is enforced
field.member(), // by Compound's constructor(s).
"more than one field attempts to collect text data", panic!("more than one field attempts to collect text data");
));
} }
builder_data_def.extend(quote! { builder_data_def.extend(quote! {

View file

@ -329,4 +329,12 @@ impl FieldDef {
} }
} }
} }
/// Return true if this field's parsing consumes text data.
pub(crate) fn is_text_field(&self) -> bool {
match self.kind {
FieldKind::Text { .. } => true,
_ => false,
}
}
} }

View file

@ -140,7 +140,9 @@ The `text` meta causes the field to be mapped to the text content of the
element. For `FromXml`, the field's type must implement [`FromXmlText`] and element. For `FromXml`, the field's type must implement [`FromXmlText`] and
for `IntoXml`, the field's type must implement [`IntoXmlText`]. for `IntoXml`, the field's type must implement [`IntoXmlText`].
The `text` meta supports no options or value. The `text` meta supports no options or value. Only a single field per struct
may be annotated with `#[xml(text)]` at a time, to avoid parsing ambiguities.
This is also true if only `IntoXml` is derived on a field, for consistency.
##### Example ##### Example