diff --git a/strum/src/lib.rs b/strum/src/lib.rs index 3b5af9cf..85c50980 100644 --- a/strum/src/lib.rs +++ b/strum/src/lib.rs @@ -155,9 +155,9 @@ pub trait EnumMessage { /// `EnumProperty` is a trait that makes it possible to store additional information /// with enum variants. This trait is designed to be used with the macro of the same -/// name in the `strum_macros` crate. Currently, the only string literals are supported -/// in attributes, the other methods will be implemented as additional attribute types -/// become stabilized. +/// name in the `strum_macros` crate. Currently, string, integer and boolean literals +/// are supported in attributes, the other methods will be implemented as additional +/// attribute types become stabilized. /// /// # Example /// @@ -171,7 +171,7 @@ pub trait EnumMessage { /// #[strum(props(Teacher="Ms.Frizzle", Room="201"))] /// History, /// #[strum(props(Teacher="Mr.Smith"))] -/// #[strum(props(Room="103"))] +/// #[strum(props(Room=103))] /// Mathematics, /// #[strum(props(Time="2:30"))] /// Science, @@ -179,16 +179,13 @@ pub trait EnumMessage { /// /// let history = Class::History; /// assert_eq!("Ms.Frizzle", history.get_str("Teacher").unwrap()); +/// let maths = Class::Mathematics; +/// assert_eq!(103, maths.get_int("Room").unwrap()); /// ``` pub trait EnumProperty { fn get_str(&self, prop: &str) -> Option<&'static str>; - fn get_int(&self, _prop: &str) -> Option { - Option::None - } - - fn get_bool(&self, _prop: &str) -> Option { - Option::None - } + fn get_int(&self, prop: &str) -> Option; + fn get_bool(&self, prop: &str) -> Option; } /// A cheap reference-to-reference conversion. Used to convert a value to a diff --git a/strum_macros/src/helpers/metadata.rs b/strum_macros/src/helpers/metadata.rs index 94100a7f..d5f2b07e 100644 --- a/strum_macros/src/helpers/metadata.rs +++ b/strum_macros/src/helpers/metadata.rs @@ -9,6 +9,7 @@ use syn::{ }; use super::case_style::CaseStyle; +use super::PropertyValue; pub mod kw { use syn::custom_keyword; @@ -176,7 +177,7 @@ pub enum VariantMeta { }, Props { kw: kw::props, - props: Vec<(LitStr, LitStr)>, + props: Vec<(LitStr, PropertyValue)>, }, } @@ -239,7 +240,7 @@ impl Parse for VariantMeta { } } -struct Prop(Ident, LitStr); +struct Prop(Ident, PropertyValue); impl Parse for Prop { fn parse(input: ParseStream) -> syn::Result { diff --git a/strum_macros/src/helpers/mod.rs b/strum_macros/src/helpers/mod.rs index 23d60b53..695be718 100644 --- a/strum_macros/src/helpers/mod.rs +++ b/strum_macros/src/helpers/mod.rs @@ -40,3 +40,33 @@ pub fn occurrence_error(fst: T, snd: T, attr: &str) -> syn::Error { e.combine(syn::Error::new_spanned(fst, "first one here")); e } + +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum PropertyValue { + Str(syn::LitStr), + Num(syn::LitInt), + Bool(syn::LitBool), +} + +impl syn::parse::Parse for PropertyValue { + fn parse(input: syn::parse::ParseStream) -> syn::Result { + let value = if input.peek(syn::LitBool) { + PropertyValue::Bool(input.parse()?) + } else if input.peek(syn::LitInt) { + PropertyValue::Num(input.parse()?) + } else { + PropertyValue::Str(input.parse()?) + }; + Ok(value) + } +} + +impl quote::ToTokens for PropertyValue { + fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { + match self { + PropertyValue::Str(s) => s.to_tokens(tokens), + PropertyValue::Num(n) => n.to_tokens(tokens), + PropertyValue::Bool(b) => b.to_tokens(tokens), + } + } +} diff --git a/strum_macros/src/helpers/variant_props.rs b/strum_macros/src/helpers/variant_props.rs index 681c7c06..0e566df0 100644 --- a/strum_macros/src/helpers/variant_props.rs +++ b/strum_macros/src/helpers/variant_props.rs @@ -4,6 +4,7 @@ use syn::{Ident, LitStr, Variant}; use super::case_style::{CaseStyle, CaseStyleHelpers}; use super::metadata::{kw, VariantExt, VariantMeta}; use super::occurrence_error; +use super::PropertyValue; pub trait HasStrumVariantProperties { fn get_variant_properties(&self) -> syn::Result; @@ -18,7 +19,7 @@ pub struct StrumVariantProperties { pub message: Option, pub detailed_message: Option, pub documentation: Vec, - pub string_props: Vec<(LitStr, LitStr)>, + pub string_props: Vec<(LitStr, PropertyValue)>, serialize: Vec, pub to_string: Option, ident: Option, diff --git a/strum_macros/src/lib.rs b/strum_macros/src/lib.rs index 0e93b8a0..a8cd42c7 100644 --- a/strum_macros/src/lib.rs +++ b/strum_macros/src/lib.rs @@ -761,12 +761,12 @@ pub fn enum_messages(input: proc_macro::TokenStream) -> proc_macro::TokenStream /// Add custom properties to enum variants. /// /// Enables the encoding of arbitrary constants into enum variants. This method -/// currently only supports adding additional string values. Other types of literals are still -/// experimental in the rustc compiler. The generated code works by nesting match statements. -/// The first match statement matches on the type of the enum, and the inner match statement -/// matches on the name of the property requested. This design works well for enums with a small -/// number of variants and properties, but scales linearly with the number of variants so may not -/// be the best choice in all situations. +/// currently only supports adding additional string, integer and boolean values. Other types +/// of literals are still experimental in the rustc compiler. The generated code works by +/// nesting match statements. The first match statement matches on the type of the enum, +/// and the inner match statement matches on the name of the property requested. This design +/// works well for enums with a small number of variants and properties, but scales linearly +/// with the number of variants so may not be the best choice in all situations. /// /// ``` /// diff --git a/strum_macros/src/macros/enum_properties.rs b/strum_macros/src/macros/enum_properties.rs index 2583096b..7cd666c8 100644 --- a/strum_macros/src/macros/enum_properties.rs +++ b/strum_macros/src/macros/enum_properties.rs @@ -2,6 +2,7 @@ use proc_macro2::TokenStream; use quote::quote; use syn::{Data, DeriveInput, Fields}; +use crate::helpers::PropertyValue; use crate::helpers::{non_enum_error, HasStrumVariantProperties, HasTypeProperties}; pub fn enum_properties_inner(ast: &DeriveInput) -> syn::Result { @@ -14,11 +15,15 @@ pub fn enum_properties_inner(ast: &DeriveInput) -> syn::Result { let type_properties = ast.get_type_properties()?; let strum_module_path = type_properties.crate_module_path(); - let mut arms = Vec::new(); + let mut string_props = Vec::new(); + let mut num_props = Vec::new(); + let mut bool_props = Vec::new(); for variant in variants { let ident = &variant.ident; let variant_properties = variant.get_variant_properties()?; let mut string_arms = Vec::new(); + let mut bool_arms = Vec::new(); + let mut num_arms = Vec::new(); // But you can disable the messages. if variant_properties.disabled.is_some() { continue; @@ -31,29 +36,67 @@ pub fn enum_properties_inner(ast: &DeriveInput) -> syn::Result { }; for (key, value) in variant_properties.string_props { - string_arms.push(quote! { #key => ::core::option::Option::Some( #value )}); + match value { + PropertyValue::Str(s) => { + string_arms.push(quote! { #key => ::core::option::Option::Some( #s )}) + } + PropertyValue::Num(n) => { + num_arms.push(quote! { #key => ::core::option::Option::Some( #n )}) + } + PropertyValue::Bool(b) => { + bool_arms.push(quote! { #key => ::core::option::Option::Some( #b )}) + } + } } string_arms.push(quote! { _ => ::core::option::Option::None }); + bool_arms.push(quote! { _ => ::core::option::Option::None }); + num_arms.push(quote! { _ => ::core::option::Option::None }); - arms.push(quote! { + string_props.push(quote! { &#name::#ident #params => { match prop { #(#string_arms),* } } }); + bool_props.push(quote! { + &#name::#ident #params => { + match prop { + #(#bool_arms),* + } + } + }); + num_props.push(quote! { + &#name::#ident #params => { + match prop { + #(#num_arms),* + } + } + }); } - if arms.len() < variants.len() { - arms.push(quote! { _ => ::core::option::Option::None }); - } + string_props.push(quote! { _ => ::core::option::Option::None }); + + bool_props.push(quote! { _ => ::core::option::Option::None }); + + num_props.push(quote! { _ => ::core::option::Option::None }); Ok(quote! { impl #impl_generics #strum_module_path::EnumProperty for #name #ty_generics #where_clause { fn get_str(&self, prop: &str) -> ::core::option::Option<&'static str> { match self { - #(#arms),* + #(#string_props),* + } + } + fn get_bool(&self, prop: &str) -> ::core::option::Option { + match self { + #(#bool_props),* + } + } + fn get_int(&self, prop: &str) -> ::core::option::Option { + match self { + #(#num_props),* } } } diff --git a/strum_tests/tests/enum_props.rs b/strum_tests/tests/enum_props.rs index c32c07e0..129f8020 100644 --- a/strum_tests/tests/enum_props.rs +++ b/strum_tests/tests/enum_props.rs @@ -41,9 +41,16 @@ fn crate_module_path_test() { enum Test { #[strum(props(key = "value"))] A, + #[strum(props(answer = 42))] B, + #[strum(props(to_be = false))] + C, } let a = Test::A; assert_eq!("value", a.get_str("key").unwrap()); + let b = Test::B; + assert_eq!(42, b.get_int("answer").unwrap()); + let c = Test::C; + assert_eq!(false, c.get_bool("to_be").unwrap()); }