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}