From af58a6d403dfaa4c76cc00916b6f2e1add603534 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=82=89=E3=82=8A=E3=81=8A=20=28YOSHIOKA=20Takuma=29?= Date: Thu, 24 Aug 2017 11:18:23 +0900 Subject: [PATCH] Add `derive(AsRefStr)` implementation (#10) Implements AsRef as a non-allocating alternative to deriving ToString. Docs will come later --- strum_macros/src/as_ref_str.rs | 62 +++++++++++++++++++++++++++++++++ strum_macros/src/lib.rs | 11 ++++++ strum_macros/src/to_string.rs | 2 +- strum_tests/tests/as_ref_str.rs | 46 ++++++++++++++++++++++++ strum_tests/tests/to_string.rs | 1 + 5 files changed, 121 insertions(+), 1 deletion(-) create mode 100644 strum_macros/src/as_ref_str.rs create mode 100644 strum_tests/tests/as_ref_str.rs diff --git a/strum_macros/src/as_ref_str.rs b/strum_macros/src/as_ref_str.rs new file mode 100644 index 0000000..e060347 --- /dev/null +++ b/strum_macros/src/as_ref_str.rs @@ -0,0 +1,62 @@ + +use quote; +use syn; + +use helpers::{unique_attr, extract_attrs, is_disabled}; + +pub fn as_ref_str_inner(ast: &syn::DeriveInput) -> quote::Tokens { + let name = &ast.ident; + let (impl_generics, ty_generics, where_clause) = ast.generics.split_for_impl(); + let variants = match ast.body { + syn::Body::Enum(ref v) => v, + _ => panic!("AsRefStr only works on Enums"), + }; + + let mut arms = Vec::new(); + for variant in variants { + use syn::VariantData::*; + let ident = &variant.ident; + + if is_disabled(&variant.attrs) { + continue; + } + + // Look at all the serialize attributes. + // Use `to_string` attribute (not `as_ref_str` or something) to keep things consistent + // (i.e. always `enum.as_ref().to_string() == enum.to_string()`). + let output = if let Some(n) = unique_attr(&variant.attrs, "strum", "to_string") { + n + } else { + let mut attrs = extract_attrs(&variant.attrs, "strum", "serialize"); + // We always take the longest one. This is arbitary, but is *mostly* deterministic + attrs.sort_by_key(|s| -(s.len() as isize)); + if let Some(n) = attrs.first() { + n + } else { + ident.as_ref() + } + }; + + let params = match variant.data { + Unit => quote::Ident::from(""), + Tuple(..) => quote::Ident::from("(..)"), + Struct(..) => quote::Ident::from("{..}"), + }; + + arms.push(quote!{ #name::#ident #params => #output }); + } + + if arms.len() < variants.len() { + arms.push(quote!{ _ => panic!("AsRef::as_ref() called on disabled variant.")}) + } + + quote!{ + impl #impl_generics ::std::convert::AsRef for #name #ty_generics #where_clause { + fn as_ref(&self) -> &str { + match *self { + #(#arms),* + } + } + } + } +} diff --git a/strum_macros/src/lib.rs b/strum_macros/src/lib.rs index c25861b..62f2108 100644 --- a/strum_macros/src/lib.rs +++ b/strum_macros/src/lib.rs @@ -13,6 +13,7 @@ extern crate quote; extern crate proc_macro; mod helpers; +mod as_ref_str; mod to_string; mod from_string; mod enum_iter; @@ -46,6 +47,16 @@ pub fn from_string(input: TokenStream) -> TokenStream { toks.parse().unwrap() } +#[proc_macro_derive(AsRefStr,attributes(strum))] +pub fn as_ref_str(input: TokenStream) -> TokenStream { + let s = input.to_string(); + let ast = syn::parse_derive_input(&s).unwrap(); + + let toks = as_ref_str::as_ref_str_inner(&ast); + debug_print_generated(&ast, &toks); + toks.parse().unwrap() +} + #[proc_macro_derive(ToString,attributes(strum))] pub fn to_string(input: TokenStream) -> TokenStream { let s = input.to_string(); diff --git a/strum_macros/src/to_string.rs b/strum_macros/src/to_string.rs index 256cb90..8eae679 100644 --- a/strum_macros/src/to_string.rs +++ b/strum_macros/src/to_string.rs @@ -57,4 +57,4 @@ pub fn to_string_inner(ast: &syn::DeriveInput) -> quote::Tokens { } } } -} \ No newline at end of file +} diff --git a/strum_tests/tests/as_ref_str.rs b/strum_tests/tests/as_ref_str.rs new file mode 100644 index 0000000..a7a54cf --- /dev/null +++ b/strum_tests/tests/as_ref_str.rs @@ -0,0 +1,46 @@ +extern crate strum; +#[macro_use] +extern crate strum_macros; + +use std::str::FromStr; + +#[derive(Debug,Eq,PartialEq,EnumString,AsRefStr)] +enum Color { + #[strum(to_string="RedRed")] + Red, + #[strum(serialize="b", to_string="blue")] + Blue { hue: usize }, + #[strum(serialize="y", serialize="yellow")] + Yellow, + #[strum(default="true")] + Green(String), +} + +#[test] +fn color_simple() { + assert_eq!(Color::Red, Color::from_str("RedRed").unwrap()); +} + +#[test] +fn as_red_str() { + assert_eq!("RedRed", + (Color::Red).as_ref()); + assert_eq!(Color::Red, + Color::from_str((Color::Red).as_ref()).unwrap()); +} + +#[test] +fn as_blue_str() { + assert_eq!("blue", + (Color::Blue { hue: 0 }).as_ref()); +} + +#[test] +fn as_yellow_str() { + assert_eq!("yellow", (Color::Yellow).as_ref()); +} + +#[test] +fn as_green_str() { + assert_eq!("Green", (Color::Green(String::default())).as_ref()); +} diff --git a/strum_tests/tests/to_string.rs b/strum_tests/tests/to_string.rs index 2364971..1847f85 100644 --- a/strum_tests/tests/to_string.rs +++ b/strum_tests/tests/to_string.rs @@ -35,6 +35,7 @@ fn to_yellow_string() { #[test] fn to_red_string() { + assert_eq!(String::from("RedRed"), (Color::Red).to_string()); assert_eq!(Color::Red, Color::from_str((Color::Red).to_string().as_ref()).unwrap()); }