enum_from_functions/lib.rs
1/*!
2This crate contains a procedural macro attribute that can be placed on an `impl` block. It will generate an `enum`
3based on the functions defined in the `impl` block. The generated `enum` will have a variant for each function, and a
4new function `map` will be added to the `impl` block that will call the appropriate function based on the variant.
5
6An example:
7```
8# use enum_from_functions::enum_from_functions;
9#[enum_from_functions]
10impl Enum {
11 async fn foo() -> &'static str {
12 "Foo"
13 }
14 unsafe fn bar(baz: i32) -> &'static str {
15 "Bar"
16 }
17}
18# fn main() {
19# futures::executor::block_on(
20# async {
21# unsafe {
22# assert_eq!(Enum::map(Enum::Foo).await, "Foo");
23# assert_eq!(Enum::map(Enum::Bar { baz: 1337 }).await, "Bar");
24# }
25# }
26# )
27# }
28```
29expands to:
30```ignore
31enum Enum {
32 Foo,
33 Bar {
34 baz: i32
35 },
36}
37
38impl Enum {
39 async fn foo() -> &'static str {
40 "Foo"
41 }
42 unsafe fn bar(baz: i32) -> &'static str {
43 "Bar"
44 }
45
46 async unsafe fn map(&self) -> &'static str {
47 match self {
48 Enum::Foo => Enum::foo().await,
49 Enum::Bar(baz) => Enum::bar(baz),
50 }
51 }
52}
53```
54The signatures of functions in the `impl` block may be different, so long as they all have the same return type.
55
56Note that `fn f() -> T` and `async fn f() -> T` are considered to return the same type, even though the latter
57technically returns a `impl Future<Output = T>`. See
58[the `async` keyword documentation](https://doc.rust-lang.org/std/keyword.async.html) for more information.
59```
60# use enum_from_functions::enum_from_functions;
61#[enum_from_functions]
62impl Enum {
63 fn foo(baz: i32) -> &'static str {
64 "Foo"
65 }
66 async fn bar(&self, baz: bool) -> &'static str {
67 "Bar"
68 }
69}
70```
71```compile_fail
72# use enum_from_functions::enum_from_functions;
73// Causes a compile error because the return types don't match.
74#[enum_from_functions]
75impl Enum {
76 fn foo() -> &'static str {
77 "Foo"
78 }
79 fn bar() -> String {
80 "Bar".to_owned()
81 }
82}
83```
84`async`, `const` and `unsafe` functions are supported. The presence of any of these keywords will result in the
85generated `map` function having the same keyword. For this reason, `async` and `const` functions cannot be present in
86the same `impl` block (though `unsafe` functions can be present with either of the other two).
87```compile_fail
88# use enum_from_functions::enum_from_functions;
89#[enum_from_functions]
90impl Enum {
91 async fn foo() -> &'static str {
92 "Foo"
93 }
94 const fn bar() -> &'static str {
95 "Bar"
96 }
97
98 // This would result in `async const map(...` which is not supported in Rust.
99}
100```
101You can also create an empty `enum` by not providing any functions in the `impl` block (though I'm not sure why you
102would want to do this).
103```
104# use enum_from_functions::enum_from_functions;
105#[enum_from_functions]
106impl EmptyEnum {}
107```
108If you need to export the generated `enum` type out of its parent module, provide the `pub` argument to the macro
109attribute.
110```
111mod internal {
112# use enum_from_functions::enum_from_functions;
113 #[enum_from_functions(pub)]
114 impl Visible {
115 fn example() -> bool {
116 true
117 }
118 }
119}
120
121// Will compile because the generated `enum` is visible outside of the `internal` module.
122use internal::Visible;
123```
124```compile_fail
125mod internal {
126# use enum_from_functions::enum_from_functions;
127 #[enum_from_functions]
128 impl NotVisible {
129 fn example() -> bool {
130 false
131 }
132 }
133}
134
135// Causes a compile error because the generated `enum` is not visible outside of the `internal` module.
136use internal::NotVisible;
137```
138Items in the `impl` block that are not functions will be ignored and passed through to the output unchanged.
139Similarly, any attributes applied before *or* after the macro attribute will be applied to the generated `enum`
140declaration.
141```
142# use enum_from_functions::enum_from_functions;
143#[enum_from_functions]
144##[derive(Debug)]
145impl Enum {
146 const FOO: &'static str = "Foo";
147 fn foo() -> &'static str {
148 Self::FOO
149 }
150
151 const BAR: &'static str = "Bar";
152 fn bar() -> &'static str {
153 Self::BAR
154 }
155
156 const BAZ: &'static str = "Baz";
157 fn baz() -> &'static str {
158 Self::BAZ
159 }
160}
161# fn main() {
162# assert_eq!(Enum::map(Enum::Foo), "Foo");
163# assert_eq!(Enum::map(Enum::Bar), "Bar");
164# assert_eq!(Enum::map(Enum::Baz), "Baz");
165# let _ = format!("{:?}", Enum::Foo);
166# }
167```
168*/
169
170mod extract;
171mod generate;
172
173use generate::WithoutTypes;
174use proc_macro::TokenStream;
175use proc_macro_error::{abort, emit_error, proc_macro_error};
176use quote::quote;
177use syn::{parse_macro_input, parse_quote, ExprBlock, Field, Fields, ItemImpl};
178
179/**
180A procedural macro attribute that generates an `enum` based on the functions defined in the `impl` block it annotates.
181See the crate documentation for more information.
182*/
183#[proc_macro_error]
184#[proc_macro_attribute]
185pub fn enum_from_functions(args: TokenStream, input: TokenStream) -> TokenStream {
186 let pub_token = match extract::pub_token(args) {
187 Ok(pub_token) => pub_token,
188 Err(err) => {
189 emit_error!(err.span(), err);
190 None
191 }
192 };
193
194 let (parsed_input, attributes) = {
195 let mut parsed_input = parse_macro_input!(input as ItemImpl);
196 let attributes = parsed_input.attrs.clone();
197 parsed_input.attrs.clear();
198 (parsed_input, attributes)
199 };
200
201 let enum_name = &*parsed_input.self_ty;
202 let functions = match extract::Functions::try_from(&parsed_input) {
203 Ok(functions) => functions,
204 Err(err) => abort!(err.span(), err),
205 };
206
207 // Unpack the struct here because we can't in the `quote` block.
208 let (return_type, asyncness, constness, unsafety, calls, variants) = {
209 (
210 &functions.return_type,
211 functions.asyncness,
212 functions.constness,
213 functions.unsafety,
214 &functions.calls,
215 generate::Variants::from(&functions),
216 )
217 };
218
219 let variants_iter = variants.0.iter();
220 let variant_names = variants.0.iter().map(|variant| &variant.ident);
221 let variant_fields = variants.0.iter().map(|variant| -> Option<ExprBlock> {
222 if let Fields::Named(fields) = &variant.fields {
223 let no_types = Field::without_types(&fields.named);
224 Some(parse_quote! { { #no_types } })
225 } else {
226 None
227 }
228 });
229
230 quote! {
231 #(#attributes)*
232 #pub_token enum #enum_name {
233 #(#variants_iter,)*
234 }
235
236 #parsed_input
237
238 impl #enum_name {
239 #pub_token #asyncness #constness #unsafety fn map(self) #return_type {
240 match self {
241 #(Self::#variant_names #variant_fields => #calls,)*
242 }
243 }
244 }
245 }
246 .into()
247}