]> git.proxmox.com Git - rustc.git/blob - vendor/strum_macros/src/macros/enum_discriminants.rs
New upstream version 1.63.0+dfsg1
[rustc.git] / vendor / strum_macros / src / macros / enum_discriminants.rs
1 use crate::helpers::{MetaHelpers, MetaIteratorHelpers, MetaListHelpers};
2 use proc_macro2::{Span, TokenStream};
3 use syn;
4
5 use helpers::extract_meta;
6
7 /// Attributes to copy from the main enum's variants to the discriminant enum's variants.
8 ///
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"];
12
13 pub fn enum_discriminants_inner(ast: &syn::DeriveInput) -> TokenStream {
14 let name = &ast.ident;
15 let vis = &ast.vis;
16
17 let variants = match ast.data {
18 syn::Data::Enum(ref v) => &v.variants,
19 _ => panic!("EnumDiscriminants only works on Enums"),
20 };
21
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>>();
27
28 let derives = discriminant_attrs
29 .find_attribute("derive")
30 .map(|meta| meta.path())
31 .collect::<Vec<_>>();
32
33 let derives = quote! {
34 #[derive(Clone, Copy, Debug, PartialEq, Eq, #(#derives),*)]
35 };
36
37 // Work out the name
38 let default_name = syn::Path::from(syn::Ident::new(
39 &format!("{}Discriminants", name.to_string()),
40 Span::call_site(),
41 ));
42
43 let discriminants_name = discriminant_attrs
44 .iter()
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."),
52 None => Some(val),
53 })
54 .map(|meta| meta.expand_inner())
55 .and_then(|metas| metas.into_iter().map(|meta| meta.path()).next())
56 .unwrap_or(&default_name);
57
58 // Pass through all other attributes
59 let pass_though_attributes = discriminant_attrs
60 .iter()
61 .filter(|meta| {
62 let path = meta.path();
63 !path.is_ident("derive") && !path.is_ident("name")
64 })
65 .map(|meta| quote! { #[ #meta ] })
66 .collect::<Vec<_>>();
67
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;
72
73 // Don't copy across the "strum" meta attribute.
74 let attrs = variant.attrs.iter().filter(|attr| {
75 ATTRIBUTES_TO_COPY
76 .iter()
77 .any(|attr_whitelisted| attr.path.is_ident(attr_whitelisted))
78 });
79
80 discriminants.push(quote! { #(#attrs)* #ident });
81 }
82
83 // Ideally:
84 //
85 // * For `Copy` types, we `impl From<TheEnum> for TheEnumDiscriminants`
86 // * For `!Copy` types, we `impl<'enum> From<&'enum TheEnum> for TheEnumDiscriminants`
87 //
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.
91 //
92 // See <https://github.com/dtolnay/syn/issues/433>
93 // ---
94 // let is_copy = unique_meta_list(type_meta.iter(), "derive")
95 // .map(extract_list_metas)
96 // .map(|metas| {
97 // metas
98 // .filter_map(get_meta_ident)
99 // .any(|derive| derive.to_string() == "Copy")
100 // }).unwrap_or(false);
101
102 let arms = variants
103 .iter()
104 .map(|variant| {
105 let ident = &variant.ident;
106
107 use syn::Fields::*;
108 let params = match variant.fields {
109 Unit => quote! {},
110 Unnamed(ref _fields) => {
111 quote! { (..) }
112 }
113 Named(ref _fields) => {
114 quote! { { .. } }
115 }
116 };
117
118 quote! { #name::#ident #params => #discriminants_name::#ident }
119 })
120 .collect::<Vec<_>>();
121
122 let from_fn_body = quote! { match val { #(#arms),* } };
123
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 {
128 #from_fn_body
129 }
130 }
131 };
132 let impl_from_ref = {
133 let mut generics = ast.generics.clone();
134
135 let lifetime = parse_quote!('_enum);
136 let enum_life = quote! { & #lifetime };
137 generics.params.push(lifetime);
138
139 // Shadows the earlier `impl_generics`
140 let (impl_generics, _, _) = generics.split_for_impl();
141
142 quote! {
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 {
145 #from_fn_body
146 }
147 }
148 }
149 };
150
151 quote! {
152 /// Auto-generated discriminant enum variants
153 #derives
154 #(#pass_though_attributes)*
155 #vis enum #discriminants_name {
156 #(#discriminants),*
157 }
158
159 #impl_from
160 #impl_from_ref
161 }
162 }