enum_unwrapper/
lib.rs

1/*
2Copyright 2023 Benjamin Richcreek
3
4Licensed under the Apache License, Version 2.0 (the "License");
5you may not use this file except in compliance with the License.
6You may obtain a copy of the License at
7
8http://www.apache.org/licenses/LICENSE-2.0
9
10Unless required by applicable law or agreed to in writing, software
11distributed under the License is distributed on an "AS IS" BASIS,
12WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13See the License for the specific language governing permissions and
14limitations under the License.
15*/
16//! # Enum Unwrapper
17//!`enum_unwrapper` is a lightweight procedural macro for "unwrapping" [`enum`](<https://doc.rust-lang.org/1.58.1/std/keyword.enum.html>)s into their inner types when the variant is known through automatic implementation of [`TryFrom`].
18//!
19//!`enum_unrapper` does this by allowing the user to add a procedural macro attribute, [`macro@unique_try_froms`] to [`enum`](<https://doc.rust-lang.org/1.58.1/std/keyword.enum.html>) definitions.
20//!
21//!For more information and examples, check the attribute's [documentation](macro@unique_try_froms).
22use syn;
23use quote::quote;
24use proc_macro::TokenStream;
25/// # Unique TryFroms
26/// Add this attribute to [`enum`](<https://doc.rust-lang.org/1.58.1/std/keyword.enum.html>) definitions, and it will implement [`TryFrom`] for each standalone type contained in a variant of that [`enum`](<https://doc.rust-lang.org/1.58.1/std/keyword.enum.html>)
27/// # Example
28/// ```no_run
29/// #[unique_try_froms()]
30/// enum NumberHolder {
31///    U8(u8),
32///    U16(u16),
33///}
34///fn main() {
35///    let small_number = NumberHolder::U8(4);
36///    assert_eq!(4,u8::try_from(small_number).unwrap());
37///    let big_number = NumberHolder::U16(444);
38///    assert_eq!(444,u16::try_from(big_number).unwrap());
39///}
40///```
41///note: this example is not automatically tested due to restrictions on `proc_macro` crates
42/// # Panics
43/// The macro panics when attached to anything other than an [`enum`](<https://doc.rust-lang.org/1.58.1/std/keyword.enum.html>) definition.
44///
45/// The macro panics if attached to an [`enum`](<https://doc.rust-lang.org/1.58.1/std/keyword.enum.html>) with one or more variants containing multiple fields, such as
46/// ```no_run
47///    Variant(u8,u8),
48///```
49/// In such cases it is recomended to condense the data into one type, like so:
50///```no_run
51/// Variant([u8;2]),
52///```
53///
54///The macro currently panics if attached to an [`enum`](<https://doc.rust-lang.org/1.58.1/std/keyword.enum.html>) definition with variants containing identical types.
55#[proc_macro_attribute]
56pub fn unique_try_froms (_exempt_types: TokenStream, user_enum: TokenStream) -> TokenStream {
57    let parsed_enum: &syn::ItemEnum  = &syn::parse(user_enum).expect("This attribute should only be attached to a enum definition");
58    let enum_name = &parsed_enum.ident;
59    let ident_extractor = |variant: &syn::Variant| -> syn::Ident {
60        variant.ident.clone()
61    };
62    let inner_type_extractor = |variant: &syn::Variant| -> syn::Type {
63        match &variant.fields {
64            syn::Fields::Unnamed(wrapped) => return wrapped.unnamed.first().expect("Each enum variant should contain one inner value").ty.clone(),
65            _ => panic!("An unexpected error occoured, please only use unnamed enum variants")
66        }
67    };
68    let enum_variants = parsed_enum.variants.iter().map(ident_extractor);
69    let variant_types = parsed_enum.variants.iter().map(inner_type_extractor);
70    quote! {
71        #parsed_enum
72        #(impl TryFrom<#enum_name> for #variant_types {
73            type Error = &'static str;
74            fn try_from(value: #enum_name) ->  Result<Self,Self::Error> {
75                match value {
76                    #enum_name::#enum_variants(inner) => return Ok(inner),
77                    _ => return Err("Only variants containing an inner value of the same type as the target should be passed to this function"),
78                }
79            }
80        })*
81    }.into()
82}