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}