Repository: Peternator7/strum
Pull request #34 (2018-09-19 05:41:37)

Feature/33/derive enum variants
Implemented #33.
~~there's an error that I can't work out, see commit `429040a`, uncommenting that code causes the compiler to panic (it doesn't find the `strum` attribute, even though it's clearly defined).~~ Fixed it.

This branch was branched off #32, so it's a bit noisy from that.

azriel91: Solved the issue I hit earlier and added docs, phew. Also allow the user to rename the generated enum.

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
index 0000000..c9f539e
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,26 @@
+# Changelog
+
+## Unreleased
+
+### Added
+
+* Serialization case can be controlled using `#[strum(serialize_all = "snake_case")]`. ([#21][#21])
+* `#[derive(EnumDiscriminants)]` generates enum with variants without fields. ([#33][#33])
+
+[#21]: https://github.com/Peternator7/strum/issues/21
+[#33]: https://github.com/Peternator7/strum/issues/33
+
+## 0.10.0
+
+### Added
+
+* Implemented `Clone` for `EnumIter`s. ([#18][#18])
+* Added `AsStaticRef` derive to allow enums to `impl AsStaticRef<str>`. ([#23][#23])
+
+### Fixed
+
+* `#[allow(missing_docs)]` on generated `EnumIter`s. ([#19][#19])
+
+[#18]: https://github.com/Peternator7/strum/pull/18
+[#19]: https://github.com/Peternator7/strum/issues/19
+[#23]: https://github.com/Peternator7/strum/issues/23
diff --git a/README.md b/README.md
index b7b6331..614b810 100644
--- a/README.md
+++ b/README.md
@@ -14,8 +14,8 @@ Cargo.toml. Strum_macros contains the macros needed to derive all the traits in
 
 ```toml
 [dependencies]
-strum = "0.10.0"
-strum_macros = "0.10.0"
+strum = "0.11.0"
+strum_macros = "0.11.0"
 ```
 
 And add these lines to the root of your project, either lib.rs or main.rs.
@@ -91,10 +91,10 @@ Strum has implemented the following macros:
     */
     ```
 
-    Note that the implementation of `FromStr` only matches on the name of the variant.
-    Strum, where possible, avoids operations that have an unknown runtime cost, and parsing strings
-    is potentially an expensive operation. If you do need that behavior, consider the more powerful
-    Serde library for your serialization.
+    Note that the implementation of `FromStr` by default only matches on the name of the
+    variant. There is an option to match on different case conversions through the
+    `#[strum(serialize_all = "snake_case")]` type attribute. See the **Additional Attributes**
+    Section for more information on using this feature.
 
 2. `Display` / `ToString`: prints out the given enum. This enables you to perform round trip
     style conversions from enum into string and back again for unit style variants. `ToString` and 
@@ -296,10 +296,129 @@ Strum has implemented the following macros:
      }
      ```
 
+7. `EnumDiscriminants`: Given an enum named `MyEnum`, generates another enum called
+    `MyEnumDiscriminants` with the same variants, without any data fields. This is useful when you
+    wish to determine the variant of an enum from a String, but the variants contain any
+    non-`Default` fields. By default, the generated enum has the following derives:
+    `Clone, Copy, Debug, PartialEq, Eq`. You can add additional derives using the
+    `#[strum_discriminants(derive(AdditionalDerive))]` attribute.
+
+    Here's an example:
+
+    ```rust
+    extern crate strum;
+    #[macro_use] extern crate strum_macros;
+
+    // Bring trait into scope
+    use std::str::FromStr;
+
+    #[derive(Debug)]
+    struct NonDefault;
+
+    #[allow(dead_code)]
+    #[derive(Debug, EnumDiscriminants)]
+    #[strum_discriminants(derive(EnumString))]
+    enum MyEnum {
+        Variant0(NonDefault),
+        Variant1 { a: NonDefault },
+    }
+
+    fn main() {
+        assert_eq!(
+            MyEnumDiscriminants::Variant0,
+            MyEnumDiscriminants::from_str("Variant0").unwrap()
+        );
+    }
+    ```
+
+    You can also rename the generated enum using the `#[strum_discriminants(name(OtherName))]`
+    attribute:
+
+    ```rust
+    extern crate strum;
+    #[macro_use] extern crate strum_macros;
+    // You need to bring the type into scope to use it!!!
+    use strum::IntoEnumIterator;
+
+    #[allow(dead_code)]
+    #[derive(Debug, EnumDiscriminants)]
+    #[strum_discriminants(derive(EnumIter))]
+    #[strum_discriminants(name(MyVariants))]
+    enum MyEnum {
+        Variant0(bool),
+        Variant1 { a: bool },
+    }
+
+    fn main() {
+        assert_eq!(
+            vec![MyVariants::Variant0, MyVariants::Variant1],
+            MyVariants::iter().collect::<Vec<_>>()
+        );
+    }
+    ```
+
+    The derived enum also has the following trait implementations:
+
+    * `impl From<MyEnum> for MyEnumDiscriminants`
+    * `impl<'_enum> From<&'_enum MyEnum> for MyEnumDiscriminants`
+
+    These allow you to get the *Discriminants* enum variant from the original enum:
+
+    ```rust
+    extern crate strum;
+    #[macro_use] extern crate strum_macros;
+
+    #[derive(Debug, EnumDiscriminants)]
+    #[strum_discriminants(name(MyVariants))]
+    enum MyEnum {
+        Variant0(bool),
+        Variant1 { a: bool },
+    }
+
+    fn main() {
+        assert_eq!(MyVariants::Variant0, MyEnum::Variant0(true).into());
+    }
+    ```
+
 # Additional Attributes
 
-Strum supports several custom attributes to modify the generated code. Custom attributes are
-applied to a variant by adding #[strum(parameter="value")] to the variant.
+Strum supports several custom attributes to modify the generated code. At the enum level, the
+`#[strum(serialize_all = "snake_case")]` attribute can be used to change the case used when
+serializing to and deserializing from strings:
+
+```rust
+extern crate strum;
+#[macro_use]
+extern crate strum_macros;
+
+#[derive(Debug, Eq, PartialEq, ToString)]
+#[strum(serialize_all = "snake_case")]
+enum Brightness {
+    DarkBlack,
+    Dim {
+        glow: usize,
+    },
+    #[strum(serialize = "bright")]
+    BrightWhite,
+}
+
+fn main() {
+    assert_eq!(
+        String::from("dark_black"),
+        Brightness::DarkBlack.to_string().as_ref()
+    );
+    assert_eq!(
+        String::from("dim"),
+        Brightness::Dim { glow: 0 }.to_string().as_ref()
+    );
+    assert_eq!(
+        String::from("bright"),
+        Brightness::BrightWhite.to_string().as_ref()
+    );
+}
+```
+
+Custom attributes are applied to a variant by adding `#[strum(parameter="value")]` to the variant.
 
 - `serialize="..."`: Changes the text that `FromStr()` looks for when parsing a string. This attribute can
    be applied multiple times to an element and the enum variant will be parsed if any of them match.
diff --git a/strum/Cargo.toml b/strum/Cargo.toml
index 99aab30..055929d 100644
--- a/strum/Cargo.toml
+++ b/strum/Cargo.toml
@@ -13,8 +13,7 @@ homepage = "https://github.com/Peternator7/strum"
 readme = "../README.md"
 
 [dev-dependencies]
-strum_macros = "0.10.0"
-# strum_macros = { path = "../strum_macros" }
+strum_macros = { path = "../strum_macros", version = "0.10.0" }
 
 [badges]
 travis-ci = { repository = "Peternator7/strum" }
\ No newline at end of file
diff --git a/strum/src/lib.rs b/strum/src/lib.rs
index 2c1388d..81bcaf6 100644
--- a/strum/src/lib.rs
+++ b/strum/src/lib.rs
@@ -14,8 +14,8 @@
 //!
 //! ```toml
 //! [dependencies]
-//! strum = "0.9.0"
-//! strum_macros = "0.9.0"
+//! strum = "0.11.0"
+//! strum_macros = "0.11.0"
 //! ```
 //!
 //! And add these lines to the root of your project, either lib.rs or main.rs.
@@ -79,10 +79,10 @@
 //!     # fn main() {}
 //!     ```
 //!
-//!     Note that the implementation of `FromStr` only matches on the name of the variant.
-//!     Strum, where possible, avoids operations that have an unknown runtime cost, and parsing strings
-//!     is potentially an expensive operation. If you do need that behavior, consider the more powerful
-//!     Serde library for your serialization.
+//!     Note that the implementation of `FromStr` by default only matches on the name of the
+//!     variant. There is an option to match on different case conversions through the
+//!     `#[strum(serialize_all = "snake_case")]` type attribute. See the **Additional Attributes**
+//!     Section for more information on using this feature.
 //!
 //! 2. `Display`, `ToString`: both derives print out the given enum variant. This enables you to perform round trip
 //!    style conversions from enum into string and back again for unit style variants. `ToString` and `Display`
@@ -124,14 +124,14 @@
 //!     a borrowed `str` instead of a `String` so you can save an allocation.
 //!
 //! 4. `AsStaticStr`: this is similar to `AsRefStr`, but returns a `'static` reference to a string which is helpful
-//!    in some scenarios. This macro implements `strum::AsStaticRef<str>` which adds a method `.as_static()` that 
+//!    in some scenarios. This macro implements `strum::AsStaticRef<str>` which adds a method `.as_static()` that
 //!    returns a `&'static str`.
-//! 
+//!
 //!    ```rust
 //!    # extern crate strum;
 //!    # #[macro_use] extern crate strum_macros;
 //!    use strum::AsStaticRef;
-//! 
+//!
 //!    #[derive(AsStaticStr)]
 //!    enum State<'a> {
 //!        Initial(&'a str),
@@ -143,14 +143,14 @@
 //!        // The following won't work because the lifetime is incorrect so we can use.as_static() instead.
 //!        // let wrong: &'static str = state.as_ref();
 //!        let right: &'static str = state.as_static();
-//!        println!("{}", right); 
+//!        println!("{}", right);
 //!    }
-//! 
+//!
 //!    fn main() {
 //!        print_state(&"hello world".to_string())
 //!    }
 //!    ```
-//! 
+//!
 //! 4. `EnumIter`: iterate over the variants of an Enum. Any additional data on your variants will be
 //!     set to `Default::default()`. The macro implements `strum::IntoEnumIter` on your enum and
 //!     creates a new type called `YourEnumIter` that implements both `Iterator` and `ExactSizeIterator`.
@@ -290,11 +290,128 @@
 //!     }
 //!     ```
 //!
+//! 7. `EnumDiscriminants`: Given an enum named `MyEnum`, generates another enum called
+//!     `MyEnumDiscriminants` with the same variants, without any data fields. This is useful when you
+//!     wish to determine the variant of an enum from a String, but the variants contain any
+//!     non-`Default` fields. By default, the generated enum has the following derives:
+//!     `Clone, Copy, Debug, PartialEq, Eq`. You can add additional derives using the
+//!     `#[strum_discriminants(derive(AdditionalDerive))]` attribute.
+//!
+//!     Here's an example:
+//!
+//!     ```rust
+//!     # extern crate strum;
+//!     # #[macro_use] extern crate strum_macros;
+//!
+//!     // Bring trait into scope
+//!     use std::str::FromStr;
+//!
+//!     #[derive(Debug)]
+//!     struct NonDefault;
+//!
+//!     #[allow(dead_code)]
+//!     #[derive(Debug, EnumDiscriminants)]
+//!     #[strum_discriminants(derive(EnumString))]
+//!     enum MyEnum {
+//!         Variant0(NonDefault),
+//!         Variant1 { a: NonDefault },
+//!     }
+//!
+//!     fn main() {
+//!         assert_eq!(
+//!             MyEnumDiscriminants::Variant0,
+//!             MyEnumDiscriminants::from_str("Variant0").unwrap()
+//!         );
+//!     }
+//!     ```
+//!
+//!     You can also rename the generated enum using the `#[strum_discriminants(name(OtherName))]`
+//!     attribute:
+//!
+//!     ```rust
+//!     # extern crate strum;
+//!     # #[macro_use] extern crate strum_macros;
+//!     // You need to bring the type into scope to use it!!!
+//!     use strum::IntoEnumIterator;
+//!
+//!     #[allow(dead_code)]
+//!     #[derive(Debug, EnumDiscriminants)]
+//!     #[strum_discriminants(name(MyVariants), derive(EnumIter))]
+//!     enum MyEnum {
+//!         Variant0(bool),
+//!         Variant1 { a: bool },
+//!     }
+//!
+//!     fn main() {
+//!         assert_eq!(
+//!             vec![MyVariants::Variant0, MyVariants::Variant1],
+//!             MyVariants::iter().collect::<Vec<_>>()
+//!         );
+//!     }
+//!     ```
+//!
+//!     The derived enum also has the following trait implementations:
+//!
+//!     * `impl From<MyEnum> for MyEnumDiscriminants`
+//!     * `impl<'_enum> From<&'_enum MyEnum> for MyEnumDiscriminants`
+//!
+//!     These allow you to get the *Discriminants* enum variant from the original enum:
+//!
+//!     ```rust
+//!     extern crate strum;
+//!     #[macro_use] extern crate strum_macros;
+//!
+//!     #[derive(Debug, EnumDiscriminants)]
+//!     #[strum_discriminants(name(MyVariants))]
+//!     enum MyEnum {
+//!         Variant0(bool),
+//!         Variant1 { a: bool },
+//!     }
+//!
+//!     fn main() {
+//!         assert_eq!(MyVariants::Variant0, MyEnum::Variant0(true).into());
+//!     }
+//!     ```
 //!
 //! # Additional Attributes
 //!
-//! Strum supports several custom attributes to modify the generated code. Custom attributes are
-//! applied to a variant by adding #[strum(parameter="value")] to the variant.
+//! Strum supports several custom attributes to modify the generated code. At the enum level, the
+//! `#[strum(serialize_all = "snake_case")]` attribute can be used to change the case used when
+//! serializing to and deserializing from strings:
+//!
+//! ```rust
+//! extern crate strum;
+//! #[macro_use]
+//! extern crate strum_macros;
+//!
+//! #[derive(Debug, Eq, PartialEq, ToString)]
+//! #[strum(serialize_all = "snake_case")]
+//! enum Brightness {
+//!     DarkBlack,
+//!     Dim {
+//!         glow: usize,
+//!     },
+//!     #[strum(serialize = "bright")]
+//!     BrightWhite,
+//! }
+//!
+//! fn main() {
+//!     assert_eq!(
+//!         String::from("dark_black"),
+//!         Brightness::DarkBlack.to_string().as_ref()
+//!     );
+//!     assert_eq!(
+//!         String::from("dim"),
+//!         Brightness::Dim { glow: 0 }.to_string().as_ref()
+//!     );
+//!     assert_eq!(
+//!         String::from("bright"),
+//!         Brightness::BrightWhite.to_string().as_ref()
+//!     );
+//! }
+//! ```
+//!
+//! Custom attributes are applied to a variant by adding `#[strum(parameter="value")]` to the variant.
 //!
 //! - `serialize="..."`: Changes the text that `FromStr()` looks for when parsing a string. This attribute can
 //!    be applied multiple times to an element and the enum variant will be parsed if any of them match.
@@ -402,7 +519,7 @@
 
 /// The ParseError enum is a collection of all the possible reasons
 /// an enum can fail to parse from a string.
-#[derive(Debug,Clone,Copy,Eq,PartialEq,Hash)]
+#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
 pub enum ParseError {
     VariantNotFound,
 }
@@ -544,7 +661,6 @@ pub trait EnumProperty {
     }
 }
 
-
 /// A cheap reference-to-reference conversion. Used to convert a value to a
 /// reference value with `'static` lifetime within generic code.
 pub trait AsStaticRef<T>
diff --git a/strum_macros/Cargo.toml b/strum_macros/Cargo.toml
index be868d8..0af14fd 100644
--- a/strum_macros/Cargo.toml
+++ b/strum_macros/Cargo.toml
@@ -17,6 +17,7 @@ proc-macro = true
 name = "strum_macros"
 
 [dependencies]
+heck = "0.3"
 proc-macro2 = "0.4"
 quote = "0.6"
-syn = { version = "0.14", features = ["parsing"] }
+syn = { version = "0.15", features = ["parsing", "extra-traits"] }
diff --git a/strum_macros/src/as_ref_str.rs b/strum_macros/src/as_ref_str.rs
index 09cbc08..e89ece5 100644
--- a/strum_macros/src/as_ref_str.rs
+++ b/strum_macros/src/as_ref_str.rs
@@ -1,7 +1,7 @@
 use proc_macro2::TokenStream;
 use syn;
 
-use helpers::{unique_attr, extract_attrs, extract_meta, is_disabled};
+use helpers::{extract_attrs, extract_meta, is_disabled, unique_attr};
 
 fn get_arms(ast: &syn::DeriveInput) -> Vec<TokenStream> {
     let name = &ast.ident;
diff --git a/strum_macros/src/case_style.rs b/strum_macros/src/case_style.rs
new file mode 100644
index 0000000..17acb17
--- /dev/null
+++ b/strum_macros/src/case_style.rs
@@ -0,0 +1,34 @@
+#[derive(Clone, Copy, Debug, PartialEq, Eq)]
+pub enum CaseStyle {
+    CamelCase,
+    KebabCase,
+    MixedCase,
+    ShoutySnakeCase,
+    SnakeCase,
+    TitleCase,
+}
+
+impl<'s> From<&'s str> for CaseStyle {
+    fn from(text: &'s str) -> CaseStyle {
+        match text {
+            "camel_case" => CaseStyle::CamelCase,
+            "kebab_case" => CaseStyle::KebabCase,
+            "mixed_case" => CaseStyle::MixedCase,
+            "shouty_snake_case" | "shouty_snek_case" => CaseStyle::ShoutySnakeCase,
+            "snake_case" | "snek_case" => CaseStyle::SnakeCase,
+            "title_case" => CaseStyle::TitleCase,
+            _ => panic!(
+                "Unexpected case style for serialize_all: `{}`. Valid values are: `{:?}`",
+                text,
+                [
+                    "camel_case",
+                    "kebab_case",
+                    "mixed_case",
+                    "shouty_snake_case",
+                    "snake_case",
+                    "title_case"
+                ]
+            ),
+        }
+    }
+}
diff --git a/strum_macros/src/display.rs b/strum_macros/src/display.rs
index 340f476..c0223bd 100644
--- a/strum_macros/src/display.rs
+++ b/strum_macros/src/display.rs
@@ -1,7 +1,8 @@
 use proc_macro2::TokenStream;
 use syn;
 
-use helpers::{unique_attr, extract_attrs, extract_meta, is_disabled};
+use case_style::CaseStyle;
+use helpers::{convert_case, extract_attrs, extract_meta, is_disabled, unique_attr};
 
 pub fn display_inner(ast: &syn::DeriveInput) -> TokenStream {
     let name = &ast.ident;
@@ -11,6 +12,10 @@ pub fn display_inner(ast: &syn::DeriveInput) -> TokenStream {
         _ => panic!("Display only works on Enums"),
     };
 
+    let type_meta = extract_meta(&ast.attrs);
+    let case_style = unique_attr(&type_meta, "strum", "serialize_all")
+        .map(|style| CaseStyle::from(style.as_ref()));
+
     let mut arms = Vec::new();
     for variant in variants {
         use syn::Fields::*;
@@ -31,7 +36,7 @@ pub fn display_inner(ast: &syn::DeriveInput) -> TokenStream {
             if let Some(n) = attrs.pop() {
                 n
             } else {
-                ident.to_string()
+                convert_case(ident, case_style)
             }
         };
 
diff --git a/strum_macros/src/enum_discriminants.rs b/strum_macros/src/enum_discriminants.rs
new file mode 100644
index 0000000..2108f67
--- /dev/null
+++ b/strum_macros/src/enum_discriminants.rs
@@ -0,0 +1,143 @@
+use proc_macro2::{Span, TokenStream};
+use syn;
+
+use helpers::{
+    extract_list_metas, extract_meta, filter_metas, get_meta_ident, get_meta_list, unique_meta_list,
+};
+
+pub fn enum_discriminants_inner(ast: &syn::DeriveInput) -> TokenStream {
+    let name = &ast.ident;
+    let vis = &ast.vis;
+
+    let variants = match ast.data {
+        syn::Data::Enum(ref v) => &v.variants,
+        _ => panic!("EnumDiscriminants only works on Enums"),
+    };
+
+    // Derives for the generated enum
+    let type_meta = extract_meta(&ast.attrs);
+    let discriminant_attrs = get_meta_list(type_meta.iter(), "strum_discriminants")
+        .flat_map(|meta| extract_list_metas(meta).collect::<Vec<_>>())
+        .collect::<Vec<&syn::Meta>>();
+    let derives = get_meta_list(discriminant_attrs.iter().map(|&m| m), "derive")
+        .flat_map(extract_list_metas)
+        .filter_map(get_meta_ident)
+        .collect::<Vec<_>>();
+
+    let derives = quote! {
+        #[derive(Clone, Copy, Debug, PartialEq, Eq, #(#derives),*)]
+    };
+
+    // Work out the name
+    let default_name = syn::Ident::new(
+        &format!("{}Discriminants", name.to_string()),
+        Span::call_site(),
+    );
+
+    let discriminants_name = unique_meta_list(discriminant_attrs.iter().map(|&m| m), "name")
+        .map(extract_list_metas)
+        .and_then(|metas| metas.filter_map(get_meta_ident).next())
+        .unwrap_or(&default_name);
+
+    // Pass through all other attributes
+    let pass_though_attributes =
+        filter_metas(discriminant_attrs.iter().map(|&m| m), |meta| match meta {
+            syn::Meta::List(ref metalist) => metalist.ident != "derive" && metalist.ident != "name",
+            _ => true,
+        }).map(|meta| quote! { #[ #meta ] })
+        .collect::<Vec<_>>();
+
+    // Add the variants without fields, but exclude the `strum` meta item
+    let mut discriminants = Vec::new();
+    for variant in variants {
+        let ident = &variant.ident;
+
+        // Don't copy across the "strum" meta attribute.
+        let attrs = variant.attrs.iter().filter(|attr| {
+            attr.interpret_meta().map_or(true, |meta| match meta {
+                syn::Meta::List(ref metalist) => metalist.ident != "strum",
+                _ => true,
+            })
+        });
+
+        discriminants.push(quote!{ #(#attrs)* #ident });
+    }
+
+    // Ideally:
+    //
+    // * For `Copy` types, we `impl From<TheEnum> for TheEnumDiscriminants`
+    // * For `!Copy` types, we `impl<'enum> From<&'enum TheEnum> for TheEnumDiscriminants`
+    //
+    // That way we ensure users are not able to pass a `Copy` type by reference. However, the
+    // `#[derive(..)]` attributes are not in the parsed tokens, so we are not able to check if a
+    // type is `Copy`, so we just implement both.
+    //
+    // See <https://github.com/dtolnay/syn/issues/433>
+    // ---
+    // let is_copy = unique_meta_list(type_meta.iter(), "derive")
+    //     .map(extract_list_metas)
+    //     .map(|metas| {
+    //         metas
+    //             .filter_map(get_meta_ident)
+    //             .any(|derive| derive.to_string() == "Copy")
+    //     }).unwrap_or(false);
+
+    let arms = variants
+        .iter()
+        .map(|variant| {
+            let ident = &variant.ident;
+
+            use syn::Fields::*;
+            let params = match variant.fields {
+                Unit => quote!{},
+                Unnamed(ref _fields) => {
+                    quote! { (..) }
+                }
+                Named(ref _fields) => {
+                    quote! { { .. } }
+                }
+            };
+
+            quote! { #name::#ident #params => #discriminants_name::#ident }
+        }).collect::<Vec<_>>();
+    let from_fn_body = quote! { match val { #(#arms),* } };
+
+    let (impl_generics, ty_generics, where_clause) = ast.generics.split_for_impl();
+    let impl_from = quote! {
+        impl #impl_generics ::std::convert::From< #name #ty_generics > for #discriminants_name #where_clause {
+            fn from(val: #name #ty_generics) -> #discriminants_name {
+                #from_fn_body
+            }
+        }
+    };
+    let impl_from_ref = {
+        let mut generics = ast.generics.clone();
+
+        let lifetime = parse_quote!('_enum);
+        let enum_life = quote! { & #lifetime };
+        generics.params.push(lifetime);
+
+        // Shadows the earlier `impl_generics`
+        let (impl_generics, _, _) = generics.split_for_impl();
+
+        quote! {
+            impl #impl_generics ::std::convert::From< #enum_life #name #ty_generics > for #discriminants_name #where_clause {
+                fn from(val: #enum_life #name #ty_generics) -> #discriminants_name {
+                    #from_fn_body
+                }
+            }
+        }
+    };
+
+    quote!{
+        /// Auto-generated discriminant enum variants
+        #derives
+        #(#pass_though_attributes)*
+        #vis enum #discriminants_name {
+            #(#discriminants),*
+        }
+
+        #impl_from
+        #impl_from_ref
+    }
+}
diff --git a/strum_macros/src/enum_iter.rs b/strum_macros/src/enum_iter.rs
index 69fdec7..1cb3af6 100644
--- a/strum_macros/src/enum_iter.rs
+++ b/strum_macros/src/enum_iter.rs
@@ -10,8 +10,10 @@ pub fn enum_iter_inner(ast: &syn::DeriveInput) -> TokenStream {
     let vis = &ast.vis;
 
     if gen.lifetimes().count() > 0 {
-        panic!("Enum Iterator isn't supported on Enums with lifetimes. The resulting enums would \
-                 be unbounded.");
+        panic!(
+            "Enum Iterator isn't supported on Enums with lifetimes. The resulting enums would \
+             be unbounded."
+        );
     }
 
     let phantom_data = if gen.type_params().count() > 0 {
@@ -42,7 +44,10 @@ pub fn enum_iter_inner(ast: &syn::DeriveInput) -> TokenStream {
                 quote! { (#(#defaults),*) }
             }
             Named(ref fields) => {
-                let fields = fields.named.iter().map(|field| field.ident.as_ref().unwrap());
+                let fields = fields
+                    .named
+                    .iter()
+                    .map(|field| field.ident.as_ref().unwrap());
                 quote! { {#(#fields: ::std::default::Default::default()),*} }
             }
         };
diff --git a/strum_macros/src/enum_messages.rs b/strum_macros/src/enum_messages.rs
index feed375..f380de6 100644
--- a/strum_macros/src/enum_messages.rs
+++ b/strum_macros/src/enum_messages.rs
@@ -1,7 +1,7 @@
 use proc_macro2::TokenStream;
 use syn;
 
-use helpers::{unique_attr, extract_attrs, extract_meta, is_disabled};
+use helpers::{extract_attrs, extract_meta, is_disabled, unique_attr};
 
 pub fn enum_message_inner(ast: &syn::DeriveInput) -> TokenStream {
     let name = &ast.ident;
diff --git a/strum_macros/src/enum_properties.rs b/strum_macros/src/enum_properties.rs
index b859ae0..bc63ea9 100644
--- a/strum_macros/src/enum_properties.rs
+++ b/strum_macros/src/enum_properties.rs
@@ -8,35 +8,40 @@ fn extract_properties(meta: &[Meta]) -> Vec<(&syn::Ident, &syn::Lit)> {
     use syn::{MetaList, MetaNameValue, NestedMeta};
     meta.iter()
         .filter_map(|meta| match *meta {
-            Meta::List(MetaList { ref ident, ref nested, .. }) => {
+            Meta::List(MetaList {
+                ref ident,
+                ref nested,
+                ..
+            }) => {
                 if ident == "strum" {
                     Some(nested)
                 } else {
                     None
                 }
-            },
+            }
             _ => None,
-        })
-        .flat_map(|prop| prop)
+        }).flat_map(|prop| prop)
         .filter_map(|prop| match *prop {
-            NestedMeta::Meta(Meta::List(MetaList { ref ident, ref nested, .. })) => {
+            NestedMeta::Meta(Meta::List(MetaList {
+                ref ident,
+                ref nested,
+                ..
+            })) => {
                 if ident == "props" {
                     Some(nested)
                 } else {
                     None
                 }
-            },
+            }
             _ => None,
-        })
-        .flat_map(|prop| prop)
+        }).flat_map(|prop| prop)
         // Only look at key value pairs
         .filter_map(|prop| match *prop {
-            NestedMeta::Meta(Meta::NameValue(MetaNameValue { ref ident, ref lit, .. })) => {
-                Some((ident, lit))
-            },
+            NestedMeta::Meta(Meta::NameValue(MetaNameValue {
+                ref ident, ref lit, ..
+            })) => Some((ident, lit)),
             _ => None,
-        })
-        .collect()
+        }).collect()
 }
 
 pub fn enum_properties_inner(ast: &syn::DeriveInput) -> TokenStream {
diff --git a/strum_macros/src/from_string.rs b/strum_macros/src/from_string.rs
index 9575579..a688369 100644
--- a/strum_macros/src/from_string.rs
+++ b/strum_macros/src/from_string.rs
@@ -1,7 +1,8 @@
 use proc_macro2::TokenStream;
 use syn;
 
-use helpers::{unique_attr, extract_attrs, extract_meta, is_disabled};
+use case_style::CaseStyle;
+use helpers::{convert_case, extract_attrs, extract_meta, is_disabled, unique_attr};
 
 pub fn from_string_inner(ast: &syn::DeriveInput) -> TokenStream {
     let name = &ast.ident;
@@ -11,6 +12,10 @@ pub fn from_string_inner(ast: &syn::DeriveInput) -> TokenStream {
         _ => panic!("FromString only works on Enums"),
     };
 
+    let type_meta = extract_meta(&ast.attrs);
+    let case_style = unique_attr(&type_meta, "strum", "serialize_all")
+        .map(|style| CaseStyle::from(style.as_ref()));
+
     let mut has_default = false;
     let mut default =
         quote! { _ => ::std::result::Result::Err(::strum::ParseError::VariantNotFound) };
@@ -48,20 +53,23 @@ pub fn from_string_inner(ast: &syn::DeriveInput) -> TokenStream {
             continue;
         }
 
-        // If we don't have any custom variants, add the default name.
+        // If we don't have any custom variants, add the default serialized name.
         if attrs.len() == 0 {
-            attrs.push(ident.to_string());
+            attrs.push(convert_case(ident, case_style));
         }
 
         let params = match variant.fields {
             Unit => quote!{},
             Unnamed(ref fields) => {
-                let defaults = ::std::iter::repeat(quote!(Default::default()))
-                    .take(fields.unnamed.len());
+                let defaults =
+                    ::std::iter::repeat(quote!(Default::default())).take(fields.unnamed.len());
                 quote! { (#(#defaults),*) }
             }
             Named(ref fields) => {
-                let fields = fields.named.iter().map(|field| field.ident.as_ref().unwrap());
+                let fields = fields
+                    .named
+                    .iter()
+                    .map(|field| field.ident.as_ref().unwrap());
                 quote! { {#(#fields: Default::default()),*} }
             }
         };
diff --git a/strum_macros/src/helpers.rs b/strum_macros/src/helpers.rs
index b56a6d2..265500f 100644
--- a/strum_macros/src/helpers.rs
+++ b/strum_macros/src/helpers.rs
@@ -1,12 +1,99 @@
+use heck::{CamelCase, KebabCase, MixedCase, ShoutySnakeCase, SnakeCase, TitleCase};
+use syn::{Attribute, Ident, Meta, MetaList};
 
-use syn::{Attribute, Meta};
+use case_style::CaseStyle;
 
 pub fn extract_meta(attrs: &[Attribute]) -> Vec<Meta> {
-    attrs.iter()
+    attrs
+        .iter()
         .filter_map(|attribute| attribute.interpret_meta())
         .collect()
 }
 
+pub fn filter_metas<'meta, MetaIt, F>(metas: MetaIt, filter: F) -> impl Iterator<Item = &'meta Meta>
+where
+    MetaIt: Iterator<Item = &'meta Meta>,
+    F: Fn(&Meta) -> bool,
+{
+    metas.filter_map(move |meta| if filter(meta) { Some(meta) } else { None })
+}
+
+pub fn filter_meta_lists<'meta, MetaIt, F>(
+    metas: MetaIt,
+    filter: F,
+) -> impl Iterator<Item = &'meta MetaList>
+where
+    MetaIt: Iterator<Item = &'meta Meta>,
+    F: Fn(&MetaList) -> bool,
+{
+    metas.filter_map(move |meta| match meta {
+        Meta::List(ref metalist) => {
+            if filter(metalist) {
+                Some(metalist)
+            } else {
+                None
+            }
+        }
+        _ => None,
+    })
+}
+
+/// Returns the `MetaList`s with the given attr name.
+///
+/// For example, `get_meta_list(type_meta.iter(), "strum_discriminant")` for the following snippet
+/// will return an iterator with `#[strum_discriminant(derive(EnumIter))]` and
+/// `#[strum_discriminant(name(MyEnumVariants))]`.
+///
+/// ```rust,ignore
+/// #[derive(Debug)]
+/// #[strum_discriminant(derive(EnumIter))]
+/// #[strum_discriminant(name(MyEnumVariants))]
+/// enum MyEnum { A }
+/// ```
+pub fn get_meta_list<'meta, MetaIt>(
+    metas: MetaIt,
+    attr: &'meta str,
+) -> impl Iterator<Item = &'meta MetaList>
+where
+    MetaIt: Iterator<Item = &'meta Meta>,
+{
+    filter_meta_lists(metas, move |metalist| metalist.ident == attr)
+}
+
+/// Returns the `MetaList` that matches the given name from the list of `Meta`s, or `None`.
+///
+/// # Panics
+///
+/// Panics if more than one `Meta` exists with the name.
+pub fn unique_meta_list<'meta, MetaIt>(metas: MetaIt, attr: &'meta str) -> Option<&'meta MetaList>
+where
+    MetaIt: Iterator<Item = &'meta Meta>,
+{
+    let mut curr = get_meta_list(metas.into_iter(), attr).collect::<Vec<_>>();
+    if curr.len() > 1 {
+        panic!("More than one `{}` attribute found on type", attr);
+    }
+
+    curr.pop()
+}
+
+/// Returns an iterator of the `Meta`s from the given `MetaList`.
+pub fn extract_list_metas<'meta>(metalist: &'meta MetaList) -> impl Iterator<Item = &'meta Meta> {
+    use syn::NestedMeta;
+    metalist.nested.iter().filter_map(|nested| match *nested {
+        NestedMeta::Meta(ref meta) => Some(meta),
+        _ => None,
+    })
+}
+
+/// Returns the `Ident` of the `Meta::Word`, or `None`.
+pub fn get_meta_ident<'meta>(meta: &'meta Meta) -> Option<&'meta Ident> {
+    match *meta {
+        Meta::Word(ref ident) => Some(ident),
+        _ => None,
+    }
+}
+
 pub fn extract_attrs(meta: &[Meta], attr: &str, prop: &str) -> Vec<String> {
     use syn::{Lit, MetaNameValue, NestedMeta};
     meta.iter()
@@ -18,10 +105,9 @@ pub fn extract_attrs(meta: &[Meta], attr: &str, prop: &str) -> Vec<String> {
                 } else {
                     None
                 }
-            },
+            }
             _ => None,
-        })
-        .flat_map(|nested| nested)
+        }).flat_map(|nested| nested)
         // Get all the inner elements as long as they start with ser.
         .filter_map(|meta| match *meta {
             NestedMeta::Meta(Meta::NameValue(MetaNameValue {
@@ -34,10 +120,9 @@ pub fn extract_attrs(meta: &[Meta], attr: &str, prop: &str) -> Vec<String> {
                 } else {
                     None
                 }
-            },
+            }
             _ => None,
-        })
-        .collect()
+        }).collect()
 }
 
 pub fn unique_attr(attrs: &[Meta], attr: &str, prop: &str) -> Option<String> {
@@ -57,3 +142,19 @@ pub fn is_disabled(attrs: &[Meta]) -> bool {
         _ => panic!("Can't have multiple values for 'disabled'"),
     }
 }
+
+pub fn convert_case(ident: &Ident, case_style: Option<CaseStyle>) -> String {
+    let ident_string = ident.to_string();
+    if let Some(case_style) = case_style {
+        match case_style {
+            CaseStyle::CamelCase => ident_string.to_camel_case(),
+            CaseStyle::KebabCase => ident_string.to_kebab_case(),
+            CaseStyle::MixedCase => ident_string.to_mixed_case(),
+            CaseStyle::ShoutySnakeCase => ident_string.to_shouty_snake_case(),
+            CaseStyle::SnakeCase => ident_string.to_snake_case(),
+            CaseStyle::TitleCase => ident_string.to_title_case(),
+        }
+    } else {
+        ident_string
+    }
+}
diff --git a/strum_macros/src/lib.rs b/strum_macros/src/lib.rs
index 7bb1b7a..3003a64 100644
--- a/strum_macros/src/lib.rs
+++ b/strum_macros/src/lib.rs
@@ -7,25 +7,29 @@
 //!
 //! The documentation for this crate is found in the `strum` crate.
 
-#![recursion_limit="128"]
+#![recursion_limit = "128"]
 
+extern crate heck;
+#[macro_use]
 extern crate syn;
 #[macro_use]
 extern crate quote;
 extern crate proc_macro;
 extern crate proc_macro2;
 
-mod helpers;
 mod as_ref_str;
+mod case_style;
 mod display;
-mod to_string;
-mod from_string;
+mod enum_discriminants;
 mod enum_iter;
 mod enum_messages;
 mod enum_properties;
+mod from_string;
+mod helpers;
+mod to_string;
 
-use std::env;
 use proc_macro2::TokenStream;
+use std::env;
 
 fn debug_print_generated(ast: &syn::DeriveInput, toks: &TokenStream) {
     let debug = env::var("STRUM_DEBUG");
@@ -40,7 +44,7 @@ fn debug_print_generated(ast: &syn::DeriveInput, toks: &TokenStream) {
     }
 }
 
-#[proc_macro_derive(EnumString,attributes(strum))]
+#[proc_macro_derive(EnumString, attributes(strum))]
 pub fn from_string(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
     let ast = syn::parse(input).unwrap();
 
@@ -49,7 +53,7 @@ pub fn from_string(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
     toks.into()
 }
 
-#[proc_macro_derive(AsRefStr,attributes(strum))]
+#[proc_macro_derive(AsRefStr, attributes(strum))]
 pub fn as_ref_str(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
     let ast = syn::parse(input).unwrap();
 
@@ -58,7 +62,7 @@ pub fn as_ref_str(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
     toks.into()
 }
 
-#[proc_macro_derive(AsStaticStr,attributes(strum))]
+#[proc_macro_derive(AsStaticStr, attributes(strum))]
 pub fn as_static_str(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
     let ast = syn::parse(input).unwrap();
 
@@ -67,7 +71,7 @@ pub fn as_static_str(input: proc_macro::TokenStream) -> proc_macro::TokenStream
     toks.into()
 }
 
-#[proc_macro_derive(ToString,attributes(strum))]
+#[proc_macro_derive(ToString, attributes(strum))]
 pub fn to_string(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
     let ast = syn::parse(input).unwrap();
 
@@ -76,7 +80,7 @@ pub fn to_string(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
     toks.into()
 }
 
-#[proc_macro_derive(Display,attributes(strum))]
+#[proc_macro_derive(Display, attributes(strum))]
 pub fn display(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
     let ast = syn::parse(input).unwrap();
 
@@ -85,7 +89,7 @@ pub fn display(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
     toks.into()
 }
 
-#[proc_macro_derive(EnumIter,attributes(strum))]
+#[proc_macro_derive(EnumIter, attributes(strum))]
 pub fn enum_iter(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
     let ast = syn::parse(input).unwrap();
 
@@ -94,7 +98,7 @@ pub fn enum_iter(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
     toks.into()
 }
 
-#[proc_macro_derive(EnumMessage,attributes(strum))]
+#[proc_macro_derive(EnumMessage, attributes(strum))]
 pub fn enum_messages(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
     let ast = syn::parse(input).unwrap();
 
@@ -103,7 +107,7 @@ pub fn enum_messages(input: proc_macro::TokenStream) -> proc_macro::TokenStream
     toks.into()
 }
 
-#[proc_macro_derive(EnumProperty,attributes(strum))]
+#[proc_macro_derive(EnumProperty, attributes(strum))]
 pub fn enum_properties(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
     let ast = syn::parse(input).unwrap();
 
@@ -111,3 +115,12 @@ pub fn enum_properties(input: proc_macro::TokenStream) -> proc_macro::TokenStrea
     debug_print_generated(&ast, &toks);
     toks.into()
 }
+
+#[proc_macro_derive(EnumDiscriminants, attributes(strum, strum_discriminants))]
+pub fn enum_discriminants(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
+    let ast = syn::parse(input).unwrap();
+
+    let toks = enum_discriminants::enum_discriminants_inner(&ast);
+    debug_print_generated(&ast, &toks);
+    toks.into()
+}
diff --git a/strum_macros/src/to_string.rs b/strum_macros/src/to_string.rs
index e8ee996..a8a94a9 100644
--- a/strum_macros/src/to_string.rs
+++ b/strum_macros/src/to_string.rs
@@ -1,7 +1,8 @@
 use proc_macro2::TokenStream;
 use syn;
 
-use helpers::{unique_attr, extract_attrs, extract_meta, is_disabled};
+use case_style::CaseStyle;
+use helpers::{convert_case, extract_attrs, extract_meta, is_disabled, unique_attr};
 
 pub fn to_string_inner(ast: &syn::DeriveInput) -> TokenStream {
     let name = &ast.ident;
@@ -11,6 +12,10 @@ pub fn to_string_inner(ast: &syn::DeriveInput) -> TokenStream {
         _ => panic!("ToString only works on Enums"),
     };
 
+    let type_meta = extract_meta(&ast.attrs);
+    let case_style = unique_attr(&type_meta, "strum", "serialize_all")
+        .map(|style| CaseStyle::from(style.as_ref()));
+
     let mut arms = Vec::new();
     for variant in variants {
         use syn::Fields::*;
@@ -31,7 +36,7 @@ pub fn to_string_inner(ast: &syn::DeriveInput) -> TokenStream {
             if let Some(n) = attrs.pop() {
                 n
             } else {
-                ident.to_string()
+                convert_case(ident, case_style)
             }
         };
 


Test patch:
diff --git a/strum_tests/Cargo.toml b/strum_tests/Cargo.toml
index adddc94..eacb8ba 100644
--- a/strum_tests/Cargo.toml
+++ b/strum_tests/Cargo.toml
@@ -1,6 +1,6 @@
 [package]
 name = "strum_tests"
-version = "0.9.1"
+version = "0.10.0"
 authors = ["Peter Glotfelty <peglotfe@microsoft.com>"]
 
 [dependencies]
diff --git a/strum_tests/src/main.rs b/strum_tests/src/main.rs
index aefed81..67e2e36 100644
--- a/strum_tests/src/main.rs
+++ b/strum_tests/src/main.rs
@@ -3,18 +3,18 @@ extern crate strum;
 extern crate strum_macros;
 
 #[allow(dead_code)]
-#[derive(Debug,Eq,PartialEq,EnumString,ToString)]
+#[derive(Debug, Eq, PartialEq, EnumString, ToString)]
 enum Color {
-    #[strum(to_string="RedRed")]
+    #[strum(to_string = "RedRed")]
     Red,
-    #[strum(serialize="b", to_string="blue")]
+    #[strum(serialize = "b", to_string = "blue")]
     Blue { hue: usize },
-    #[strum(serialize="y",serialize="yellow")]
+    #[strum(serialize = "y", serialize = "yellow")]
     Yellow,
-    #[strum(disabled="true")]
+    #[strum(disabled = "true")]
     Green(String),
 }
 
 fn main() {
     println!("Tests crate");
-}
\ No newline at end of file
+}
diff --git a/strum_tests/tests/as_ref_no_strum.rs b/strum_tests/tests/as_ref_no_strum.rs
index 12d68f6..446a082 100644
--- a/strum_tests/tests/as_ref_no_strum.rs
+++ b/strum_tests/tests/as_ref_no_strum.rs
@@ -1,15 +1,15 @@
 #[macro_use]
 extern crate strum_macros;
 
-#[derive(Debug,Eq,PartialEq,AsRefStr)]
+#[derive(Debug, Eq, PartialEq, AsRefStr)]
 enum Color {
-    #[strum(to_string="RedRed")]
+    #[strum(to_string = "RedRed")]
     Red,
-    #[strum(serialize="b", to_string="blue")]
+    #[strum(serialize = "b", to_string = "blue")]
     Blue { hue: usize },
-    #[strum(serialize="y", serialize="yellow")]
+    #[strum(serialize = "y", serialize = "yellow")]
     Yellow,
-    #[strum(default="true")]
+    #[strum(default = "true")]
     Green(String),
 }
 
diff --git a/strum_tests/tests/as_ref_str.rs b/strum_tests/tests/as_ref_str.rs
index 9db2a70..c2b1e20 100644
--- a/strum_tests/tests/as_ref_str.rs
+++ b/strum_tests/tests/as_ref_str.rs
@@ -5,15 +5,15 @@ extern crate strum_macros;
 use std::str::FromStr;
 use strum::AsStaticRef;
 
-#[derive(Debug,Eq,PartialEq,EnumString,AsRefStr,AsStaticStr)]
+#[derive(Debug, Eq, PartialEq, EnumString, AsRefStr, AsStaticStr)]
 enum Color {
-    #[strum(to_string="RedRed")]
+    #[strum(to_string = "RedRed")]
     Red,
-    #[strum(serialize="b", to_string="blue")]
+    #[strum(serialize = "b", to_string = "blue")]
     Blue { hue: usize },
-    #[strum(serialize="y", serialize="yellow")]
+    #[strum(serialize = "y", serialize = "yellow")]
     Yellow,
-    #[strum(default="true")]
+    #[strum(default = "true")]
     Green(String),
 }
 
diff --git a/strum_tests/tests/display.rs b/strum_tests/tests/display.rs
index 000b0df..3642f60 100644
--- a/strum_tests/tests/display.rs
+++ b/strum_tests/tests/display.rs
@@ -2,15 +2,15 @@ extern crate strum;
 #[macro_use]
 extern crate strum_macros;
 
-#[derive(Debug,Eq,PartialEq, EnumString, Display)]
+#[derive(Debug, Eq, PartialEq, EnumString, Display)]
 enum Color {
-    #[strum(to_string="RedRed")]
+    #[strum(to_string = "RedRed")]
     Red,
-    #[strum(serialize="b", to_string="blue")]
+    #[strum(serialize = "b", to_string = "blue")]
     Blue { hue: usize },
-    #[strum(serialize="y", serialize="yellow")]
+    #[strum(serialize = "y", serialize = "yellow")]
     Yellow,
-    #[strum(default="true")]
+    #[strum(default = "true")]
     Green(String),
 }
 
@@ -28,3 +28,30 @@ fn to_yellow_string() {
 fn to_red_string() {
     assert_eq!(String::from("RedRed"), format!("{}", Color::Red));
 }
+
+#[derive(Display, Debug, Eq, PartialEq)]
+#[strum(serialize_all = "snake_case")]
+enum Brightness {
+    DarkBlack,
+    Dim {
+        glow: usize,
+    },
+    #[strum(serialize = "bright")]
+    BrightWhite,
+}
+
+#[test]
+fn brightness_to_string() {
+    assert_eq!(
+        String::from("dark_black"),
+        Brightness::DarkBlack.to_string().as_ref()
+    );
+    assert_eq!(
+        String::from("dim"),
+        Brightness::Dim { glow: 0 }.to_string().as_ref()
+    );
+    assert_eq!(
+        String::from("bright"),
+        Brightness::BrightWhite.to_string().as_ref()
+    );
+}
diff --git a/strum_tests/tests/enum_discriminants.rs b/strum_tests/tests/enum_discriminants.rs
new file mode 100644
index 0000000..e64a668
--- /dev/null
+++ b/strum_tests/tests/enum_discriminants.rs
@@ -0,0 +1,186 @@
+extern crate strum;
+#[macro_use]
+extern crate strum_macros;
+
+use strum::IntoEnumIterator;
+
+#[allow(dead_code)]
+#[derive(Debug, Eq, PartialEq, EnumDiscriminants)]
+#[strum_discriminants(derive(EnumIter))]
+enum Simple {
+    Variant0,
+    Variant1,
+}
+
+#[test]
+fn simple_test() {
+    let discriminants = SimpleDiscriminants::iter().collect::<Vec<_>>();
+    let expected = vec![SimpleDiscriminants::Variant0, SimpleDiscriminants::Variant1];
+
+    assert_eq!(expected, discriminants);
+}
+
+#[derive(Debug)]
+struct NonDefault;
+
+#[allow(dead_code)]
+#[derive(Debug, EnumDiscriminants)]
+#[strum_discriminants(derive(EnumIter))]
+enum WithFields {
+    Variant0(NonDefault),
+    Variant1 { a: NonDefault },
+}
+
+#[test]
+fn fields_test() {
+    let discriminants = WithFieldsDiscriminants::iter().collect::<Vec<_>>();
+    let expected = vec![
+        WithFieldsDiscriminants::Variant0,
+        WithFieldsDiscriminants::Variant1,
+    ];
+
+    assert_eq!(expected, discriminants);
+}
+
+trait Foo {}
+trait Bar {}
+
+#[allow(dead_code)]
+#[derive(Debug, Eq, PartialEq, EnumDiscriminants)]
+#[strum_discriminants(derive(EnumIter))]
+enum Complicated<U: Foo, V: Bar> {
+    /// With Docs
+    A(U),
+    B {
+        v: V,
+    },
+    C,
+}
+
+#[test]
+fn complicated_test() {
+    let discriminants = ComplicatedDiscriminants::iter().collect::<Vec<_>>();
+    let expected = vec![
+        ComplicatedDiscriminants::A,
+        ComplicatedDiscriminants::B,
+        ComplicatedDiscriminants::C,
+    ];
+
+    assert_eq!(expected, discriminants);
+}
+
+// This test exists to ensure that we do not copy across the `#[strum(default = "true")]` meta
+// attribute. If we do without deriving any `strum` derivations on the generated discriminant enum,
+// Rust will generate a compiler error saying it doesn't understand the `strum` attribute.
+#[allow(dead_code)]
+#[derive(Debug, EnumDiscriminants)]
+enum WithDefault {
+    #[strum(default = "true")]
+    A(String),
+    B,
+}
+
+#[test]
+fn with_default_test() {
+    assert!(WithDefaultDiscriminants::A != WithDefaultDiscriminants::B);
+}
+
+#[allow(dead_code)]
+#[derive(Debug, Eq, PartialEq, EnumDiscriminants)]
+#[strum_discriminants(name(EnumBoo), derive(EnumIter))]
+enum Renamed {
+    Variant0(bool),
+    Variant1(i32),
+}
+
+#[test]
+fn renamed_test() {
+    let discriminants = EnumBoo::iter().collect::<Vec<_>>();
+    let expected = vec![EnumBoo::Variant0, EnumBoo::Variant1];
+
+    assert_eq!(expected, discriminants);
+}
+
+#[allow(dead_code)]
+#[derive(Debug, Eq, PartialEq, EnumDiscriminants)]
+#[strum_discriminants(name(SplitAttributesBoo), derive(Display))]
+#[strum_discriminants(derive(EnumIter))]
+enum SplitAttributes {
+    Variant0(bool),
+    Variant1(i32),
+}
+
+#[test]
+fn split_attributes_test() {
+    let discriminants = SplitAttributesBoo::iter().collect::<Vec<_>>();
+    let expected = vec![SplitAttributesBoo::Variant0, SplitAttributesBoo::Variant1];
+
+    assert_eq!(expected, discriminants);
+    assert_eq!("Variant0", format!("{}", SplitAttributesBoo::Variant0));
+}
+
+#[allow(dead_code)]
+#[derive(Debug, Eq, PartialEq, EnumDiscriminants)]
+#[strum_discriminants(
+    name(PassThroughBoo),
+    derive(Display, EnumIter, EnumString),
+    strum(serialize_all = "snake_case"),
+)]
+enum PassThrough {
+    DarkBlack(bool),
+    BrightWhite(i32),
+}
+
+#[test]
+fn arbitrary_attributes_pass_through() {
+    use std::str::FromStr;
+
+    let discriminants = PassThroughBoo::iter().collect::<Vec<_>>();
+    let expected = vec![PassThroughBoo::DarkBlack, PassThroughBoo::BrightWhite];
+
+    assert_eq!(expected, discriminants);
+    assert_eq!("dark_black", PassThroughBoo::DarkBlack.to_string());
+    assert_eq!(
+        PassThroughBoo::DarkBlack,
+        PassThroughBoo::from_str("dark_black").unwrap()
+    );
+}
+
+#[derive(Debug, Eq, PartialEq, EnumDiscriminants)]
+enum EnumInto {
+    A(bool),
+    B(i32),
+}
+
+#[test]
+fn from_test() {
+    assert_eq!(EnumIntoDiscriminants::A, EnumInto::A(true).into());
+    assert_eq!(EnumIntoDiscriminants::B, EnumInto::B(1).into());
+}
+
+#[test]
+fn from_ref_test() {
+    assert_eq!(EnumIntoDiscriminants::A, (&EnumInto::A(true)).into());
+    assert_eq!(EnumIntoDiscriminants::B, (&EnumInto::B(1)).into());
+}
+
+#[derive(Debug)]
+struct Rara;
+
+#[derive(Debug, Eq, PartialEq, EnumDiscriminants)]
+#[strum_discriminants(name(EnumIntoComplexVars),)]
+enum EnumIntoComplex<'a, T: 'a> {
+    A(&'a T),
+}
+
+#[test]
+fn from_test_complex() {
+    let rara = Rara;
+    assert_eq!(EnumIntoComplexVars::A, EnumIntoComplex::A(&rara).into());
+}
+
+#[test]
+fn from_ref_test_complex() {
+    let rara = Rara;
+    assert_eq!(EnumIntoComplexVars::A, (&EnumIntoComplex::A(&rara)).into());
+}
diff --git a/strum_tests/tests/enum_iter.rs b/strum_tests/tests/enum_iter.rs
index bd66b3b..e3cffa5 100644
--- a/strum_tests/tests/enum_iter.rs
+++ b/strum_tests/tests/enum_iter.rs
@@ -4,7 +4,7 @@ extern crate strum_macros;
 
 use strum::IntoEnumIterator;
 
-#[derive(Debug,Eq,PartialEq,EnumIter)]
+#[derive(Debug, Eq, PartialEq, EnumIter)]
 enum Week {
     Sunday,
     Monday,
@@ -18,18 +18,20 @@ enum Week {
 #[test]
 fn simple_test() {
     let results = Week::iter().collect::<Vec<_>>();
-    let expected = vec![Week::Sunday,
-                        Week::Monday,
-                        Week::Tuesday,
-                        Week::Wednesday,
-                        Week::Thursday,
-                        Week::Friday,
-                        Week::Saturday];
+    let expected = vec![
+        Week::Sunday,
+        Week::Monday,
+        Week::Tuesday,
+        Week::Wednesday,
+        Week::Thursday,
+        Week::Friday,
+        Week::Saturday,
+    ];
 
     assert_eq!(expected, results);
 }
 
-#[derive(Debug,Eq,PartialEq,EnumIter)]
+#[derive(Debug, Eq, PartialEq, EnumIter)]
 enum Complicated<U: Default, V: Default> {
     A(U),
     B { v: V },
@@ -39,19 +41,21 @@ enum Complicated<U: Default, V: Default> {
 #[test]
 fn complicated_test() {
     let results = Complicated::iter().collect::<Vec<_>>();
-    let expected = vec![Complicated::A(0),
-                        Complicated::B { v: String::new() },
-                        Complicated::C];
+    let expected = vec![
+        Complicated::A(0),
+        Complicated::B { v: String::new() },
+        Complicated::C,
+    ];
 
     assert_eq!(expected, results);
 }
 
 #[test]
 fn len_test() {
-    let mut i = Complicated::<(),()>::iter();
+    let mut i = Complicated::<(), ()>::iter();
     assert_eq!(3, i.len());
     i.next();
-    
+
     assert_eq!(2, i.len());
     i.next();
 
@@ -82,15 +86,17 @@ fn clone_test() {
 #[test]
 fn cycle_test() {
     let results = Week::iter().cycle().take(10).collect::<Vec<_>>();
-    let expected = vec![Week::Sunday,
-                        Week::Monday,
-                        Week::Tuesday,
-                        Week::Wednesday,
-                        Week::Thursday,
-                        Week::Friday,
-                        Week::Saturday,
-                        Week::Sunday,
-                        Week::Monday,
-                        Week::Tuesday];
+    let expected = vec![
+        Week::Sunday,
+        Week::Monday,
+        Week::Tuesday,
+        Week::Wednesday,
+        Week::Thursday,
+        Week::Friday,
+        Week::Saturday,
+        Week::Sunday,
+        Week::Monday,
+        Week::Tuesday,
+    ];
     assert_eq!(expected, results);
 }
diff --git a/strum_tests/tests/enum_message.rs b/strum_tests/tests/enum_message.rs
index a0683d5..4e5415b 100644
--- a/strum_tests/tests/enum_message.rs
+++ b/strum_tests/tests/enum_message.rs
@@ -4,17 +4,17 @@ extern crate strum_macros;
 
 use strum::EnumMessage;
 
-#[derive(Debug,Eq,PartialEq,EnumMessage)]
+#[derive(Debug, Eq, PartialEq, EnumMessage)]
 enum Pets {
-    #[strum(message="I'm a dog")]
+    #[strum(message = "I'm a dog")]
     Dog,
-    #[strum(message="I'm a cat")]
-    #[strum(detailed_message="I'm a very exquisite striped cat")]
+    #[strum(message = "I'm a cat")]
+    #[strum(detailed_message = "I'm a very exquisite striped cat")]
     Cat,
-    #[strum(detailed_message="My fish is named Charles McFish")]
+    #[strum(detailed_message = "My fish is named Charles McFish")]
     Fish,
     Bird,
-    #[strum(disabled="true")]
+    #[strum(disabled = "true")]
     Hamster,
 }
 
@@ -27,15 +27,19 @@ fn simple_message() {
 #[test]
 fn detailed_message() {
     assert_eq!("I'm a cat", (Pets::Cat).get_message().unwrap());
-    assert_eq!("I'm a very exquisite striped cat",
-               (Pets::Cat).get_detailed_message().unwrap());
+    assert_eq!(
+        "I'm a very exquisite striped cat",
+        (Pets::Cat).get_detailed_message().unwrap()
+    );
 }
 
 #[test]
 fn only_detailed_message() {
     assert_eq!(None, (Pets::Fish).get_message());
-    assert_eq!("My fish is named Charles McFish",
-               (Pets::Fish).get_detailed_message().unwrap());
+    assert_eq!(
+        "My fish is named Charles McFish",
+        (Pets::Fish).get_detailed_message().unwrap()
+    );
 }
 
 #[test]
@@ -48,4 +52,4 @@ fn no_message() {
 fn disabled_message() {
     assert_eq!(None, (Pets::Hamster).get_message());
     assert_eq!(None, (Pets::Hamster).get_detailed_message());
-}
\ No newline at end of file
+}
diff --git a/strum_tests/tests/enum_props.rs b/strum_tests/tests/enum_props.rs
index db36164..7af0aec 100644
--- a/strum_tests/tests/enum_props.rs
+++ b/strum_tests/tests/enum_props.rs
@@ -6,7 +6,7 @@ use strum::EnumProperty;
 
 #[derive(Debug, EnumProperty)]
 enum Test {
-    #[strum(props(key="value"))]
+    #[strum(props(key = "value"))]
     A,
     B,
 }
@@ -27,4 +27,4 @@ fn prop_test_not_found() {
 fn prop_test_not_found_2() {
     let b = Test::B;
     assert_eq!(None, b.get_str("key"));
-}
\ No newline at end of file
+}
diff --git a/strum_tests/tests/from_str.rs b/strum_tests/tests/from_str.rs
index 9716021..3203f19 100644
--- a/strum_tests/tests/from_str.rs
+++ b/strum_tests/tests/from_str.rs
@@ -4,15 +4,17 @@ extern crate strum_macros;
 
 use std::str::FromStr;
 
-#[derive(Debug,Eq,PartialEq,EnumString)]
+#[derive(Debug, Eq, PartialEq, EnumString)]
 enum Color {
     Red,
-    Blue { hue: usize },
-    #[strum(serialize="y",serialize="yellow")]
+    Blue {
+        hue: usize,
+    },
+    #[strum(serialize = "y", serialize = "yellow")]
     Yellow,
-    #[strum(default="true")]
+    #[strum(default = "true")]
     Green(String),
-    #[strum(to_string="purp")]
+    #[strum(to_string = "purp")]
     Purple,
 }
 
@@ -39,11 +41,40 @@ fn color_to_string() {
 
 #[test]
 fn color_default() {
-    assert_eq!(Color::Green(String::from("not found")),
-               Color::from_str("not found").unwrap());
+    assert_eq!(
+        Color::Green(String::from("not found")),
+        Color::from_str("not found").unwrap()
+    );
 }
 
-#[derive(Debug,Eq,PartialEq,EnumString)]
+#[derive(Debug, Eq, PartialEq, EnumString)]
+#[strum(serialize_all = "snake_case")]
+enum Brightness {
+    DarkBlack,
+    Dim {
+        glow: usize,
+    },
+    #[strum(serialize = "Bright")]
+    BrightWhite,
+}
+
+#[test]
+fn brightness_serialize_all() {
+    assert_eq!(
+        Brightness::DarkBlack,
+        Brightness::from_str("dark_black").unwrap()
+    );
+    assert_eq!(
+        Brightness::Dim { glow: 0 },
+        Brightness::from_str("dim").unwrap()
+    );
+    assert_eq!(
+        Brightness::BrightWhite,
+        Brightness::from_str("Bright").unwrap()
+    );
+}
+
+#[derive(Debug, Eq, PartialEq, EnumString)]
 enum Week {
     Sunday,
     Monday,
@@ -56,8 +87,10 @@ enum Week {
 
 #[test]
 fn week_not_found() {
-    assert_eq!(Result::Err(::strum::ParseError::VariantNotFound),
-               Week::from_str("Humpday"));
+    assert_eq!(
+        Result::Err(::strum::ParseError::VariantNotFound),
+        Week::from_str("Humpday")
+    );
 }
 
 #[test]
@@ -71,7 +104,7 @@ fn week_found() {
     assert_eq!(Result::Ok(Week::Saturday), Week::from_str("Saturday"));
 }
 
-#[derive(Debug,Eq,PartialEq,EnumString)]
+#[derive(Debug, Eq, PartialEq, EnumString)]
 enum Lifetime<'a> {
     Life(&'a str),
     None,
@@ -82,7 +115,7 @@ fn lifetime_test() {
     assert_eq!(Lifetime::Life(""), Lifetime::from_str("Life").unwrap());
 }
 
-#[derive(Debug,Eq,PartialEq,EnumString)]
+#[derive(Debug, Eq, PartialEq, EnumString)]
 enum Generic<T: Default> {
     Gen(T),
     None,
diff --git a/strum_tests/tests/to_string.rs b/strum_tests/tests/to_string.rs
index e7db30e..a345b6f 100644
--- a/strum_tests/tests/to_string.rs
+++ b/strum_tests/tests/to_string.rs
@@ -5,22 +5,24 @@ extern crate strum_macros;
 use std::str::FromStr;
 use std::string::ToString;
 
-#[derive(Debug,Eq,PartialEq, EnumString, ToString)]
+#[derive(Debug, Eq, PartialEq, EnumString, ToString)]
 enum Color {
-    #[strum(to_string="RedRed")]
+    #[strum(to_string = "RedRed")]
     Red,
-    #[strum(serialize="b", to_string="blue")]
+    #[strum(serialize = "b", to_string = "blue")]
     Blue { hue: usize },
-    #[strum(serialize="y", serialize="yellow")]
+    #[strum(serialize = "y", serialize = "yellow")]
     Yellow,
-    #[strum(default="true")]
+    #[strum(default = "true")]
     Green(String),
 }
 
 #[test]
 fn to_blue_string() {
-    assert_eq!(String::from("blue"),
-               (Color::Blue { hue: 0 }).to_string().as_ref());
+    assert_eq!(
+        String::from("blue"),
+        (Color::Blue { hue: 0 }).to_string().as_ref()
+    );
 }
 
 #[test]
@@ -31,6 +33,35 @@ fn to_yellow_string() {
 #[test]
 fn to_red_string() {
     assert_eq!(String::from("RedRed"), (Color::Red).to_string());
-    assert_eq!(Color::Red,
-               Color::from_str((Color::Red).to_string().as_ref()).unwrap());
+    assert_eq!(
+        Color::Red,
+        Color::from_str((Color::Red).to_string().as_ref()).unwrap()
+    );
+}
+
+#[derive(Debug, Eq, PartialEq, ToString)]
+#[strum(serialize_all = "snake_case")]
+enum Brightness {
+    DarkBlack,
+    Dim {
+        glow: usize,
+    },
+    #[strum(serialize = "bright")]
+    BrightWhite,
+}
+
+#[test]
+fn brightness_to_string() {
+    assert_eq!(
+        String::from("dark_black"),
+        Brightness::DarkBlack.to_string().as_ref()
+    );
+    assert_eq!(
+        String::from("dim"),
+        Brightness::Dim { glow: 0 }.to_string().as_ref()
+    );
+    assert_eq!(
+        String::from("bright"),
+        Brightness::BrightWhite.to_string().as_ref()
+    );
 }