derive_enum_from_into/
lib.rs1#![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
42fn 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}