From 6297e252835641eae9aaec2d2dcd51a682493a8f Mon Sep 17 00:00:00 2001 From: Brenno Lemos Date: Thu, 21 Sep 2023 17:39:28 -0300 Subject: [PATCH 1/4] feat: introduce `StaticVariantsArray` trait and derive test: `StaticVariantsArray` macro tests --- README.md | 2 + strum/src/lib.rs | 13 +++- strum_macros/src/helpers/mod.rs | 8 +++ strum_macros/src/lib.rs | 32 ++++++++++ strum_macros/src/macros/mod.rs | 1 + .../src/macros/static_variants_array.rs | 36 +++++++++++ strum_tests/tests/static_variants_array.rs | 60 +++++++++++++++++++ 7 files changed, 151 insertions(+), 1 deletion(-) create mode 100644 strum_macros/src/macros/static_variants_array.rs create mode 100644 strum_tests/tests/static_variants_array.rs diff --git a/README.md b/README.md index 491c24f2..c4ae3db3 100644 --- a/README.md +++ b/README.md @@ -46,6 +46,7 @@ Strum has implemented the following macros: | [EnumMessage] | Add a verbose message to an enum variant. | | [EnumDiscriminants] | Generate a new type with only the discriminant names. | | [EnumCount] | Add a constant `usize` equal to the number of variants. | +| [StaticVariantsArray] | Adds an associated `ALL_VARIANTS` constant which is an array of all enum discriminants | # Contributing @@ -80,3 +81,4 @@ Strumming is also a very whimsical motion, much like writing Rust code. [EnumDiscriminants]: https://docs.rs/strum_macros/0.25/strum_macros/derive.EnumDiscriminants.html [EnumCount]: https://docs.rs/strum_macros/0.25/strum_macros/derive.EnumCount.html [FromRepr]: https://docs.rs/strum_macros/0.25/strum_macros/derive.FromRepr.html +[StaticVariantsArray]: https://docs.rs/strum_macros/0.25/strum_macros/derive.StaticVariantsArray.html diff --git a/strum/src/lib.rs b/strum/src/lib.rs index 7fbe5e2d..31d7626f 100644 --- a/strum/src/lib.rs +++ b/strum/src/lib.rs @@ -211,6 +211,16 @@ pub trait VariantNames { const VARIANTS: &'static [&'static str]; } +/// A trait for retrieving a static array containing all the variants in an Enum. +/// This trait can be autoderived by `strum_macros`. For derived usage, all the +/// variants in the enumerator need to be unit-types, which means you can't autoderive +/// enums with inner data in one or more variants. Consider using it alongside +/// [`EnumDiscriminants`] if you require inner data but still want to have an +/// static array of variants. +pub trait StaticVariantsArray: std::marker::Sized + 'static { + const ALL_VARIANTS: &'static [Self]; +} + #[cfg(feature = "derive")] pub use strum_macros::*; @@ -241,5 +251,6 @@ DocumentMacroRexports! { EnumVariantNames, FromRepr, IntoStaticStr, - ToString + ToString, + StaticVariantsArray } diff --git a/strum_macros/src/helpers/mod.rs b/strum_macros/src/helpers/mod.rs index 142ea0b8..006194d4 100644 --- a/strum_macros/src/helpers/mod.rs +++ b/strum_macros/src/helpers/mod.rs @@ -15,6 +15,14 @@ pub fn non_enum_error() -> syn::Error { syn::Error::new(Span::call_site(), "This macro only supports enums.") } +pub fn non_unit_variant_error() -> syn::Error { + syn::Error::new( + Span::call_site(), + "This macro only supports enums of strictly unit variants. Consider \ + using it in conjunction with [`EnumDiscriminants`]" + ) +} + pub fn strum_discriminants_passthrough_error(span: &impl Spanned) -> syn::Error { syn::Error::new( span.span(), diff --git a/strum_macros/src/lib.rs b/strum_macros/src/lib.rs index 82db12ad..b03d2609 100644 --- a/strum_macros/src/lib.rs +++ b/strum_macros/src/lib.rs @@ -194,6 +194,38 @@ pub fn variant_names(input: proc_macro::TokenStream) -> proc_macro::TokenStream toks.into() } +/// Adds a static array with all of the Enum's variants. +/// +/// Implements `Strum::StaticVariantsArray` which adds an associated constant `ALL_VARIANTS`. +/// This constant contains an array with all the variants of the enumerator. +/// +/// This trait can only be autoderived if the enumerator is composed only of unit-type variants, +/// meaning that the variants must not have any data. +/// +/// ``` +/// use strum::StaticVariantsArray; +/// use strum_macros::StaticVariantsArray; +/// +/// #[derive(StaticVariantsArray, Debug, PartialEq, Eq)] +/// enum Op { +/// Add, +/// Sub, +/// Mul, +/// Div, +/// } +/// +/// assert_eq!(Op::ALL_VARIANTS, &[Op::Add, Op::Sub, Op::Mul, Op::Div]); +/// ``` +#[proc_macro_derive(StaticVariantsArray, attributes(strum))] +pub fn static_variants_array(input: proc_macro::TokenStream) -> proc_macro::TokenStream { + let ast = syn::parse_macro_input!(input as DeriveInput); + + let toks = macros::static_variants_array::static_variants_array_inner(&ast) + .unwrap_or_else(|err| err.to_compile_error()); + debug_print_generated(&ast, &toks); + toks.into() +} + #[proc_macro_derive(AsStaticStr, attributes(strum))] #[deprecated( since = "0.22.0", diff --git a/strum_macros/src/macros/mod.rs b/strum_macros/src/macros/mod.rs index 8df8cd6d..9486e018 100644 --- a/strum_macros/src/macros/mod.rs +++ b/strum_macros/src/macros/mod.rs @@ -7,6 +7,7 @@ pub mod enum_properties; pub mod enum_try_as; pub mod enum_variant_names; pub mod from_repr; +pub mod static_variants_array; mod strings; diff --git a/strum_macros/src/macros/static_variants_array.rs b/strum_macros/src/macros/static_variants_array.rs new file mode 100644 index 00000000..2b4130cf --- /dev/null +++ b/strum_macros/src/macros/static_variants_array.rs @@ -0,0 +1,36 @@ +use proc_macro2::TokenStream; +use quote::quote; +use syn::{Data, DeriveInput, Fields}; + +use crate::helpers::{non_enum_error, HasTypeProperties, non_unit_variant_error}; + +pub fn static_variants_array_inner(ast: &DeriveInput) -> syn::Result { + let name = &ast.ident; + let gen = &ast.generics; + let (impl_generics, ty_generics, where_clause) = gen.split_for_impl(); + + let variants = match &ast.data { + Data::Enum(v) => &v.variants, + _ => return Err(non_enum_error()), + }; + + let type_properties = ast.get_type_properties()?; + let strum_module_path = type_properties.crate_module_path(); + + let idents = variants + .iter() + .cloned() + .map(|v| { + match v.fields { + Fields::Unit => Ok(v.ident), + _ => Err(non_unit_variant_error()) + } + }) + .collect::>>()?; + + Ok(quote! { + impl #impl_generics #strum_module_path::StaticVariantsArray for #name #ty_generics #where_clause { + const ALL_VARIANTS: &'static [Self] = &[ #(#name::#idents),* ]; + } + }) +} diff --git a/strum_tests/tests/static_variants_array.rs b/strum_tests/tests/static_variants_array.rs new file mode 100644 index 00000000..8f67c4dc --- /dev/null +++ b/strum_tests/tests/static_variants_array.rs @@ -0,0 +1,60 @@ +use strum::{StaticVariantsArray, EnumDiscriminants}; + +mod core {} // ensure macros call `::core` + +#[test] +fn simple() { + #[derive(StaticVariantsArray, PartialEq, Eq, Debug)] + enum Operation { + Add, + Sub, + Mul, + Div, + } + + assert_eq!( + Operation::ALL_VARIANTS, + &[ + Operation::Add, + Operation::Sub, + Operation::Mul, + Operation::Div, + ] + ) +} + +#[test] +fn in_enum_discriminants() { + #[allow(dead_code)] + #[derive(EnumDiscriminants)] + #[strum_discriminants(derive(StaticVariantsArray))] + #[strum_discriminants(name(GeometricShapeDiscriminants))] + enum GeometricShape { + Point, + Circle(i32), + Rectangle { + width: i32, + height: i32, + } + } + + assert_eq!( + GeometricShapeDiscriminants::ALL_VARIANTS, + &[ + GeometricShapeDiscriminants::Point, + GeometricShapeDiscriminants::Circle, + GeometricShapeDiscriminants::Rectangle, + ] + ) +} + +#[test] +fn empty_enum() { + #[derive(StaticVariantsArray, PartialEq, Eq, Debug)] + enum Empty {} + + assert_eq!( + Empty::ALL_VARIANTS, + &[], + ) +} From 726d6444935450dce151de573b73f98eac29ef4c Mon Sep 17 00:00:00 2001 From: Brenno Lemos Date: Thu, 21 Sep 2023 17:46:59 -0300 Subject: [PATCH 2/4] test: variants with values --- strum_tests/tests/static_variants_array.rs | 33 ++++++++++++++++++++-- 1 file changed, 30 insertions(+), 3 deletions(-) diff --git a/strum_tests/tests/static_variants_array.rs b/strum_tests/tests/static_variants_array.rs index 8f67c4dc..f5f72c11 100644 --- a/strum_tests/tests/static_variants_array.rs +++ b/strum_tests/tests/static_variants_array.rs @@ -20,7 +20,7 @@ fn simple() { Operation::Mul, Operation::Div, ] - ) + ); } #[test] @@ -45,7 +45,7 @@ fn in_enum_discriminants() { GeometricShapeDiscriminants::Circle, GeometricShapeDiscriminants::Rectangle, ] - ) + ); } #[test] @@ -56,5 +56,32 @@ fn empty_enum() { assert_eq!( Empty::ALL_VARIANTS, &[], - ) + ); } + +#[test] +fn variants_with_values() { + #[derive(StaticVariantsArray, PartialEq, Eq, Debug)] + enum WeekDay { + Sunday = 0, + Monday = 1, + Tuesday = 2, + Wednesday = 3, + Thursday = 4, + Friday = 5, + Saturday = 6, + } + + assert_eq!( + WeekDay::ALL_VARIANTS, + &[ + WeekDay::Sunday, + WeekDay::Monday, + WeekDay::Tuesday, + WeekDay::Wednesday, + WeekDay::Thursday, + WeekDay::Friday, + WeekDay::Saturday, + ], + ); +} \ No newline at end of file From ef1e13638e8391af7e029b327230c7a5aa343b40 Mon Sep 17 00:00:00 2001 From: Brenno Lemos Date: Fri, 17 Nov 2023 21:49:10 -0300 Subject: [PATCH 3/4] chore: attempt to resolve tests by using relative dependency --- strum_macros/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/strum_macros/Cargo.toml b/strum_macros/Cargo.toml index c1b97622..3c513827 100644 --- a/strum_macros/Cargo.toml +++ b/strum_macros/Cargo.toml @@ -26,4 +26,4 @@ rustversion = "1.0" syn = { version = "2.0", features = ["parsing", "extra-traits"] } [dev-dependencies] -strum = "0.25" +strum = { path = "../strum" } From cb8b753ee0d9d5d8231b6cf392f116cea86f1fac Mon Sep 17 00:00:00 2001 From: Brenno Lemos Date: Fri, 17 Nov 2023 22:38:12 -0300 Subject: [PATCH 4/4] fix(doctest): avoid importing traits twice because of macro derive --- strum_macros/src/lib.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/strum_macros/src/lib.rs b/strum_macros/src/lib.rs index b03d2609..4ec505ce 100644 --- a/strum_macros/src/lib.rs +++ b/strum_macros/src/lib.rs @@ -204,7 +204,6 @@ pub fn variant_names(input: proc_macro::TokenStream) -> proc_macro::TokenStream /// /// ``` /// use strum::StaticVariantsArray; -/// use strum_macros::StaticVariantsArray; /// /// #[derive(StaticVariantsArray, Debug, PartialEq, Eq)] /// enum Op { @@ -722,7 +721,7 @@ pub fn enum_properties(input: proc_macro::TokenStream) -> proc_macro::TokenStrea /// // Bring trait into scope /// use std::str::FromStr; /// use strum::{IntoEnumIterator, EnumMessage}; -/// use strum_macros::{EnumDiscriminants, EnumIter, EnumString, EnumMessage}; +/// use strum_macros::{EnumDiscriminants, EnumIter, EnumString}; /// /// #[derive(Debug)] /// struct NonDefault;