Repository: Peternator7/strum
Pull request #250 (2023-01-07 01:01:18)

Fix camel_case to PascalCase bug
Specifying camel_case for serialize_all will result in a PascalCase. Is this the intended behavior?

This pull request assumes that this is unintended behavior and fixes it.

---

This is not related to this pull request, but I have a point of concern. If you allow a snake_case specification for each variant, I would like to see it supported for all variants. Otherwise, I think it would be a good idea to reject the `camel_case` and `kebab_case` specifications.

https://github.com/Peternator7/strum/pull/250/files#diff-801759161311a5149bd0d6c227fea4237bb6d8a8bb9e84f4703d305e2f131ae1R129

The serde crate rename_all attribute appears to reject it.

https://github.com/serde-rs/serde/blob/ce0844b9ecc32377b5e4545d759d385a8c46bc6a/serde_derive/src/internals/case.rs#L38-L59


Peternator7: Hi @bouzuya, I think this is an awkward case where it's by design. Heck (which provides the casing) refers to the cases as lowerCamelCase and UpperCamelCase so the original names for the case styles followed it's convention. As you noticed, the newer case-styles follow the pattern serde does of using the style itself in the name and that follows the more generally used convention of camelCase and PascalCase.

[The docs](https://docs.rs/strum/latest/strum/additional_attributes/index.html#attributes-on-enums) recommend using the new naming format so I consider the old names to be soft-deprecated, but I don't think it's worth causing a breaking change by removing the old names or what they do.
bouzuya: Thank you for your answer. I understood as follows.

- Some people interpret `camel_case` as `(upper_)camel_case` (=`PascalCase`)
- Names like `camel_case` are soft-deprecated
- The documentation recommends `camelCase`.
- Breaking changes by removing `camel_case` is not good for backward compatibility; removing `camel_case` is not worth it.

Based on your answer, I am going to stop changing the behavior (removing `camel_case`) and leave a comment stating that it is soft-deprecated.

Patch:
diff --git a/.clippy.toml b/.clippy.toml
index c7f0cc0..56ce04e 100644
--- a/.clippy.toml
+++ b/.clippy.toml
@@ -1,1 +1,1 @@
-msrv = "1.32.0"
+msrv = "1.56.1"
diff --git a/.travis.yml b/.travis.yml
index 71eafa8..eb04ad5 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -3,7 +3,7 @@ rust:
   - stable
   - beta
   - nightly
-  - 1.32.0
+  - 1.56.1
 matrix:
   allow_failures:
     - rust: nightly
diff --git a/CHANGELOG.md b/CHANGELOG.md
index c8ce511..de1f1ba 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,62 @@
 # Changelog
 
+## 0.25.1 (strum_macros)
+
+* [#276](https://github.com/Peternator7/strum/pull/276). Fixes [#275](https://github.com/Peternator7/strum/issues/275) and 
+  [#281](https://github.com/Peternator7/strum/issues/281). Not sure exactly why this broke, perhaps incompatibilities between
+  syn 1 and 2. PR fixes the issue by only looking at attributes of the "list" type `[attr(item, item)]`. 
+
+## 0.25.0
+
+### Breaking Changes
+
+* [#261](https://github.com/Peternator7/strum/pull/261) Upgrade syn dependency to version 2. This bumps the msrv to 
+  1.56. It's impractical to maintain a package where a core dependency of the ecosystem has a different msrv than this one.
+
+* [270](https://github.com/Peternator7/strum/pull/270) Change the `to_string` behavior when using `default`. Now, when
+  using `default`, the `display` method will return the display version of the value contained in the enum rather than
+  the name of the variant.
+
+  ```rust
+  #[derive(strum::Display)]
+  enum Color {
+    Red,
+    Blue,
+    Green,
+    #[strum(default)]
+    Other(String)
+  }
+
+  fn main() {
+    // This used to print "Other", now it prints "Purple"
+    assert_eq!(Color::Other("Purple".to_string()).to_string(), "Purple");
+  }
+  ```
+
+  If you want the old behavior, you can use the `to_string` attribute to override this behavior. See the PR for an example.
+
+* [268](https://github.com/Peternator7/strum/pull/268) Update the behavior of `EnumCount` to exclude variants that are
+  `disabled`. This is a breaking change, but the behavior makes it more consistent with other methods.
+
+### New Features
+
+* [#257](https://github.com/Peternator7/strum/pull/257) This PR adds the `EnumIs` macro that automatically implements
+  `is_{variant_name}` methods for each variant. 
+
+  ```rust
+  #[derive(EnumIs)]
+  enum Color {
+      Red,
+      Blue,
+      Green,
+  }
+
+  #[test]
+  fn simple_test() {
+      assert!(Color::Red.is_red());
+  }
+  ```
+
 ## 0.24.3 (strum_macros)
 
 * [#231](https://github.com/Peternator7/strum/pull/231) Add ignore lints for EnumIter not implementing Copy or Debug
diff --git a/README.md b/README.md
index e37b67a..491c24f 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.56.1. 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.
 
@@ -22,11 +22,11 @@ Cargo.toml. Strum_macros contains the macros needed to derive all the traits in
 
 ```toml
 [dependencies]
-strum = "0.24"
-strum_macros = "0.24"
+strum = "0.25"
+strum_macros = "0.25"
 
 # You can also use the "derive" feature, and import the macros directly from "strum"
-# strum = { version = "0.24", features = ["derive"] }
+# strum = { version = "0.25", features = ["derive"] }
 ```
 
 # Strum Macros
@@ -68,14 +68,15 @@ information through strings.
 Strumming is also a very whimsical motion, much like writing Rust code.
 
 [Macro-Renames]: https://github.com/Peternator7/strum/wiki/Macro-Renames
-[EnumString]: https://docs.rs/strum_macros/0.24/strum_macros/derive.EnumString.html
-[Display]: https://docs.rs/strum_macros/0.24/strum_macros/derive.Display.html
-[AsRefStr]: https://docs.rs/strum_macros/0.24/strum_macros/derive.AsRefStr.html
-[IntoStaticStr]: https://docs.rs/strum_macros/0.24/strum_macros/derive.IntoStaticStr.html
-[EnumVariantNames]: https://docs.rs/strum_macros/0.24/strum_macros/derive.EnumVariantNames.html
-[EnumIter]: https://docs.rs/strum_macros/0.24/strum_macros/derive.EnumIter.html
-[EnumProperty]: https://docs.rs/strum_macros/0.24/strum_macros/derive.EnumProperty.html
-[EnumMessage]: https://docs.rs/strum_macros/0.24/strum_macros/derive.EnumMessage.html
-[EnumDiscriminants]: https://docs.rs/strum_macros/0.24/strum_macros/derive.EnumDiscriminants.html
-[EnumCount]: https://docs.rs/strum_macros/0.24/strum_macros/derive.EnumCount.html
-[FromRepr]: https://docs.rs/strum_macros/0.24/strum_macros/derive.FromRepr.html
+[EnumString]: https://docs.rs/strum_macros/0.25/strum_macros/derive.EnumString.html
+[Display]: https://docs.rs/strum_macros/0.25/strum_macros/derive.Display.html
+[AsRefStr]: https://docs.rs/strum_macros/0.25/strum_macros/derive.AsRefStr.html
+[IntoStaticStr]: https://docs.rs/strum_macros/0.25/strum_macros/derive.IntoStaticStr.html
+[EnumVariantNames]: https://docs.rs/strum_macros/0.25/strum_macros/derive.EnumVariantNames.html
+[EnumIter]: https://docs.rs/strum_macros/0.25/strum_macros/derive.EnumIter.html
+[EnumIs]: https://docs.rs/strum_macros/0.25/strum_macros/derive.EnumIs.html
+[EnumProperty]: https://docs.rs/strum_macros/0.25/strum_macros/derive.EnumProperty.html
+[EnumMessage]: https://docs.rs/strum_macros/0.25/strum_macros/derive.EnumMessage.html
+[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
diff --git a/appveyor.yml b/appveyor.yml
index 9bf3377..74a2225 100644
--- a/appveyor.yml
+++ b/appveyor.yml
@@ -38,10 +38,10 @@ environment:
 ### MSVC Toolchains ###
 
   # MSRC 64-bit MSVC
-    - channel: 1.32.0
+    - channel: 1.56.1
       target: x86_64-pc-windows-msvc
   # MSRC 32-bit MSVC
-    - channel: 1.32.0
+    - channel: 1.56.1
       target: i686-pc-windows-msvc
   # Stable 64-bit MSVC
     - channel: stable
@@ -67,10 +67,10 @@ environment:
 ### GNU Toolchains ###
 
   # MSRC 64-bit GNU
-    - channel: 1.32.0
+    - channel: 1.56.1
       target: x86_64-pc-windows-gnu
   # MSRC 32-bit GNU
-    - channel: 1.32.0
+    - channel: 1.56.1
       target: i686-pc-windows-gnu
   # Stable 64-bit GNU
     - channel: stable
@@ -135,7 +135,7 @@ test_script:
 for:
 - matrix:
     only:
-      - channel: 1.32.0
+      - channel: 1.56.1
 
   test_script:
   - cargo test --verbose %cargoflags%
\ No newline at end of file
diff --git a/strum/Cargo.toml b/strum/Cargo.toml
index 4472ca8..5713776 100644
--- a/strum/Cargo.toml
+++ b/strum/Cargo.toml
@@ -1,6 +1,6 @@
 [package]
 name = "strum"
-version = "0.24.1"
+version = "0.25.0"
 edition = "2018"
 authors = ["Peter Glotfelty <peter.glotfelty@microsoft.com>"]
 license = "MIT"
@@ -15,11 +15,11 @@ repository = "https://github.com/Peternator7/strum"
 readme = "../README.md"
 
 [dependencies]
-strum_macros = { path = "../strum_macros", optional = true, version = "0.24" }
+strum_macros = { path = "../strum_macros", optional = true, version = "0.25" }
 phf = { version = "0.10", features = ["macros"], optional = true }
 
 [dev-dependencies]
-strum_macros = { path = "../strum_macros", version = "0.24" }
+strum_macros = { path = "../strum_macros", version = "0.25" }
 
 [badges]
 travis-ci = { repository = "Peternator7/strum" }
diff --git a/strum/src/lib.rs b/strum/src/lib.rs
index a9e8444..7fbe5e2 100644
--- a/strum/src/lib.rs
+++ b/strum/src/lib.rs
@@ -16,11 +16,11 @@
 //!
 //! ```toml
 //! [dependencies]
-//! strum = "0.24"
-//! strum_macros = "0.24"
+//! strum = "0.25"
+//! strum_macros = "0.25"
 //!
 //! # You can also access strum_macros exports directly through strum using the "derive" feature
-//! strum = { version = "0.24", features = ["derive"] }
+//! strum = { version = "0.25", features = ["derive"] }
 //! ```
 //!
 
@@ -101,6 +101,19 @@ pub trait IntoEnumIterator: Sized {
     fn iter() -> Self::Iterator;
 }
 
+pub trait VariantIterator: Sized {
+    type Iterator: Iterator<Item = Self>;
+
+    fn iter() -> Self::Iterator;
+}
+
+pub trait VariantMetadata {
+    const VARIANT_COUNT: usize;
+    const VARIANT_NAMES: &'static [&'static str];
+
+    fn variant_name(&self) -> &'static str;
+}
+
 /// Associates additional pieces of information with an Enum. This can be
 /// autoimplemented by deriving `EnumMessage` and annotating your variants with
 /// `#[strum(message="...")]`.
diff --git a/strum_macros/Cargo.toml b/strum_macros/Cargo.toml
index bdb1c3c..d294cb1 100644
--- a/strum_macros/Cargo.toml
+++ b/strum_macros/Cargo.toml
@@ -1,6 +1,6 @@
 [package]
 name = "strum_macros"
-version = "0.24.3"
+version = "0.25.1"
 edition = "2018"
 authors = ["Peter Glotfelty <peter.glotfelty@microsoft.com>"]
 license = "MIT"
@@ -23,7 +23,7 @@ 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"
+strum = "0.25"
diff --git a/strum_macros/src/helpers/case_style.rs b/strum_macros/src/helpers/case_style.rs
index d714f86..d6b2270 100644
--- a/strum_macros/src/helpers/case_style.rs
+++ b/strum_macros/src/helpers/case_style.rs
@@ -59,12 +59,14 @@ impl FromStr for CaseStyle {
 
     fn from_str(text: &str) -> Result<Self, ()> {
         Ok(match text {
-            "camel_case" | "PascalCase" => CaseStyle::PascalCase,
+            // "camel_case" is a soft-deprecated case-style left for backward compatibility.
+            // <https://github.com/Peternator7/strum/pull/250#issuecomment-1374682221>
+            "PascalCase" | "camel_case" => CaseStyle::PascalCase,
             "camelCase" => CaseStyle::CamelCase,
             "snake_case" | "snek_case" => CaseStyle::SnakeCase,
-            "kebab_case" | "kebab-case" => CaseStyle::KebabCase,
+            "kebab-case" | "kebab_case" => CaseStyle::KebabCase,
             "SCREAMING-KEBAB-CASE" => CaseStyle::ScreamingKebabCase,
-            "shouty_snake_case" | "shouty_snek_case" | "SCREAMING_SNAKE_CASE" => {
+            "SCREAMING_SNAKE_CASE" | "shouty_snake_case" | "shouty_snek_case" => {
                 CaseStyle::ShoutySnakeCase
             }
             "title_case" => CaseStyle::TitleCase,
@@ -113,10 +115,46 @@ impl CaseStyleHelpers for Ident {
     }
 }
 
-#[test]
-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)));
+#[cfg(test)]
+mod tests {
+    use super::*;
+
+    #[test]
+    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)));
+    }
+
+    #[test]
+    fn test_impl_from_str_for_case_style_pascal_case() {
+        use CaseStyle::*;
+        let f = CaseStyle::from_str;
+
+        assert_eq!(PascalCase, f("PascalCase").unwrap());
+        assert_eq!(PascalCase, f("camel_case").unwrap());
+
+        assert_eq!(CamelCase, f("camelCase").unwrap());
+
+        assert_eq!(SnakeCase, f("snake_case").unwrap());
+        assert_eq!(SnakeCase, f("snek_case").unwrap());
+
+        assert_eq!(KebabCase, f("kebab-case").unwrap());
+        assert_eq!(KebabCase, f("kebab_case").unwrap());
+
+        assert_eq!(ScreamingKebabCase, f("SCREAMING-KEBAB-CASE").unwrap());
+
+        assert_eq!(ShoutySnakeCase, f("SCREAMING_SNAKE_CASE").unwrap());
+        assert_eq!(ShoutySnakeCase, f("shouty_snake_case").unwrap());
+        assert_eq!(ShoutySnakeCase, f("shouty_snek_case").unwrap());
+
+        assert_eq!(LowerCase, f("lowercase").unwrap());
+
+        assert_eq!(UpperCase, f("UPPERCASE").unwrap());
+
+        assert_eq!(TitleCase, f("title_case").unwrap());
+
+        assert_eq!(MixedCase, f("mixed_case").unwrap());
+    }
 }
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/enum_iter.rs b/strum_macros/src/macros/enum_iter.rs
index d136d30..ce8237f 100644
--- a/strum_macros/src/macros/enum_iter.rs
+++ b/strum_macros/src/macros/enum_iter.rs
@@ -80,8 +80,8 @@ pub fn enum_iter_inner(ast: &DeriveInput) -> syn::Result<TokenStream> {
             marker: ::core::marker::PhantomData #phantom_data,
         }
 
-        impl #impl_generics core::fmt::Debug for #iter_name #ty_generics #where_clause {
-            fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
+        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
                 // can really show is how many elements are left.
                 f.debug_struct(#iter_name_debug_struct)
diff --git a/strum_macros/src/macros/from_repr.rs b/strum_macros/src/macros/from_repr.rs
index ec9f557..92fd7ad 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,21 @@ 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 = if let Ok(ts) = attr
+            .meta
+            .require_list()
+            .map(|metas| metas.to_token_stream().into_iter())
+        {
+            ts
+        } else {
+            continue;
+        };
+        // 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_nostd_tests/Cargo.toml b/strum_nostd_tests/Cargo.toml
index 1c0ee47..6403638 100644
--- a/strum_nostd_tests/Cargo.toml
+++ b/strum_nostd_tests/Cargo.toml
@@ -1,6 +1,6 @@
 [package]
 name = "strum_nostd_tests"
-version = "0.24.0"
+version = "0.25.0"
 edition = "2018"
 
 # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
diff --git a/strum_tests/Cargo.toml b/strum_tests/Cargo.toml
index 6a179ea..3f28b1c 100644
--- a/strum_tests/Cargo.toml
+++ b/strum_tests/Cargo.toml
@@ -1,6 +1,6 @@
 [package]
 name = "strum_tests"
-version = "0.24.0"
+version = "0.25.0"
 edition = "2018"
 authors = ["Peter Glotfelty <peglotfe@microsoft.com>"]
 
diff --git a/strum_tests/tests/as_ref_no_strum.rs b/strum_tests/tests/as_ref_no_strum.rs
index 82e3adc..e8261f4 100644
--- a/strum_tests/tests/as_ref_no_strum.rs
+++ b/strum_tests/tests/as_ref_no_strum.rs
@@ -1,5 +1,7 @@
 use strum_macros::AsRefStr;
 
+mod core {} // ensure macros call `::core`
+
 #[derive(Debug, Eq, PartialEq, AsRefStr)]
 enum Color {
     #[strum(to_string = "RedRed")]
diff --git a/strum_tests/tests/as_ref_str.rs b/strum_tests/tests/as_ref_str.rs
index f6a4321..d492f17 100644
--- a/strum_tests/tests/as_ref_str.rs
+++ b/strum_tests/tests/as_ref_str.rs
@@ -3,6 +3,8 @@
 use std::str::FromStr;
 use strum::{AsRefStr, AsStaticRef, AsStaticStr, EnumString, IntoStaticStr};
 
+mod core {} // ensure macros call `::core`
+
 #[derive(Debug, Eq, PartialEq, EnumString, AsRefStr, AsStaticStr, IntoStaticStr)]
 enum Color {
     #[strum(to_string = "RedRed")]
diff --git a/strum_tests/tests/display.rs b/strum_tests/tests/display.rs
index 2b20a56..4cec148 100644
--- a/strum_tests/tests/display.rs
+++ b/strum_tests/tests/display.rs
@@ -1,5 +1,7 @@
 use strum::{Display, EnumString};
 
+mod core {} // ensure macros call `::core`
+
 #[derive(Debug, Eq, PartialEq, EnumString, Display)]
 enum Color {
     #[strum(to_string = "RedRed")]
@@ -44,6 +46,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..3e24d63 100644
--- a/strum_tests/tests/enum_count.rs
+++ b/strum_tests/tests/enum_count.rs
@@ -1,5 +1,7 @@
 use strum::{EnumCount, EnumIter, IntoEnumIterator};
 
+mod core {} // ensure macros call `::core`
+
 #[derive(Debug, EnumCount, EnumIter)]
 enum Week {
     Sunday,
@@ -11,12 +13,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_discriminants.rs b/strum_tests/tests/enum_discriminants.rs
index 9985f0e..eb29e6d 100644
--- a/strum_tests/tests/enum_discriminants.rs
+++ b/strum_tests/tests/enum_discriminants.rs
@@ -1,6 +1,9 @@
 use enum_variant_type::EnumVariantType;
 use strum::{Display, EnumDiscriminants, EnumIter, EnumMessage, EnumString, IntoEnumIterator};
 
+
+mod core {} // ensure macros call `::core`
+
 #[allow(dead_code)]
 #[derive(Debug, Eq, PartialEq, EnumDiscriminants)]
 #[strum_discriminants(derive(EnumIter))]
diff --git a/strum_tests/tests/enum_is.rs b/strum_tests/tests/enum_is.rs
new file mode 100644
index 0000000..789881c
--- /dev/null
+++ b/strum_tests/tests/enum_is.rs
@@ -0,0 +1,72 @@
+use strum::EnumIs;
+
+mod core {} // ensure macros call `::core`
+
+#[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/enum_iter.rs b/strum_tests/tests/enum_iter.rs
index 7468267..313ed5c 100644
--- a/strum_tests/tests/enum_iter.rs
+++ b/strum_tests/tests/enum_iter.rs
@@ -1,5 +1,7 @@
 use strum::{EnumIter, IntoEnumIterator};
 
+mod core {} // ensure macros call `::core`
+
 #[derive(Debug, Eq, PartialEq, EnumIter)]
 enum Week {
     Sunday,
diff --git a/strum_tests/tests/enum_message.rs b/strum_tests/tests/enum_message.rs
index a5f7bde..b1c8600 100644
--- a/strum_tests/tests/enum_message.rs
+++ b/strum_tests/tests/enum_message.rs
@@ -1,5 +1,7 @@
 use strum::EnumMessage;
 
+mod core {} // ensure macros call `::core`
+
 #[derive(Debug, Eq, PartialEq, EnumMessage)]
 enum Pets {
     // This comment is not collected since it starts with "//" instead of "///".
diff --git a/strum_tests/tests/enum_props.rs b/strum_tests/tests/enum_props.rs
index 68fbb4d..c32c07e 100644
--- a/strum_tests/tests/enum_props.rs
+++ b/strum_tests/tests/enum_props.rs
@@ -1,5 +1,7 @@
 use strum::EnumProperty;
 
+mod core {} // ensure macros call `::core`
+
 #[derive(Debug, EnumProperty)]
 enum Test {
     #[strum(props(key = "value"))]
diff --git a/strum_tests/tests/enum_variant_names.rs b/strum_tests/tests/enum_variant_names.rs
index f2ca98b..afd8e4d 100644
--- a/strum_tests/tests/enum_variant_names.rs
+++ b/strum_tests/tests/enum_variant_names.rs
@@ -1,6 +1,8 @@
 use structopt::StructOpt;
 use strum::{EnumString, EnumVariantNames, VariantNames};
 
+mod core {} // ensure macros call `::core`
+
 #[test]
 fn simple() {
     #[allow(dead_code)]
diff --git a/strum_tests/tests/from_repr.rs b/strum_tests/tests/from_repr.rs
index bb7263f..4e8f0ec 100644
--- a/strum_tests/tests/from_repr.rs
+++ b/strum_tests/tests/from_repr.rs
@@ -1,6 +1,9 @@
 use strum::FromRepr;
 
+mod core {} // ensure macros call `::core`
+
 #[derive(Debug, FromRepr, PartialEq)]
+/// Day of the week
 #[repr(u8)]
 enum Week {
     Sunday,
diff --git a/strum_tests/tests/from_str.rs b/strum_tests/tests/from_str.rs
index 845768c..fd71194 100644
--- a/strum_tests/tests/from_str.rs
+++ b/strum_tests/tests/from_str.rs
@@ -1,6 +1,8 @@
 use std::str::FromStr;
 use strum::EnumString;
 
+mod core {} // ensure macros call `::core`
+
 #[derive(Debug, Eq, PartialEq, EnumString)]
 enum Color {
     Red,
diff --git a/strum_tests/tests/phf.rs b/strum_tests/tests/phf.rs
index 163418e..da2a854 100644
--- a/strum_tests/tests/phf.rs
+++ b/strum_tests/tests/phf.rs
@@ -1,3 +1,5 @@
+mod core {} // ensure macros call `::core`
+
 #[cfg(feature = "test_phf")]
 #[test]
 fn from_str_with_phf() {
diff --git a/strum_tests/tests/serialize_all.rs b/strum_tests/tests/serialize_all.rs
index b754816..84895d1 100644
--- a/strum_tests/tests/serialize_all.rs
+++ b/strum_tests/tests/serialize_all.rs
@@ -3,6 +3,8 @@ use std::str::FromStr;
 use std::string::ToString;
 use strum::{Display, EnumString, IntoStaticStr};
 
+mod core {} // ensure macros call `::core`
+
 #[derive(Debug, Eq, PartialEq, EnumString, Display, IntoStaticStr)]
 #[strum(serialize_all = "title_case")]
 enum Foo1 {
@@ -32,3 +34,30 @@ fn test_serialize_all_upper_case() {
     assert_eq!(Foo2::DarkBlack, Foo2::from_str("DARKBLACK").unwrap());
     assert_eq!("DARKBLACK", <&'static str>::from(Foo2::DarkBlack));
 }
+
+// This is a soft-deprecated behavior. Use `camelCase` instead.
+#[derive(Debug, Eq, PartialEq, EnumString, Display, IntoStaticStr)]
+#[strum(serialize_all = "camel_case")]
+enum Foo3 {
+    CamelCase,
+}
+
+#[test]
+fn test_serialize_all_written_in_snake_case_camel_case() {
+    assert_eq!("CamelCase", Foo3::CamelCase.to_string());
+    assert_eq!(Foo3::CamelCase, Foo3::from_str("CamelCase").unwrap());
+    assert_eq!("CamelCase", <&'static str>::from(Foo3::CamelCase));
+}
+
+#[derive(Debug, Eq, PartialEq, EnumString, Display, IntoStaticStr)]
+#[strum(serialize_all = "camelCase")]
+enum Foo4 {
+    CamelCase,
+}
+
+#[test]
+fn test_serialize_all_camel_case() {
+    assert_eq!("camelCase", Foo4::CamelCase.to_string());
+    assert_eq!(Foo4::CamelCase, Foo4::from_str("camelCase").unwrap());
+    assert_eq!("camelCase", <&'static str>::from(Foo4::CamelCase));
+}
diff --git a/strum_tests/tests/to_string.rs b/strum_tests/tests/to_string.rs
index 8b59a9e..c3c2973 100644
--- a/strum_tests/tests/to_string.rs
+++ b/strum_tests/tests/to_string.rs
@@ -4,6 +4,8 @@ use std::str::FromStr;
 use std::string::ToString;
 use strum::{EnumString, ToString};
 
+mod core {} // ensure macros call `::core`
+
 #[derive(Debug, Eq, PartialEq, EnumString, ToString)]
 enum Color {
     #[strum(to_string = "RedRed")]
@@ -38,6 +40,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 {