derive_enum_from_into/
lib.rs

1#![doc = include_str!("../README.md")]
2
3mod from;
4mod into;
5
6use proc_macro2::Ident;
7use quote::quote;
8use syn::{Data, DataEnum, DeriveInput, Field, Fields, GenericArgument, GenericParam, TypePath};
9
10type VariantFieldEntry = (Ident, Field);
11
12#[proc_macro_derive(EnumFrom, attributes(from_ignore))]
13pub fn derive_from(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
14    let derive_input = syn::parse::<DeriveInput>(input).unwrap();
15    if let Data::Enum(r#enum) = derive_input.data {
16        from::derive_enum_from(r#enum, derive_input.ident, derive_input.generics).into()
17    } else {
18        quote!( compile_error!("Can only derive EnumFrom on enums"); ).into()
19    }
20}
21
22#[proc_macro_derive(EnumTryInto, attributes(try_into_references, try_into_ignore))]
23pub fn derive_enum_into(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
24    let derive_input = syn::parse::<DeriveInput>(input).unwrap();
25    if let Data::Enum(r#enum) = derive_input.data {
26        into::derive_enum_into(
27            r#enum,
28            derive_input.ident,
29            derive_input.attrs,
30            derive_input.generics,
31        )
32        .into()
33    } else {
34        quote!( compile_error!("Can only derive EnumTryInto on enums"); ).into()
35    }
36}
37
38pub(crate) enum Unique<T> {
39    Unique(T),
40    NonUnique,
41}
42
43/// Gets variants with a single field without a specified *ignore* attribute
44fn get_unnamed_single_non_ignored_variants<'a>(
45    r#enum: &'a DataEnum,
46    ignore_ident: &'a str,
47) -> impl Iterator<Item = VariantFieldEntry> + 'a {
48    r#enum.variants.iter().filter_map(move |variant| {
49        if let Fields::Unnamed(unnamed_fields) = &variant.fields {
50            if unnamed_fields.unnamed.len() == 1
51                && !variant
52                    .attrs
53                    .iter()
54                    .any(|attr| attr.path().is_ident(ignore_ident))
55            {
56                Some((
57                    variant.ident.clone(),
58                    unnamed_fields.unnamed.first().unwrap().clone(),
59                ))
60            } else {
61                None
62            }
63        } else {
64            None
65        }
66    })
67}
68
69fn type_args_from_parameters<'a>(
70    params: impl Iterator<Item = &'a GenericParam> + 'a,
71) -> impl Iterator<Item = GenericArgument> + 'a {
72    fn ident_to_ty(ident: Ident) -> TypePath {
73        TypePath {
74            path: syn::Path {
75                leading_colon: None,
76                segments: std::iter::once(syn::PathSegment {
77                    ident,
78                    arguments: Default::default(),
79                })
80                .collect(),
81            },
82            qself: None,
83        }
84    }
85
86    params.map(|param| match param {
87        GenericParam::Type(ty) => GenericArgument::Type(ident_to_ty(ty.ident.clone()).into()),
88        GenericParam::Lifetime(lt) => GenericArgument::Lifetime(lt.lifetime.clone()),
89        GenericParam::Const(cst) => GenericArgument::Type(ident_to_ty(cst.ident.clone()).into()),
90    })
91}
92
93#[cfg(test)]
94mod test {
95    use quote::quote;
96    use syn::{parse_quote, DataEnum, DeriveInput, Generics};
97
98    fn dissect_input(input: DeriveInput) -> (DataEnum, proc_macro2::Ident, Generics) {
99        if let syn::Data::Enum(data_enum) = input.data {
100            (data_enum, input.ident, input.generics)
101        } else {
102            unreachable!();
103        }
104    }
105
106    #[test]
107    fn duplicate_type_fields_detection() {
108        let input: DeriveInput = parse_quote! {
109            enum X {
110                A(i32),
111                B(String),
112                C(String),
113            }
114        };
115        let (enum1, name, generics) = dissect_input(input);
116
117        let result = crate::from::derive_enum_from(enum1, name, generics);
118        assert_eq!(
119            result.to_string(),
120            quote! {
121                #[automatically_derived]
122                impl ::core::convert::From<i32> for X {
123                    #[inline]
124                    fn from(item: i32) -> Self {
125                        Self::A(item)
126                    }
127                }
128            }
129            .to_string()
130        )
131    }
132
133    #[test]
134    fn lifetimes() {
135        let input: DeriveInput = parse_quote! {
136            enum X<'a> {
137                A(&'a i32),
138            }
139        };
140        let (enum1, name, generics) = dissect_input(input);
141
142        let result = crate::from::derive_enum_from(enum1, name, generics);
143        assert_eq!(
144            result.to_string(),
145            quote! {
146                #[automatically_derived]
147                impl<'a> ::core::convert::From<&'a i32> for X<'a> {
148                    #[inline]
149                    fn from(item: &'a i32) -> Self {
150                        Self::A(item)
151                    }
152                }
153            }
154            .to_string()
155        )
156    }
157}