derive_enum_from_into/
lib.rs

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