Repository: Peternator7/strum (rust)

Issue:
Add the `suffix` attribute
This is similar to #296 , but instead of prefixing str infront, we add it behind:

```rs
use strum_macros::AsRefStr;

#[derive(AsRefStr)]
#[strum(prefix = "path/to/file/", suffix = ".asset")]
enum AssetTypes {
    Filename,
}

fn main() {
    println!("{}", AssetTypes::Filename.as_ref());  // prints "path/to/file/Filename.asset"
}
```

This could be useful for using `enum` types for specifying file paths, among many other things.

Pull request description:
Implement a `suffix` attribute for serialization of enum variants 
This PR implements a `suffix` attribute for serializing enums via `Display` and `AsRefStr`. Much of the logic was borrowed (lifted) from the implementation of the `prefix` attribute since the implementation mechanism is fundamentally similar.

This PR also adds corresponding tests and documentation of the new feature, in addition to further tests of the `prefix` attribute to clarify behavior.

fixes #392 

Patch:
diff --git a/README.md b/README.md
index 6fef752..4a92cec 100644
--- a/README.md
+++ b/README.md
@@ -47,7 +47,6 @@ Strum has implemented the following macros:
 | [EnumCount] | Add a constant `usize` equal to the number of variants. |
 | [VariantArray] | Adds an associated `VARIANTS` constant which is an array of all enum discriminants |
 | [VariantNames] | Adds an associated `VARIANTS` constant which is an array of discriminant names |
-| [EnumTable] | *Experimental*, creates a new type that stores an item of a specified type for each variant of the enum. |
 
 # Contributing
 
@@ -85,4 +84,3 @@ Strumming is also a very whimsical motion, much like writing Rust code.
 [FromRepr]: https://docs.rs/strum_macros/latest/strum_macros/derive.FromRepr.html
 [VariantArray]: https://docs.rs/strum_macros/latest/strum_macros/derive.VariantArray.html
 [VariantNames]: https://docs.rs/strum_macros/latest/strum_macros/derive.VariantNames.html
-[EnumTable]: https://docs.rs/strum_macros/latest/strum_macros/derive.EnumTable.html
diff --git a/strum/Cargo.toml b/strum/Cargo.toml
index 2fe217c..54a2db5 100644
--- a/strum/Cargo.toml
+++ b/strum/Cargo.toml
@@ -17,7 +17,7 @@ rust-version = "1.66.1"
 
 [dependencies]
 strum_macros = { path = "../strum_macros", optional = true, version = "0.27.1" }
-phf = { version = "0.11", features = ["macros"], optional = true }
+phf = { version = "0.12", features = ["macros"], optional = true }
 
 [dev-dependencies]
 strum_macros = { path = "../strum_macros" }
diff --git a/strum_macros/Cargo.toml b/strum_macros/Cargo.toml
index 06f73ab..2a6a64b 100644
--- a/strum_macros/Cargo.toml
+++ b/strum_macros/Cargo.toml
@@ -23,7 +23,6 @@ name = "strum_macros"
 heck = "0.5.0"
 proc-macro2 = "1.0"
 quote = "1.0"
-rustversion = "1.0"
 syn = { version = "2.0", features = ["parsing"] }
 
 [dev-dependencies]
diff --git a/strum_macros/src/helpers/metadata.rs b/strum_macros/src/helpers/metadata.rs
index b572b31..056a4d8 100644
--- a/strum_macros/src/helpers/metadata.rs
+++ b/strum_macros/src/helpers/metadata.rs
@@ -19,6 +19,7 @@ pub mod kw {
     custom_keyword!(const_into_str);
     custom_keyword!(use_phf);
     custom_keyword!(prefix);
+    custom_keyword!(suffix);
     custom_keyword!(parse_err_ty);
     custom_keyword!(parse_err_fn);
 
@@ -56,6 +57,10 @@ pub enum EnumMeta {
         kw: kw::prefix,
         prefix: LitStr,
     },
+    Suffix {
+        kw: kw::suffix,
+        suffix: LitStr,
+    },
     ParseErrTy {
         kw: kw::parse_err_ty,
         path: Path,
@@ -94,6 +99,11 @@ impl Parse for EnumMeta {
             input.parse::<Token![=]>()?;
             let prefix = input.parse()?;
             Ok(EnumMeta::Prefix { kw, prefix })
+        } else if lookahead.peek(kw::suffix) {
+            let kw = input.parse::<kw::suffix>()?;
+            input.parse::<Token![=]>()?;
+            let suffix = input.parse()?;
+            Ok(EnumMeta::Suffix { kw, suffix })
         } else if lookahead.peek(kw::parse_err_ty) {
             let kw = input.parse::<kw::parse_err_ty>()?;
             input.parse::<Token![=]>()?;
diff --git a/strum_macros/src/helpers/type_props.rs b/strum_macros/src/helpers/type_props.rs
index fec6cc8..3bbc7f7 100644
--- a/strum_macros/src/helpers/type_props.rs
+++ b/strum_macros/src/helpers/type_props.rs
@@ -24,6 +24,7 @@ pub struct StrumTypeProperties {
     pub discriminant_vis: Option<Visibility>,
     pub use_phf: bool,
     pub prefix: Option<LitStr>,
+    pub suffix: Option<LitStr>,
     pub enum_repr: Option<TokenStream>,
     pub const_into_str: bool,
     pub discriminant_docs: Vec<LitStr>,
@@ -43,6 +44,7 @@ impl HasTypeProperties for DeriveInput {
         let mut use_phf_kw = None;
         let mut crate_module_path_kw = None;
         let mut prefix_kw = None;
+        let mut suffix_kw = None;
         let mut const_into_str = None;
 
         for meta in strum_meta {
@@ -90,6 +92,14 @@ impl HasTypeProperties for DeriveInput {
                     prefix_kw = Some(kw);
                     output.prefix = Some(prefix);
                 }
+                EnumMeta::Suffix { suffix, kw } => {
+                    if let Some(fst_kw) = suffix_kw {
+                        return Err(occurrence_error(fst_kw, kw, "suffix"));
+                    }
+
+                    suffix_kw = Some(kw);
+                    output.suffix = Some(suffix);
+                }
                 EnumMeta::ParseErrTy { path, kw } => {
                     if let Some(fst_kw) = parse_err_ty_kw {
                         return Err(occurrence_error(fst_kw, kw, "parse_err_ty"));
diff --git a/strum_macros/src/helpers/variant_props.rs b/strum_macros/src/helpers/variant_props.rs
index a9ed43d..f8f4f57 100644
--- a/strum_macros/src/helpers/variant_props.rs
+++ b/strum_macros/src/helpers/variant_props.rs
@@ -35,6 +35,7 @@ impl StrumVariantProperties {
         &self,
         case_style: Option<CaseStyle>,
         prefix: Option<&LitStr>,
+        suffix: Option<&LitStr>,
     ) -> LitStr {
         let mut output = self.to_string.as_ref().cloned().unwrap_or_else(|| {
             self.serialize
@@ -48,6 +49,10 @@ impl StrumVariantProperties {
             output = LitStr::new(&(prefix.value() + &output.value()), output.span());
         }
 
+        if let Some(suffix) = suffix {
+            output = LitStr::new(&(output.value() + &suffix.value()), output.span());
+        }
+
         output
     }
 
diff --git a/strum_macros/src/lib.rs b/strum_macros/src/lib.rs
index a310a53..7b6aaf5 100644
--- a/strum_macros/src/lib.rs
+++ b/strum_macros/src/lib.rs
@@ -180,6 +180,18 @@ pub fn from_string(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
 ///
 /// assert_eq!("/redred", ColorWithPrefix::Red.as_ref());
 /// assert_eq!("/Green", ColorWithPrefix::Green.as_ref());
+///
+/// // With suffix on all variants
+/// #[derive(AsRefStr, Debug)]
+/// #[strum(suffix = ".rs")]
+/// enum ColorWithSuffix {
+///     #[strum(serialize = "redred")]
+///     Red,
+///     Green,
+/// }
+///
+/// assert_eq!("redred.rs", ColorWithSuffix::Red.as_ref());
+/// assert_eq!("Green.rs", ColorWithSuffix::Green.as_ref());
 /// ```
 #[proc_macro_derive(AsRefStr, attributes(strum))]
 pub fn as_ref_str(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
@@ -381,7 +393,9 @@ pub fn to_string(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
 /// 3. The name of the variant will be used if there are no `serialize` or `to_string` attributes.
 /// 4. If the enum has a `strum(prefix = "some_value_")`, every variant will have that prefix prepended
 ///    to the serialization.
-/// 5. Enums with fields support string interpolation.
+/// 5. If the enum has a `strum(suffix = "_another_value")`, every variant will have that suffix appended
+///    to the serialization.
+/// 6. Enums with fields support string interpolation.
 ///    Note this means the variant will not "round trip" if you then deserialize the string.
 ///
 ///    ```rust
@@ -658,15 +672,10 @@ pub fn enum_table(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
 ///     Three = 3,
 /// }
 ///
-/// # #[rustversion::since(1.46)]
 /// const fn number_from_repr(d: u8) -> Option<Number> {
 ///     Number::from_repr(d)
 /// }
 ///
-/// # #[rustversion::before(1.46)]
-/// # fn number_from_repr(d: u8) -> Option<Number> {
-/// #     Number::from_repr(d)
-/// # }
 /// assert_eq!(None, number_from_repr(0));
 /// assert_eq!(Some(Number::One), number_from_repr(1));
 /// assert_eq!(None, number_from_repr(2));
diff --git a/strum_macros/src/macros/enum_count.rs b/strum_macros/src/macros/enum_count.rs
index b9e6aaa..2284495 100644
--- a/strum_macros/src/macros/enum_count.rs
+++ b/strum_macros/src/macros/enum_count.rs
@@ -27,6 +27,7 @@ pub(crate) fn enum_count_inner(ast: &DeriveInput) -> syn::Result<TokenStream> {
 
     Ok(quote! {
         // Implementation
+        #[automatically_derived]
         impl #impl_generics #strum_module_path::EnumCount for #name #ty_generics #where_clause {
             const COUNT: usize = #n;
         }
diff --git a/strum_macros/src/macros/enum_discriminants.rs b/strum_macros/src/macros/enum_discriminants.rs
index 2f840bb..bf39994 100644
--- a/strum_macros/src/macros/enum_discriminants.rs
+++ b/strum_macros/src/macros/enum_discriminants.rs
@@ -136,10 +136,16 @@ pub fn enum_discriminants_inner(ast: &DeriveInput) -> syn::Result<TokenStream> {
         })
         .collect::<Vec<_>>();
 
-    let from_fn_body = quote! { match val { #(#arms),* } };
+    let from_fn_body = if variants.is_empty() {
+        //this method on empty enum is impossible to be called. it is therefor left empty
+        quote! { unreachable!()}
+    } else {
+        quote! { match val { #(#arms),* } }
+    };
 
     let (impl_generics, ty_generics, where_clause) = ast.generics.split_for_impl();
     let impl_from = quote! {
+        #[automatically_derived]
         impl #impl_generics ::core::convert::From< #name #ty_generics > for #discriminants_name #where_clause {
             #[inline]
             fn from(val: #name #ty_generics) -> #discriminants_name {
@@ -158,6 +164,7 @@ pub fn enum_discriminants_inner(ast: &DeriveInput) -> syn::Result<TokenStream> {
         let (impl_generics, _, _) = generics.split_for_impl();
 
         quote! {
+            #[automatically_derived]
             impl #impl_generics ::core::convert::From< #enum_life #name #ty_generics > for #discriminants_name #where_clause {
                 #[inline]
                 fn from(val: #enum_life #name #ty_generics) -> #discriminants_name {
@@ -171,6 +178,7 @@ pub fn enum_discriminants_inner(ast: &DeriveInput) -> syn::Result<TokenStream> {
     let impl_into_discriminant = match type_properties.discriminant_vis {
         // If the visibilty is unspecified or `pub` then we implement IntoDiscriminant
         None | Some(syn::Visibility::Public(..)) => quote! {
+            #[automatically_derived]
             impl #impl_generics #strum_module_path::IntoDiscriminant for #name #ty_generics #where_clause {
                 type Discriminant = #discriminants_name;
 
diff --git a/strum_macros/src/macros/enum_is.rs b/strum_macros/src/macros/enum_is.rs
index c239628..3cff70b 100644
--- a/strum_macros/src/macros/enum_is.rs
+++ b/strum_macros/src/macros/enum_is.rs
@@ -39,6 +39,7 @@ pub fn enum_is_inner(ast: &DeriveInput) -> syn::Result<TokenStream> {
         .collect();
 
     Ok(quote! {
+        #[automatically_derived]
         impl #impl_generics #enum_name  #ty_generics #where_clause {
             #(#variants)*
         }
diff --git a/strum_macros/src/macros/enum_iter.rs b/strum_macros/src/macros/enum_iter.rs
index 6145bf9..4142024 100644
--- a/strum_macros/src/macros/enum_iter.rs
+++ b/strum_macros/src/macros/enum_iter.rs
@@ -80,6 +80,7 @@ pub fn enum_iter_inner(ast: &DeriveInput) -> syn::Result<TokenStream> {
             marker: ::core::marker::PhantomData #phantom_data,
         }
 
+        #[automatically_derived]
         impl #impl_generics ::core::fmt::Debug for #iter_name #ty_generics #where_clause {
             fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
                 // We don't know if the variants implement debug themselves so the only thing we
@@ -90,6 +91,7 @@ pub fn enum_iter_inner(ast: &DeriveInput) -> syn::Result<TokenStream> {
             }
         }
 
+        #[automatically_derived]
         impl #impl_generics #iter_name #ty_generics #where_clause {
             fn get(&self, idx: usize) -> ::core::option::Option<#name #ty_generics> {
                 match idx {
@@ -98,6 +100,7 @@ pub fn enum_iter_inner(ast: &DeriveInput) -> syn::Result<TokenStream> {
             }
         }
 
+        #[automatically_derived]
         impl #impl_generics #strum_module_path::IntoEnumIterator for #name #ty_generics #where_clause {
             type Iterator = #iter_name #ty_generics;
 
@@ -111,6 +114,7 @@ pub fn enum_iter_inner(ast: &DeriveInput) -> syn::Result<TokenStream> {
             }
         }
 
+        #[automatically_derived]
         impl #impl_generics Iterator for #iter_name #ty_generics #where_clause {
             type Item = #name #ty_generics;
 
@@ -141,6 +145,7 @@ pub fn enum_iter_inner(ast: &DeriveInput) -> syn::Result<TokenStream> {
             }
         }
 
+        #[automatically_derived]
         impl #impl_generics ExactSizeIterator for #iter_name #ty_generics #where_clause {
             #[inline]
             fn len(&self) -> usize {
@@ -148,6 +153,7 @@ pub fn enum_iter_inner(ast: &DeriveInput) -> syn::Result<TokenStream> {
             }
         }
 
+        #[automatically_derived]
         impl #impl_generics DoubleEndedIterator for #iter_name #ty_generics #where_clause {
             #[inline]
             fn next_back(&mut self) -> ::core::option::Option<<Self as Iterator>::Item> {
@@ -166,8 +172,10 @@ pub fn enum_iter_inner(ast: &DeriveInput) -> syn::Result<TokenStream> {
             }
         }
 
+        #[automatically_derived]
         impl #impl_generics ::core::iter::FusedIterator for #iter_name #ty_generics #where_clause { }
 
+        #[automatically_derived]
         impl #impl_generics Clone for #iter_name #ty_generics #where_clause {
             #[inline]
             fn clone(&self) -> #iter_name #ty_generics {
diff --git a/strum_macros/src/macros/enum_messages.rs b/strum_macros/src/macros/enum_messages.rs
index 2bc458d..6788e4c 100644
--- a/strum_macros/src/macros/enum_messages.rs
+++ b/strum_macros/src/macros/enum_messages.rs
@@ -112,6 +112,7 @@ pub fn enum_message_inner(ast: &DeriveInput) -> syn::Result<TokenStream> {
     }
 
     Ok(quote! {
+        #[automatically_derived]
         impl #impl_generics #strum_module_path::EnumMessage for #name #ty_generics #where_clause {
             #[inline]
             fn get_message(&self) -> ::core::option::Option<&'static str> {
diff --git a/strum_macros/src/macros/enum_properties.rs b/strum_macros/src/macros/enum_properties.rs
index 4ba9af3..475746a 100644
--- a/strum_macros/src/macros/enum_properties.rs
+++ b/strum_macros/src/macros/enum_properties.rs
@@ -87,6 +87,7 @@ pub fn enum_properties_inner(ast: &DeriveInput) -> syn::Result<TokenStream> {
     );
 
     Ok(quote! {
+        #[automatically_derived]
         impl #impl_generics #strum_module_path::EnumProperty for #name #ty_generics #where_clause {
             #[inline]
             fn get_str(&self, prop: &str) -> ::core::option::Option<&'static str> {
diff --git a/strum_macros/src/macros/enum_table.rs b/strum_macros/src/macros/enum_table.rs
index cfcf270..8e61a10 100644
--- a/strum_macros/src/macros/enum_table.rs
+++ b/strum_macros/src/macros/enum_table.rs
@@ -144,7 +144,7 @@ pub fn enum_table_inner(ast: &DeriveInput) -> syn::Result<TokenStream> {
 
             #[doc = #doc_closure]
             #[inline]
-            #vis fn from_closure<F: Fn(#name)->T>(func: F) -> #table_name<T> {
+            #vis fn from_closure<F: FnMut(#name)->T>(mut func: F) -> #table_name<T> {
               #table_name {
                 #(#closure_fields)*
               }
@@ -152,7 +152,7 @@ pub fn enum_table_inner(ast: &DeriveInput) -> syn::Result<TokenStream> {
 
             #[doc = #doc_transform]
             #[inline]
-            #vis fn transform<U, F: Fn(#name, &T)->U>(&self, func: F) -> #table_name<U> {
+            #vis fn transform<U, F: FnMut(#name, &T)->U>(&self, mut func: F) -> #table_name<U> {
               #table_name {
                 #(#transform_fields)*
               }
diff --git a/strum_macros/src/macros/enum_try_as.rs b/strum_macros/src/macros/enum_try_as.rs
index 470d80a..6b69e41 100644
--- a/strum_macros/src/macros/enum_try_as.rs
+++ b/strum_macros/src/macros/enum_try_as.rs
@@ -73,6 +73,7 @@ pub fn enum_try_as_inner(ast: &DeriveInput) -> syn::Result<TokenStream> {
         .collect();
 
     Ok(quote! {
+        #[automatically_derived]
         impl #impl_generics #enum_name #ty_generics #where_clause {
             #(#variants)*
         }
diff --git a/strum_macros/src/macros/enum_variant_names.rs b/strum_macros/src/macros/enum_variant_names.rs
index 8c41cc4..dc15e7e 100644
--- a/strum_macros/src/macros/enum_variant_names.rs
+++ b/strum_macros/src/macros/enum_variant_names.rs
@@ -22,8 +22,11 @@ pub fn enum_variant_names_inner(ast: &DeriveInput) -> syn::Result<TokenStream> {
         .iter()
         .map(|v| {
             let props = v.get_variant_properties()?;
-            Ok(props
-                .get_preferred_name(type_properties.case_style, type_properties.prefix.as_ref()))
+            Ok(props.get_preferred_name(
+                type_properties.case_style,
+                type_properties.prefix.as_ref(),
+                type_properties.suffix.as_ref(),
+            ))
         })
         .collect::<syn::Result<Vec<LitStr>>>()?;
 
diff --git a/strum_macros/src/macros/from_repr.rs b/strum_macros/src/macros/from_repr.rs
index f372b6e..1243e58 100644
--- a/strum_macros/src/macros/from_repr.rs
+++ b/strum_macros/src/macros/from_repr.rs
@@ -97,20 +97,12 @@ pub fn from_repr_inner(ast: &DeriveInput) -> syn::Result<TokenStream> {
     let const_if_possible = if has_additional_data {
         quote! {}
     } else {
-        #[rustversion::before(1.46)]
-        fn filter_by_rust_version(_: TokenStream) -> TokenStream {
-            quote! {}
-        }
-
-        #[rustversion::since(1.46)]
-        fn filter_by_rust_version(s: TokenStream) -> TokenStream {
-            s
-        }
-        filter_by_rust_version(quote! { const })
+        quote! { const }
     };
 
     Ok(quote! {
         #[allow(clippy::use_self)]
+        #[automatically_derived]
         impl #impl_generics #name #ty_generics #where_clause {
             #[doc = "Try to create [Self] from the raw representation"]
             #[inline]
diff --git a/strum_macros/src/macros/strings/as_ref_str.rs b/strum_macros/src/macros/strings/as_ref_str.rs
index 4248171..38eee2b 100644
--- a/strum_macros/src/macros/strings/as_ref_str.rs
+++ b/strum_macros/src/macros/strings/as_ref_str.rs
@@ -40,8 +40,11 @@ where
         // 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 = variant_properties
-            .get_preferred_name(type_properties.case_style, type_properties.prefix.as_ref());
+        let output = variant_properties.get_preferred_name(
+            type_properties.case_style,
+            type_properties.prefix.as_ref(),
+            type_properties.suffix.as_ref(),
+        );
         let params = match variant.fields {
             Fields::Unit => quote! {},
             Fields::Unnamed(..) => quote! { (..) },
@@ -71,6 +74,7 @@ pub fn as_ref_str_inner(ast: &DeriveInput) -> syn::Result<TokenStream> {
     })?;
 
     Ok(quote! {
+        #[automatically_derived]
         impl #impl_generics ::core::convert::AsRef<str> for #name #ty_generics #where_clause {
             #[inline]
             fn as_ref(&self) -> &str {
@@ -110,6 +114,7 @@ pub fn as_static_str_inner(
 
     Ok(match trait_variant {
         GenerateTraitVariant::AsStaticStr => quote! {
+            #[automatically_derived]
             impl #impl_generics #strum_module_path::AsStaticRef<str> for #name #ty_generics #where_clause {
                 #[inline]
                 fn as_static(&self) -> &'static str {
@@ -120,6 +125,7 @@ pub fn as_static_str_inner(
             }
         },
         GenerateTraitVariant::From if !type_properties.const_into_str => quote! {
+            #[automatically_derived]
             impl #impl_generics ::core::convert::From<#name #ty_generics> for &'static str #where_clause {
                 #[inline]
                 fn from(x: #name #ty_generics) -> &'static str {
@@ -128,6 +134,7 @@ pub fn as_static_str_inner(
                     }
                 }
             }
+            #[automatically_derived]
             impl #impl_generics2 ::core::convert::From<&'_derivative_strum #name #ty_generics> for &'static str #where_clause {
                 #[inline]
                 fn from(x: &'_derivative_strum #name #ty_generics) -> &'static str {
@@ -138,6 +145,7 @@ pub fn as_static_str_inner(
             }
         },
         GenerateTraitVariant::From => quote! {
+            #[automatically_derived]
             impl #impl_generics #name #ty_generics #where_clause {
                 pub const fn into_str(&self) -> &'static str {
                     match self {
@@ -145,7 +153,7 @@ pub fn as_static_str_inner(
                     }
                 }
             }
-
+            #[automatically_derived]
             impl #impl_generics ::core::convert::From<#name #ty_generics> for &'static str #where_clause {
                 fn from(x: #name #ty_generics) -> &'static str {
                     match x {
@@ -153,6 +161,7 @@ pub fn as_static_str_inner(
                     }
                 }
             }
+            #[automatically_derived]
             impl #impl_generics2 ::core::convert::From<&'_derivative_strum #name #ty_generics> for &'static str #where_clause {
                 fn from(x: &'_derivative_strum #name #ty_generics) -> &'static str {
                     x.into_str()
diff --git a/strum_macros/src/macros/strings/display.rs b/strum_macros/src/macros/strings/display.rs
index cfce6c8..e4d0b51 100644
--- a/strum_macros/src/macros/strings/display.rs
+++ b/strum_macros/src/macros/strings/display.rs
@@ -36,8 +36,11 @@ pub fn display_inner(ast: &DeriveInput) -> syn::Result<TokenStream> {
         }
 
         // Look at all the serialize attributes.
-        let output = variant_properties
-            .get_preferred_name(type_properties.case_style, type_properties.prefix.as_ref());
+        let output = variant_properties.get_preferred_name(
+            type_properties.case_style,
+            type_properties.prefix.as_ref(),
+            type_properties.suffix.as_ref(),
+        );
 
         let params = match variant.fields {
             Fields::Unit => quote! {},
@@ -160,6 +163,7 @@ pub fn display_inner(ast: &DeriveInput) -> syn::Result<TokenStream> {
     }
 
     Ok(quote! {
+        #[automatically_derived]
         impl #impl_generics ::core::fmt::Display for #name #ty_generics #where_clause {
             fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::result::Result<(), ::core::fmt::Error> {
                 match *self {
diff --git a/strum_macros/src/macros/strings/from_string.rs b/strum_macros/src/macros/strings/from_string.rs
index 505f89f..78492e5 100644
--- a/strum_macros/src/macros/strings/from_string.rs
+++ b/strum_macros/src/macros/strings/from_string.rs
@@ -171,6 +171,7 @@ pub fn from_string_inner(ast: &DeriveInput) -> syn::Result<TokenStream> {
 
     let from_str = quote! {
         #[allow(clippy::use_self)]
+        #[automatically_derived]
         impl #impl_generics ::core::str::FromStr for #name #ty_generics #where_clause {
             type Err = #default_err_ty;
 
@@ -195,18 +196,6 @@ pub fn from_string_inner(ast: &DeriveInput) -> syn::Result<TokenStream> {
     })
 }
 
-#[rustversion::before(1.34)]
-fn try_from_str(
-    _name: &proc_macro2::Ident,
-    _impl_generics: &syn::ImplGenerics,
-    _ty_generics: &syn::TypeGenerics,
-    _where_clause: Option<&syn::WhereClause>,
-    _strum_module_path: &syn::Path,
-) -> TokenStream {
-    Default::default()
-}
-
-#[rustversion::since(1.34)]
 fn try_from_str(
     name: &proc_macro2::Ident,
     impl_generics: &syn::ImplGenerics,
@@ -216,6 +205,7 @@ fn try_from_str(
 ) -> TokenStream {
     quote! {
         #[allow(clippy::use_self)]
+        #[automatically_derived]
         impl #impl_generics ::core::convert::TryFrom<&str> for #name #ty_generics #where_clause {
             type Error = #default_err_ty;
 
diff --git a/strum_macros/src/macros/strings/mod.rs b/strum_macros/src/macros/strings/mod.rs
index 041e63b..9e4e797 100644
--- a/strum_macros/src/macros/strings/mod.rs
+++ b/strum_macros/src/macros/strings/mod.rs
@@ -23,7 +23,7 @@ where
         Fields::Unnamed(f) if f.unnamed.len() == 1 => {
             let ident = &quote! { field0 };
             let ref_kw = match f.unnamed.last().unwrap().ty {
-                syn::Type::Reference(..) => quote! { },
+                syn::Type::Reference(..) => quote! {},
                 _ => quote! { ref },
             };
 
@@ -33,7 +33,7 @@ where
         Fields::Named(f) if f.named.len() == 1 => {
             let field = f.named.last().unwrap();
             let ref_kw = match field.ty {
-                syn::Type::Reference(..) => quote! { },
+                syn::Type::Reference(..) => quote! {},
                 _ => quote! { ref },
             };
 
diff --git a/strum_macros/src/macros/strings/to_string.rs b/strum_macros/src/macros/strings/to_string.rs
index 6d44068..32b1917 100644
--- a/strum_macros/src/macros/strings/to_string.rs
+++ b/strum_macros/src/macros/strings/to_string.rs
@@ -39,8 +39,11 @@ pub fn to_string_inner(ast: &DeriveInput) -> syn::Result<TokenStream> {
         }
 
         // Look at all the serialize attributes.
-        let output = variant_properties
-            .get_preferred_name(type_properties.case_style, type_properties.prefix.as_ref());
+        let output = variant_properties.get_preferred_name(
+            type_properties.case_style,
+            type_properties.prefix.as_ref(),
+            type_properties.suffix.as_ref(),
+        );
 
         let params = match variant.fields {
             Fields::Unit => quote! {},
@@ -57,6 +60,7 @@ pub fn to_string_inner(ast: &DeriveInput) -> syn::Result<TokenStream> {
 
     Ok(quote! {
         #[allow(clippy::use_self)]
+        #[automatically_derived]
         impl #impl_generics ::std::string::ToString for #name #ty_generics #where_clause {
             fn to_string(&self) -> ::std::string::String {
                 match *self {


Test patch:
diff --git a/strum_nostd_tests/Cargo.toml b/strum_nostd_tests/Cargo.toml
index 945c609..f07ec9a 100644
--- a/strum_nostd_tests/Cargo.toml
+++ b/strum_nostd_tests/Cargo.toml
@@ -7,6 +7,3 @@ rust-version = "1.66.1"
 [dependencies]
 strum = { path = "../strum", features = ["derive"] }
 strum_macros = { path = "../strum_macros", features = [] }
-
-[dev-dependencies]
-rustversion = "1.0"
diff --git a/strum_nostd_tests/src/lib.rs b/strum_nostd_tests/src/lib.rs
index 251fe03..4e07e96 100644
--- a/strum_nostd_tests/src/lib.rs
+++ b/strum_nostd_tests/src/lib.rs
@@ -25,13 +25,8 @@ mod tests {
     }
 
     #[test]
-    #[rustversion::since(1.34)]
     fn try_from_str_no_std() {
         use core::convert::TryFrom;
         assert_eq!(Color::Yellow, Color::try_from("yellow").unwrap());
     }
-
-    #[test]
-    #[rustversion::before(1.34)]
-    fn try_from_str_no_std() {}
 }
diff --git a/strum_tests/Cargo.toml b/strum_tests/Cargo.toml
index 1fbd0ab..570817d 100644
--- a/strum_tests/Cargo.toml
+++ b/strum_tests/Cargo.toml
@@ -15,6 +15,3 @@ strum_macros = { path = "../strum_macros", features = [] }
 clap = "=4.2.7"
 enum_variant_type = "=0.2.0"
 structopt = "=0.3.26"
-
-[dev-dependencies]
-rustversion = "1.0"
diff --git a/strum_tests/tests/as_ref_str.rs b/strum_tests/tests/as_ref_str.rs
index 1d07290..d30b4a5 100644
--- a/strum_tests/tests/as_ref_str.rs
+++ b/strum_tests/tests/as_ref_str.rs
@@ -1,7 +1,7 @@
 #![allow(deprecated)]
 
 use std::str::FromStr;
-use strum::{AsRefStr, AsStaticRef, AsStaticStr, EnumString, IntoStaticStr};
+use strum::{AsRefStr, AsStaticRef, AsStaticStr, Display, EnumString, IntoStaticStr};
 
 mod core {} // ensure macros call `::core`
 
@@ -61,7 +61,13 @@ fn as_green_str() {
 #[test]
 fn as_fuchsia_str() {
     assert_eq!("Purple", (Color::Inner(InnerColor::Purple)).as_ref());
-    assert_eq!("Purple", (Color::InnerField { inner: InnerColor::Purple }).as_ref());
+    assert_eq!(
+        "Purple",
+        (Color::InnerField {
+            inner: InnerColor::Purple
+        })
+        .as_ref()
+    );
 }
 
 #[derive(IntoStaticStr)]
@@ -128,7 +134,9 @@ enum Brightness {
     #[strum(transparent)]
     Gray(&'static str),
     #[strum(transparent)]
-    Grey { inner: &'static str }
+    Grey {
+        inner: &'static str,
+    },
 }
 
 #[test]
@@ -145,7 +153,10 @@ fn brightness_serialize_all() {
     assert_eq!("dim", <&'static str>::from(Brightness::Dim { glow: 0 }));
     assert_eq!("Bright", <&'static str>::from(Brightness::BrightWhite));
     assert_eq!("Gray", <&'static str>::from(Brightness::Gray("Gray")));
-    assert_eq!("Grey", <&'static str>::from(Brightness::Grey { inner: "Grey" }));
+    assert_eq!(
+        "Grey",
+        <&'static str>::from(Brightness::Grey { inner: "Grey" })
+    );
 }
 
 #[derive(IntoStaticStr)]
@@ -226,3 +237,38 @@ fn test_const_into_static_str() {
     const BRIGHT_WHITE: &'static str = BrightnessConst::BrightWhite.into_str();
     assert_eq!("Bright", BRIGHT_WHITE);
 }
+
+#[derive(AsRefStr, Display, EnumString)]
+#[strum(prefix = "/etc/data/assets/", suffix = ".json")]
+enum Asset {
+    #[strum(serialize = "cfg", serialize = "configuration", to_string = "config")]
+    Config,
+    #[strum(serialize = "params")]
+    Params,
+    #[strum(default)]
+    Generic(String),
+}
+
+#[test]
+fn test_prefix_and_suffix() {
+    assert_eq!(
+        String::from("/etc/data/assets/config.json"),
+        (Asset::Config).to_string()
+    );
+    assert_eq!("/etc/data/assets/config.json", (Asset::Config).as_ref(),);
+
+    assert_eq!(
+        String::from("/etc/data/assets/params.json"),
+        (Asset::Params).to_string()
+    );
+    assert_eq!("/etc/data/assets/params.json", (Asset::Params).as_ref());
+
+    assert_eq!(
+        String::from("some-file"),
+        (Asset::Generic("some-file".into()).to_string()),
+    );
+    assert_eq!(
+        "/etc/data/assets/Generic.json",
+        (Asset::Generic("()".into()).as_ref()),
+    )
+}
diff --git a/strum_tests/tests/enum_discriminants.rs b/strum_tests/tests/enum_discriminants.rs
index d6e4668..fcbcf17 100644
--- a/strum_tests/tests/enum_discriminants.rs
+++ b/strum_tests/tests/enum_discriminants.rs
@@ -3,6 +3,7 @@ use std::mem::{align_of, size_of};
 use enum_variant_type::EnumVariantType;
 use strum::{
     Display, EnumDiscriminants, EnumIter, EnumMessage, EnumString, FromRepr, IntoEnumIterator,
+    VariantArray,
 };
 
 mod core {} // ensure macros call `::core`
@@ -211,7 +212,6 @@ fn from_ref_test_complex() {
 }
 
 #[allow(dead_code)]
-#[rustversion::since(1.34)]
 #[derive(Debug, Eq, PartialEq, EnumDiscriminants, EnumVariantType)]
 #[strum_discriminants(
     name(VariantFilterAttrDiscs),
@@ -225,8 +225,7 @@ enum VariantFilterAttr {
     BrightWhite(i32),
 }
 
-#[rustversion::attr(since(1.34), test)]
-#[rustversion::since(1.34)]
+#[test]
 fn filter_variant_attributes_pass_through() {
     use std::str::FromStr;
 
@@ -245,7 +244,6 @@ fn filter_variant_attributes_pass_through() {
 }
 
 #[test]
-#[rustversion::since(1.34)]
 fn override_visibility() {
     mod private {
         use super::*;
@@ -265,27 +263,6 @@ fn override_visibility() {
     );
 }
 
-#[test]
-#[rustversion::before(1.34)]
-fn override_visibility() {
-    mod private {
-        use super::*;
-
-        #[allow(dead_code)]
-        #[derive(EnumDiscriminants)]
-        #[strum_discriminants(name(PubDiscriminants), vis(r#pub))]
-        enum PrivateEnum {
-            VariantA(bool),
-            VariantB(bool),
-        }
-    }
-
-    assert_ne!(
-        private::PubDiscriminants::VariantA,
-        private::PubDiscriminants::VariantB,
-    );
-}
-
 #[test]
 fn crate_module_path_test() {
     pub mod nested {
@@ -363,3 +340,16 @@ fn with_explicit_discriminant_value() {
         WithExplicitDicriminantValueDiscriminants::Variant0 as u8
     );
 }
+
+#[allow(dead_code)]
+#[derive(Debug, Eq, PartialEq, EnumIter, EnumDiscriminants)]
+#[strum_discriminants(derive(EnumIter, VariantArray))]
+enum Empty {}
+
+#[test]
+fn empty_test() {
+    let discriminants = Empty::iter().map(|i| i.into()).collect::<Vec<_>>();
+    let expected: Vec<_> = EmptyDiscriminants::VARIANTS.to_vec();
+
+    assert_eq!(expected, discriminants);
+}
diff --git a/strum_tests/tests/from_repr.rs b/strum_tests/tests/from_repr.rs
index 52603ac..4daf3e1 100644
--- a/strum_tests/tests/from_repr.rs
+++ b/strum_tests/tests/from_repr.rs
@@ -25,7 +25,6 @@ fn simple_test() {
     assert_eq!(Week::from_repr(9), None);
 }
 
-#[rustversion::since(1.46)]
 #[test]
 fn const_test() {
     // This is to test that it works in a const fn
diff --git a/strum_tests/tests/from_str.rs b/strum_tests/tests/from_str.rs
index 2f3acde..8ec8222 100644
--- a/strum_tests/tests/from_str.rs
+++ b/strum_tests/tests/from_str.rs
@@ -31,10 +31,9 @@ enum Color {
 #[derive(Debug, Eq, PartialEq, EnumString, strum::Display)]
 enum Color2 {
     #[strum(default)]
-    Purple { inner: String }
+    Purple { inner: String },
 }
 
-#[rustversion::since(1.34)]
 fn assert_from_str<'a, T>(a: T, from: &'a str)
 where
     T: PartialEq + std::str::FromStr + std::convert::TryFrom<&'a str> + std::fmt::Debug,
@@ -45,15 +44,6 @@ where
     assert_eq!(a, std::convert::TryFrom::try_from(from).unwrap());
 }
 
-#[rustversion::before(1.34)]
-fn assert_from_str<T>(a: T, from: &str)
-where
-    T: PartialEq + std::str::FromStr + std::fmt::Debug,
-    <T as std::str::FromStr>::Err: std::fmt::Debug,
-{
-    assert_eq!(a, T::from_str(from).unwrap());
-}
-
 #[test]
 fn color_simple() {
     assert_from_str(Color::Red, "Red");
@@ -82,8 +72,19 @@ fn color_default() {
 
 #[test]
 fn color2_default() {
-    assert_from_str(Color2::Purple { inner: String::from("test") }, "test");
-    assert_eq!(String::from("test"), Color2::Purple { inner: String::from("test") }.to_string());
+    assert_from_str(
+        Color2::Purple {
+            inner: String::from("test"),
+        },
+        "test",
+    );
+    assert_eq!(
+        String::from("test"),
+        Color2::Purple {
+            inner: String::from("test")
+        }
+        .to_string()
+    );
 }
 
 #[test]
@@ -183,7 +184,6 @@ fn case_insensitive_enum_no_case_insensitive() {
     assert!(CaseInsensitiveEnum::from_str("nocaseinsensitive").is_err());
 }
 
-#[rustversion::since(1.34)]
 #[test]
 fn case_insensitive_enum_no_case_insensitive_try_from() {
     assert_from_str(CaseInsensitiveEnum::NoCaseInsensitive, "NoCaseInsensitive");
diff --git a/strum_tests/tests/prefix.rs b/strum_tests/tests/prefix.rs
index ca394dd..2173744 100644
--- a/strum_tests/tests/prefix.rs
+++ b/strum_tests/tests/prefix.rs
@@ -20,3 +20,16 @@ fn prefix_redred() {
     assert_eq!(String::from("colour/RedRed"), (Color::Red).to_string());
     assert_eq!(("colour/RedRed"), (Color::Red).as_ref());
 }
+
+#[test]
+fn prefix_green_default() {
+    assert_eq!(
+        String::from("green"),
+        (Color::Green("green".into())).to_string()
+    );
+
+    assert_eq!(
+        String::from("colour/Green"),
+        (Color::Green("green".into())).as_ref()
+    );
+}
diff --git a/strum_tests/tests/suffix.rs b/strum_tests/tests/suffix.rs
new file mode 100644
index 0000000..acf0113
--- /dev/null
+++ b/strum_tests/tests/suffix.rs
@@ -0,0 +1,44 @@
+use strum::{Display, EnumString};
+use strum_macros::AsRefStr;
+
+#[allow(dead_code)]
+#[derive(Debug, EnumString, Display, AsRefStr)]
+#[strum(suffix = ".color")]
+enum Color {
+    #[strum(to_string = "RedRed")]
+    Red,
+    #[strum(serialize = "b", to_string = "blue")]
+    Blue { hue: usize },
+    #[strum(serialize = "y", serialize = "yellow")]
+    Yellow,
+    #[strum(default)]
+    Green(String),
+}
+
+#[test]
+fn suffix_redred() {
+    let c = Color::Red;
+    assert_eq!(String::from("RedRed.color"), c.to_string());
+    assert_eq!("RedRed.color", c.as_ref());
+}
+
+#[test]
+fn suffix_blue() {
+    let c = Color::Blue { hue: 10 };
+    assert_eq!(String::from("blue.color"), c.to_string());
+    assert_eq!("blue.color", c.as_ref());
+}
+
+#[test]
+fn suffix_yellow() {
+    let c = Color::Yellow;
+    assert_eq!(String::from("yellow.color"), c.to_string());
+    assert_eq!("yellow.color", c.as_ref());
+}
+
+#[test]
+fn suffix_green_default() {
+    let c = Color::Green("basic-green".into());
+    assert_eq!(String::from("basic-green"), c.to_string());
+    assert_eq!("Green.color", c.as_ref());
+}