diplomat/
enum_convert.rs

1use quote::{quote, ToTokens};
2use syn::parse::{Parse, ParseStream};
3use syn::*;
4
5// An attribute that is a list of idents
6pub struct EnumConvertAttribute {
7    path: Path,
8
9    needs_wildcard: bool,
10}
11
12impl Parse for EnumConvertAttribute {
13    fn parse(input: ParseStream) -> Result<Self> {
14        let paths = input.parse_terminated(Path::parse, Token![,])?;
15        if paths.is_empty() {
16            return Err(input.error("#[diplomat::enum_convert] needs a path argument"));
17        }
18        let needs_wildcard = if paths.len() == 2 {
19            if let Some(ident) = paths[1].get_ident() {
20                if ident == "needs_wildcard" {
21                    true
22                } else {
23                    return Err(input.error(
24                        "#[diplomat::enum_convert] only recognizes needs_wildcard keyword",
25                    ));
26                }
27            } else {
28                return Err(
29                    input.error("#[diplomat::enum_convert] only recognizes needs_wildcard keyword")
30                );
31            }
32        } else if paths.len() > 1 {
33            return Err(input.error("#[diplomat::enum_convert] only supports up to two arguments"));
34        } else {
35            // no needs_wildcard marker
36            false
37        };
38        Ok(EnumConvertAttribute {
39            path: paths[0].clone(),
40            needs_wildcard,
41        })
42    }
43}
44
45pub fn gen_enum_convert(attr: EnumConvertAttribute, input: ItemEnum) -> proc_macro2::TokenStream {
46    let mut from_arms = vec![];
47    let mut into_arms = vec![];
48
49    let this_name = &input.ident;
50    let other_name = &attr.path;
51    for variant in &input.variants {
52        if variant.fields != Fields::Unit {
53            return Error::new(variant.ident.span(), "variant may not have fields")
54                .to_compile_error();
55        }
56
57        let variant_name = &variant.ident;
58        from_arms.push(quote!(#other_name::#variant_name => Self::#variant_name));
59        into_arms.push(quote!(#this_name::#variant_name => Self::#variant_name));
60    }
61
62    if attr.needs_wildcard {
63        let error = format!(
64            "Encountered unknown field for {}",
65            other_name.to_token_stream()
66        );
67        from_arms.push(quote!(_ => unreachable!(#error)))
68    }
69    quote! {
70        impl From<#other_name> for #this_name {
71            fn from(other: #other_name) -> Self {
72                match other {
73                    #(#from_arms,)*
74                }
75            }
76        }
77        impl From<#this_name> for #other_name {
78            fn from(this: #this_name) -> Self {
79                match this {
80                    #(#into_arms,)*
81                }
82            }
83        }
84    }
85}