enum_as_inner/
lib.rs

1// Copyright 2015-2018 Benjamin Fry <benjaminfry@me.com>
2//
3// Licensed under the Apache License, Version 2.0, <LICENSE-APACHE or
4// http://apache.org/licenses/LICENSE-2.0> or the MIT license <LICENSE-MIT or
5// http://opensource.org/licenses/MIT>, at your option. This file may not be
6// copied, modified, or distributed except according to those terms.
7
8//! # enum-as-inner
9//!
10//! A deriving proc-macro for generating functions to automatically give access to the inner members of enum.
11//!
12//! ## Basic unnamed field case
13//!
14//! The basic case is meant for single item enums, like:
15//!
16//! ```rust
17//! use enum_as_inner::EnumAsInner;
18//!
19//! #[derive(Debug, EnumAsInner)]
20//! enum OneEnum {
21//!     One(u32),
22//! }
23//!
24//! let one = OneEnum::One(1);
25//!
26//! assert_eq!(*one.as_one().unwrap(), 1);
27//! assert_eq!(one.into_one().unwrap(), 1);
28//! ```
29//!
30//! where the result is either a reference for inner items or a tuple containing the inner items.
31//!
32//! ## Unit case
33//!
34//! This will return true if enum's variant matches the expected type
35//!
36//! ```rust
37//! use enum_as_inner::EnumAsInner;
38//!
39//! #[derive(EnumAsInner)]
40//! enum UnitVariants {
41//!     Zero,
42//!     One,
43//!     Two,
44//! }
45//!
46//! let unit = UnitVariants::Two;
47//!
48//! assert!(unit.is_two());
49//! ```
50//!
51//! ## Mutliple, unnamed field case
52//!
53//! This will return a tuple of the inner types:
54//!
55//! ```rust
56//! use enum_as_inner::EnumAsInner;
57//!
58//! #[derive(Debug, EnumAsInner)]
59//! enum ManyVariants {
60//!     One(u32),
61//!     Two(u32, i32),
62//!     Three(bool, u32, i64),
63//! }
64//!
65//! let many = ManyVariants::Three(true, 1, 2);
66//!
67//! assert!(many.is_three());
68//! assert_eq!(many.as_three().unwrap(), (&true, &1_u32, &2_i64));
69//! assert_eq!(many.into_three().unwrap(), (true, 1_u32, 2_i64));
70//! ```
71//!
72//! ## Multiple, named field case
73//!
74//! This will return a tuple of the inner types, like the unnamed option:
75//!
76//! ```rust
77//! use enum_as_inner::EnumAsInner;
78//!
79//! #[derive(Debug, EnumAsInner)]
80//! enum ManyVariants {
81//!     One { one: u32 },
82//!     Two { one: u32, two: i32 },
83//!     Three { one: bool, two: u32, three: i64 },
84//! }
85//!
86//! let many = ManyVariants::Three { one: true, two: 1, three: 2 };
87//!
88//! assert!(many.is_three());
89//! assert_eq!(many.as_three().unwrap(), (&true, &1_u32, &2_i64));
90//! assert_eq!(many.into_three().unwrap(), (true, 1_u32, 2_i64));
91//! ```
92
93#![warn(
94    clippy::default_trait_access,
95    clippy::dbg_macro,
96    clippy::print_stdout,
97    clippy::unimplemented,
98    clippy::use_self,
99    missing_copy_implementations,
100    missing_docs,
101    non_snake_case,
102    non_upper_case_globals,
103    rust_2018_idioms,
104    unreachable_pub
105)]
106
107use heck::ToSnakeCase;
108use proc_macro2::{Ident, Span, TokenStream};
109use quote::quote;
110use syn::{parse_macro_input, DeriveInput};
111
112/// returns first the types to return, the match names, and then tokens to the field accesses
113fn unit_fields_return(variant_name: &syn::Ident, function_name: &Ident, doc: &str) -> TokenStream {
114    quote!(
115        #[doc = #doc]
116        #[inline]
117        pub fn #function_name(&self) -> bool {
118            matches!(self, Self::#variant_name)
119        }
120    )
121}
122
123/// returns first the types to return, the match names, and then tokens to the field accesses
124fn unnamed_fields_return(
125    variant_name: &syn::Ident,
126    (function_name_is, doc_is): (&Ident, &str),
127    (function_name_mut_ref, doc_mut_ref): (&Ident, &str),
128    (function_name_ref, doc_ref): (&Ident, &str),
129    (function_name_val, doc_val): (&Ident, &str),
130    fields: &syn::FieldsUnnamed,
131) -> TokenStream {
132    let (returns_mut_ref, returns_ref, returns_val, matches) = match fields.unnamed.len() {
133        1 => {
134            let field = fields.unnamed.first().expect("no fields on type");
135
136            let returns = &field.ty;
137            let returns_mut_ref = quote!(&mut #returns);
138            let returns_ref = quote!(&#returns);
139            let returns_val = quote!(#returns);
140            let matches = quote!(inner);
141
142            (returns_mut_ref, returns_ref, returns_val, matches)
143        }
144        0 => (quote!(()), quote!(()), quote!(()), quote!()),
145        _ => {
146            let mut returns_mut_ref = TokenStream::new();
147            let mut returns_ref = TokenStream::new();
148            let mut returns_val = TokenStream::new();
149            let mut matches = TokenStream::new();
150
151            for (i, field) in fields.unnamed.iter().enumerate() {
152                let rt = &field.ty;
153                let match_name = Ident::new(&format!("match_{}", i), Span::call_site());
154                returns_mut_ref.extend(quote!(&mut #rt,));
155                returns_ref.extend(quote!(&#rt,));
156                returns_val.extend(quote!(#rt,));
157                matches.extend(quote!(#match_name,));
158            }
159
160            (
161                quote!((#returns_mut_ref)),
162                quote!((#returns_ref)),
163                quote!((#returns_val)),
164                quote!(#matches),
165            )
166        }
167    };
168
169    quote!(
170        #[doc = #doc_is ]
171        #[inline]
172        #[allow(unused_variables)]
173        pub fn #function_name_is(&self) -> bool {
174            matches!(self, Self::#variant_name(#matches))
175        }
176
177        #[doc = #doc_mut_ref ]
178        #[inline]
179        pub fn #function_name_mut_ref(&mut self) -> ::core::option::Option<#returns_mut_ref> {
180            match self {
181                Self::#variant_name(#matches) => {
182                    ::core::option::Option::Some((#matches))
183                }
184                _ => ::core::option::Option::None
185            }
186        }
187
188        #[doc = #doc_ref ]
189        #[inline]
190        pub fn #function_name_ref(&self) -> ::core::option::Option<#returns_ref> {
191            match self {
192                Self::#variant_name(#matches) => {
193                    ::core::option::Option::Some((#matches))
194                }
195                _ => ::core::option::Option::None
196            }
197        }
198
199        #[doc = #doc_val ]
200        #[inline]
201        pub fn #function_name_val(self) -> ::core::result::Result<#returns_val, Self> {
202            match self {
203                Self::#variant_name(#matches) => {
204                    ::core::result::Result::Ok((#matches))
205                },
206                _ => ::core::result::Result::Err(self)
207            }
208        }
209    )
210}
211
212/// returns first the types to return, the match names, and then tokens to the field accesses
213fn named_fields_return(
214    variant_name: &syn::Ident,
215    (function_name_is, doc_is): (&Ident, &str),
216    (function_name_mut_ref, doc_mut_ref): (&Ident, &str),
217    (function_name_ref, doc_ref): (&Ident, &str),
218    (function_name_val, doc_val): (&Ident, &str),
219    fields: &syn::FieldsNamed,
220) -> TokenStream {
221    let (returns_mut_ref, returns_ref, returns_val, matches) = match fields.named.len() {
222        1 => {
223            let field = fields.named.first().expect("no fields on type");
224            let match_name = field.ident.as_ref().expect("expected a named field");
225
226            let returns = &field.ty;
227            let returns_mut_ref = quote!(&mut #returns);
228            let returns_ref = quote!(&#returns);
229            let returns_val = quote!(#returns);
230            let matches = quote!(#match_name);
231
232            (returns_mut_ref, returns_ref, returns_val, matches)
233        }
234        0 => (quote!(()), quote!(()), quote!(()), quote!(())),
235        _ => {
236            let mut returns_mut_ref = TokenStream::new();
237            let mut returns_ref = TokenStream::new();
238            let mut returns_val = TokenStream::new();
239            let mut matches = TokenStream::new();
240
241            for field in fields.named.iter() {
242                let rt = &field.ty;
243                let match_name = field.ident.as_ref().expect("expected a named field");
244
245                returns_mut_ref.extend(quote!(&mut #rt,));
246                returns_ref.extend(quote!(&#rt,));
247                returns_val.extend(quote!(#rt,));
248                matches.extend(quote!(#match_name,));
249            }
250
251            (
252                quote!((#returns_mut_ref)),
253                quote!((#returns_ref)),
254                quote!((#returns_val)),
255                quote!(#matches),
256            )
257        }
258    };
259
260    quote!(
261        #[doc = #doc_is ]
262        #[inline]
263        #[allow(unused_variables)]
264        pub fn #function_name_is(&self) -> bool {
265            matches!(self, Self::#variant_name{ #matches })
266        }
267
268        #[doc = #doc_mut_ref ]
269        #[inline]
270        pub fn #function_name_mut_ref(&mut self) -> ::core::option::Option<#returns_mut_ref> {
271            match self {
272                Self::#variant_name{ #matches } => {
273                    ::core::option::Option::Some((#matches))
274                }
275                _ => ::core::option::Option::None
276            }
277        }
278
279        #[doc = #doc_ref ]
280        #[inline]
281        pub fn #function_name_ref(&self) -> ::core::option::Option<#returns_ref> {
282            match self {
283                Self::#variant_name{ #matches } => {
284                    ::core::option::Option::Some((#matches))
285                }
286                _ => ::core::option::Option::None
287            }
288        }
289
290        #[doc = #doc_val ]
291        #[inline]
292        pub fn #function_name_val(self) -> ::core::result::Result<#returns_val, Self> {
293            match self {
294                Self::#variant_name{ #matches } => {
295                    ::core::result::Result::Ok((#matches))
296                }
297                _ => ::core::result::Result::Err(self)
298            }
299        }
300    )
301}
302
303fn impl_all_as_fns(ast: &DeriveInput) -> TokenStream {
304    let name = &ast.ident;
305    let generics = &ast.generics;
306
307    let enum_data = if let syn::Data::Enum(data) = &ast.data {
308        data
309    } else {
310        panic!("{} is not an enum", name);
311    };
312
313    let mut stream = TokenStream::new();
314
315    for variant_data in &enum_data.variants {
316        let variant_name = &variant_data.ident;
317        let function_name_ref = Ident::new(
318            &format!("as_{}", variant_name).to_snake_case(),
319            Span::call_site(),
320        );
321        let doc_ref = format!(
322            "Optionally returns references to the inner fields if this is a `{}::{}`, otherwise `None`",
323            name,
324            variant_name,
325        );
326        let function_name_mut_ref = Ident::new(
327            &format!("as_{}_mut", variant_name).to_snake_case(),
328            Span::call_site(),
329        );
330        let doc_mut_ref = format!(
331            "Optionally returns mutable references to the inner fields if this is a `{}::{}`, otherwise `None`",
332            name,
333            variant_name,
334        );
335
336        let function_name_val = Ident::new(
337            &format!("into_{}", variant_name).to_snake_case(),
338            Span::call_site(),
339        );
340        let doc_val = format!(
341            "Returns the inner fields if this is a `{}::{}`, otherwise returns back the enum in the `Err` case of the result",
342            name,
343            variant_name,
344        );
345
346        let function_name_is = Ident::new(
347            &format!("is_{}", variant_name).to_snake_case(),
348            Span::call_site(),
349        );
350        let doc_is = format!(
351            "Returns true if this is a `{}::{}`, otherwise false",
352            name, variant_name,
353        );
354
355        let tokens = match &variant_data.fields {
356            syn::Fields::Unit => unit_fields_return(variant_name, &function_name_is, &doc_is),
357            syn::Fields::Unnamed(unnamed) => unnamed_fields_return(
358                variant_name,
359                (&function_name_is, &doc_is),
360                (&function_name_mut_ref, &doc_mut_ref),
361                (&function_name_ref, &doc_ref),
362                (&function_name_val, &doc_val),
363                unnamed,
364            ),
365            syn::Fields::Named(named) => named_fields_return(
366                variant_name,
367                (&function_name_is, &doc_is),
368                (&function_name_mut_ref, &doc_mut_ref),
369                (&function_name_ref, &doc_ref),
370                (&function_name_val, &doc_val),
371                named,
372            ),
373        };
374
375        stream.extend(tokens);
376    }
377
378    let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
379
380    quote!(
381        impl #impl_generics #name #ty_generics #where_clause {
382            #stream
383        }
384    )
385}
386
387/// Derive functions on an Enum for easily accessing individual items in the Enum
388#[proc_macro_derive(EnumAsInner)]
389pub fn enum_as_inner(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
390    // get a usable token stream
391    let ast: DeriveInput = parse_macro_input!(input as DeriveInput);
392
393    // Build the impl
394    let expanded: TokenStream = impl_all_as_fns(&ast);
395
396    // Return the generated impl
397    proc_macro::TokenStream::from(expanded)
398}