xso: implement exhaustive enums

These more closely mirror how enums work currently with the macros.
Non-exhaustive enums may be useful though and kind of were the natural
thing to implement.
This commit is contained in:
Jonas Schäfer 2024-07-10 16:35:29 +02:00
commit c028c3b91a
6 changed files with 127 additions and 2 deletions

View file

@ -40,12 +40,14 @@ impl NameVariant {
span: meta_span,
namespace,
name,
exhaustive,
debug,
builder,
iterator,
} = XmlCompoundMeta::parse_from_attributes(&decl.attrs)?;
reject_key!(debug flag not on "enum variants" only on "enums and structs");
reject_key!(exhaustive flag not on "enum variants" only on "enums");
reject_key!(namespace not on "enum variants" only on "enums and structs");
reject_key!(builder not on "enum variants" only on "enums and structs");
reject_key!(iterator not on "enum variants" only on "enums and structs");
@ -142,6 +144,9 @@ pub(crate) struct EnumDef {
/// The variants of the enum.
variants: Vec<NameVariant>,
/// Flag indicating whether the enum is exhaustive.
exhaustive: bool,
/// Name of the target type.
target_ty_ident: Ident,
@ -169,6 +174,7 @@ impl EnumDef {
span: meta_span,
namespace,
name,
exhaustive,
debug,
builder,
iterator,
@ -210,6 +216,7 @@ impl EnumDef {
Ok(Self {
namespace,
variants,
exhaustive: exhaustive.is_set(),
target_ty_ident: ident.clone(),
builder_ty_ident,
item_iter_ty_ident,
@ -245,6 +252,15 @@ impl ItemDef for EnumDef {
}
});
if self.exhaustive {
let mismatch_err = format!("This is not a {} element.", target_ty_ident);
statemachine.set_fallback(quote! {
::core::result::Result::Err(::xso::error::FromEventsError::Invalid(
::xso::error::Error::Other(#mismatch_err),
))
})
}
let defs = statemachine.render(
vis,
builder_ty_ident,

View file

@ -235,6 +235,9 @@ pub(crate) struct XmlCompoundMeta {
/// The value assigned to `iterator` inside `#[xml(..)]`, if any.
pub(crate) iterator: Option<Ident>,
/// The exhaustive flag.
pub(crate) exhaustive: Flag,
}
impl XmlCompoundMeta {
@ -248,6 +251,7 @@ impl XmlCompoundMeta {
let mut builder = None;
let mut iterator = None;
let mut debug = Flag::Absent;
let mut exhaustive = Flag::Absent;
attr.parse_nested_meta(|meta| {
if meta.path.is_ident("name") {
@ -280,6 +284,12 @@ impl XmlCompoundMeta {
}
iterator = Some(meta.value()?.parse()?);
Ok(())
} else if meta.path.is_ident("exhaustive") {
if exhaustive.is_set() {
return Err(Error::new_spanned(meta.path, "duplicate `exhaustive` key"));
}
exhaustive = (&meta.path).into();
Ok(())
} else {
Err(Error::new_spanned(meta.path, "unsupported key"))
}
@ -292,6 +302,7 @@ impl XmlCompoundMeta {
debug,
builder,
iterator,
exhaustive,
})
}

View file

@ -177,6 +177,7 @@ impl FromEventsSubmachine {
advance_match_arms,
variants: vec![FromEventsEntryPoint { init: self.init }],
pre_init: TokenStream::default(),
fallback: None,
}
}
@ -373,6 +374,12 @@ pub(crate) struct FromEventsStateMachine {
/// Extra code run during pre-init phase.
pre_init: TokenStream,
/// Code to run as fallback if none of the branches matched the start
/// event.
///
/// If absent, a `FromEventsError::Mismatch` is generated.
fallback: Option<TokenStream>,
/// A sequence of enum variant declarations, separated and terminated by
/// commas.
state_defs: TokenStream,
@ -402,6 +409,7 @@ impl FromEventsStateMachine {
advance_match_arms: TokenStream::default(),
pre_init: TokenStream::default(),
variants: Vec::new(),
fallback: None,
}
}
@ -409,6 +417,7 @@ impl FromEventsStateMachine {
///
/// This *discards* the other state machine's pre-init code.
pub(crate) fn merge(&mut self, other: FromEventsStateMachine) {
assert!(other.fallback.is_none());
self.defs.extend(other.defs);
self.state_defs.extend(other.state_defs);
self.advance_match_arms.extend(other.advance_match_arms);
@ -424,6 +433,14 @@ impl FromEventsStateMachine {
self.pre_init = code;
}
/// Set the fallback code to use if none of the branches matches the start
/// event.
///
/// By default, a `FromEventsError::Mismatch` is generated.
pub(crate) fn set_fallback(&mut self, code: TokenStream) {
self.fallback = Some(code);
}
/// Render the state machine as a token stream.
///
/// The token stream contains the following pieces:
@ -446,6 +463,7 @@ impl FromEventsStateMachine {
advance_match_arms,
variants,
pre_init,
fallback,
} = self;
let mut init_body = pre_init;
@ -460,6 +478,12 @@ impl FromEventsStateMachine {
})
}
let fallback = fallback.unwrap_or_else(|| {
quote! {
::core::result::Result::Err(::xso::error::FromEventsError::Mismatch { name, attrs })
}
});
let output_ty_ref = make_ty_ref(output_ty);
let docstr = format!("Build a {0} from XML events.\n\nThis type is generated using the [`macro@xso::FromXml`] derive macro and implements [`xso::FromEventsBuilder`] for {0}.", output_ty_ref);
@ -520,7 +544,7 @@ impl FromEventsStateMachine {
) -> ::core::result::Result<Self, ::xso::error::FromEventsError> {
#init_body
{ let _ = &mut attrs; }
::core::result::Result::Err(::xso::error::FromEventsError::Mismatch { name, attrs })
#fallback
}
}
})

View file

@ -12,7 +12,7 @@ use syn::*;
use crate::common::{AsXmlParts, FromXmlParts, ItemDef};
use crate::compound::Compound;
use crate::meta::{NameRef, NamespaceRef, XmlCompoundMeta};
use crate::meta::{reject_key, Flag, NameRef, NamespaceRef, XmlCompoundMeta};
/// Definition of a struct and how to parse it.
pub(crate) struct StructDef {
@ -48,11 +48,14 @@ impl StructDef {
span: meta_span,
namespace,
name,
exhaustive,
debug,
builder,
iterator,
} = meta;
reject_key!(exhaustive flag not on "structs" only on "enums");
let Some(namespace) = namespace else {
return Err(Error::new(meta_span, "`namespace` is required on structs"));
};