mirror of https://gitlab.com/xmpp-rs/xmpp-rs.git
xso_proc: unify meta option parsing
That makes it easier to add new options to the various attributes.
This commit is contained in:
parent
e08a403a25
commit
c8e03ec1f8
|
@ -12,100 +12,129 @@ structs in the [`crate::field`] module, are responsible for ensuring that the
|
|||
given combinations make sense and emit compile-time errors if they do not.
|
||||
*/
|
||||
use std::fmt;
|
||||
use std::ops::ControlFlow;
|
||||
|
||||
use proc_macro2::{Span, TokenStream};
|
||||
|
||||
use quote::ToTokens;
|
||||
use syn::{spanned::Spanned, *};
|
||||
|
||||
/// Concatenate a list of identifiers into a comma-separated string.
|
||||
/// Helper trait to parse a value of some kind from a
|
||||
/// [`syn::meta::ParseNestedMeta`].
|
||||
///
|
||||
/// Used for generating error messages.
|
||||
macro_rules! concat_options {
|
||||
($head:ident, $($field:ident,)+) => {
|
||||
concat!(stringify!($head), ", ", concat_options!($($field,)+))
|
||||
};
|
||||
($last:ident,) => {
|
||||
stringify!($last)
|
||||
/// This, combined with the [`parse_meta!`] macro, reduces code duplication
|
||||
/// in the various parsing functions significantly.
|
||||
trait MetaParse {
|
||||
/// The identifier to match against the path of the meta.
|
||||
fn name(&self) -> &'static str;
|
||||
|
||||
/// The actual workhorse: Assuming that the path matches [`Self::name`],
|
||||
/// parse the data from the meta.
|
||||
fn force_parse_at_meta<'x>(&mut self, meta: meta::ParseNestedMeta<'x>) -> Result<()>;
|
||||
|
||||
/// Test the path against [`Self::name`] and parse the value if the path
|
||||
/// matches.
|
||||
///
|
||||
/// Otherwise, return [`std::ops::ControlFlow::Continue`] with the meta
|
||||
/// to allow other things to be parsed from it.
|
||||
fn parse_at_meta<'x>(
|
||||
&mut self,
|
||||
meta: meta::ParseNestedMeta<'x>,
|
||||
) -> Result<ControlFlow<(), meta::ParseNestedMeta<'x>>> {
|
||||
if !meta.path.is_ident(self.name()) {
|
||||
return Ok(ControlFlow::Continue(meta));
|
||||
}
|
||||
Ok(ControlFlow::Break(self.force_parse_at_meta(meta)?))
|
||||
}
|
||||
}
|
||||
|
||||
/// Format an "unsupported option" error message.
|
||||
///
|
||||
/// The allowed options should be passed as identifiers.
|
||||
macro_rules! unsupported_option_message {
|
||||
($($option:ident,)+) => {
|
||||
concat!("unsupported option. supported options: ", concat_options!($($option,)+), ".")
|
||||
/// Parse a [`Flag`] from a meta.
|
||||
struct ParseFlag<'a>(&'static str, &'a mut Flag);
|
||||
|
||||
impl<'a> MetaParse for ParseFlag<'a> {
|
||||
fn name(&self) -> &'static str {
|
||||
self.0
|
||||
}
|
||||
|
||||
fn force_parse_at_meta<'x>(&mut self, meta: meta::ParseNestedMeta<'x>) -> Result<()> {
|
||||
if self.1.is_set() {
|
||||
return Err(Error::new_spanned(
|
||||
meta.path,
|
||||
format!("flag {} is already set", self.name()),
|
||||
));
|
||||
}
|
||||
*self.1 = Flag::Present(meta.path.clone());
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Parse some fields out of a [`syn::meta::ParseNestedMeta`] struct.
|
||||
/// Parse any parseable value from a meta.
|
||||
struct ParseValue<'a, T: parse::Parse>(&'static str, &'a mut Option<T>);
|
||||
|
||||
impl<'a, T: parse::Parse> MetaParse for ParseValue<'a, T> {
|
||||
fn name(&self) -> &'static str {
|
||||
self.0
|
||||
}
|
||||
|
||||
fn force_parse_at_meta<'x>(&mut self, meta: meta::ParseNestedMeta<'x>) -> Result<()> {
|
||||
if self.1.is_some() {
|
||||
return Err(Error::new_spanned(
|
||||
meta.path,
|
||||
format!("duplicate {} option", self.name()),
|
||||
));
|
||||
}
|
||||
*self.1 = Some(meta.value()?.parse()?);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Parse a `Vec<ExtractMeta>` from a meta.
|
||||
struct ParseExtracts<'a>(&'a mut Vec<ExtractMeta>);
|
||||
|
||||
impl<'a> MetaParse for ParseExtracts<'a> {
|
||||
fn name(&self) -> &'static str {
|
||||
"extract"
|
||||
}
|
||||
|
||||
fn force_parse_at_meta<'x>(&mut self, meta: meta::ParseNestedMeta<'x>) -> Result<()> {
|
||||
meta.parse_nested_meta(|meta| {
|
||||
self.0.push(ExtractMeta::parse_from_meta(meta)?);
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Helper macro to chain multiple [`MetaParse`] structs in a series.
|
||||
///
|
||||
/// The first argument, `$meta`, must be the `ParseNestedMeta` struct. The
|
||||
/// remaining arguments, except for the last, must be identifiers of arguments
|
||||
/// to parse out of `$meta`.
|
||||
///
|
||||
/// Each `$field` is stringified and matched against `$meta.path.is_ident`
|
||||
/// in the order they are given. Each `$field` must also be available as
|
||||
/// mutable `Option<T>` variable outside the scope of this macro. If the
|
||||
/// `$field` matches the `$meta`, the value is extracted and parsed and
|
||||
/// assigned as `Some(..)` to the `$field`.
|
||||
///
|
||||
/// If the field has already been assigned to, an error is returned.
|
||||
///
|
||||
/// Lastly, if no `$field` matched the identifier of the `$meta.path`, the
|
||||
/// `$else` block is evaluated. This can (and must) be used to return an error
|
||||
/// or chain a call to [`parse_some_flags!`].
|
||||
macro_rules! parse_some_fields {
|
||||
($meta:ident, $($field:ident,)+ $else:block) => {
|
||||
$(
|
||||
if $meta.path.is_ident(stringify!($field)) {
|
||||
if $field.is_some() {
|
||||
return Err(syn::Error::new_spanned(
|
||||
$meta.path,
|
||||
concat!("duplicate ", stringify!($field), " option"),
|
||||
));
|
||||
/// This attempts to parse one `option` after the other, each of which must
|
||||
/// be an expression evaluating to a thing implementing [`MetaParse`]. If any
|
||||
/// matches, `Ok(())` is returned. If none matches, an error message containing
|
||||
/// the allowed option names by calling `name()` on the `option` values is
|
||||
/// returned.
|
||||
macro_rules! parse_meta {
|
||||
($meta:ident, $($option:expr,)+) => {
|
||||
#[allow(unused_assignments)]
|
||||
{
|
||||
let meta = $meta;
|
||||
$(
|
||||
let meta = match $option.parse_at_meta(meta) {
|
||||
Ok(ControlFlow::Continue(meta)) => meta,
|
||||
Ok(ControlFlow::Break(())) => return Ok(()),
|
||||
Err(e) => return Err(e),
|
||||
};
|
||||
)+
|
||||
let mut error = format!("unsupported option. supported options are: ");
|
||||
let mut first = true;
|
||||
$(
|
||||
error.reserve($option.name().len() + 2);
|
||||
if !first {
|
||||
error.push_str(", ");
|
||||
}
|
||||
$field = Some($meta.value()?.parse()?);
|
||||
Ok(())
|
||||
} else
|
||||
)+
|
||||
$else
|
||||
}
|
||||
}
|
||||
|
||||
/// Parse some flags out of a [`syn::meta::ParseNestedMeta`] struct.
|
||||
///
|
||||
/// The first argument, `$meta`, must be the `ParseNestedMeta` struct. The
|
||||
/// remaining arguments, except for the last, must be identifiers of arguments
|
||||
/// to parse out of `$meta`.
|
||||
///
|
||||
/// Each `$flag` is stringified and matched against `$meta.path.is_ident`
|
||||
/// in the order they are given. Each `$flag` must also be available as
|
||||
/// mutable [`Flag`] variable outside the scope of this macro. If the
|
||||
/// `$flag` matches the `$meta`, the `$flag` is assigned `Flag::Present` with
|
||||
/// the path of the meta as value.
|
||||
///
|
||||
/// If the flag has already been set, an error is returned.
|
||||
///
|
||||
/// Lastly, if no `$flag` matched the identifier of the `$meta.path`, the
|
||||
/// `$else` block is evaluated. This can (and must) be used to return an error
|
||||
/// or chain a call to [`parse_some_fields!`].
|
||||
macro_rules! parse_some_flags {
|
||||
($meta:ident, $($flag:ident,)+ $else:block) => {
|
||||
$(
|
||||
if $meta.path.is_ident(stringify!($flag)) {
|
||||
if $flag.is_set() {
|
||||
return Err(syn::Error::new_spanned(
|
||||
$meta.path,
|
||||
concat!("duplicate ", stringify!($flag), " flag"),
|
||||
));
|
||||
}
|
||||
$flag = Flag::Present($meta.path);
|
||||
Ok(())
|
||||
} else
|
||||
)+
|
||||
$else
|
||||
first = false;
|
||||
error.push_str($option.name());
|
||||
)+
|
||||
Err(Error::new_spanned(meta.path, error))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -325,21 +354,16 @@ impl XmlCompoundMeta {
|
|||
let mut prepare: Option<Path> = None;
|
||||
|
||||
attr.parse_nested_meta(|meta| {
|
||||
parse_some_fields!(meta, name, namespace, validate, prepare, {
|
||||
parse_some_flags!(meta, fallback, transparent, exhaustive, {
|
||||
Err(syn::Error::new_spanned(
|
||||
meta.path,
|
||||
unsupported_option_message!(
|
||||
name,
|
||||
namespace,
|
||||
validate,
|
||||
fallback,
|
||||
transparent,
|
||||
exhaustive,
|
||||
),
|
||||
))
|
||||
})
|
||||
})
|
||||
parse_meta!(
|
||||
meta,
|
||||
ParseValue("name", &mut name),
|
||||
ParseValue("namespace", &mut namespace),
|
||||
ParseFlag("fallback", &mut fallback),
|
||||
ParseFlag("transparent", &mut transparent),
|
||||
ParseFlag("exhaustive", &mut exhaustive),
|
||||
ParseValue("validate", &mut validate),
|
||||
ParseValue("prepare", &mut prepare),
|
||||
)
|
||||
})?;
|
||||
|
||||
Ok(Self {
|
||||
|
@ -440,35 +464,13 @@ impl AttributeMeta {
|
|||
let mut ty: Option<Type> = None;
|
||||
let mut default_on_missing: Flag = Flag::Absent;
|
||||
meta.parse_nested_meta(|meta| {
|
||||
parse_some_fields!(meta, namespace, name, {
|
||||
if meta.path.is_ident("type") {
|
||||
if ty.is_some() {
|
||||
return Err(syn::Error::new_spanned(
|
||||
meta.path,
|
||||
"duplicate type option",
|
||||
));
|
||||
}
|
||||
ty = Some(meta.value()?.parse()?);
|
||||
Ok(())
|
||||
} else if meta.path.is_ident("default") {
|
||||
if default_on_missing.is_set() {
|
||||
return Err(syn::Error::new_spanned(
|
||||
meta.path,
|
||||
"duplicate default option",
|
||||
));
|
||||
}
|
||||
default_on_missing = Flag::Present(meta.path);
|
||||
Ok(())
|
||||
} else {
|
||||
Err(Error::new_spanned(
|
||||
meta.path,
|
||||
format!(
|
||||
"unsupported option. supported options are: {}",
|
||||
concat_options!(namespace, name, default, type,),
|
||||
),
|
||||
))
|
||||
}
|
||||
})
|
||||
parse_meta!(
|
||||
meta,
|
||||
ParseValue("name", &mut name),
|
||||
ParseValue("namespace", &mut namespace),
|
||||
ParseValue("type", &mut ty),
|
||||
ParseFlag("default", &mut default_on_missing),
|
||||
)
|
||||
})?;
|
||||
Ok(Self {
|
||||
namespace,
|
||||
|
@ -530,25 +532,9 @@ impl ExtractMeta {
|
|||
if meta.path.is_ident("text") {
|
||||
if meta.input.peek(token::Paren) {
|
||||
let mut ty: Option<Type> = None;
|
||||
#[rustfmt::skip] // rustfmt transforms the code so that the attribute inside parse_meta! is on an expression which is not allowed
|
||||
meta.parse_nested_meta(|meta| {
|
||||
if meta.path.is_ident("type") {
|
||||
if ty.is_some() {
|
||||
return Err(syn::Error::new_spanned(
|
||||
meta.path,
|
||||
"duplicate type option",
|
||||
));
|
||||
}
|
||||
ty = Some(meta.value()?.parse()?);
|
||||
Ok(())
|
||||
} else {
|
||||
Err(Error::new_spanned(
|
||||
meta.path,
|
||||
format!(
|
||||
"unsupported option. supported options are: {}",
|
||||
concat_options!(type,),
|
||||
),
|
||||
))
|
||||
}
|
||||
parse_meta!(meta, ParseValue("type", &mut ty),)
|
||||
})?;
|
||||
Ok(Self::Text { ty })
|
||||
} else {
|
||||
|
@ -735,29 +721,13 @@ impl XmlFieldMeta {
|
|||
let mut extract: Vec<ExtractMeta> = Vec::new();
|
||||
let mut default_on_missing = Flag::Absent;
|
||||
meta.parse_nested_meta(|meta| {
|
||||
parse_some_fields!(meta, namespace, name, {
|
||||
if meta.path.is_ident("extract") {
|
||||
meta.parse_nested_meta(|meta| {
|
||||
extract.push(ExtractMeta::parse_from_meta(meta)?);
|
||||
Ok(())
|
||||
})?;
|
||||
Ok(())
|
||||
} else if meta.path.is_ident("default") {
|
||||
if default_on_missing.is_set() {
|
||||
return Err(syn::Error::new_spanned(
|
||||
meta.path,
|
||||
"duplicate default option",
|
||||
));
|
||||
}
|
||||
default_on_missing = Flag::Present(meta.path);
|
||||
Ok(())
|
||||
} else {
|
||||
Err(Error::new_spanned(
|
||||
meta.path,
|
||||
unsupported_option_message!(namesace, name, extract,),
|
||||
))
|
||||
}
|
||||
})
|
||||
parse_meta!(
|
||||
meta,
|
||||
ParseValue("name", &mut name),
|
||||
ParseValue("namespace", &mut namespace),
|
||||
ParseExtracts(&mut extract),
|
||||
ParseFlag("default", &mut default_on_missing),
|
||||
)
|
||||
})?;
|
||||
Ok((namespace, name, extract, default_on_missing))
|
||||
} else {
|
||||
|
|
Loading…
Reference in New Issue