1use proc_macro::TokenStream;
7use quote::quote;
8use syn::parse::{Parse, ParseStream};
9use syn::{Error, Fields, Ident, ItemEnum, LitInt, Result, Token, parse_macro_input};
10
11struct ReprFitsArgs {
12 bits: u32,
13}
14
15impl Parse for ReprFitsArgs {
16 fn parse(input: ParseStream<'_>) -> Result<Self> {
17 let name = input.parse::<Ident>()?;
18 if name != "bits" {
19 return Err(Error::new(name.span(), "expected `bits = N`"));
20 }
21
22 input.parse::<Token![=]>()?;
23 let bits = input.parse::<LitInt>()?.base10_parse::<u32>()?;
24 if bits >= 128 {
25 return Err(Error::new(name.span(), "`bits` must be less than 128"));
26 }
27
28 Ok(Self { bits })
29 }
30}
31
32#[proc_macro_attribute]
64pub fn repr_fits(args: TokenStream, input: TokenStream) -> TokenStream {
65 let args = parse_macro_input!(args as ReprFitsArgs);
66 let item = parse_macro_input!(input as ItemEnum);
67
68 let enum_ident = &item.ident;
69 let mut variant_idents = Vec::with_capacity(item.variants.len());
70 let mut errors = Vec::new();
71
72 for variant in &item.variants {
73 match variant.fields {
74 Fields::Unit => variant_idents.push(&variant.ident),
75 _ => errors.push(Error::new_spanned(variant, "`repr_fits` only supports unit enum variants")),
76 }
77 }
78
79 if !errors.is_empty() {
80 let compile_errors = errors.into_iter().map(Error::into_compile_error);
81 return quote! {
82 #item
83 #(#compile_errors)*
84 }
85 .into();
86 }
87
88 let bits = args.bits;
89 let assertions = variant_idents.iter().map(|variant_ident| {
90 let message = format!("{enum_ident}::{variant_ident} discriminant does not fit in {bits} bits");
91 quote! {
92 assert!((#enum_ident::#variant_ident as u128) < (1u128 << #bits), #message);
93 }
94 });
95
96 quote! {
97 #item
98
99 const _: () = {
100 #(#assertions)*
101 };
102 }
103 .into()
104}