//! [![github]](https://github.com/dtolnay/enumn) [![crates-io]](https://crates.io/crates/enumn) [![docs-rs]](https://docs.rs/enumn) //! //! [github]: https://img.shields.io/badge/github-8da0cb?style=for-the-badge&labelColor=555555&logo=github //! [crates-io]: https://img.shields.io/badge/crates.io-fc8d62?style=for-the-badge&labelColor=555555&logo=rust //! [docs-rs]: https://img.shields.io/badge/docs.rs-66c2a5?style=for-the-badge&labelColor=555555&logo=docs.rs //! //!
//! //! Convert number to enum. //! //! This crate provides a derive macro to generate a function for converting a //! primitive integer into the corresponding variant of an enum. //! //! The generated function is named `n` and has the following signature: //! //! ``` //! # const IGNORE: &str = stringify! { //! impl YourEnum { //! pub fn n(value: Repr) -> Option; //! } //! # }; //! ``` //! //! where `Repr` is an integer type of the right size as described in more //! detail below. //! //! # Example //! //! ``` //! use enumn::N; //! //! #[derive(PartialEq, Debug, N)] //! enum Status { //! LegendaryTriumph, //! QualifiedSuccess, //! FortuitousRevival, //! IndeterminateStalemate, //! RecoverableSetback, //! DireMisadventure, //! AbjectFailure, //! } //! //! fn main() { //! let s = Status::n(1); //! assert_eq!(s, Some(Status::QualifiedSuccess)); //! //! let s = Status::n(9); //! assert_eq!(s, None); //! } //! ``` //! //! # Signature //! //! The generated signature depends on whether the enum has a `#[repr(..)]` //! attribute. If a `repr` is specified, the input to `n` will be required to be //! of that type. //! //! ``` //! #[derive(enumn::N)] //! # enum E0 { //! # IGNORE //! # } //! # //! #[repr(u8)] //! enum E { //! /* ... */ //! # IGNORE //! } //! //! // expands to: //! impl E { //! pub fn n(value: u8) -> Option { //! /* ... */ //! # unimplemented!() //! } //! } //! ``` //! //! On the other hand if no `repr` is specified then we get a signature that is //! generic over a variety of possible types. //! //! ``` //! # enum E {} //! # //! impl E { //! pub fn n>(value: REPR) -> Option { //! /* ... */ //! # unimplemented!() //! } //! } //! ``` //! //! # Discriminants //! //! The conversion respects explicitly specified enum discriminants. Consider //! this enum: //! //! ``` //! #[derive(enumn::N)] //! enum Letter { //! A = 65, //! B = 66, //! } //! ``` //! //! Here `Letter::n(65)` would return `Some(Letter::A)`. #![doc(html_root_url = "https://docs.rs/enumn/0.1.14")] #![allow( clippy::missing_panics_doc, clippy::needless_doctest_main, clippy::single_match_else )] extern crate proc_macro; use proc_macro::TokenStream; use quote::quote; use syn::{parse_macro_input, Data, DeriveInput, Error, Fields, Ident}; #[proc_macro_derive(N)] pub fn derive(input: TokenStream) -> TokenStream { let input = parse_macro_input!(input as DeriveInput); let variants = match input.data { Data::Enum(data) => data.variants, Data::Struct(_) | Data::Union(_) => panic!("input must be an enum"), }; for variant in &variants { match variant.fields { Fields::Unit => {} Fields::Named(_) | Fields::Unnamed(_) => { let span = variant.ident.span(); let err = Error::new(span, "enumn: variant with data is not supported"); return err.to_compile_error().into(); } } } // Parse repr attribute like #[repr(u16)]. let mut repr = None; for attr in input.attrs { if attr.path().is_ident("repr") { if let Ok(name) = attr.parse_args::() { match name.to_string().as_str() { "u8" | "u16" | "u32" | "u64" | "u128" | "usize" | "i8" | "i16" | "i32" | "i64" | "i128" | "isize" => { repr = Some(quote!(#name)); } _ => {} } } } } let signature; let value; match repr { Some(ref repr) => { signature = quote! { fn n(value: #repr) }; value = quote!(value); } None => { repr = Some(quote!(i64)); signature = quote! { fn n>(value: REPR) }; value = quote! { >::into(value) }; } } let ident = input.ident; let declare_discriminants = variants.iter().map(|variant| { let variant = &variant.ident; quote! { const #variant: #repr = #ident::#variant as #repr; } }); let match_discriminants = variants.iter().map(|variant| { let variant = &variant.ident; quote! { discriminant::#variant => Some(#ident::#variant), } }); TokenStream::from(quote! { impl #ident { pub #signature -> Option { #[allow(non_camel_case_types)] struct discriminant; #[allow(non_upper_case_globals)] impl discriminant { #(#declare_discriminants)* } match #value { #(#match_discriminants)* _ => None, } } } }) }