xso-proc: add support for defaulting in attribute parsing

This commit is contained in:
Jonas Schäfer 2024-06-26 15:56:43 +02:00
commit 0c57be3e61
5 changed files with 178 additions and 3 deletions

View file

@ -13,9 +13,9 @@ use syn::{spanned::Spanned, *};
use rxml_validation::NcName;
use crate::error_message::{self, ParentRef};
use crate::meta::{NameRef, NamespaceRef, XmlFieldMeta};
use crate::meta::{Flag, NameRef, NamespaceRef, XmlFieldMeta};
use crate::scope::{FromEventsScope, IntoEventsScope};
use crate::types::{from_xml_text_fn, into_optional_xml_text_fn};
use crate::types::{default_fn, from_xml_text_fn, into_optional_xml_text_fn};
/// Code slices necessary for declaring and initializing a temporary variable
/// for parsing purposes.
@ -67,6 +67,10 @@ enum FieldKind {
/// The XML name of the attribute.
xml_name: NameRef,
// Flag indicating whether the value should be defaulted if the
// attribute is absent.
default_: Flag,
},
}
@ -81,6 +85,7 @@ impl FieldKind {
span,
namespace,
name,
default_,
} => {
let xml_name = match name {
Some(v) => v,
@ -107,6 +112,7 @@ impl FieldKind {
Ok(Self::Attribute {
xml_name,
xml_namespace: namespace,
default_,
})
}
}
@ -181,6 +187,7 @@ impl FieldDef {
FieldKind::Attribute {
ref xml_name,
ref xml_namespace,
ref default_,
} => {
let FromEventsScope { ref attrs, .. } = scope;
let ty = self.ty.clone();
@ -196,12 +203,24 @@ impl FieldDef {
let from_xml_text = from_xml_text_fn(ty.clone());
let on_absent = match default_ {
Flag::Absent => quote! {
return ::core::result::Result::Err(::xso::error::Error::Other(#missing_msg).into())
},
Flag::Present(_) => {
let default_ = default_fn(ty.clone());
quote! {
#default_()
}
}
};
return Ok(FieldBuilderPart::Init {
value: FieldTempInit {
init: quote! {
match #attrs.remove(#xml_namespace, #xml_name).map(#from_xml_text).transpose()? {
::core::option::Option::Some(v) => v,
::core::option::Option::None => return ::core::result::Result::Err(::xso::error::Error::Other(#missing_msg).into()),
::core::option::Option::None => #on_absent,
}
},
ty: self.ty.clone(),
@ -224,6 +243,7 @@ impl FieldDef {
FieldKind::Attribute {
ref xml_name,
ref xml_namespace,
..
} => {
let IntoEventsScope { ref attrs, .. } = scope;
@ -237,6 +257,9 @@ impl FieldDef {
let into_optional_xml_text = into_optional_xml_text_fn(self.ty.clone());
return Ok(FieldIteratorPart::Header {
// This is a neat little trick:
// Option::from(x) converts x to an Option<T> *unless* it
// already is an Option<_>.
setter: quote! {
#into_optional_xml_text(#bound_name)?.and_then(|#bound_name| #attrs.insert(
#xml_namespace,

View file

@ -108,6 +108,39 @@ impl quote::ToTokens for NameRef {
}
}
/// Represents a boolean flag from a `#[xml(..)]` attribute meta.
#[derive(Clone, Copy, Debug)]
pub(crate) enum Flag {
/// The flag is not set.
Absent,
/// The flag was set.
Present(
/// The span of the syntax element which enabled the flag.
///
/// This is used to generate useful error messages by pointing at the
/// specific place the flag was activated.
#[allow(dead_code)]
Span,
),
}
impl Flag {
/// Return true if the flag is set, false otherwise.
pub(crate) fn is_set(&self) -> bool {
match self {
Self::Absent => false,
Self::Present(_) => true,
}
}
}
impl<T: Spanned> From<T> for Flag {
fn from(other: T) -> Flag {
Flag::Present(other.span())
}
}
/// Contents of an `#[xml(..)]` attribute on a struct, enum variant, or enum.
#[derive(Debug)]
pub(crate) struct XmlCompoundMeta {
@ -261,6 +294,9 @@ pub(crate) enum XmlFieldMeta {
/// The XML name supplied.
name: Option<NameRef>,
/// The `default` flag.
default_: Flag,
},
}
@ -279,11 +315,13 @@ impl XmlFieldMeta {
span: meta.path.span(),
name: Some(name),
namespace,
default_: Flag::Absent,
})
} else if meta.input.peek(syn::token::Paren) {
// full syntax
let mut name: Option<NameRef> = None;
let mut namespace: Option<NamespaceRef> = None;
let mut default_ = Flag::Absent;
meta.parse_nested_meta(|meta| {
if meta.path.is_ident("name") {
if name.is_some() {
@ -312,6 +350,12 @@ impl XmlFieldMeta {
}
namespace = Some(meta.value()?.parse()?);
Ok(())
} else if meta.path.is_ident("default") {
if default_.is_set() {
return Err(Error::new_spanned(meta.path, "duplicate `default` key"));
}
default_ = (&meta.path).into();
Ok(())
} else {
Err(Error::new_spanned(meta.path, "unsupported key"))
}
@ -320,6 +364,7 @@ impl XmlFieldMeta {
span: meta.path.span(),
name,
namespace,
default_,
})
} else {
// argument-less syntax
@ -327,6 +372,7 @@ impl XmlFieldMeta {
span: meta.path.span(),
name: None,
namespace: None,
default_: Flag::Absent,
})
}
}

View file

@ -114,3 +114,44 @@ pub(crate) fn into_optional_xml_text_fn(ty: Type) -> Expr {
},
})
}
/// Construct a [`syn::Expr`] referring to
/// `<#of_ty as ::std::default::Default>::default`.
pub(crate) fn default_fn(of_ty: Type) -> Expr {
let span = of_ty.span();
Expr::Path(ExprPath {
attrs: Vec::new(),
qself: Some(QSelf {
lt_token: syn::token::Lt { spans: [span] },
ty: Box::new(of_ty),
position: 3,
as_token: Some(syn::token::As { span }),
gt_token: syn::token::Gt { spans: [span] },
}),
path: Path {
leading_colon: Some(syn::token::PathSep {
spans: [span, span],
}),
segments: [
PathSegment {
ident: Ident::new("std", span),
arguments: PathArguments::None,
},
PathSegment {
ident: Ident::new("default", span),
arguments: PathArguments::None,
},
PathSegment {
ident: Ident::new("Default", span),
arguments: PathArguments::None,
},
PathSegment {
ident: Ident::new("default", span),
arguments: PathArguments::None,
},
]
.into_iter()
.collect(),
},
})
}