1extern crate proc_macro;
2
3mod config;
4mod discriminant;
5mod repr;
6
7use config::Config;
8
9use discriminant::Discriminant;
10use proc_macro2::{Span, TokenStream};
11use quote::{format_ident, quote, ToTokens};
12use repr::Repr;
13use std::collections::HashSet;
14use syn::Attribute;
15use syn::{
16 parse_macro_input, punctuated::Punctuated, spanned::Spanned, Error, Ident, ItemEnum, Visibility,
17};
18
19fn set_token_stream_span(tokens: TokenStream, span: Span) -> TokenStream {
21 tokens
22 .into_iter()
23 .map(|mut tt| {
24 tt.set_span(span);
25 tt
26 })
27 .collect()
28}
29
30fn check_no_alias<'a>(
33 enum_: &ItemEnum,
34 variants: impl Iterator<Item = (&'a Ident, &'a Discriminant, Span)> + Clone,
35) -> syn::Result<TokenStream> {
36 let mut values: HashSet<i128> = HashSet::new();
38 for (_, variant, span) in variants {
39 if let &Discriminant::Literal(value) = variant {
40 if !values.insert(value) {
41 return Err(Error::new(
42 span,
43 format!("discriminant value `{value}` assigned more than once"),
44 ));
45 }
46 } else {
47 let mut checking_enum = syn::ItemEnum {
48 ident: format_ident!("_Check{}", enum_.ident, span = Span::call_site()),
49 vis: Visibility::Inherited,
50 ..enum_.clone()
51 };
52 checking_enum.attrs.retain(|attr| {
53 matches!(
54 attr.path().to_token_stream().to_string().as_str(),
55 "repr" | "allow" | "warn" | "deny" | "forbid"
56 )
57 });
58 return Ok(quote!(
59 #[allow(dead_code)]
60 #checking_enum
61 ));
62 }
63 }
64 Ok(TokenStream::default())
65}
66
67fn emit_debug_impl<'a>(
68 ident: &Ident,
69 variants: impl Iterator<Item = &'a Ident> + Clone,
70 attrs: impl Iterator<Item = &'a Vec<Attribute>> + Clone,
71) -> TokenStream {
72 let attrs = attrs.map(|attrs| {
73 let iter = attrs.iter().filter(|attr| attr.path().is_ident("cfg"));
75 quote!(#(#iter)*)
76 });
77 quote!(impl ::core::fmt::Debug for #ident {
78 fn fmt(&self, fmt: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
79 #![allow(unreachable_patterns)]
80 let s = match *self {
81 #( #attrs Self::#variants => stringify!(#variants), )*
82 _ => {
83 return fmt.debug_tuple(stringify!(#ident)).field(&self.0).finish();
84 }
85 };
86 fmt.pad(s)
87 }
88 })
89}
90
91fn path_matches_prelude_derive(
92 got_path: &syn::Path,
93 expected_path_after_std: &[&'static str],
94) -> bool {
95 let &[a, b] = expected_path_after_std else {
96 unimplemented!("checking against stdlib paths with != 2 parts");
97 };
98 let segments: Vec<&syn::PathSegment> = got_path.segments.iter().collect();
99 if segments
100 .iter()
101 .any(|segment| !matches!(segment.arguments, syn::PathArguments::None))
102 {
103 return false;
104 }
105 match &segments[..] {
106 [maybe_core_or_std, maybe_a, maybe_b] => {
108 (maybe_core_or_std.ident == "core" || maybe_core_or_std.ident == "std")
109 && maybe_a.ident == a
110 && maybe_b.ident == b
111 }
112 [maybe_a, maybe_b] => {
114 maybe_a.ident == a && maybe_b.ident == b && got_path.leading_colon.is_none()
115 }
116 [maybe_b] => maybe_b.ident == b && got_path.leading_colon.is_none(),
118 _ => false,
119 }
120}
121
122fn open_enum_impl(
123 enum_: ItemEnum,
124 Config {
125 allow_alias,
126 repr_visibility,
127 }: Config,
128) -> Result<TokenStream, Error> {
129 let mut struct_attrs: Vec<TokenStream> = Vec::with_capacity(enum_.attrs.len() + 5);
131 struct_attrs.push(quote!(#[allow(clippy::exhaustive_structs)]));
132
133 if !enum_.generics.params.is_empty() {
134 return Err(Error::new(enum_.generics.span(), "enum cannot be generic"));
135 }
136 let mut variants = Vec::with_capacity(enum_.variants.len());
137 let mut last_field = Discriminant::Literal(-1);
138 for variant in &enum_.variants {
139 if !matches!(variant.fields, syn::Fields::Unit) {
140 return Err(Error::new(variant.span(), "enum cannot contain fields"));
141 }
142
143 let (value, value_span) = if let Some((_, discriminant)) = &variant.discriminant {
144 let span = discriminant.span();
145 (Discriminant::new(discriminant.clone())?, span)
146 } else {
147 last_field = last_field
148 .next_value()
149 .ok_or_else(|| Error::new(variant.span(), "enum discriminant overflowed"))?;
150 (last_field.clone(), variant.ident.span())
151 };
152 last_field = value.clone();
153 variants.push((&variant.ident, value, value_span, &variant.attrs))
154 }
155
156 let mut impl_attrs: Vec<TokenStream> = vec![quote!(#[allow(non_upper_case_globals)])];
157 let mut explicit_repr: Option<Repr> = None;
158
159 let mut extra_derives = vec![quote!(::core::cmp::PartialEq), quote!(::core::cmp::Eq)];
161 let mut extra_attrs: Vec<TokenStream> = Vec::with_capacity(enum_.attrs.len());
162
163 let mut make_custom_debug_impl = false;
164 for attr in &enum_.attrs {
165 let mut include_in_struct = true;
166 match attr.path().to_token_stream().to_string().as_str() {
168 "derive" => {
169 if let Ok(derive_paths) =
170 attr.parse_args_with(Punctuated::<syn::Path, syn::Token![,]>::parse_terminated)
171 {
172 for derive in &derive_paths {
173 const PARTIAL_EQ_PATH: &[&str] = &["cmp", "PartialEq"];
175 const EQ_PATH: &[&str] = &["cmp", "Eq"];
176 const DEBUG_PATH: &[&str] = &["fmt", "Debug"];
177
178 if path_matches_prelude_derive(derive, PARTIAL_EQ_PATH)
179 || path_matches_prelude_derive(derive, EQ_PATH)
180 {
181 continue;
183 }
184 if path_matches_prelude_derive(derive, DEBUG_PATH) && !allow_alias {
185 make_custom_debug_impl = true;
186 continue;
188 }
189 extra_derives.push(derive.to_token_stream());
190 }
191 include_in_struct = false;
192 }
193 }
194 "allow" | "warn" | "deny" | "forbid" => impl_attrs.push(attr.to_token_stream()),
196 "repr" => {
197 assert!(explicit_repr.is_none(), "duplicate explicit repr");
198 explicit_repr = Some(attr.parse_args()?);
199 include_in_struct = false;
200 }
201 "non_exhaustive" => {
202 return Err(Error::new(attr.path().span(), "`non_exhaustive` cannot be applied to an open enum; it is already non-exhaustive"));
204 }
205 _ => {}
206 }
207 if include_in_struct {
208 extra_attrs.push(attr.to_token_stream());
209 }
210 }
211
212 let typecheck_repr: Repr = explicit_repr.unwrap_or(Repr::Isize);
214
215 let inner_repr = match explicit_repr {
217 Some(explicit_repr) => {
218 struct_attrs.push(quote!(#[repr(transparent)]));
220 explicit_repr
221 }
222 None => {
223 repr::autodetect_inner_repr(variants.iter().map(|v| &v.1))
226 }
227 };
228
229 if !extra_derives.is_empty() {
230 struct_attrs.push(quote!(#[derive(#(#extra_derives),*)]));
231 }
232 struct_attrs.append(&mut extra_attrs);
233
234 let alias_check = if allow_alias {
235 TokenStream::default()
236 } else {
237 check_no_alias(&enum_, variants.iter().map(|(i, v, s, _)| (*i, v, *s)))?
238 };
239
240 let syn::ItemEnum { ident, vis, .. } = enum_;
241
242 let ident_nospan = {
247 let mut ident_nospan = ident.clone();
248 ident_nospan.set_span(Span::call_site());
249 ident_nospan
250 };
251
252 let debug_impl = if make_custom_debug_impl {
253 emit_debug_impl(
254 &ident_nospan,
255 variants.iter().map(|(i, _, _, _)| *i),
256 variants.iter().map(|(_, _, _, a)| *a),
257 )
258 } else {
259 TokenStream::default()
260 };
261
262 let fields = variants
263 .into_iter()
264 .map(|(name, value, value_span, attrs)| {
265 let mut value = value.into_token_stream();
266 value = set_token_stream_span(value, value_span);
267 let inner = if typecheck_repr == inner_repr {
268 value
269 } else {
270 quote!(::core::convert::identity::<#typecheck_repr>(#value) as #inner_repr)
271 };
272 quote!(
273 #(#attrs)*
274 pub const #name: #ident_nospan = #ident_nospan(#inner);
275 )
276 });
277
278 Ok(quote! {
279 #(#struct_attrs)*
280 #vis struct #ident(#repr_visibility #inner_repr);
281
282 #(#impl_attrs)*
283 impl #ident_nospan {
284 #(
285 #fields
286 )*
287 }
288 #debug_impl
289 #alias_check
290 })
291}
292
293#[proc_macro_attribute]
294pub fn open_enum(
295 attrs: proc_macro::TokenStream,
296 input: proc_macro::TokenStream,
297) -> proc_macro::TokenStream {
298 let enum_ = parse_macro_input!(input as syn::ItemEnum);
299 let config = parse_macro_input!(attrs as Config);
300 open_enum_impl(enum_, config)
301 .unwrap_or_else(Error::into_compile_error)
302 .into()
303}
304
305#[cfg(test)]
306mod tests {
307 use super::*;
308
309 #[test]
310 fn test_path_matches_stdlib_derive() {
311 const DEBUG_PATH: &[&str] = &["fmt", "Debug"];
312
313 for success_case in [
314 "::core::fmt::Debug",
315 "::std::fmt::Debug",
316 "core::fmt::Debug",
317 "std::fmt::Debug",
318 "fmt::Debug",
319 "Debug",
320 ] {
321 assert!(
322 path_matches_prelude_derive(&syn::parse_str(success_case).unwrap(), DEBUG_PATH),
323 "{success_case}"
324 );
325 }
326
327 for fail_case in [
328 "::fmt::Debug",
329 "::Debug",
330 "zerocopy::IntoBytes",
331 "::zerocopy::IntoBytes",
332 "PartialEq",
333 "core::cmp::Eq",
334 ] {
335 assert!(
336 !path_matches_prelude_derive(&syn::parse_str(fail_case).unwrap(), DEBUG_PATH),
337 "{fail_case}"
338 );
339 }
340 }
341}