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
107 use heck::ToSnakeCase;
108 use proc_macro2::{Ident, Span, TokenStream};
109 use quote::quote;
110 use syn::{parse_macro_input, DeriveInput};
111
112 /// returns first the types to return, the match names, and then tokens to the field accesses
unit_fields_return(variant_name: &syn::Ident, function_name: &Ident, doc: &str) -> TokenStream113 fn 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
unnamed_fields_return( variant_name: &syn::Ident, (function_name_is, doc_is): (&Ident, &str), (function_name_mut_ref, doc_mut_ref): (&Ident, &str), (function_name_ref, doc_ref): (&Ident, &str), (function_name_val, doc_val): (&Ident, &str), fields: &syn::FieldsUnnamed, ) -> TokenStream124 fn 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
named_fields_return( variant_name: &syn::Ident, (function_name_is, doc_is): (&Ident, &str), (function_name_mut_ref, doc_mut_ref): (&Ident, &str), (function_name_ref, doc_ref): (&Ident, &str), (function_name_val, doc_val): (&Ident, &str), fields: &syn::FieldsNamed, ) -> TokenStream213 fn 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
impl_all_as_fns(ast: &DeriveInput) -> TokenStream303 fn 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)]
enum_as_inner(input: proc_macro::TokenStream) -> proc_macro::TokenStream389 pub 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 }
399