Repository: Peternator7/strum (rust)

Issue:
is_* methods
I wrote a little derive macro that generates `is_*` methods for each enum variant, and I wonder if strum would be a good home for it. If so, I'll open a PR.

Example:
```rust
#[derive(Is)]
enum Foo {
    Bar {},
    Biz
}
```
The `Is` derive will add `is_bar()` and `is_biz()` methods, similar to `Option::is_some()`, `Option::is_none()`, `Result::is_ok()`, etc.

I think that:
```rust
if foo.is_bar() {
    ....
}
```
is a lot cleaner then:
```rust
if matches!(foo, Foo::Bar { .. }) {
    ....
}
```


Thoughts?

Pull request description:
Added enum_is
Resolves #252

Patch:
diff --git a/README.md b/README.md
index e37b67a..484630c 100644
--- a/README.md
+++ b/README.md
@@ -11,7 +11,7 @@ Strum is a set of macros and traits for working with enums and strings easier in
 
 # Compatibility
 
-Strum is currently with versions of rustc >= 1.32.0. Pull Requests that improve compatibility with older
+Strum is currently compatible with versions of rustc >= 1.32.0. Pull Requests that improve compatibility with older
 versions are welcome. The project goal is to support a rust version for at least 2 years after release 
 and even longer is preferred since this project changes slowly.
 
diff --git a/strum/src/additional_attributes.rs b/strum/src/additional_attributes.rs
index 5e32914..a29f1d6 100644
--- a/strum/src/additional_attributes.rs
+++ b/strum/src/additional_attributes.rs
@@ -17,13 +17,12 @@
 //!   - `UPPERCASE`
 //!   - `title_case`
 //!   - `mixed_case`
+//!   - `Train-Case`
 //!
 //!   ```rust
-//!   use std::string::ToString;
-//!   use strum;
 //!   use strum_macros;
 //!   
-//!   #[derive(Debug, Eq, PartialEq, strum_macros::ToString)]
+//!   #[derive(Debug, Eq, PartialEq, strum_macros::Display)]
 //!   #[strum(serialize_all = "snake_case")]
 //!   enum Brightness {
 //!       DarkBlack,
diff --git a/strum_macros/Cargo.toml b/strum_macros/Cargo.toml
index c0c349e..60e5b57 100644
--- a/strum_macros/Cargo.toml
+++ b/strum_macros/Cargo.toml
@@ -19,11 +19,11 @@ proc-macro = true
 name = "strum_macros"
 
 [dependencies]
-heck = "0.4"
+heck = "0.4.1"
 proc-macro2 = "1.0"
 quote = "1.0"
 rustversion = "1.0"
-syn = { version = "1.0", features = ["parsing", "extra-traits"] }
+syn = { version = "2.0", features = ["parsing", "extra-traits"] }
 
 [dev-dependencies]
 strum = "0.24"
diff --git a/strum_macros/src/helpers/case_style.rs b/strum_macros/src/helpers/case_style.rs
index 4253826..d714f86 100644
--- a/strum_macros/src/helpers/case_style.rs
+++ b/strum_macros/src/helpers/case_style.rs
@@ -1,5 +1,5 @@
 use heck::{
-    ToKebabCase, ToLowerCamelCase, ToShoutySnakeCase, ToSnakeCase, ToTitleCase, ToUpperCamelCase,
+    ToKebabCase, ToLowerCamelCase, ToShoutySnakeCase, ToSnakeCase, ToTitleCase, ToUpperCamelCase, ToTrainCase,
 };
 use std::str::FromStr;
 use syn::{
@@ -20,6 +20,7 @@ pub enum CaseStyle {
     LowerCase,
     ScreamingKebabCase,
     PascalCase,
+    TrainCase,
 }
 
 const VALID_CASE_STYLES: &[&str] = &[
@@ -33,6 +34,7 @@ const VALID_CASE_STYLES: &[&str] = &[
     "UPPERCASE",
     "title_case",
     "mixed_case",
+    "Train-Case",
 ];
 
 impl Parse for CaseStyle {
@@ -69,6 +71,7 @@ impl FromStr for CaseStyle {
             "mixed_case" => CaseStyle::MixedCase,
             "lowercase" => CaseStyle::LowerCase,
             "UPPERCASE" => CaseStyle::UpperCase,
+            "Train-Case" => CaseStyle::TrainCase,
             _ => return Err(()),
         })
     }
@@ -92,6 +95,7 @@ impl CaseStyleHelpers for Ident {
                 CaseStyle::UpperCase => ident_string.to_uppercase(),
                 CaseStyle::LowerCase => ident_string.to_lowercase(),
                 CaseStyle::ScreamingKebabCase => ident_string.to_kebab_case().to_uppercase(),
+                CaseStyle::TrainCase => ident_string.to_train_case(),
                 CaseStyle::CamelCase => {
                     let camel_case = ident_string.to_upper_camel_case();
                     let mut pascal = String::with_capacity(camel_case.len());
@@ -114,4 +118,5 @@ fn test_convert_case() {
     let id = Ident::new("test_me", proc_macro2::Span::call_site());
     assert_eq!("testMe", id.convert_case(Some(CaseStyle::CamelCase)));
     assert_eq!("TestMe", id.convert_case(Some(CaseStyle::PascalCase)));
+    assert_eq!("Test-Me", id.convert_case(Some(CaseStyle::TrainCase)));
 }
diff --git a/strum_macros/src/helpers/metadata.rs b/strum_macros/src/helpers/metadata.rs
index 56e4c78..d638ae3 100644
--- a/strum_macros/src/helpers/metadata.rs
+++ b/strum_macros/src/helpers/metadata.rs
@@ -1,12 +1,11 @@
-use proc_macro2::{Span, TokenStream};
+use proc_macro2::TokenStream;
 use syn::{
     parenthesized,
     parse::{Parse, ParseStream},
     parse2, parse_str,
     punctuated::Punctuated,
-    spanned::Spanned,
-    Attribute, DeriveInput, Ident, Lit, LitBool, LitStr, Meta, MetaNameValue, Path, Token, Variant,
-    Visibility,
+    Attribute, DeriveInput, Expr, ExprLit, Ident, Lit, LitBool, LitStr, Meta, MetaNameValue, Path,
+    Token, Variant, Visibility,
 };
 
 use super::case_style::CaseStyle;
@@ -76,17 +75,6 @@ impl Parse for EnumMeta {
     }
 }
 
-impl Spanned for EnumMeta {
-    fn span(&self) -> Span {
-        match self {
-            EnumMeta::SerializeAll { kw, .. } => kw.span(),
-            EnumMeta::AsciiCaseInsensitive(kw) => kw.span(),
-            EnumMeta::Crate { kw, .. } => kw.span(),
-            EnumMeta::UsePhf(use_phf) => use_phf.span(),
-        }
-    }
-}
-
 pub enum EnumDiscriminantsMeta {
     Derive { kw: kw::derive, paths: Vec<Path> },
     Name { kw: kw::name, name: Ident },
@@ -100,7 +88,7 @@ impl Parse for EnumDiscriminantsMeta {
             let kw = input.parse()?;
             let content;
             parenthesized!(content in input);
-            let paths = content.parse_terminated::<_, Token![,]>(Path::parse)?;
+            let paths = content.parse_terminated(Path::parse, Token![,])?;
             Ok(EnumDiscriminantsMeta::Derive {
                 kw,
                 paths: paths.into_iter().collect(),
@@ -127,17 +115,6 @@ impl Parse for EnumDiscriminantsMeta {
     }
 }
 
-impl Spanned for EnumDiscriminantsMeta {
-    fn span(&self) -> Span {
-        match self {
-            EnumDiscriminantsMeta::Derive { kw, .. } => kw.span,
-            EnumDiscriminantsMeta::Name { kw, .. } => kw.span,
-            EnumDiscriminantsMeta::Vis { kw, .. } => kw.span,
-            EnumDiscriminantsMeta::Other { path, .. } => path.span(),
-        }
-    }
-}
-
 pub trait DeriveInputExt {
     /// Get all the strum metadata associated with an enum.
     fn get_metadata(&self) -> syn::Result<Vec<EnumMeta>>;
@@ -228,7 +205,7 @@ impl Parse for VariantMeta {
             let kw = input.parse()?;
             let content;
             parenthesized!(content in input);
-            let props = content.parse_terminated::<_, Token![,]>(Prop::parse)?;
+            let props = content.parse_terminated(Prop::parse, Token![,])?;
             Ok(VariantMeta::Props {
                 kw,
                 props: props
@@ -256,22 +233,6 @@ impl Parse for Prop {
     }
 }
 
-impl Spanned for VariantMeta {
-    fn span(&self) -> Span {
-        match self {
-            VariantMeta::Message { kw, .. } => kw.span,
-            VariantMeta::DetailedMessage { kw, .. } => kw.span,
-            VariantMeta::Documentation { value } => value.span(),
-            VariantMeta::Serialize { kw, .. } => kw.span,
-            VariantMeta::ToString { kw, .. } => kw.span,
-            VariantMeta::Disabled(kw) => kw.span,
-            VariantMeta::Default(kw) => kw.span,
-            VariantMeta::AsciiCaseInsensitive { kw, .. } => kw.span,
-            VariantMeta::Props { kw, .. } => kw.span,
-        }
-    }
-}
-
 pub trait VariantExt {
     /// Get all the metadata associated with an enum variant.
     fn get_metadata(&self) -> syn::Result<Vec<VariantMeta>>;
@@ -282,26 +243,32 @@ impl VariantExt for Variant {
         let result = get_metadata_inner("strum", &self.attrs)?;
         self.attrs
             .iter()
-            .filter(|attr| attr.path.is_ident("doc"))
+            .filter(|attr| attr.path().is_ident("doc"))
             .try_fold(result, |mut vec, attr| {
                 if let Meta::NameValue(MetaNameValue {
-                    lit: Lit::Str(value),
+                    value:
+                        Expr::Lit(ExprLit {
+                            lit: Lit::Str(value),
+                            ..
+                        }),
                     ..
-                }) = attr.parse_meta()?
+                }) = &attr.meta
                 {
-                    vec.push(VariantMeta::Documentation { value })
+                    vec.push(VariantMeta::Documentation {
+                        value: value.clone(),
+                    })
                 }
                 Ok(vec)
             })
     }
 }
 
-fn get_metadata_inner<'a, T: Parse + Spanned>(
+fn get_metadata_inner<'a, T: Parse>(
     ident: &str,
     it: impl IntoIterator<Item = &'a Attribute>,
 ) -> syn::Result<Vec<T>> {
     it.into_iter()
-        .filter(|attr| attr.path.is_ident(ident))
+        .filter(|attr| attr.path().is_ident(ident))
         .try_fold(Vec::new(), |mut vec, attr| {
             vec.extend(attr.parse_args_with(Punctuated::<T, Token![,]>::parse_terminated)?);
             Ok(vec)
diff --git a/strum_macros/src/helpers/variant_props.rs b/strum_macros/src/helpers/variant_props.rs
index a39d3ea..f637253 100644
--- a/strum_macros/src/helpers/variant_props.rs
+++ b/strum_macros/src/helpers/variant_props.rs
@@ -19,7 +19,7 @@ pub struct StrumVariantProperties {
     pub documentation: Vec<LitStr>,
     pub string_props: Vec<(LitStr, LitStr)>,
     serialize: Vec<LitStr>,
-    to_string: Option<LitStr>,
+    pub to_string: Option<LitStr>,
     ident: Option<Ident>,
 }
 
diff --git a/strum_macros/src/lib.rs b/strum_macros/src/lib.rs
index cc1c2a1..a37ccdc 100644
--- a/strum_macros/src/lib.rs
+++ b/strum_macros/src/lib.rs
@@ -384,6 +384,31 @@ pub fn enum_iter(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
     toks.into()
 }
 
+/// Generated `is_*()` methods for each variant.
+/// E.g. `Color.is_red()`.
+///
+/// ```
+///
+/// use strum_macros::EnumIs;
+///
+/// #[derive(EnumIs, Debug)]
+/// enum Color {
+///     Red,
+///     Green { range: usize },
+/// }
+///
+/// assert!(Color::Red.is_red());
+/// assert!(Color::Green{range: 0}.is_green());
+/// ```
+#[proc_macro_derive(EnumIs, attributes(strum))]
+pub fn enum_is(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
+    let ast = syn::parse_macro_input!(input as DeriveInput);
+
+    let toks = macros::enum_is::enum_is_inner(&ast).unwrap_or_else(|err| err.to_compile_error());
+    debug_print_generated(&ast, &toks);
+    toks.into()
+}
+
 /// Add a function to enum that allows accessing variants by its discriminant
 ///
 /// This macro adds a standalone function to obtain an enum variant by its discriminant. The macro adds
diff --git a/strum_macros/src/macros/enum_count.rs b/strum_macros/src/macros/enum_count.rs
index 44c7f2e..b9e6aaa 100644
--- a/strum_macros/src/macros/enum_count.rs
+++ b/strum_macros/src/macros/enum_count.rs
@@ -2,11 +2,18 @@ use proc_macro2::TokenStream;
 use quote::quote;
 use syn::{Data, DeriveInput};
 
+use crate::helpers::variant_props::HasStrumVariantProperties;
 use crate::helpers::{non_enum_error, HasTypeProperties};
 
 pub(crate) fn enum_count_inner(ast: &DeriveInput) -> syn::Result<TokenStream> {
     let n = match &ast.data {
-        Data::Enum(v) => v.variants.len(),
+        Data::Enum(v) => v.variants.iter().try_fold(0usize, |acc, v| {
+            if v.get_variant_properties()?.disabled.is_none() {
+                Ok::<usize, syn::Error>(acc + 1usize)
+            } else {
+                Ok::<usize, syn::Error>(acc)
+            }
+        })?,
         _ => return Err(non_enum_error()),
     };
     let type_properties = ast.get_type_properties()?;
diff --git a/strum_macros/src/macros/enum_discriminants.rs b/strum_macros/src/macros/enum_discriminants.rs
index 66eee46..4e54a30 100644
--- a/strum_macros/src/macros/enum_discriminants.rs
+++ b/strum_macros/src/macros/enum_discriminants.rs
@@ -53,14 +53,16 @@ pub fn enum_discriminants_inner(ast: &DeriveInput) -> syn::Result<TokenStream> {
             .filter(|attr| {
                 ATTRIBUTES_TO_COPY
                     .iter()
-                    .any(|attr_whitelisted| attr.path.is_ident(attr_whitelisted))
+                    .any(|attr_whitelisted| attr.path().is_ident(attr_whitelisted))
             })
             .map(|attr| {
-                if attr.path.is_ident("strum_discriminants") {
-                    let passthrough_group = attr
-                        .tokens
-                        .clone()
-                        .into_iter()
+                if attr.path().is_ident("strum_discriminants") {
+                    let mut ts = attr.meta.require_list()?.to_token_stream().into_iter();
+
+                    // Discard strum_discriminants(...)
+                    let _ = ts.next();
+
+                    let passthrough_group = ts
                         .next()
                         .ok_or_else(|| strum_discriminants_passthrough_error(attr))?;
                     let passthrough_attribute = match passthrough_group {
diff --git a/strum_macros/src/macros/enum_is.rs b/strum_macros/src/macros/enum_is.rs
new file mode 100644
index 0000000..bde3851
--- /dev/null
+++ b/strum_macros/src/macros/enum_is.rs
@@ -0,0 +1,61 @@
+use crate::helpers::{non_enum_error, HasStrumVariantProperties};
+use heck::ToSnakeCase;
+use proc_macro2::TokenStream;
+use quote::{format_ident, quote};
+use syn::{Data, DeriveInput};
+
+pub fn enum_is_inner(ast: &DeriveInput) -> syn::Result<TokenStream> {
+    let variants = match &ast.data {
+        Data::Enum(v) => &v.variants,
+        _ => return Err(non_enum_error()),
+    };
+
+    let enum_name = &ast.ident;
+
+    let variants: Vec<_> = variants
+        .iter()
+        .filter_map(|variant| {
+            if variant.get_variant_properties().ok()?.disabled.is_some() {
+                return None;
+            }
+
+            let variant_name = &variant.ident;
+            let fn_name = format_ident!("is_{}", snakify(&variant_name.to_string()));
+
+            Some(quote! {
+                #[must_use]
+                #[inline]
+                pub const fn #fn_name(&self) -> bool {
+                    match self {
+                        &#enum_name::#variant_name { .. } => true,
+                        _ => false
+                    }
+                }
+            })
+        })
+        .collect();
+
+    Ok(quote! {
+        impl #enum_name {
+            #(#variants)*
+        }
+    }
+    .into())
+}
+
+/// heck doesn't treat numbers as new words, but this function does.
+/// E.g. for input `Hello2You`, heck would output `hello2_you`, and snakify would output `hello_2_you`.
+fn snakify(s: &str) -> String {
+    let mut output: Vec<char> = s.to_string().to_snake_case().chars().collect();
+    let mut num_starts = vec![];
+    for (pos, c) in output.iter().enumerate() {
+        if c.is_digit(10) && pos != 0 && !output[pos - 1].is_digit(10) {
+            num_starts.push(pos);
+        }
+    }
+    // need to do in reverse, because after inserting, all chars after the point of insertion are off
+    for i in num_starts.into_iter().rev() {
+        output.insert(i, '_')
+    }
+    output.into_iter().collect()
+}
diff --git a/strum_macros/src/macros/from_repr.rs b/strum_macros/src/macros/from_repr.rs
index ec9f557..811467e 100644
--- a/strum_macros/src/macros/from_repr.rs
+++ b/strum_macros/src/macros/from_repr.rs
@@ -1,6 +1,6 @@
 use heck::ToShoutySnakeCase;
 use proc_macro2::{Span, TokenStream};
-use quote::{format_ident, quote};
+use quote::{format_ident, quote, ToTokens};
 use syn::{Data, DeriveInput, Fields, PathArguments, Type, TypeParen};
 
 use crate::helpers::{non_enum_error, HasStrumVariantProperties};
@@ -14,8 +14,13 @@ pub fn from_repr_inner(ast: &DeriveInput) -> syn::Result<TokenStream> {
 
     let mut discriminant_type: Type = syn::parse("usize".parse().unwrap()).unwrap();
     for attr in attrs {
-        let path = &attr.path;
-        let tokens = &attr.tokens;
+        let path = attr.path();
+
+        let mut ts = attr.meta.require_list()?.to_token_stream().into_iter();
+        // Discard the path
+        let _ = ts.next();
+        let tokens: TokenStream = ts.collect();
+
         if path.leading_colon.is_some() {
             continue;
         }
diff --git a/strum_macros/src/macros/mod.rs b/strum_macros/src/macros/mod.rs
index b412969..a44be08 100644
--- a/strum_macros/src/macros/mod.rs
+++ b/strum_macros/src/macros/mod.rs
@@ -1,5 +1,6 @@
 pub mod enum_count;
 pub mod enum_discriminants;
+pub mod enum_is;
 pub mod enum_iter;
 pub mod enum_messages;
 pub mod enum_properties;
diff --git a/strum_macros/src/macros/strings/as_ref_str.rs b/strum_macros/src/macros/strings/as_ref_str.rs
index 4dfdc4b..617b887 100644
--- a/strum_macros/src/macros/strings/as_ref_str.rs
+++ b/strum_macros/src/macros/strings/as_ref_str.rs
@@ -80,7 +80,7 @@ pub fn as_static_str_inner(
     let mut generics = ast.generics.clone();
     generics
         .params
-        .push(syn::GenericParam::Lifetime(syn::LifetimeDef::new(
+        .push(syn::GenericParam::Lifetime(syn::LifetimeParam::new(
             parse_quote!('_derivative_strum),
         )));
     let (impl_generics2, _, _) = generics.split_for_impl();
diff --git a/strum_macros/src/macros/strings/display.rs b/strum_macros/src/macros/strings/display.rs
index 82a34b0..fcc5936 100644
--- a/strum_macros/src/macros/strings/display.rs
+++ b/strum_macros/src/macros/strings/display.rs
@@ -32,7 +32,21 @@ pub fn display_inner(ast: &DeriveInput) -> syn::Result<TokenStream> {
             Fields::Named(..) => quote! { {..} },
         };
 
-        arms.push(quote! { #name::#ident #params => f.pad(#output) });
+        if variant_properties.to_string.is_none() && variant_properties.default.is_some() {
+            match &variant.fields {
+                Fields::Unnamed(fields) if fields.unnamed.len() == 1 => {
+                    arms.push(quote! { #name::#ident(ref s) => f.pad(s) });
+                }
+                _ => {
+                    return Err(syn::Error::new_spanned(
+                        variant,
+                        "Default only works on newtype structs with a single String field",
+                    ))
+                }
+            }
+        } else {
+            arms.push(quote! { #name::#ident #params => f.pad(#output) });
+        }
     }
 
     if arms.len() < variants.len() {
diff --git a/strum_macros/src/macros/strings/to_string.rs b/strum_macros/src/macros/strings/to_string.rs
index 7e4c483..9a1e661 100644
--- a/strum_macros/src/macros/strings/to_string.rs
+++ b/strum_macros/src/macros/strings/to_string.rs
@@ -22,6 +22,22 @@ pub fn to_string_inner(ast: &DeriveInput) -> syn::Result<TokenStream> {
             continue;
         }
 
+        // display variants like Green("lime") as "lime"
+        if variant_properties.to_string.is_none() && variant_properties.default.is_some() {
+            match &variant.fields {
+                Fields::Unnamed(fields) if fields.unnamed.len() == 1 => {
+                    arms.push(quote! { #name::#ident(ref s) => ::std::string::String::from(s) });
+                    continue;
+                }
+                _ => {
+                    return Err(syn::Error::new_spanned(
+                        variant,
+                        "Default only works on newtype structs with a single String field",
+                    ))
+                }
+            }
+        }
+
         // Look at all the serialize attributes.
         let output = variant_properties.get_preferred_name(type_properties.case_style);
 


Test patch:
diff --git a/strum_tests/tests/display.rs b/strum_tests/tests/display.rs
index 2b20a56..2e1b996 100644
--- a/strum_tests/tests/display.rs
+++ b/strum_tests/tests/display.rs
@@ -44,6 +44,28 @@ fn to_red_string() {
     assert_eq!(String::from("RedRed"), format!("{}", Color::Red));
 }
 
+#[test]
+fn to_green_string() {
+    assert_eq!(
+        String::from("lime"),
+        format!("{}", Color::Green("lime".into()))
+    );
+}
+
+#[derive(Debug, Eq, PartialEq, EnumString, Display)]
+enum ColorWithDefaultAndToString {
+    #[strum(default, to_string = "GreenGreen")]
+    Green(String),
+}
+
+#[test]
+fn to_green_with_default_and_to_string() {
+    assert_eq!(
+        String::from("GreenGreen"),
+        format!("{}", ColorWithDefaultAndToString::Green("lime".into()))
+    );
+}
+
 #[derive(Display, Debug, Eq, PartialEq)]
 #[strum(serialize_all = "snake_case")]
 enum Brightness {
diff --git a/strum_tests/tests/enum_count.rs b/strum_tests/tests/enum_count.rs
index 794e563..db98110 100644
--- a/strum_tests/tests/enum_count.rs
+++ b/strum_tests/tests/enum_count.rs
@@ -11,12 +11,29 @@ enum Week {
     Saturday,
 }
 
+#[allow(dead_code)]
+#[derive(Debug, EnumCount, EnumIter)]
+enum Pets {
+    Dog,
+    Cat,
+    Fish,
+    Bird,
+    #[strum(disabled)]
+    Hamster,
+}
+
 #[test]
 fn simple_test() {
     assert_eq!(7, Week::COUNT);
     assert_eq!(Week::iter().count(), Week::COUNT);
 }
 
+#[test]
+fn disabled_test() {
+    assert_eq!(4, Pets::COUNT);
+    assert_eq!(Pets::iter().count(), Pets::COUNT);
+}
+
 #[test]
 fn crate_module_path_test() {
     pub mod nested {
diff --git a/strum_tests/tests/enum_is.rs b/strum_tests/tests/enum_is.rs
new file mode 100644
index 0000000..186cb23
--- /dev/null
+++ b/strum_tests/tests/enum_is.rs
@@ -0,0 +1,70 @@
+use strum::EnumIs;
+
+#[derive(EnumIs)]
+enum Foo {
+    Unit,
+    Named0 {},
+    Named1 { _a: char },
+    Named2 { _a: u32, _b: String },
+    Unnamed0(),
+    Unnamed1(Option<u128>),
+    Unnamed2(bool, u8),
+    MultiWordName,
+    #[strum(disabled)]
+    #[allow(dead_code)]
+    Disabled,
+}
+
+#[test]
+fn simple_test() {
+    assert!(Foo::Unit.is_unit());
+}
+
+#[test]
+fn named_0() {
+    assert!(Foo::Named0 {}.is_named_0());
+}
+
+#[test]
+fn named_1() {
+    let foo = Foo::Named1 {
+        _a: Default::default(),
+    };
+    assert!(foo.is_named_1());
+}
+
+#[test]
+fn named_2() {
+    let foo = Foo::Named2 {
+        _a: Default::default(),
+        _b: Default::default(),
+    };
+    assert!(foo.is_named_2());
+}
+
+#[test]
+fn unnamed_0() {
+    assert!(Foo::Unnamed0().is_unnamed_0());
+}
+
+#[test]
+fn unnamed_1() {
+    let foo = Foo::Unnamed1(Default::default());
+    assert!(foo.is_unnamed_1());
+}
+
+#[test]
+fn unnamed_2() {
+    let foo = Foo::Unnamed2(Default::default(), Default::default());
+    assert!(foo.is_unnamed_2());
+}
+
+#[test]
+fn multi_word() {
+    assert!(Foo::MultiWordName.is_multi_word_name());
+}
+
+#[test]
+fn doesnt_match_other_variations() {
+    assert!(!Foo::Unit.is_multi_word_name());
+}
diff --git a/strum_tests/tests/to_string.rs b/strum_tests/tests/to_string.rs
index 8b59a9e..91538fc 100644
--- a/strum_tests/tests/to_string.rs
+++ b/strum_tests/tests/to_string.rs
@@ -38,6 +38,36 @@ fn to_red_string() {
     );
 }
 
+#[test]
+fn to_green_string_with_default() {
+    assert_eq!(
+        String::from("lime"),
+        (Color::Green("lime".into())).to_string()
+    );
+    assert_eq!(
+        Color::Green("lime".into()),
+        Color::from_str("lime").unwrap()
+    );
+}
+
+#[derive(Debug, Eq, PartialEq, EnumString, ToString)]
+enum ColorWithDefaultAndToString {
+    #[strum(default, to_string = "GreenGreen")]
+    Green(String),
+}
+
+#[test]
+fn to_green_with_default_and_to_string() {
+    assert_eq!(
+        String::from("GreenGreen"),
+        (ColorWithDefaultAndToString::Green("lime".into())).to_string()
+    );
+    assert_eq!(
+        ColorWithDefaultAndToString::Green("lime".into()),
+        ColorWithDefaultAndToString::from_str("lime").unwrap()
+    );
+}
+
 #[derive(Debug, Eq, PartialEq, ToString)]
 #[strum(serialize_all = "snake_case")]
 enum Brightness {