xmpp-rs/xso-proc/src/compound.rs

429 lines
15 KiB
Rust

/*!
# Types to represent compounds
A [`Compound`] is either an enum variant or a struct. These types are used by
[`crate::enums`] and [`crate::structs`], as well as for extracted child fields
in order to build the code to convert to/from XML nodes.
*/
use proc_macro2::{Span, TokenStream};
use quote::quote;
use syn::{spanned::Spanned, *};
use crate::error_message::ParentRef;
use crate::field::{FieldDef, FieldParsePart};
/// Expand the given identifier to resolve as member of the
/// `UnknownChildPolicy` enum. If the identifier is absent, invoke
/// `UnknownChildPolicy::default()` instead.
fn default_on_unknown_child(p: Option<Ident>) -> Expr {
match p {
Some(v) => Expr::Path(ExprPath {
attrs: Vec::new(),
qself: None,
path: Path {
leading_colon: Some(token::PathSep {
spans: [Span::call_site(), Span::call_site()],
}),
segments: [
PathSegment::from(Ident::new("xso", Span::call_site())),
PathSegment::from(Ident::new("UnknownChildPolicy", Span::call_site())),
PathSegment::from(v),
]
.into_iter()
.collect(),
},
}),
None => syn::parse2(quote! { ::xso::UnknownChildPolicy::default() })
.expect("failed to construct default unknown child policy"),
}
}
/// Expand the given identifier to resolve as member of the
/// `UnknownAttributePolicy` enum. If the identifier is absent, invoke
/// `UnknownAttributePolicy::default()` instead.
fn default_on_unknown_attribute(p: Option<Ident>) -> Expr {
match p {
Some(v) => Expr::Path(ExprPath {
attrs: Vec::new(),
qself: None,
path: Path {
leading_colon: Some(token::PathSep {
spans: [Span::call_site(), Span::call_site()],
}),
segments: [
PathSegment::from(Ident::new("xso", Span::call_site())),
PathSegment::from(Ident::new("UnknownAttributePolicy", Span::call_site())),
PathSegment::from(v),
]
.into_iter()
.collect(),
},
}),
None => syn::parse2(quote! { ::xso::UnknownAttributePolicy::default() })
.expect("failed to construct default unknown attribute policy"),
}
}
/// An struct or enum variant's contents.
///
/// This struct is used to generate the parsing/serialisation loop, but most
/// notably it is *not* responsible for matching the incoming element; that
/// is the responsibility of the caller of the corresponding functions.
#[derive(Debug)]
pub(crate) struct Compound {
/// The fields, in declaration order.
fields: Vec<FieldDef>,
/// Information about the field annotated with `#[xml(namespace)]`, if
/// any.
namespace_field: Option<(Span, Type, Member)>,
/// Member of the `UnknownChildPolicy` enum to use when handling unknown
/// children.
on_unknown_child: Expr,
/// Member of the `UnknownAttributePolicy` enum to use when handling
/// unknown attributes.
on_unknown_attribute: Expr,
}
impl Compound {
/// Construct a new compound from an iterator of [`FieldDef`] structs.
pub(crate) fn new<T: Iterator<Item = Result<FieldDef>>>(
on_unknown_child: Option<Ident>,
on_unknown_attribute: Option<Ident>,
input: T,
) -> Result<Self> {
let mut fields = Vec::with_capacity(input.size_hint().1.unwrap_or(0));
let mut text_field: Option<Span> = None;
let mut namespace_field: Option<(Span, Type, Member)> = None;
let mut collect_wildcard_field: Option<Span> = None;
for field in input {
let field = field?;
if let Some(ty) = field.namespace_field_type() {
if namespace_field.is_some() {
return Err(Error::new_spanned(
field.ident,
"only one #[xml(namespace)] field is allowed",
));
}
namespace_field = Some((field.span, ty.clone(), field.ident.clone()));
};
if field.kind.is_text() {
if text_field.is_some() {
return Err(Error::new_spanned(
field.ident,
"only one #[xml(text)] field is allowed",
));
}
text_field = Some(field.ident.span());
}
if field.kind.is_child_wildcard() {
if collect_wildcard_field.is_some() {
return Err(Error::new_spanned(
field.ident,
"only one #[xml(elements)] field without namespace/name selector is allowed",
));
}
collect_wildcard_field = Some(field.ident.span());
}
fields.push(field);
}
Ok(Self {
fields,
namespace_field,
on_unknown_child: default_on_unknown_child(on_unknown_child),
on_unknown_attribute: default_on_unknown_attribute(on_unknown_attribute),
})
}
/// Construct a compound from [`syn::Fields`].
///
/// This a convenience wrapper around [`Self::new`], converting the
/// [`syn::Field`] structs to [`FieldDef`].
pub(crate) fn from_fields(
on_unknown_child: Option<Ident>,
on_unknown_attribute: Option<Ident>,
fields: &Fields,
) -> Result<Self> {
Self::new(
on_unknown_child,
on_unknown_attribute,
fields.iter().enumerate().map(|(i, field)| {
FieldDef::from_field(field, i.try_into().expect("too many fields"))
}),
)
}
/// Obtain references to the information about the
/// `#[xml(namespace)]`-annotated field, if this compound has one.
pub(crate) fn namespace_field(&self) -> Option<(Span, &Type, &Member)> {
self.namespace_field.as_ref().map(|(a, b, c)| (*a, b, c))
}
/// Number of fields.
pub(crate) fn field_count(&self) -> usize {
self.fields.len()
}
/// Construct a token stream which contains an expression which parses
/// the contents `minidom::Element` at `residual` into the compound.
///
/// - `container_name` is used both for error messages and to construct
/// the resulting compound. If it directly refers to a path, that path
/// is used as constructor. Otherwise, the compound is constructed as
/// tuple.
///
/// - `container_namespace_expr` must be an expression which evaluates to
/// the parsed namespace of the parent element. If
/// [`Self::namespace_field`] is not `None`, this must be an expression
/// which can be moved out of and which matches the type, as that
/// expression will be used to initialize that field.
///
/// In all other cases, this expression only needs to be usable in a
/// `std::cmp::PartialEq<str>` context.
///
/// - `residual` must be the identifier at which the element is found.
/// - `forgive_attributes` must be a (potentially empty) slice of XML
/// attribute names to ignore during the unknown attribute check. This
/// can be used to ignore attributes which have been used in element
/// matching (e.g. enum discriminators).
pub(crate) fn build_try_from_element(
&self,
container_name: &ParentRef,
container_namespace_expr: &Expr,
residual: &Ident,
forgive_attributes: &[&str],
) -> Result<TokenStream> {
let readable_name = container_name.to_string();
let on_unknown_child = &self.on_unknown_child;
let on_unknown_attribute = &self.on_unknown_attribute;
let mut init = quote! {};
let mut tupinit = quote! {};
let mut attrcheck = quote! {
#(
if key == #forgive_attributes {
continue;
}
)*
};
let mut tempinit = quote! {};
let mut childiter = quote! {};
let mut childfallback = quote! {
#on_unknown_child.trigger(concat!("Unknown child in ", #readable_name, "."))?;
};
let mut had_fallback: bool = false;
for field in self.fields.iter() {
let ident = field.ident.clone();
let FieldParsePart {
tempinit: field_tempinit,
childiter: field_childiter,
attrcheck: field_attrcheck,
value,
childfallback: field_childfallback,
} = field.build_try_from_element(container_name, container_namespace_expr)?;
attrcheck = quote! { #attrcheck #field_attrcheck };
tempinit = quote! { #tempinit #field_tempinit };
childiter = quote! { #childiter #field_childiter };
if let Some(field_childfallback) = field_childfallback {
if had_fallback {
panic!(
"internal error: multiple fields attempting to collect all child elements."
);
}
had_fallback = true;
childfallback = field_childfallback;
}
init = quote! {
#init
#ident: #value,
};
tupinit = quote! {
#tupinit
#value,
};
}
let construct = match container_name {
ParentRef::Named(ref path) => quote! {
#path {
#init
}
},
ParentRef::Unnamed { .. } | ParentRef::Wrapper { .. } => quote! {
( #tupinit )
},
};
Ok(quote! {
{
for (key, _) in #residual.attrs() {
#attrcheck
#on_unknown_attribute.trigger(concat!("Unknown attribute in ", #readable_name, "."))?;
}
#tempinit
for mut residual in #residual.take_contents_as_children() {
#childiter
#childfallback
}
#construct
}
})
}
/// Construct an expression consuming the `builder` and returning it,
/// filled with the data from the compound.
///
/// - `container_name` is used for error messages.
///
/// - `container_namespace_expr` must be an expression which evaluates to
/// the parsed namespace of the parent element. This needs to implement
/// `::xso::DynNamespaceEnum` if any fields use
/// `#[xml(namespace = super)]`.
///
/// - `builder` must be an expression which can be moved out from and
/// which is the `minidom::Builder` into which the element should be
/// constructed.
///
/// - `access_field`: Accessor function for fields within the compound.
/// That function will be called for each field and the expression is
/// used exactly once to obtain the data of the field inside the
/// compound.
///
/// This indirection is necessary to be able to handle both structs
/// (where fields are accessed as struct members) and enumeration
/// variants (where fields are bound to local names).
///
/// Note that the field referenced by [`Self::namespace_field`] is not
/// accessed by this function.
pub(crate) fn build_into_element(
&self,
container_name: &ParentRef,
container_namespace_expr: &Expr,
builder: &Ident,
mut access_field: impl FnMut(Member) -> Expr,
) -> Result<TokenStream> {
let mut build = quote! {};
for field in self.fields.iter() {
let field_build = field.build_into_element(
container_name,
container_namespace_expr,
&mut access_field,
)?;
build = quote! {
#build
builder = #field_build;
};
}
Ok(quote! {
{
let mut builder = #builder;
#build
builder
}
})
}
/// Return an iterator which returns the [`syn::Member`] structs to access
/// the compound's fields in declaration order.
///
/// For tuple-like compounds that's basically counting up from 0, for
/// named compounds this emits the field names in declaration order.
pub(crate) fn iter_members(&self) -> Box<dyn Iterator<Item = Member> + '_> {
Box::new(self.fields.iter().map(|x| x.ident.clone().into()))
}
/// If and only if this compound has exactly one field, return a reference
/// to that field's type.
pub(crate) fn single_type(&self) -> Option<&Type> {
if self.fields.len() != 1 {
None
} else {
Some(&self.fields[0].ty)
}
}
/// Return a [`DynCompound`] refering to `self`, if and only if this
/// compound has a valid `#[xml(namespace)]` field.
///
/// The function and type name refer to this being a precondition for a
/// valid `namespace = dyn` struct or enum variant.
pub(crate) fn as_dyn(&self) -> Option<DynCompound<'_>> {
let (_, ty, member) = self.namespace_field.as_ref()?;
Some(DynCompound {
namespace_ty: ty,
namespace_member: member,
fields: &self.fields,
})
}
}
/// Reference to a [`Compound`] which has proven that it has a
/// `#[xml(namespace)]` field.
///
/// This simplifies some checks here and there.
pub(crate) struct DynCompound<'x> {
/// The type of the `#[xml(namespace)]` field.
namespace_ty: &'x Type,
/// The member referring to the `#[xml(namespace)]` field.
namespace_member: &'x Member,
/// The fields of the compound.
fields: &'x [FieldDef],
}
impl<'x> DynCompound<'x> {
/// Return a reference to the [`Type`] of the field annotated with
/// `#[xml(namespace)]`.
pub(crate) fn namespace_ty(&self) -> &'x Type {
self.namespace_ty
}
/// Build the implementation of
/// `DynNamespace::namespace(&self) -> &Self::Namespace`.
pub(crate) fn build_get_namespace(
&self,
mut access_field: impl FnMut(Member) -> Expr,
) -> Result<TokenStream> {
let member = access_field(self.namespace_member.clone());
Ok(quote! {
&#member
})
}
/// Build the implementation of
/// `DynNamespace::set_namespace<T: Into<Self::Namespace>>(&mut self, ns: T)`.
pub(crate) fn build_set_namespace(
&self,
input: &Ident,
mut access_field: impl FnMut(Member) -> Expr,
) -> Result<TokenStream> {
let member = access_field(self.namespace_member.clone());
let mut field_impls = quote! {};
for field in self.fields {
let field_impl = field.build_set_namespace(input, &mut access_field);
field_impls.extend(field_impl);
}
Ok(quote! {
#field_impls
#member = #input;
})
}
}