use proc_macro2::TokenStream;
use syn::{Error, Ident, Item, ItemEnum, Meta};
pub(crate) struct Parsed {
pub attr: Ident,
pub item: ItemEnum,
}
fn format_error_output(msg: &str, help: &str) -> String {
format!("{msg}\n\n = help: {help}\n\n")
}
pub(crate) fn parse(attr: TokenStream, input: TokenStream) -> syn::Result<Parsed> {
const HELP_ATTR: &str = "`repr_conv` expects a single attribute which is an integer type";
const HELP_ITEM: &str = "`repr_conv` can only be used on fieldless enums";
const INTEGRAL_TYPES_SORTED: [&str; 12] = [
"i128", "i16", "i32", "i64", "i8", "isize", "u128", "u16", "u32", "u64", "u8", "usize",
];
let attr = match syn::parse2::<Meta>(attr) {
Ok(Meta::Path(attr)) => {
attr.require_ident()?;
let attr = attr.get_ident().unwrap().clone();
match INTEGRAL_TYPES_SORTED.binary_search(&attr.to_string().as_str()) {
Ok(_) => Ok(attr),
Err(_) => Err(Error::new_spanned(
&attr,
format_error_output("unsupported representation type", HELP_ATTR),
)),
}
}
Ok(attr) => Err(Error::new_spanned(
&attr,
format_error_output("invalid attribute format", HELP_ATTR),
)),
Err(err) => Err(Error::new(
err.span(),
format_error_output(&err.to_string(), HELP_ATTR),
)),
}?;
let item = match syn::parse2::<Item>(input.clone()) {
Ok(Item::Enum(item)) => Ok(item),
Ok(_) => Err(Error::new_spanned(
&input,
format_error_output("item is not an enum", HELP_ITEM),
)),
Err(_) => unreachable!(), }?;
for variant in item.variants.iter() {
if !variant.fields.is_empty() {
return Err(Error::new_spanned(
&item,
format_error_output("must be a fieldless enum", HELP_ITEM),
));
}
}
Ok(Parsed { attr, item })
}
#[cfg(test)]
mod tests {
use quote::quote;
use super::*;
#[test]
fn valid_syntax_fieldless_enum() {
assert!(parse(
quote!(u32),
quote!(
enum Fieldless {
Zero = 0,
One,
Two = 0x02,
}
),
)
.is_ok());
}
#[test]
fn invalid_syntax_empty_attr() {
assert!(parse(
quote!(),
quote!(
enum Fieldless {
Zero = 0,
One,
Two = 0x02,
}
),
)
.is_err());
}
#[test]
fn invalid_syntax_non_ident_attr() {
assert!(parse(
quote!(Option<u16>),
quote!(
enum Fieldless {
Zero = 0,
One,
Two = 0x02,
}
),
)
.is_err());
}
#[test]
fn invalid_syntax_list_attr() {
assert!(parse(
quote!(u16, u32),
quote!(
enum Fieldless {
Zero = 0,
One,
Two = 0x02,
}
),
)
.is_err());
}
#[test]
fn invalid_syntax_name_value_attr() {
assert!(parse(
quote!(type = u16),
quote!(
enum Fieldless {
Zero = 0,
One,
Two = 0x02,
}
),
)
.is_err());
}
#[test]
fn invalid_syntax_malformed_attr() {
assert!(parse(
quote!(!@#$%),
quote!(
enum Fieldless {
Zero = 0,
One,
Two = 0x02,
}
),
)
.is_err());
}
#[test]
fn invalid_syntax_unsupported_attr() {
assert!(parse(
quote!(C), quote!(
enum Fieldless {
Zero = 0,
One,
Two = 0x02,
}
),
)
.is_err());
}
#[test]
fn invalid_syntax_non_enum_item() {
assert!(parse(
quote!(u32),
quote!(
struct NotAnEnum {
zero: u32,
one: String,
two: (),
}
),
)
.is_err());
}
#[test]
fn invalid_syntax_enum_with_fields() {
assert!(parse(
quote!(u32),
quote!(
enum WithFields {
Zero,
One(u16),
Two { s: String },
}
),
)
.is_err());
}
}