unite/
lib.rs

1//! This crate provides the [`unite!`](macro.unite.html) macro to easily compose existing types into an enum.
2//!
3//! ```toml
4//! [dependencies]
5//! unite = "0.1"
6//! ```
7//!
8//! # Usage
9//! ```
10//! use unite::unite;
11//!
12//! struct A;
13//! struct B;
14//!
15//! unite! {
16//!     enum Any { A, B, C = i32 }
17//! }
18//! ```
19
20use heck::SnakeCase;
21use proc_macro::TokenStream;
22use quote::{format_ident, quote};
23use syn::{
24    braced,
25    parse::{Parse, ParseStream},
26    parse_macro_input,
27    punctuated::Punctuated,
28    Attribute, Ident, Token, Type, Visibility,
29};
30
31/// Helper macro to compose existing types into an enum.
32///
33/// # Examples
34/// ```
35/// use unite::unite;
36///
37/// pub struct One(bool);
38/// pub struct Two(i32);
39/// pub struct Three(f64);
40///
41/// unite! {
42///     /// A new enum with a variant for each struct.
43///     pub enum Any { One, Two, Three }
44/// }
45/// ```
46///
47/// This expands to:
48///
49/// ```
50/// # struct One;
51/// # struct Two;
52/// # struct Three;
53/// pub enum Any {
54///     One(One),
55///     Two(Two),
56///     Three(Three),
57/// }
58/// ```
59///
60/// ## Renaming
61/// By default the enum variants use the same name as the type, but renaming is possible.
62///
63/// ```
64/// # use unite::unite;
65/// # struct SameName;
66/// unite! {
67///     enum Foo {
68///         SameName,
69///         Renamed = i32,
70///     }
71/// }
72/// ```
73///
74/// ## Helpers
75/// The generated enums come with helper functions to access their variants with ease.
76/// Variant names are automatically converted into `snake_case` for the function names.
77///
78/// ```
79/// # struct One;
80/// # struct Two;
81/// # struct Three;
82/// # unite::unite! { enum Any { One, Two, Three } }
83/// let mut any: Any;
84/// # any = Any::One(One);
85///
86/// // checks whether the enum is a specific variant
87/// let is_one: bool = any.is_one();
88///
89/// // attempts to cast the enum to a specific variant
90/// let as_two: Option<&Two> = any.as_two();
91/// let as_three_mut: Option<&mut Three> = any.as_three_mut();
92/// ```
93///
94/// The generated enums also inherently implement [`From<Variant>`].
95///
96/// ```
97/// # struct One(bool);
98/// # struct Two(i32);
99/// # struct Three(f64);
100/// # unite::unite! { enum Any { One, Two, Three } }
101/// let any: Any = One(true).into();
102/// ```
103#[proc_macro]
104pub fn unite(input: TokenStream) -> TokenStream {
105    // parse input
106    let Enum {
107        attributes,
108        visibility,
109        name,
110        variants,
111    } = parse_macro_input!(input as Enum);
112
113    // generate type information for all enum variants
114    let variants_data = variants
115        .into_iter()
116        .map(
117            |Variant {
118                 attributes,
119                 name,
120                 ty,
121             }| {
122                let ty = if let Some(ty) = &ty {
123                    quote! { #ty }
124                } else {
125                    quote! { #name }
126                };
127                (attributes, name, ty)
128            },
129        )
130        .collect::<Vec<_>>();
131
132    // generate enum variants
133    let variants = variants_data.iter().map(|(attributes, variant, ty)| {
134        quote! {
135            #(#attributes)*
136            #variant(#ty)
137        }
138    });
139
140    // generate helper functions
141    let funcs = variants_data.iter().map(|(_, variant, ty)| {
142        // convert name to snake case
143        let snake_case = variant.to_string().to_snake_case();
144
145        // generate is check name & doc
146        let is_name = format_ident!("is_{}", snake_case);
147        let is_doc = format!(
148            "Checks whether this [`{name}`] is a [`{variant}`]({name}::{variant}).",
149            name = name,
150            variant = variant
151        );
152
153        // generate as cast name & doc
154        let as_name = format_ident!("as_{}", snake_case);
155        let as_doc = format!(
156            "Attempts to cast this [`{name}`] to a reference to the underlying [`{variant}`]({name}::{variant}).",
157            name = name,
158            variant = variant,
159        );
160
161        // generate as mut cast name & doc
162        let as_mut_name = format_ident!("as_{}_mut", snake_case);
163        let as_mut_doc = format!(
164            "Attempts to cast this [`{name}`] to a mutable reference to the underlying [`{variant}`]({name}::{variant}).",
165            name = name,
166            variant = variant,
167        );
168
169        quote! {
170            #[doc = #is_doc]
171            pub fn #is_name(&self) -> bool {
172                matches!(self, #name::#variant(_))
173            }
174
175            #[doc = #as_doc]
176            pub fn #as_name(&self) -> Option<&#ty> {
177                if let #name::#variant(contents) = self {
178                    Some(contents)
179                } else {
180                    None
181                }
182            }
183
184            #[doc = #as_mut_doc]
185            pub fn #as_mut_name(&mut self) -> Option<&mut #ty> {
186                if let #name::#variant(contents) = self {
187                    Some(contents)
188                } else {
189                    None
190                }
191            }
192        }
193    });
194
195    // generate helper impls
196    let impls = variants_data.iter().map(|(_, variant, ty)| {
197        quote! {
198            impl From<#ty> for #name {
199                fn from(inner: #ty) -> Self {
200                    Self::#variant(inner)
201                }
202            }
203        }
204    });
205
206    // generate result enum
207    let result = quote! {
208        #(#attributes)*
209        #visibility enum #name {
210            #(#variants),*
211        }
212
213        impl #name {
214            #(#funcs)*
215        }
216
217        #(#impls)*
218    };
219
220    TokenStream::from(result)
221}
222
223struct Enum {
224    attributes: Vec<Attribute>,
225    visibility: Visibility,
226    name: Ident,
227    variants: Punctuated<Variant, Token![,]>,
228}
229
230impl Parse for Enum {
231    fn parse(input: ParseStream) -> syn::Result<Self> {
232        let attributes = input.call(Attribute::parse_outer)?;
233        let visibility = input.parse()?;
234        input.parse::<Token![enum]>()?;
235        let name = input.parse()?;
236
237        let inner;
238        braced!(inner in input);
239        let variants = inner.parse_terminated(Variant::parse)?;
240
241        Ok(Self {
242            attributes,
243            visibility,
244            name,
245            variants,
246        })
247    }
248}
249
250struct Variant {
251    attributes: Vec<Attribute>,
252    name: Ident,
253    ty: Option<Type>,
254}
255
256impl Parse for Variant {
257    fn parse(input: ParseStream) -> syn::Result<Self> {
258        let attributes = input.call(Attribute::parse_outer)?;
259        let name = input.parse()?;
260        let ty = if input.peek(Token![=]) {
261            input.parse::<Token![=]>()?;
262            Some(input.parse()?)
263        } else {
264            None
265        };
266        Ok(Self {
267            attributes,
268            name,
269            ty,
270        })
271    }
272}