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

145 lines
4.7 KiB
Rust

/*!
Helpers used both for enums and structs.
*/
use proc_macro2::TokenStream;
use quote::quote;
use syn::*;
use crate::error_message::ParentRef;
/// Extract the relevant parts from an [`Item`]'s [`Generics`] so
/// that they can be used inside [`quote::quote`] to form `impl` items.
///
/// The returned parts are:
/// - The list of parameters incl. bounds enclosed in `< .. >`, for use right
/// after the `impl` keyword. If there are no parameters, this part is
/// empty.
/// - The list of parameters without bounds enclosed in `< .. >`, for use when
/// referring to the Item's type. If there are no parameters, this part is
/// empty.
/// - The where clause, if any.
///
/// The results are formed so that they can be used unconditionally, i.e. the
/// parameter lists are completely empty token streams if and only if the
/// [`Generics`] do not contain any parameters.
pub(crate) fn bake_generics(generics: Generics) -> (TokenStream, TokenStream, Option<WhereClause>) {
let params = generics.params;
let where_clause = generics.where_clause;
if params.len() > 0 {
let mut params_ref = Vec::new();
for param in params.iter() {
params_ref.push(match param {
GenericParam::Lifetime(lt) => GenericArgument::Lifetime(lt.lifetime.clone()),
GenericParam::Type(ty) => GenericArgument::Type(Type::Path(TypePath {
qself: None,
path: ty.ident.clone().into(),
})),
GenericParam::Const(cst) => GenericArgument::Const(Expr::Path(ExprPath {
attrs: Vec::new(),
qself: None,
path: cst.ident.clone().into(),
})),
});
}
(
quote! {
< #params >
},
quote! {
< #( #params_ref ),* >
},
where_clause,
)
} else {
(quote! {}, quote! {}, where_clause)
}
}
/// Build a statement calling the validator function at `validate`, if any.
///
/// This assumes that the argument for `validate` is called `result`.
pub(crate) fn build_validate(validate: Option<&Path>) -> Stmt {
syn::parse2(if let Some(validate) = validate {
quote! {
#validate(&mut result)?;
}
} else {
quote! {
{ let _ = &mut result; };
}
})
.expect("failed to build validation code")
}
/// Build a statement calling the preparation function at `prepare`, if any.
///
/// The argument passed to `prepare` is `value_ident`.
pub(crate) fn build_prepare(prepare: Option<&Path>, value_ident: &Ident) -> TokenStream {
if let Some(prepare) = prepare {
quote! {
#prepare(&mut #value_ident);
}
} else {
quote! {
{ let _ = &mut #value_ident; };
}
}
}
pub trait ItemDef: std::fmt::Debug {
/// Construct an expression which consumes `residual` and evaluates to
/// `Result<T, Error>`.
///
/// - `item_name` may contain either the path necessary to construct an
/// instance of the item or a nested parent ref. The latter case may not
/// be supported by all implementations of `ItemDef`.
///
/// - `residual` must be the identifier of the `minidom::Element` to
/// process.
fn build_try_from_element(
&self,
item_name: &ParentRef,
residual: &Ident,
) -> Result<TokenStream>;
/// Construct an expression which consumes the `T` value at `value_ident`
/// and returns a `minidom::Element`.
///
/// - `item_name` is used primarily for diagnostic messages.
///
/// - `value_ident` must be the identifier at which the entire struct can
/// be reached. It is used during preparation.
fn build_into_element(&self, item_name: &ParentRef, value_ident: &Ident)
-> Result<TokenStream>;
/// Construct a token stream containing the entire body of the
/// `impl DynNamespace` block.
///
/// Can only be used on `namespace = dyn` items; any other variants will
/// cause an appropriate compile-time error.
fn build_dyn_namespace(&self) -> Result<TokenStream>;
}
impl<T: ItemDef + ?Sized> ItemDef for Box<T> {
fn build_try_from_element(
&self,
item_name: &ParentRef,
residual: &Ident,
) -> Result<TokenStream> {
(**self).build_try_from_element(item_name, residual)
}
fn build_into_element(
&self,
item_name: &ParentRef,
value_ident: &Ident,
) -> Result<TokenStream> {
(**self).build_into_element(item_name, value_ident)
}
fn build_dyn_namespace(&self) -> Result<TokenStream> {
(**self).build_dyn_namespace()
}
}