diff options
Diffstat (limited to 'enumn')
-rw-r--r-- | enumn/Cargo.toml | 12 | ||||
-rw-r--r-- | enumn/src/lib.rs | 205 | ||||
-rw-r--r-- | enumn/src/tests.rs | 69 |
3 files changed, 286 insertions, 0 deletions
diff --git a/enumn/Cargo.toml b/enumn/Cargo.toml new file mode 100644 index 0000000..84649ca --- /dev/null +++ b/enumn/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "enumn" +version = "0.1.0" +authors = ["The Chromium OS Authors"] + +[lib] +proc-macro = true + +[dependencies] +proc-macro2 = "0.4" +quote = "0.6" +syn = "0.15" diff --git a/enumn/src/lib.rs b/enumn/src/lib.rs new file mode 100644 index 0000000..f4c30a7 --- /dev/null +++ b/enumn/src/lib.rs @@ -0,0 +1,205 @@ +// Copyright 2018 The Chromium OS Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +//! 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: +//! +//! ```rust +//! # const IGNORE: &str = stringify! { +//! impl YourEnum { +//! pub fn n(value: Repr) -> Option<Self>; +//! } +//! # }; +//! ``` +//! +//! where `Repr` is an integer type of the right size as described in more +//! detail below. +//! +//! # Example +//! +//! ```rust +//! extern crate enumn; +//! +//! #[derive(PartialEq, Debug, enumn::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. +//! +//! ```rust +//! #[derive(enumn::N)] +//! #[repr(u8)] +//! enum E { +//! /* ... */ +//! # IGNORE +//! } +//! +//! // expands to: +//! impl E { +//! pub fn n(value: u8) -> Option<Self> { +//! /* ... */ +//! # unimplemented!() +//! } +//! } +//! ``` +//! +//! On the other hand if no `repr` is specified then we get a signature that is +//! generic over a variety of possible types. +//! +//! ```rust +//! # enum E {} +//! # +//! impl E { +//! pub fn n<REPR: Into<i64>>(value: REPR) -> Option<Self> { +//! /* ... */ +//! # unimplemented!() +//! } +//! } +//! ``` +//! +//! # Discriminants +//! +//! The conversion respects explictly specified enum discriminants. Consider +//! this enum: +//! +//! ```rust +//! #[derive(enumn::N)] +//! enum Letter { +//! A = 65, +//! B = 66, +//! } +//! ``` +//! +//! Here `Letter::n(65)` would return `Some(Letter::A)`. + +#![recursion_limit = "128"] + +extern crate proc_macro; +extern crate proc_macro2; +extern crate quote; +extern crate syn; + +#[cfg(test)] +mod tests; + +use proc_macro::TokenStream; +use quote::quote; +use syn::parse::Error; +use syn::{parse_macro_input, parse_quote, Data, DeriveInput, Fields, Meta, NestedMeta}; + +fn testable_derive(input: DeriveInput) -> proc_macro2::TokenStream { + 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(); + } + } + } + + // Parse repr attribute like #[repr(u16)]. + let mut repr = None; + for attr in input.attrs { + if let Ok(Meta::List(list)) = attr.parse_meta() { + if list.ident == "repr" { + if let Some(NestedMeta::Meta(Meta::Word(word))) = list.nested.into_iter().next() { + match word.to_string().as_str() { + "u8" | "u16" | "u32" | "u64" | "u128" | "usize" | "i8" | "i16" | "i32" + | "i64" | "i128" | "isize" => { + repr = Some(word); + } + _ => {} + } + } + } + } + } + + let signature; + let value; + match repr { + Some(ref repr) => { + signature = quote! { + fn n(value: #repr) + }; + value = quote!(value); + } + None => { + repr = Some(parse_quote!(i64)); + signature = quote! { + fn n<REPR: Into<i64>>(value: REPR) + }; + value = quote! { + <REPR as Into<i64>>::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), + } + }); + + quote! { + impl #ident { + pub #signature -> Option<Self> { + struct discriminant; + impl discriminant { + #(#declare_discriminants)* + } + match #value { + #(#match_discriminants)* + _ => None, + } + } + } + } +} + +#[proc_macro_derive(N)] +pub fn derive(input: TokenStream) -> TokenStream { + let input = parse_macro_input!(input as DeriveInput); + let expanded = testable_derive(input); + TokenStream::from(expanded) +} diff --git a/enumn/src/tests.rs b/enumn/src/tests.rs new file mode 100644 index 0000000..b7e0cda --- /dev/null +++ b/enumn/src/tests.rs @@ -0,0 +1,69 @@ +// Copyright 2018 The Chromium OS Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +use quote::quote; +use syn::{parse_quote, DeriveInput}; + +#[test] +fn test_repr() { + let input: DeriveInput = parse_quote! { + #[repr(u8)] + enum E { + A, + B, + C, + } + }; + let actual = ::testable_derive(input); + let expected = quote! { + impl E { + pub fn n(value: u8) -> Option<Self> { + struct discriminant; + impl discriminant { + const A: u8 = E::A as u8; + const B: u8 = E::B as u8; + const C: u8 = E::C as u8; + } + match value { + discriminant::A => Some(E::A), + discriminant::B => Some(E::B), + discriminant::C => Some(E::C), + _ => None, + } + } + } + }; + assert_eq!(actual.to_string(), expected.to_string()); +} + +#[test] +fn test_no_repr() { + let input: DeriveInput = parse_quote! { + enum E { + A, + B, + C, + } + }; + let actual = ::testable_derive(input); + let expected = quote! { + impl E { + pub fn n<REPR: Into<i64>>(value: REPR) -> Option<Self> { + struct discriminant; + impl discriminant { + const A: i64 = E::A as i64; + const B: i64 = E::B as i64; + const C: i64 = E::C as i64; + } + match <REPR as Into<i64>>::into(value) { + discriminant::A => Some(E::A), + discriminant::B => Some(E::B), + discriminant::C => Some(E::C), + _ => None, + } + } + } + }; + assert_eq!(actual.to_string(), expected.to_string()); +} |