enum_repr_derive/
lib.rs

1//! # enum-repr-derive
2//!
3//! [![Build Status](https://www.travis-ci.org/ssalonen/enum-repr-derive.svg?branch=master)](https://www.travis-ci.org/ssalonen/enum-repr-derive)
4//! [![Crate](https://img.shields.io/crates/v/enum-repr-derive.svg)](https://crates.io/enum-repr-derive)
5//! [![Documentation](https://docs.rs/enum-repr-derive/badge.svg)](https://docs.rs/enum-repr-derive)
6//!
7//! Procedural derive macro for converting fieldless enums to (`Into`) and from (`TryFrom`) its repr type.
8//!
9//! See the [Nomicon section on `repr`](https://doc.rust-lang.org/nomicon/other-reprs.html#repru-repri) for more details on fieldless enums.
10//!
11//! ## Example code
12//!
13//! By using this library the following code just works:
14//!
15//! ```rust
16//! extern crate enum_repr_derive;
17//! use enum_repr_derive::{FromEnumToRepr, TryFromReprToEnum};
18//! use std::convert::TryFrom;
19//!
20//! #[repr(i8)]
21//! #[derive(TryFromReprToEnum, FromEnumToRepr, Copy, Clone, Debug, PartialEq)]
22//! enum Foo {
23//!     VAR1 = -1,
24//!     VAR2 = -3,
25//! }
26//! assert_eq!(Foo::try_from(-1), Ok(Foo::VAR1));
27//! assert_eq!(Foo::try_from(-3), Ok(Foo::VAR2));
28//! assert_eq!(Foo::try_from(-9), Err(-9));
29//! assert_eq!(Into::<i8>::into(Foo::VAR1), -1);
30//! assert_eq!(Into::<i8>::into(Foo::VAR2), -3);
31//! assert_eq!(i8::from(Foo::VAR1), -1);
32//! assert_eq!(i8::from(Foo::VAR2), -3);
33//! ```
34//!
35//! ## License
36//!
37//! Licensed under MIT. See `LICENSE` file.
38//!
39//! ## For developers
40//!
41//! Release: `cargo release`
42//!
43
44extern crate proc_macro;
45use proc_macro2::{Delimiter, Span, TokenStream, TokenTree};
46use proc_macro_error::{abort, abort_call_site, proc_macro_error};
47use quote::quote;
48use syn::{parse_macro_input, AttrStyle, Attribute, Data, DeriveInput, Expr, Ident};
49
50#[proc_macro_derive(TryFromReprToEnum)]
51#[proc_macro_error]
52pub fn derive_try_from(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
53    let input = parse_macro_input!(input as DeriveInput);
54    let reprtype = find_repr_type(input.attrs);
55    let enum_name = input.ident;
56
57    let enum_data = get_enum_data(&input.data);
58    let match_expr = match_impl(&enum_name, enum_data);
59
60    proc_macro::TokenStream::from(quote! {
61
62        impl core::convert::TryFrom<#reprtype> for #enum_name  {
63            type Error = #reprtype;
64
65            fn try_from(val : #reprtype) -> Result<Self, <Self as core::convert::TryFrom<#reprtype>>::Error> {
66                #match_expr
67            }
68        }
69    })
70}
71
72#[proc_macro_derive(FromEnumToRepr)]
73#[proc_macro_error]
74pub fn derive_into_primitive_and_from_enum(
75    input: proc_macro::TokenStream,
76) -> proc_macro::TokenStream {
77    let input = parse_macro_input!(input as DeriveInput);
78    let reprtype = find_repr_type(input.attrs);
79    let enum_name = input.ident;
80
81    proc_macro::TokenStream::from(quote! {
82
83        impl core::convert::From<#enum_name> for #reprtype  {
84               fn from(enum_value : #enum_name) -> Self {
85                enum_value as Self
86            }
87        }
88    })
89}
90
91fn find_repr_type(attrs: Vec<Attribute>) -> Ident {
92    for attr in attrs {
93        match attr.style {
94            AttrStyle::Outer => {
95                if attr.path.is_ident(&Ident::new("repr", Span::call_site())) {
96                    let tokens = attr.tokens;
97                    let mut repr_tokens_iter = tokens.into_iter();
98                    let first_token: TokenTree = repr_tokens_iter.next().unwrap();
99                    if repr_tokens_iter.next().is_some() {
100                        abort!(
101                            first_token.span(),
102                            "Repr is malformed, expecting repr(TYPE)"
103                        );
104                    }
105                    let repr_type = match first_token.clone() {
106                        TokenTree::Group(repr_items) => {
107                            if repr_items.delimiter() != Delimiter::Parenthesis {
108                                abort!(repr_items.span(), "Repr is malformed, expecting repr(TYPE)")
109                            }
110                            let mut repr_types_iter = repr_items.stream().into_iter();
111                            let first_repr_item = repr_types_iter.next().unwrap();
112                            // Unwrap if many repr types are specified
113                            if let Some(second_repr_type) = repr_types_iter.next() {
114                                abort!(
115                                    second_repr_type.span(),
116                                    "Many repr types specified. Expecting only one."
117                                )
118                            }
119                            match first_repr_item.clone() {
120                                TokenTree::Ident(repr_type) => repr_type,
121                                unexpected_type => abort!(
122                                    first_repr_item.span(),
123                                    "Unexpected type in repr {}",
124                                    unexpected_type
125                                ),
126                            }
127                        }
128                        unexpected_token => abort!(
129                            first_token.span(),
130                            "Unexpected token with repr {}",
131                            unexpected_token
132                        ),
133                    };
134                    return repr_type;
135                }
136            }
137            _ => {
138                // skipping non-outer attributes
139                continue;
140            }
141        }
142    }
143    abort_call_site!("Repr not found");
144}
145
146fn get_enum_data(data: &Data) -> Vec<(Ident, Expr)> {
147    let mut enum_data: Vec<(Ident, Expr)> = Vec::new();
148    match *data {
149        Data::Enum(ref data) => {
150            for variant in data.variants.iter() {
151                let pair = variant.discriminant.as_ref().unwrap();
152                let expr = pair.1.clone();
153                enum_data.push((variant.ident.clone(), expr));
154            }
155        }
156        Data::Struct(_) | Data::Union(_) => {
157            abort_call_site!("Unexpected type! Use derive with enums only")
158        }
159    }
160    enum_data
161}
162
163fn match_impl(enum_name: &Ident, enum_data: Vec<(Ident, Expr)>) -> TokenStream {
164    let mut match_arms = TokenStream::new();
165    for (id, expr) in enum_data {
166        match_arms.extend(quote! { #expr => Ok(#enum_name::#id),});
167    }
168    match_arms.extend(quote! { unexpected => Err(unexpected) });
169    quote! {
170        match val {
171            #match_arms
172        }
173    }
174}