1#![doc(
4 html_logo_url = "https://ardaku.github.io/mm/logo.svg",
5 html_favicon_url = "https://ardaku.github.io/mm/icon.svg",
6 html_root_url = "https://docs.rs/typeful"
7)]
8#![forbid(unsafe_code, missing_docs)]
9#![warn(
10 anonymous_parameters,
11 missing_copy_implementations,
12 missing_debug_implementations,
13 nonstandard_style,
14 rust_2018_idioms,
15 single_use_lifetimes,
16 trivial_casts,
17 trivial_numeric_casts,
18 unreachable_pub,
19 unused_extern_crates,
20 unused_qualifications,
21 variant_size_differences
22)]
23
24mod common;
25
26use proc_macro::TokenStream;
27use quote::quote;
28use syn::{
29 parse::{Parse, ParseStream},
30 punctuated::Punctuated,
31 Data, Error, Ident, Meta, Result, Token,
32};
33
34struct AttrParams {
35 attrs: Punctuated<Ident, Token![,]>,
36}
37
38impl Parse for AttrParams {
39 fn parse(input: ParseStream<'_>) -> Result<Self> {
40 Ok(Self {
41 attrs: Punctuated::parse_terminated(input)?,
42 })
43 }
44}
45
46fn impl_enum_functions(
47 ast: syn::DeriveInput,
48) -> Result<proc_macro2::TokenStream> {
49 let mut token_stream = proc_macro2::TokenStream::new();
50 let name = ast.ident;
51 let attrs = ast.attrs;
52 let data = ast.data;
53
54 for attr in attrs {
55 let Meta::List(list) = attr.meta else {
56 continue;
57 };
58 let path = &list.path;
59
60 if path.leading_colon.is_some() {
61 return Err(Error::new(
62 list.delimiter.span().join(),
63 "unexpected leading double colon",
64 ));
65 }
66
67 if path.segments.len() != 1 {
68 return Err(Error::new(
69 list.delimiter.span().join(),
70 "unexpected double colon in path",
71 ));
72 }
73
74 let path = path.segments.first().unwrap();
75
76 if !path.arguments.is_none() {
77 return Err(Error::new(
78 list.delimiter.span().join(),
79 "unexpected path arguments",
80 ));
81 }
82
83 if path.ident != "enum_functions" {
84 continue;
85 }
86
87 let Data::Enum(ref data_enum) = data else {
88 return Err(Error::new(path.ident.span(), "expected enum"));
89 };
90 let params: AttrParams = syn::parse2(list.tokens)?;
91 let variants: Punctuated<syn::Path, Token![,]> = data_enum
92 .variants
93 .iter()
94 .map(|v| -> syn::Path {
95 let ident = &v.ident;
96
97 syn::parse_quote!(Self::#ident)
98 })
99 .collect();
100 let variant_count =
101 proc_macro2::Literal::usize_suffixed(variants.len());
102 let mut has_variant_array = false;
103 let mut has_variant_count = false;
104
105 for attr in params.attrs {
106 match attr {
107 a if a == "variant_array" => {
108 if has_variant_array {
109 return Err(Error::new(
110 a.span(),
111 "duplicated attribute",
112 ));
113 }
114
115 has_variant_array = true;
116 token_stream.extend(quote! {
117 impl #name {
118 const fn variant_array<const N: usize>() -> [Self; N] {
119 if N > #variant_count {
120 panic!("requested variant array is too large")
121 }
122
123 let full_array = [#variants];
124 let mut array = [full_array[0]; N];
125 let mut i = 1;
126
127 while i < N {
128 array[i] = full_array[i];
129 i += 1;
130 }
131
132 array
133 }
134 }
135 });
136 }
137 a if a == "variant_count" => {
138 if has_variant_count {
139 return Err(Error::new(
140 a.span(),
141 "duplicated attribute",
142 ));
143 }
144
145 has_variant_count = true;
146 token_stream.extend(quote! {
147 impl #name {
148 const fn variant_count() -> usize {
149 #variant_count
150 }
151 }
152 });
153 }
154 ident => {
155 return Err(Error::new(ident.span(), "unknown attribute"))
156 }
157 }
158 }
159 }
160
161 Ok(token_stream)
162}
163
164#[proc_macro_derive(EnumFunctions, attributes(enum_functions))]
166pub fn enum_functions_derive(input: TokenStream) -> TokenStream {
167 let ast = syn::parse(input).unwrap();
168
169 common::unwrap(impl_enum_functions(ast))
170}