Revision control
Copy as Markdown
Other Tools
use quote::{quote, ToTokens};
use syn::parse::{Parse, ParseStream};
use syn::*;
// An attribute that is a list of idents
pub struct EnumConvertAttribute {
path: Path,
needs_wildcard: bool,
}
impl Parse for EnumConvertAttribute {
fn parse(input: ParseStream) -> Result<Self> {
let paths = input.parse_terminated(Path::parse, Token![,])?;
if paths.is_empty() {
return Err(input.error("#[diplomat::enum_convert] needs a path argument"));
}
let needs_wildcard = if paths.len() == 2 {
if let Some(ident) = paths[1].get_ident() {
if ident == "needs_wildcard" {
true
} else {
return Err(input.error(
"#[diplomat::enum_convert] only recognizes needs_wildcard keyword",
));
}
} else {
return Err(
input.error("#[diplomat::enum_convert] only recognizes needs_wildcard keyword")
);
}
} else if paths.len() > 1 {
return Err(input.error("#[diplomat::enum_convert] only supports up to two arguments"));
} else {
// no needs_wildcard marker
false
};
Ok(EnumConvertAttribute {
path: paths[0].clone(),
needs_wildcard,
})
}
}
pub fn gen_enum_convert(attr: EnumConvertAttribute, input: ItemEnum) -> proc_macro2::TokenStream {
let mut from_arms = vec![];
let mut into_arms = vec![];
let this_name = &input.ident;
let other_name = &attr.path;
for variant in &input.variants {
if variant.fields != Fields::Unit {
return Error::new(variant.ident.span(), "variant may not have fields")
.to_compile_error();
}
let variant_name = &variant.ident;
from_arms.push(quote!(#other_name::#variant_name => Self::#variant_name));
into_arms.push(quote!(#this_name::#variant_name => Self::#variant_name));
}
if attr.needs_wildcard {
let error = format!(
"Encountered unknown field for {}",
other_name.to_token_stream()
);
from_arms.push(quote!(_ => unreachable!(#error)))
}
quote! {
impl From<#other_name> for #this_name {
fn from(other: #other_name) -> Self {
match other {
#(#from_arms,)*
}
}
}
impl From<#this_name> for #other_name {
fn from(this: #this_name) -> Self {
match this {
#(#into_arms,)*
}
}
}
}
}