1 use crate::helpers
::{MetaHelpers, MetaIteratorHelpers, MetaListHelpers}
;
2 use proc_macro2
::{Span, TokenStream}
;
5 use helpers
::extract_meta
;
7 /// Attributes to copy from the main enum's variants to the discriminant enum's variants.
9 /// Attributes not in this list may be for other `proc_macro`s on the main enum, and may cause
10 /// compilation problems when copied across.
11 const ATTRIBUTES_TO_COPY
: &[&str] = &["doc", "cfg", "allow", "deny"];
13 pub fn enum_discriminants_inner(ast
: &syn
::DeriveInput
) -> TokenStream
{
14 let name
= &ast
.ident
;
17 let variants
= match ast
.data
{
18 syn
::Data
::Enum(ref v
) => &v
.variants
,
19 _
=> panic
!("EnumDiscriminants only works on Enums"),
22 // Derives for the generated enum
23 let type_meta
= extract_meta(&ast
.attrs
);
24 let discriminant_attrs
= type_meta
25 .find_attribute("strum_discriminants")
26 .collect
::<Vec
<&syn
::Meta
>>();
28 let derives
= discriminant_attrs
29 .find_attribute("derive")
30 .map(|meta
| meta
.path())
33 let derives
= quote
! {
34 #[derive(Clone, Copy, Debug, PartialEq, Eq, #(#derives),*)]
38 let default_name
= syn
::Path
::from(syn
::Ident
::new(
39 &format
!("{}Discriminants", name
.to_string()),
43 let discriminants_name
= discriminant_attrs
45 .filter_map(|meta
| meta
.try_metalist())
46 .filter(|list
| list
.path
.is_ident("name"))
47 // We want exactly zero or one items. Start with the assumption we have zero, i.e. None
48 // Then set our output to the first value we see. If fold is called again and we already
49 // have a value, panic.
50 .fold(None
, |acc
, val
| match acc
{
51 Some(_
) => panic
!("Expecting a single attribute 'name' in EnumDiscriminants."),
54 .map(|meta
| meta
.expand_inner())
55 .and_then(|metas
| metas
.into_iter().map(|meta
| meta
.path()).next())
56 .unwrap_or(&default_name
);
58 // Pass through all other attributes
59 let pass_though_attributes
= discriminant_attrs
62 let path
= meta
.path();
63 !path
.is_ident("derive") && !path
.is_ident("name")
65 .map(|meta
| quote
! { #[ #meta ] }
)
68 // Add the variants without fields, but exclude the `strum` meta item
69 let mut discriminants
= Vec
::new();
70 for variant
in variants
{
71 let ident
= &variant
.ident
;
73 // Don't copy across the "strum" meta attribute.
74 let attrs
= variant
.attrs
.iter().filter(|attr
| {
77 .any(|attr_whitelisted
| attr
.path
.is_ident(attr_whitelisted
))
80 discriminants
.push(quote
! { #(#attrs)* #ident }
);
85 // * For `Copy` types, we `impl From<TheEnum> for TheEnumDiscriminants`
86 // * For `!Copy` types, we `impl<'enum> From<&'enum TheEnum> for TheEnumDiscriminants`
88 // That way we ensure users are not able to pass a `Copy` type by reference. However, the
89 // `#[derive(..)]` attributes are not in the parsed tokens, so we are not able to check if a
90 // type is `Copy`, so we just implement both.
92 // See <https://github.com/dtolnay/syn/issues/433>
94 // let is_copy = unique_meta_list(type_meta.iter(), "derive")
95 // .map(extract_list_metas)
98 // .filter_map(get_meta_ident)
99 // .any(|derive| derive.to_string() == "Copy")
100 // }).unwrap_or(false);
105 let ident
= &variant
.ident
;
108 let params
= match variant
.fields
{
110 Unnamed(ref _fields
) => {
113 Named(ref _fields
) => {
118 quote
! { #name::#ident #params => #discriminants_name::#ident }
120 .collect
::<Vec
<_
>>();
122 let from_fn_body
= quote
! { match val { #(#arms),* }
};
124 let (impl_generics
, ty_generics
, where_clause
) = ast
.generics
.split_for_impl();
125 let impl_from
= quote
! {
126 impl #impl_generics ::std::convert::From< #name #ty_generics > for #discriminants_name #where_clause {
127 fn from(val
: #name #ty_generics) -> #discriminants_name {
132 let impl_from_ref
= {
133 let mut generics
= ast
.generics
.clone();
135 let lifetime
= parse_quote
!('_enum
);
136 let enum_life
= quote
! { & #lifetime }
;
137 generics
.params
.push(lifetime
);
139 // Shadows the earlier `impl_generics`
140 let (impl_generics
, _
, _
) = generics
.split_for_impl();
143 impl #impl_generics ::std::convert::From< #enum_life #name #ty_generics > for #discriminants_name #where_clause {
144 fn from(val
: #enum_life #name #ty_generics) -> #discriminants_name {
152 /// Auto-generated discriminant enum variants
154 #(#pass_though_attributes)*
155 #vis enum #discriminants_name {