Merge remote-tracking branch 'origin/upstream'

Import b/319324325
diff --git a/.cargo_vcs_info.json b/.cargo_vcs_info.json
new file mode 100644
index 0000000..98b3bc9
--- /dev/null
+++ b/.cargo_vcs_info.json
@@ -0,0 +1,6 @@
+{
+  "git": {
+    "sha1": "597f8e941fb9dec5603f6892df4109b50f615160"
+  },
+  "path_in_vcs": "strum_macros"
+}
\ No newline at end of file
diff --git a/Cargo.toml b/Cargo.toml
new file mode 100644
index 0000000..45078e7
--- /dev/null
+++ b/Cargo.toml
@@ -0,0 +1,58 @@
+# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO
+#
+# When uploading crates to the registry Cargo will automatically
+# "normalize" Cargo.toml files for maximal compatibility
+# with all versions of Cargo and also rewrite `path` dependencies
+# to registry (e.g., crates.io) dependencies.
+#
+# If you are reading this file be aware that the original Cargo.toml
+# will likely look very different (and much more reasonable).
+# See Cargo.toml.orig for the original contents.
+
+[package]
+edition = "2018"
+name = "strum_macros"
+version = "0.25.3"
+authors = ["Peter Glotfelty <peter.glotfelty@microsoft.com>"]
+description = "Helpful macros for working with enums and strings"
+homepage = "https://github.com/Peternator7/strum"
+documentation = "https://docs.rs/strum"
+readme = "README.md"
+keywords = [
+    "enum",
+    "string",
+    "macros",
+    "proc-macros",
+]
+categories = [
+    "development-tools::procedural-macro-helpers",
+    "parsing",
+]
+license = "MIT"
+repository = "https://github.com/Peternator7/strum"
+
+[lib]
+name = "strum_macros"
+proc-macro = true
+
+[dependencies.heck]
+version = "0.4.1"
+
+[dependencies.proc-macro2]
+version = "1.0"
+
+[dependencies.quote]
+version = "1.0"
+
+[dependencies.rustversion]
+version = "1.0"
+
+[dependencies.syn]
+version = "2.0"
+features = [
+    "parsing",
+    "extra-traits",
+]
+
+[dev-dependencies.strum]
+version = "0.25"
diff --git a/Cargo.toml.orig b/Cargo.toml.orig
new file mode 100644
index 0000000..533e730
--- /dev/null
+++ b/Cargo.toml.orig
@@ -0,0 +1,29 @@
+[package]
+name = "strum_macros"
+version = "0.25.3"
+edition = "2018"
+authors = ["Peter Glotfelty <peter.glotfelty@microsoft.com>"]
+license = "MIT"
+
+description = "Helpful macros for working with enums and strings"
+keywords = ["enum", "string", "macros", "proc-macros"]
+categories = ["development-tools::procedural-macro-helpers", "parsing"]
+
+documentation = "https://docs.rs/strum"
+homepage = "https://github.com/Peternator7/strum"
+repository = "https://github.com/Peternator7/strum"
+readme = "../README.md"
+
+[lib]
+proc-macro = true
+name = "strum_macros"
+
+[dependencies]
+heck = "0.4.1"
+proc-macro2 = "1.0"
+quote = "1.0"
+rustversion = "1.0"
+syn = { version = "2.0", features = ["parsing", "extra-traits"] }
+
+[dev-dependencies]
+strum = "0.25"
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..588b4a7
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2019 Peter Glotfelty
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/METADATA b/METADATA
new file mode 100644
index 0000000..705441a
--- /dev/null
+++ b/METADATA
@@ -0,0 +1,19 @@
+name: "strum_macros"
+description: "Helpful macros for working with enums and strings"
+third_party {
+  identifier {
+    type: "crates.io"
+    value: "https://crates.io/crates/strum_macros"
+  }
+  identifier {
+    type: "Archive"
+    value: "https://static.crates.io/crates/strum_macros/strum_macros-0.25.3.crate"
+  }
+  version: "0.25.3"
+  license_type: NOTICE
+  last_upgrade_date {
+    year: 2024
+    month: 1
+    day: 17
+  }
+}
diff --git a/MODULE_LICENSE_MIT b/MODULE_LICENSE_MIT
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/MODULE_LICENSE_MIT
diff --git a/OWNERS b/OWNERS
new file mode 100644
index 0000000..48bea6e
--- /dev/null
+++ b/OWNERS
@@ -0,0 +1,2 @@
+# Bug component: 688011
+include platform/prebuilts/rust:main:/OWNERS
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..491c24f
--- /dev/null
+++ b/README.md
@@ -0,0 +1,82 @@
+# Strum
+
+[![Build Status](https://travis-ci.com/Peternator7/strum.svg?branch=master)](https://travis-ci.com/Peternator7/strum)
+[![Build status](https://ci.appveyor.com/api/projects/status/ji4f6n2m5lvu11xt?svg=true)](https://ci.appveyor.com/project/Peternator7/strum)
+[![Latest Version](https://img.shields.io/crates/v/strum.svg)](https://crates.io/crates/strum)
+[![Rust Documentation](https://docs.rs/strum/badge.svg)](https://docs.rs/strum)
+![Crates.io](https://img.shields.io/crates/l/strum)
+![Crates.io](https://img.shields.io/crates/d/strum)
+
+Strum is a set of macros and traits for working with enums and strings easier in Rust.
+
+# Compatibility
+
+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.
+
+# Including Strum in Your Project
+
+Import strum and strum_macros into your project by adding the following lines to your
+Cargo.toml. Strum_macros contains the macros needed to derive all the traits in Strum.
+
+```toml
+[dependencies]
+strum = "0.25"
+strum_macros = "0.25"
+
+# You can also use the "derive" feature, and import the macros directly from "strum"
+# strum = { version = "0.25", features = ["derive"] }
+```
+
+# Strum Macros
+
+Strum has implemented the following macros:
+
+| Macro | Description |
+| --- | ----------- |
+| [EnumString] | Converts strings to enum variants based on their name. |
+| [Display] | Converts enum variants to strings |
+| [FromRepr] | Convert from an integer to an enum. |
+| [AsRefStr] | Implement `AsRef<str>` for `MyEnum` |
+| [IntoStaticStr] | Implements `From<MyEnum> for &'static str` on an enum |
+| [EnumVariantNames] | Adds an associated `VARIANTS` constant which is an array of discriminant names |
+| [EnumIter] | Creates a new type that iterates of the variants of an enum. |
+| [EnumProperty] | Add custom properties to enum variants. |
+| [EnumMessage] | Add a verbose message to an enum variant. |
+| [EnumDiscriminants] | Generate a new type with only the discriminant names. |
+| [EnumCount] | Add a constant `usize` equal to the number of variants. |
+
+# Contributing
+
+Thanks for your interest in contributing. The project is divided into 3 parts, the traits are in the
+`/strum` folder. The procedural macros are in the `/strum_macros` folder, and the integration tests are
+in `/strum_tests`. If you are adding additional features to `strum` or `strum_macros`, you should make sure
+to run the tests and add new integration tests to make sure the features work as expected.
+
+# Debugging
+
+To see the generated code, set the STRUM_DEBUG environment variable before compiling your code.
+`STRUM_DEBUG=1` will dump all of the generated code for every type. `STRUM_DEBUG=YourType` will
+only dump the code generated on a type named `YourType`.
+
+# Name
+
+Strum is short for STRing enUM because it's a library for augmenting enums with additional
+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.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/src/helpers/case_style.rs b/src/helpers/case_style.rs
new file mode 100644
index 0000000..86a8583
--- /dev/null
+++ b/src/helpers/case_style.rs
@@ -0,0 +1,177 @@
+use heck::{
+    ToKebabCase, ToLowerCamelCase, ToShoutySnakeCase, ToSnakeCase, ToTitleCase, ToUpperCamelCase, ToTrainCase,
+};
+use std::str::FromStr;
+use syn::{
+    parse::{Parse, ParseStream},
+    Ident, LitStr,
+};
+
+#[allow(clippy::enum_variant_names)]
+#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
+pub enum CaseStyle {
+    CamelCase,
+    KebabCase,
+    MixedCase,
+    ShoutySnakeCase,
+    SnakeCase,
+    TitleCase,
+    UpperCase,
+    LowerCase,
+    ScreamingKebabCase,
+    PascalCase,
+    TrainCase,
+}
+
+const VALID_CASE_STYLES: &[&str] = &[
+    "camelCase",
+    "PascalCase",
+    "kebab-case",
+    "snake_case",
+    "SCREAMING_SNAKE_CASE",
+    "SCREAMING-KEBAB-CASE",
+    "lowercase",
+    "UPPERCASE",
+    "title_case",
+    "mixed_case",
+    "Train-Case",
+];
+
+impl Parse for CaseStyle {
+    fn parse(input: ParseStream) -> syn::Result<Self> {
+        let text = input.parse::<LitStr>()?;
+        let val = text.value();
+
+        val.as_str().parse().map_err(|_| {
+            syn::Error::new_spanned(
+                &text,
+                format!(
+                    "Unexpected case style for serialize_all: `{}`. Valid values are: `{:?}`",
+                    val, VALID_CASE_STYLES
+                ),
+            )
+        })
+    }
+}
+
+impl FromStr for CaseStyle {
+    type Err = ();
+
+    fn from_str(text: &str) -> Result<Self, ()> {
+        Ok(match text {
+            // "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,
+            "SCREAMING-KEBAB-CASE" => CaseStyle::ScreamingKebabCase,
+            "SCREAMING_SNAKE_CASE" | "shouty_snake_case" | "shouty_snek_case" => {
+                CaseStyle::ShoutySnakeCase
+            }
+            "title_case" => CaseStyle::TitleCase,
+            "mixed_case" => CaseStyle::MixedCase,
+            "lowercase" => CaseStyle::LowerCase,
+            "UPPERCASE" => CaseStyle::UpperCase,
+            "Train-Case" => CaseStyle::TrainCase,
+            _ => return Err(()),
+        })
+    }
+}
+
+pub trait CaseStyleHelpers {
+    fn convert_case(&self, case_style: Option<CaseStyle>) -> String;
+}
+
+impl CaseStyleHelpers for Ident {
+    fn convert_case(&self, case_style: Option<CaseStyle>) -> String {
+        let ident_string = self.to_string();
+        if let Some(case_style) = case_style {
+            match case_style {
+                CaseStyle::PascalCase => ident_string.to_upper_camel_case(),
+                CaseStyle::KebabCase => ident_string.to_kebab_case(),
+                CaseStyle::MixedCase => ident_string.to_lower_camel_case(),
+                CaseStyle::ShoutySnakeCase => ident_string.to_shouty_snake_case(),
+                CaseStyle::SnakeCase => ident_string.to_snake_case(),
+                CaseStyle::TitleCase => ident_string.to_title_case(),
+                CaseStyle::UpperCase => ident_string.to_uppercase(),
+                CaseStyle::LowerCase => ident_string.to_lowercase(),
+                CaseStyle::ScreamingKebabCase => ident_string.to_kebab_case().to_uppercase(),
+                CaseStyle::TrainCase => ident_string.to_train_case(),
+                CaseStyle::CamelCase => {
+                    let camel_case = ident_string.to_upper_camel_case();
+                    let mut pascal = String::with_capacity(camel_case.len());
+                    let mut it = camel_case.chars();
+                    if let Some(ch) = it.next() {
+                        pascal.extend(ch.to_lowercase());
+                    }
+                    pascal.extend(it);
+                    pascal
+                }
+            }
+        } else {
+            ident_string
+        }
+    }
+}
+
+#[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());
+    }
+}
+
+/// 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`.
+pub 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/src/helpers/metadata.rs b/src/helpers/metadata.rs
new file mode 100644
index 0000000..d638ae3
--- /dev/null
+++ b/src/helpers/metadata.rs
@@ -0,0 +1,276 @@
+use proc_macro2::TokenStream;
+use syn::{
+    parenthesized,
+    parse::{Parse, ParseStream},
+    parse2, parse_str,
+    punctuated::Punctuated,
+    Attribute, DeriveInput, Expr, ExprLit, Ident, Lit, LitBool, LitStr, Meta, MetaNameValue, Path,
+    Token, Variant, Visibility,
+};
+
+use super::case_style::CaseStyle;
+
+pub mod kw {
+    use syn::custom_keyword;
+    pub use syn::token::Crate;
+
+    // enum metadata
+    custom_keyword!(serialize_all);
+    custom_keyword!(use_phf);
+
+    // enum discriminant metadata
+    custom_keyword!(derive);
+    custom_keyword!(name);
+    custom_keyword!(vis);
+
+    // variant metadata
+    custom_keyword!(message);
+    custom_keyword!(detailed_message);
+    custom_keyword!(serialize);
+    custom_keyword!(to_string);
+    custom_keyword!(disabled);
+    custom_keyword!(default);
+    custom_keyword!(props);
+    custom_keyword!(ascii_case_insensitive);
+}
+
+pub enum EnumMeta {
+    SerializeAll {
+        kw: kw::serialize_all,
+        case_style: CaseStyle,
+    },
+    AsciiCaseInsensitive(kw::ascii_case_insensitive),
+    Crate {
+        kw: kw::Crate,
+        crate_module_path: Path,
+    },
+    UsePhf(kw::use_phf),
+}
+
+impl Parse for EnumMeta {
+    fn parse(input: ParseStream) -> syn::Result<Self> {
+        let lookahead = input.lookahead1();
+        if lookahead.peek(kw::serialize_all) {
+            let kw = input.parse::<kw::serialize_all>()?;
+            input.parse::<Token![=]>()?;
+            let case_style = input.parse()?;
+            Ok(EnumMeta::SerializeAll { kw, case_style })
+        } else if lookahead.peek(kw::Crate) {
+            let kw = input.parse::<kw::Crate>()?;
+            input.parse::<Token![=]>()?;
+            let path_str: LitStr = input.parse()?;
+            let path_tokens = parse_str(&path_str.value())?;
+            let crate_module_path = parse2(path_tokens)?;
+            Ok(EnumMeta::Crate {
+                kw,
+                crate_module_path,
+            })
+        } else if lookahead.peek(kw::ascii_case_insensitive) {
+            Ok(EnumMeta::AsciiCaseInsensitive(input.parse()?))
+        } else if lookahead.peek(kw::use_phf) {
+            Ok(EnumMeta::UsePhf(input.parse()?))
+        } else {
+            Err(lookahead.error())
+        }
+    }
+}
+
+pub enum EnumDiscriminantsMeta {
+    Derive { kw: kw::derive, paths: Vec<Path> },
+    Name { kw: kw::name, name: Ident },
+    Vis { kw: kw::vis, vis: Visibility },
+    Other { path: Path, nested: TokenStream },
+}
+
+impl Parse for EnumDiscriminantsMeta {
+    fn parse(input: ParseStream) -> syn::Result<Self> {
+        if input.peek(kw::derive) {
+            let kw = input.parse()?;
+            let content;
+            parenthesized!(content in input);
+            let paths = content.parse_terminated(Path::parse, Token![,])?;
+            Ok(EnumDiscriminantsMeta::Derive {
+                kw,
+                paths: paths.into_iter().collect(),
+            })
+        } else if input.peek(kw::name) {
+            let kw = input.parse()?;
+            let content;
+            parenthesized!(content in input);
+            let name = content.parse()?;
+            Ok(EnumDiscriminantsMeta::Name { kw, name })
+        } else if input.peek(kw::vis) {
+            let kw = input.parse()?;
+            let content;
+            parenthesized!(content in input);
+            let vis = content.parse()?;
+            Ok(EnumDiscriminantsMeta::Vis { kw, vis })
+        } else {
+            let path = input.parse()?;
+            let content;
+            parenthesized!(content in input);
+            let nested = content.parse()?;
+            Ok(EnumDiscriminantsMeta::Other { path, nested })
+        }
+    }
+}
+
+pub trait DeriveInputExt {
+    /// Get all the strum metadata associated with an enum.
+    fn get_metadata(&self) -> syn::Result<Vec<EnumMeta>>;
+
+    /// Get all the `strum_discriminants` metadata associated with an enum.
+    fn get_discriminants_metadata(&self) -> syn::Result<Vec<EnumDiscriminantsMeta>>;
+}
+
+impl DeriveInputExt for DeriveInput {
+    fn get_metadata(&self) -> syn::Result<Vec<EnumMeta>> {
+        get_metadata_inner("strum", &self.attrs)
+    }
+
+    fn get_discriminants_metadata(&self) -> syn::Result<Vec<EnumDiscriminantsMeta>> {
+        get_metadata_inner("strum_discriminants", &self.attrs)
+    }
+}
+
+pub enum VariantMeta {
+    Message {
+        kw: kw::message,
+        value: LitStr,
+    },
+    DetailedMessage {
+        kw: kw::detailed_message,
+        value: LitStr,
+    },
+    Serialize {
+        kw: kw::serialize,
+        value: LitStr,
+    },
+    Documentation {
+        value: LitStr,
+    },
+    ToString {
+        kw: kw::to_string,
+        value: LitStr,
+    },
+    Disabled(kw::disabled),
+    Default(kw::default),
+    AsciiCaseInsensitive {
+        kw: kw::ascii_case_insensitive,
+        value: bool,
+    },
+    Props {
+        kw: kw::props,
+        props: Vec<(LitStr, LitStr)>,
+    },
+}
+
+impl Parse for VariantMeta {
+    fn parse(input: ParseStream) -> syn::Result<Self> {
+        let lookahead = input.lookahead1();
+        if lookahead.peek(kw::message) {
+            let kw = input.parse()?;
+            let _: Token![=] = input.parse()?;
+            let value = input.parse()?;
+            Ok(VariantMeta::Message { kw, value })
+        } else if lookahead.peek(kw::detailed_message) {
+            let kw = input.parse()?;
+            let _: Token![=] = input.parse()?;
+            let value = input.parse()?;
+            Ok(VariantMeta::DetailedMessage { kw, value })
+        } else if lookahead.peek(kw::serialize) {
+            let kw = input.parse()?;
+            let _: Token![=] = input.parse()?;
+            let value = input.parse()?;
+            Ok(VariantMeta::Serialize { kw, value })
+        } else if lookahead.peek(kw::to_string) {
+            let kw = input.parse()?;
+            let _: Token![=] = input.parse()?;
+            let value = input.parse()?;
+            Ok(VariantMeta::ToString { kw, value })
+        } else if lookahead.peek(kw::disabled) {
+            Ok(VariantMeta::Disabled(input.parse()?))
+        } else if lookahead.peek(kw::default) {
+            Ok(VariantMeta::Default(input.parse()?))
+        } else if lookahead.peek(kw::ascii_case_insensitive) {
+            let kw = input.parse()?;
+            let value = if input.peek(Token![=]) {
+                let _: Token![=] = input.parse()?;
+                input.parse::<LitBool>()?.value
+            } else {
+                true
+            };
+            Ok(VariantMeta::AsciiCaseInsensitive { kw, value })
+        } else if lookahead.peek(kw::props) {
+            let kw = input.parse()?;
+            let content;
+            parenthesized!(content in input);
+            let props = content.parse_terminated(Prop::parse, Token![,])?;
+            Ok(VariantMeta::Props {
+                kw,
+                props: props
+                    .into_iter()
+                    .map(|Prop(k, v)| (LitStr::new(&k.to_string(), k.span()), v))
+                    .collect(),
+            })
+        } else {
+            Err(lookahead.error())
+        }
+    }
+}
+
+struct Prop(Ident, LitStr);
+
+impl Parse for Prop {
+    fn parse(input: ParseStream) -> syn::Result<Self> {
+        use syn::ext::IdentExt;
+
+        let k = Ident::parse_any(input)?;
+        let _: Token![=] = input.parse()?;
+        let v = input.parse()?;
+
+        Ok(Prop(k, v))
+    }
+}
+
+pub trait VariantExt {
+    /// Get all the metadata associated with an enum variant.
+    fn get_metadata(&self) -> syn::Result<Vec<VariantMeta>>;
+}
+
+impl VariantExt for Variant {
+    fn get_metadata(&self) -> syn::Result<Vec<VariantMeta>> {
+        let result = get_metadata_inner("strum", &self.attrs)?;
+        self.attrs
+            .iter()
+            .filter(|attr| attr.path().is_ident("doc"))
+            .try_fold(result, |mut vec, attr| {
+                if let Meta::NameValue(MetaNameValue {
+                    value:
+                        Expr::Lit(ExprLit {
+                            lit: Lit::Str(value),
+                            ..
+                        }),
+                    ..
+                }) = &attr.meta
+                {
+                    vec.push(VariantMeta::Documentation {
+                        value: value.clone(),
+                    })
+                }
+                Ok(vec)
+            })
+    }
+}
+
+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))
+        .try_fold(Vec::new(), |mut vec, attr| {
+            vec.extend(attr.parse_args_with(Punctuated::<T, Token![,]>::parse_terminated)?);
+            Ok(vec)
+        })
+}
diff --git a/src/helpers/mod.rs b/src/helpers/mod.rs
new file mode 100644
index 0000000..142ea0b
--- /dev/null
+++ b/src/helpers/mod.rs
@@ -0,0 +1,32 @@
+pub use self::case_style::{CaseStyleHelpers, snakify};
+pub use self::type_props::HasTypeProperties;
+pub use self::variant_props::HasStrumVariantProperties;
+
+pub mod case_style;
+mod metadata;
+pub mod type_props;
+pub mod variant_props;
+
+use proc_macro2::Span;
+use quote::ToTokens;
+use syn::spanned::Spanned;
+
+pub fn non_enum_error() -> syn::Error {
+    syn::Error::new(Span::call_site(), "This macro only supports enums.")
+}
+
+pub fn strum_discriminants_passthrough_error(span: &impl Spanned) -> syn::Error {
+    syn::Error::new(
+        span.span(),
+        "expected a pass-through attribute, e.g. #[strum_discriminants(serde(rename = \"var0\"))]",
+    )
+}
+
+pub fn occurrence_error<T: ToTokens>(fst: T, snd: T, attr: &str) -> syn::Error {
+    let mut e = syn::Error::new_spanned(
+        snd,
+        format!("Found multiple occurrences of strum({})", attr),
+    );
+    e.combine(syn::Error::new_spanned(fst, "first one here"));
+    e
+}
diff --git a/src/helpers/type_props.rs b/src/helpers/type_props.rs
new file mode 100644
index 0000000..0d49e04
--- /dev/null
+++ b/src/helpers/type_props.rs
@@ -0,0 +1,116 @@
+use proc_macro2::TokenStream;
+use quote::quote;
+use std::default::Default;
+use syn::{parse_quote, DeriveInput, Ident, Path, Visibility};
+
+use super::case_style::CaseStyle;
+use super::metadata::{DeriveInputExt, EnumDiscriminantsMeta, EnumMeta};
+use super::occurrence_error;
+
+pub trait HasTypeProperties {
+    fn get_type_properties(&self) -> syn::Result<StrumTypeProperties>;
+}
+
+#[derive(Debug, Clone, Default)]
+pub struct StrumTypeProperties {
+    pub case_style: Option<CaseStyle>,
+    pub ascii_case_insensitive: bool,
+    pub crate_module_path: Option<Path>,
+    pub discriminant_derives: Vec<Path>,
+    pub discriminant_name: Option<Ident>,
+    pub discriminant_others: Vec<TokenStream>,
+    pub discriminant_vis: Option<Visibility>,
+    pub use_phf: bool,
+}
+
+impl HasTypeProperties for DeriveInput {
+    fn get_type_properties(&self) -> syn::Result<StrumTypeProperties> {
+        let mut output = StrumTypeProperties::default();
+
+        let strum_meta = self.get_metadata()?;
+        let discriminants_meta = self.get_discriminants_metadata()?;
+
+        let mut serialize_all_kw = None;
+        let mut ascii_case_insensitive_kw = None;
+        let mut use_phf_kw = None;
+        let mut crate_module_path_kw = None;
+        for meta in strum_meta {
+            match meta {
+                EnumMeta::SerializeAll { case_style, kw } => {
+                    if let Some(fst_kw) = serialize_all_kw {
+                        return Err(occurrence_error(fst_kw, kw, "serialize_all"));
+                    }
+
+                    serialize_all_kw = Some(kw);
+                    output.case_style = Some(case_style);
+                }
+                EnumMeta::AsciiCaseInsensitive(kw) => {
+                    if let Some(fst_kw) = ascii_case_insensitive_kw {
+                        return Err(occurrence_error(fst_kw, kw, "ascii_case_insensitive"));
+                    }
+
+                    ascii_case_insensitive_kw = Some(kw);
+                    output.ascii_case_insensitive = true;
+                }
+                EnumMeta::UsePhf(kw) => {
+                    if let Some(fst_kw) = use_phf_kw {
+                        return Err(occurrence_error(fst_kw, kw, "use_phf"));
+                    }
+
+                    use_phf_kw = Some(kw);
+                    output.use_phf = true;
+                }
+                EnumMeta::Crate {
+                    crate_module_path,
+                    kw,
+                } => {
+                    if let Some(fst_kw) = crate_module_path_kw {
+                        return Err(occurrence_error(fst_kw, kw, "Crate"));
+                    }
+
+                    crate_module_path_kw = Some(kw);
+                    output.crate_module_path = Some(crate_module_path);
+                }
+            }
+        }
+
+        let mut name_kw = None;
+        let mut vis_kw = None;
+        for meta in discriminants_meta {
+            match meta {
+                EnumDiscriminantsMeta::Derive { paths, .. } => {
+                    output.discriminant_derives.extend(paths);
+                }
+                EnumDiscriminantsMeta::Name { name, kw } => {
+                    if let Some(fst_kw) = name_kw {
+                        return Err(occurrence_error(fst_kw, kw, "name"));
+                    }
+
+                    name_kw = Some(kw);
+                    output.discriminant_name = Some(name);
+                }
+                EnumDiscriminantsMeta::Vis { vis, kw } => {
+                    if let Some(fst_kw) = vis_kw {
+                        return Err(occurrence_error(fst_kw, kw, "vis"));
+                    }
+
+                    vis_kw = Some(kw);
+                    output.discriminant_vis = Some(vis);
+                }
+                EnumDiscriminantsMeta::Other { path, nested } => {
+                    output.discriminant_others.push(quote! { #path(#nested) });
+                }
+            }
+        }
+
+        Ok(output)
+    }
+}
+
+impl StrumTypeProperties {
+    pub fn crate_module_path(&self) -> Path {
+        self.crate_module_path
+            .as_ref()
+            .map_or_else(|| parse_quote!(::strum), |path| parse_quote!(#path))
+    }
+}
diff --git a/src/helpers/variant_props.rs b/src/helpers/variant_props.rs
new file mode 100644
index 0000000..f637253
--- /dev/null
+++ b/src/helpers/variant_props.rs
@@ -0,0 +1,133 @@
+use std::default::Default;
+use syn::{Ident, LitStr, Variant};
+
+use super::case_style::{CaseStyle, CaseStyleHelpers};
+use super::metadata::{kw, VariantExt, VariantMeta};
+use super::occurrence_error;
+
+pub trait HasStrumVariantProperties {
+    fn get_variant_properties(&self) -> syn::Result<StrumVariantProperties>;
+}
+
+#[derive(Clone, Eq, PartialEq, Debug, Default)]
+pub struct StrumVariantProperties {
+    pub disabled: Option<kw::disabled>,
+    pub default: Option<kw::default>,
+    pub ascii_case_insensitive: Option<bool>,
+    pub message: Option<LitStr>,
+    pub detailed_message: Option<LitStr>,
+    pub documentation: Vec<LitStr>,
+    pub string_props: Vec<(LitStr, LitStr)>,
+    serialize: Vec<LitStr>,
+    pub to_string: Option<LitStr>,
+    ident: Option<Ident>,
+}
+
+impl StrumVariantProperties {
+    fn ident_as_str(&self, case_style: Option<CaseStyle>) -> LitStr {
+        let ident = self.ident.as_ref().expect("identifier");
+        LitStr::new(&ident.convert_case(case_style), ident.span())
+    }
+
+    pub fn get_preferred_name(&self, case_style: Option<CaseStyle>) -> LitStr {
+        self.to_string.as_ref().cloned().unwrap_or_else(|| {
+            self.serialize
+                .iter()
+                .max_by_key(|s| s.value().len())
+                .cloned()
+                .unwrap_or_else(|| self.ident_as_str(case_style))
+        })
+    }
+
+    pub fn get_serializations(&self, case_style: Option<CaseStyle>) -> Vec<LitStr> {
+        let mut attrs = self.serialize.clone();
+        if let Some(to_string) = &self.to_string {
+            attrs.push(to_string.clone());
+        }
+
+        if attrs.is_empty() {
+            attrs.push(self.ident_as_str(case_style));
+        }
+
+        attrs
+    }
+}
+
+impl HasStrumVariantProperties for Variant {
+    fn get_variant_properties(&self) -> syn::Result<StrumVariantProperties> {
+        let mut output = StrumVariantProperties {
+            ident: Some(self.ident.clone()),
+            ..Default::default()
+        };
+
+        let mut message_kw = None;
+        let mut detailed_message_kw = None;
+        let mut to_string_kw = None;
+        let mut disabled_kw = None;
+        let mut default_kw = None;
+        let mut ascii_case_insensitive_kw = None;
+        for meta in self.get_metadata()? {
+            match meta {
+                VariantMeta::Message { value, kw } => {
+                    if let Some(fst_kw) = message_kw {
+                        return Err(occurrence_error(fst_kw, kw, "message"));
+                    }
+
+                    message_kw = Some(kw);
+                    output.message = Some(value);
+                }
+                VariantMeta::DetailedMessage { value, kw } => {
+                    if let Some(fst_kw) = detailed_message_kw {
+                        return Err(occurrence_error(fst_kw, kw, "detailed_message"));
+                    }
+
+                    detailed_message_kw = Some(kw);
+                    output.detailed_message = Some(value);
+                }
+                VariantMeta::Documentation { value } => {
+                    output.documentation.push(value);
+                }
+                VariantMeta::Serialize { value, .. } => {
+                    output.serialize.push(value);
+                }
+                VariantMeta::ToString { value, kw } => {
+                    if let Some(fst_kw) = to_string_kw {
+                        return Err(occurrence_error(fst_kw, kw, "to_string"));
+                    }
+
+                    to_string_kw = Some(kw);
+                    output.to_string = Some(value);
+                }
+                VariantMeta::Disabled(kw) => {
+                    if let Some(fst_kw) = disabled_kw {
+                        return Err(occurrence_error(fst_kw, kw, "disabled"));
+                    }
+
+                    disabled_kw = Some(kw);
+                    output.disabled = Some(kw);
+                }
+                VariantMeta::Default(kw) => {
+                    if let Some(fst_kw) = default_kw {
+                        return Err(occurrence_error(fst_kw, kw, "default"));
+                    }
+
+                    default_kw = Some(kw);
+                    output.default = Some(kw);
+                }
+                VariantMeta::AsciiCaseInsensitive { kw, value } => {
+                    if let Some(fst_kw) = ascii_case_insensitive_kw {
+                        return Err(occurrence_error(fst_kw, kw, "ascii_case_insensitive"));
+                    }
+
+                    ascii_case_insensitive_kw = Some(kw);
+                    output.ascii_case_insensitive = Some(value);
+                }
+                VariantMeta::Props { props, .. } => {
+                    output.string_props.extend(props);
+                }
+            }
+        }
+
+        Ok(output)
+    }
+}
diff --git a/src/lib.rs b/src/lib.rs
new file mode 100644
index 0000000..82db12a
--- /dev/null
+++ b/src/lib.rs
@@ -0,0 +1,815 @@
+//! # Strum
+//!
+//! Strum is a set of macros and traits for working with
+//! enums and strings easier in Rust.
+//!
+
+#![recursion_limit = "128"]
+
+extern crate proc_macro;
+
+mod helpers;
+mod macros;
+
+use proc_macro2::TokenStream;
+use std::env;
+use syn::DeriveInput;
+
+fn debug_print_generated(ast: &DeriveInput, toks: &TokenStream) {
+    let debug = env::var("STRUM_DEBUG");
+    if let Ok(s) = debug {
+        if s == "1" {
+            println!("{}", toks);
+        }
+
+        if ast.ident == s {
+            println!("{}", toks);
+        }
+    }
+}
+
+/// Converts strings to enum variants based on their name.
+///
+/// auto-derives `std::str::FromStr` on the enum (for Rust 1.34 and above, `std::convert::TryFrom<&str>`
+/// will be derived as well). Each variant of the enum will match on it's own name.
+/// This can be overridden using `serialize="DifferentName"` or `to_string="DifferentName"`
+/// on the attribute as shown below.
+/// Multiple deserializations can be added to the same variant. If the variant contains additional data,
+/// they will be set to their default values upon deserialization.
+///
+/// The `default` attribute can be applied to a tuple variant with a single data parameter. When a match isn't
+/// found, the given variant will be returned and the input string will be captured in the parameter.
+///
+/// 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](https://docs.rs/strum/0.22/strum/additional_attributes/index.html)
+/// Section for more information on using this feature.
+///
+/// If you have a large enum, you may want to consider using the `use_phf` attribute here. It leverages
+/// perfect hash functions to parse much quicker than a standard `match`. (MSRV 1.46)
+///
+/// # Example howto use `EnumString`
+/// ```
+/// use std::str::FromStr;
+/// use strum_macros::EnumString;
+///
+/// #[derive(Debug, PartialEq, EnumString)]
+/// enum Color {
+///     Red,
+///     // The Default value will be inserted into range if we match "Green".
+///     Green {
+///         range: usize,
+///     },
+///
+///     // We can match on multiple different patterns.
+///     #[strum(serialize = "blue", serialize = "b")]
+///     Blue(usize),
+///
+///     // Notice that we can disable certain variants from being found
+///     #[strum(disabled)]
+///     Yellow,
+///
+///     // We can make the comparison case insensitive (however Unicode is not supported at the moment)
+///     #[strum(ascii_case_insensitive)]
+///     Black,
+/// }
+///
+/// /*
+/// //The generated code will look like:
+/// impl std::str::FromStr for Color {
+///     type Err = ::strum::ParseError;
+///
+///     fn from_str(s: &str) -> ::core::result::Result<Color, Self::Err> {
+///         match s {
+///             "Red" => ::core::result::Result::Ok(Color::Red),
+///             "Green" => ::core::result::Result::Ok(Color::Green { range:Default::default() }),
+///             "blue" => ::core::result::Result::Ok(Color::Blue(Default::default())),
+///             "b" => ::core::result::Result::Ok(Color::Blue(Default::default())),
+///             s if s.eq_ignore_ascii_case("Black") => ::core::result::Result::Ok(Color::Black),
+///             _ => ::core::result::Result::Err(::strum::ParseError::VariantNotFound),
+///         }
+///     }
+/// }
+/// */
+///
+/// // simple from string
+/// let color_variant = Color::from_str("Red").unwrap();
+/// assert_eq!(Color::Red, color_variant);
+/// // short version works too
+/// let color_variant = Color::from_str("b").unwrap();
+/// assert_eq!(Color::Blue(0), color_variant);
+/// // was disabled for parsing = returns parse-error
+/// let color_variant = Color::from_str("Yellow");
+/// assert!(color_variant.is_err());
+/// // however the variant is still normally usable
+/// println!("{:?}", Color::Yellow);
+/// let color_variant = Color::from_str("bLACk").unwrap();
+/// assert_eq!(Color::Black, color_variant);
+/// ```
+#[proc_macro_derive(EnumString, attributes(strum))]
+pub fn from_string(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
+    let ast = syn::parse_macro_input!(input as DeriveInput);
+
+    let toks =
+        macros::from_string::from_string_inner(&ast).unwrap_or_else(|err| err.to_compile_error());
+    debug_print_generated(&ast, &toks);
+    toks.into()
+}
+
+/// Converts enum variants to `&'static str`.
+///
+/// Implements `AsRef<str>` on your enum using the same rules as
+/// `Display` for determining what string is returned. The difference is that `as_ref()` returns
+/// a `&str` instead of a `String` so you don't allocate any additional memory with each call.
+///
+/// ```
+/// // You need to bring the AsRef trait into scope to use it
+/// use std::convert::AsRef;
+/// use strum_macros::AsRefStr;
+///
+/// #[derive(AsRefStr, Debug)]
+/// enum Color {
+///     #[strum(serialize = "redred")]
+///     Red,
+///     Green {
+///         range: usize,
+///     },
+///     Blue(usize),
+///     Yellow,
+/// }
+///
+/// // uses the serialize string for Display
+/// let red = Color::Red;
+/// assert_eq!("redred", red.as_ref());
+/// // by default the variants Name
+/// let yellow = Color::Yellow;
+/// assert_eq!("Yellow", yellow.as_ref());
+/// // or for string formatting
+/// println!(
+///     "blue: {} green: {}",
+///     Color::Blue(10).as_ref(),
+///     Color::Green { range: 42 }.as_ref()
+/// );
+/// ```
+#[proc_macro_derive(AsRefStr, attributes(strum))]
+pub fn as_ref_str(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
+    let ast = syn::parse_macro_input!(input as DeriveInput);
+
+    let toks =
+        macros::as_ref_str::as_ref_str_inner(&ast).unwrap_or_else(|err| err.to_compile_error());
+    debug_print_generated(&ast, &toks);
+    toks.into()
+}
+
+/// Implements `Strum::VariantNames` which adds an associated constant `VARIANTS` which is an array of discriminant names.
+///
+/// Adds an `impl` block for the `enum` that adds a static `VARIANTS` array of `&'static str` that are the discriminant names.
+/// This will respect the `serialize_all` attribute on the `enum` (like `#[strum(serialize_all = "snake_case")]`.
+///
+/// ```
+/// // import the macros needed
+/// use strum_macros::{EnumString, EnumVariantNames};
+/// // You need to import the trait, to have access to VARIANTS
+/// use strum::VariantNames;
+///
+/// #[derive(Debug, EnumString, EnumVariantNames)]
+/// #[strum(serialize_all = "kebab-case")]
+/// enum Color {
+///     Red,
+///     Blue,
+///     Yellow,
+///     RebeccaPurple,
+/// }
+/// assert_eq!(["red", "blue", "yellow", "rebecca-purple"], Color::VARIANTS);
+/// ```
+#[proc_macro_derive(EnumVariantNames, attributes(strum))]
+pub fn variant_names(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
+    let ast = syn::parse_macro_input!(input as DeriveInput);
+
+    let toks = macros::enum_variant_names::enum_variant_names_inner(&ast)
+        .unwrap_or_else(|err| err.to_compile_error());
+    debug_print_generated(&ast, &toks);
+    toks.into()
+}
+
+#[proc_macro_derive(AsStaticStr, attributes(strum))]
+#[deprecated(
+    since = "0.22.0",
+    note = "please use `#[derive(IntoStaticStr)]` instead"
+)]
+pub fn as_static_str(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
+    let ast = syn::parse_macro_input!(input as DeriveInput);
+
+    let toks = macros::as_ref_str::as_static_str_inner(
+        &ast,
+        &macros::as_ref_str::GenerateTraitVariant::AsStaticStr,
+    )
+    .unwrap_or_else(|err| err.to_compile_error());
+    debug_print_generated(&ast, &toks);
+    toks.into()
+}
+
+/// Implements `From<MyEnum> for &'static str` on an enum.
+///
+/// Implements `From<YourEnum>` and `From<&'a YourEnum>` for `&'static str`. This is
+/// useful for turning an enum variant into a static string.
+/// The Rust `std` provides a blanket impl of the reverse direction - i.e. `impl Into<&'static str> for YourEnum`.
+///
+/// ```
+/// use strum_macros::IntoStaticStr;
+///
+/// #[derive(IntoStaticStr)]
+/// enum State<'a> {
+///     Initial(&'a str),
+///     Finished,
+/// }
+///
+/// fn verify_state<'a>(s: &'a str) {
+///     let mut state = State::Initial(s);
+///     // The following won't work because the lifetime is incorrect:
+///     // let wrong: &'static str = state.as_ref();
+///     // using the trait implemented by the derive works however:
+///     let right: &'static str = state.into();
+///     assert_eq!("Initial", right);
+///     state = State::Finished;
+///     let done: &'static str = state.into();
+///     assert_eq!("Finished", done);
+/// }
+///
+/// verify_state(&"hello world".to_string());
+/// ```
+#[proc_macro_derive(IntoStaticStr, attributes(strum))]
+pub fn into_static_str(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
+    let ast = syn::parse_macro_input!(input as DeriveInput);
+
+    let toks = macros::as_ref_str::as_static_str_inner(
+        &ast,
+        &macros::as_ref_str::GenerateTraitVariant::From,
+    )
+    .unwrap_or_else(|err| err.to_compile_error());
+    debug_print_generated(&ast, &toks);
+    toks.into()
+}
+
+/// implements `std::string::ToString` on an enum
+///
+/// ```
+/// // You need to bring the ToString trait into scope to use it
+/// use std::string::ToString;
+/// use strum_macros;
+///
+/// #[derive(strum_macros::ToString, Debug)]
+/// enum Color {
+///     #[strum(serialize = "redred")]
+///     Red,
+///     Green {
+///         range: usize,
+///     },
+///     Blue(usize),
+///     Yellow,
+/// }
+///
+/// // uses the serialize string for Display
+/// let red = Color::Red;
+/// assert_eq!(String::from("redred"), red.to_string());
+/// // by default the variants Name
+/// let yellow = Color::Yellow;
+/// assert_eq!(String::from("Yellow"), yellow.to_string());
+/// ```
+#[deprecated(
+    since = "0.22.0",
+    note = "please use `#[derive(Display)]` instead. See issue https://github.com/Peternator7/strum/issues/132"
+)]
+#[proc_macro_derive(ToString, attributes(strum))]
+pub fn to_string(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
+    let ast = syn::parse_macro_input!(input as DeriveInput);
+
+    let toks =
+        macros::to_string::to_string_inner(&ast).unwrap_or_else(|err| err.to_compile_error());
+    debug_print_generated(&ast, &toks);
+    toks.into()
+}
+
+/// Converts enum variants to strings.
+///
+/// Deriving `Display` on an enum 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. `Display`
+/// choose which serialization to used based on the following criteria:
+///
+/// 1. If there is a `to_string` property, this value will be used. There can only be one per variant.
+/// 1. Of the various `serialize` properties, the value with the longest length is chosen. If that
+///    behavior isn't desired, you should use `to_string`.
+/// 1. The name of the variant will be used if there are no `serialize` or `to_string` attributes.
+///
+/// ```
+/// // You need to bring the ToString trait into scope to use it
+/// use std::string::ToString;
+/// use strum_macros::Display;
+///
+/// #[derive(Display, Debug)]
+/// enum Color {
+///     #[strum(serialize = "redred")]
+///     Red,
+///     Green {
+///         range: usize,
+///     },
+///     Blue(usize),
+///     Yellow,
+/// }
+///
+/// // uses the serialize string for Display
+/// let red = Color::Red;
+/// assert_eq!(String::from("redred"), format!("{}", red));
+/// // by default the variants Name
+/// let yellow = Color::Yellow;
+/// assert_eq!(String::from("Yellow"), yellow.to_string());
+/// // or for string formatting
+/// println!(
+///     "blue: {} green: {}",
+///     Color::Blue(10),
+///     Color::Green { range: 42 }
+/// );
+/// ```
+#[proc_macro_derive(Display, attributes(strum))]
+pub fn display(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
+    let ast = syn::parse_macro_input!(input as DeriveInput);
+
+    let toks = macros::display::display_inner(&ast).unwrap_or_else(|err| err.to_compile_error());
+    debug_print_generated(&ast, &toks);
+    toks.into()
+}
+
+/// Creates a new type that iterates of the variants of an enum.
+///
+/// Iterate over the variants of an Enum. Any additional data on your variants will be set to `Default::default()`.
+/// The macro implements `strum::IntoEnumIterator` on your enum and creates a new type called `YourEnumIter` that is the iterator object.
+/// You cannot derive `EnumIter` on any type with a lifetime bound (`<'a>`) because the iterator would surely
+/// create [unbounded lifetimes](https://doc.rust-lang.org/nightly/nomicon/unbounded-lifetimes.html).
+///
+/// ```
+///
+/// // You need to bring the trait into scope to use it!
+/// use strum::IntoEnumIterator;
+/// use strum_macros::EnumIter;
+///
+/// #[derive(EnumIter, Debug, PartialEq)]
+/// enum Color {
+///     Red,
+///     Green { range: usize },
+///     Blue(usize),
+///     Yellow,
+/// }
+///
+/// // It's simple to iterate over the variants of an enum.
+/// for color in Color::iter() {
+///     println!("My favorite color is {:?}", color);
+/// }
+///
+/// let mut ci = Color::iter();
+/// assert_eq!(Some(Color::Red), ci.next());
+/// assert_eq!(Some(Color::Green {range: 0}), ci.next());
+/// assert_eq!(Some(Color::Blue(0)), ci.next());
+/// assert_eq!(Some(Color::Yellow), ci.next());
+/// assert_eq!(None, ci.next());
+/// ```
+#[proc_macro_derive(EnumIter, attributes(strum))]
+pub fn enum_iter(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
+    let ast = syn::parse_macro_input!(input as DeriveInput);
+
+    let toks =
+        macros::enum_iter::enum_iter_inner(&ast).unwrap_or_else(|err| err.to_compile_error());
+    debug_print_generated(&ast, &toks);
+    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()
+}
+
+/// Generated `try_as_*()` methods for all tuple-style variants.
+/// E.g. `Message.try_as_write()`.
+///
+/// These methods will only be generated for tuple-style variants, not for named or unit variants.
+///
+/// ```
+/// use strum_macros::EnumTryAs;
+///
+/// #[derive(EnumTryAs, Debug)]
+/// enum Message {
+///     Quit,
+///     Move { x: i32, y: i32 },
+///     Write(String),
+///     ChangeColor(i32, i32, i32),
+/// }
+///
+/// assert_eq!(
+///     Message::Write(String::from("Hello")).try_as_write(),
+///     Some(String::from("Hello"))
+/// );
+/// assert_eq!(
+///     Message::ChangeColor(1, 2, 3).try_as_change_color(),
+///     Some((1, 2, 3))
+/// );
+/// ```
+#[proc_macro_derive(EnumTryAs, attributes(strum))]
+pub fn enum_try_as(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
+    let ast = syn::parse_macro_input!(input as DeriveInput);
+
+    let toks =
+        macros::enum_try_as::enum_try_as_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
+/// `from_repr(discriminant: usize) -> Option<YourEnum>` as a standalone function on the enum. For
+/// variants with additional data, the returned variant will use the `Default` trait to fill the
+/// data. The discriminant follows the same rules as `rustc`. The first discriminant is zero and each
+/// successive variant has a discriminant of one greater than the previous variant, except where an
+/// explicit discriminant is specified. The type of the discriminant will match the `repr` type if
+/// it is specifed.
+///
+/// When the macro is applied using rustc >= 1.46 and when there is no additional data on any of
+/// the variants, the `from_repr` function is marked `const`. rustc >= 1.46 is required
+/// to allow `match` statements in `const fn`. The no additional data requirement is due to the
+/// inability to use `Default::default()` in a `const fn`.
+///
+/// You cannot derive `FromRepr` on any type with a lifetime bound (`<'a>`) because the function would surely
+/// create [unbounded lifetimes](https://doc.rust-lang.org/nightly/nomicon/unbounded-lifetimes.html).
+///
+/// ```
+///
+/// use strum_macros::FromRepr;
+///
+/// #[derive(FromRepr, Debug, PartialEq)]
+/// enum Color {
+///     Red,
+///     Green { range: usize },
+///     Blue(usize),
+///     Yellow,
+/// }
+///
+/// assert_eq!(Some(Color::Red), Color::from_repr(0));
+/// assert_eq!(Some(Color::Green {range: 0}), Color::from_repr(1));
+/// assert_eq!(Some(Color::Blue(0)), Color::from_repr(2));
+/// assert_eq!(Some(Color::Yellow), Color::from_repr(3));
+/// assert_eq!(None, Color::from_repr(4));
+///
+/// // Custom discriminant tests
+/// #[derive(FromRepr, Debug, PartialEq)]
+/// #[repr(u8)]
+/// enum Vehicle {
+///     Car = 1,
+///     Truck = 3,
+/// }
+///
+/// assert_eq!(None, Vehicle::from_repr(0));
+/// ```
+///
+/// On versions of rust >= 1.46, the `from_repr` function is marked `const`.
+///
+/// ```rust
+/// use strum_macros::FromRepr;
+///
+/// #[derive(FromRepr, Debug, PartialEq)]
+/// #[repr(u8)]
+/// enum Number {
+///     One = 1,
+///     Three = 3,
+/// }
+///
+/// # #[rustversion::since(1.46)]
+/// const fn number_from_repr(d: u8) -> Option<Number> {
+///     Number::from_repr(d)
+/// }
+///
+/// # #[rustversion::before(1.46)]
+/// # fn number_from_repr(d: u8) -> Option<Number> {
+/// #     Number::from_repr(d)
+/// # }
+/// assert_eq!(None, number_from_repr(0));
+/// assert_eq!(Some(Number::One), number_from_repr(1));
+/// assert_eq!(None, number_from_repr(2));
+/// assert_eq!(Some(Number::Three), number_from_repr(3));
+/// assert_eq!(None, number_from_repr(4));
+/// ```
+
+#[proc_macro_derive(FromRepr, attributes(strum))]
+pub fn from_repr(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
+    let ast = syn::parse_macro_input!(input as DeriveInput);
+
+    let toks =
+        macros::from_repr::from_repr_inner(&ast).unwrap_or_else(|err| err.to_compile_error());
+    debug_print_generated(&ast, &toks);
+    toks.into()
+}
+
+/// Add a verbose message to an enum variant.
+///
+/// Encode strings into the enum itself. The `strum_macros::EmumMessage` macro implements the `strum::EnumMessage` trait.
+/// `EnumMessage` looks for `#[strum(message="...")]` attributes on your variants.
+/// You can also provided a `detailed_message="..."` attribute to create a seperate more detailed message than the first.
+///
+/// `EnumMessage` also exposes the variants doc comments through `get_documentation()`. This is useful in some scenarios,
+/// but `get_message` should generally be preferred. Rust doc comments are intended for developer facing documentation,
+/// not end user messaging.
+///
+/// ```
+/// // You need to bring the trait into scope to use it
+/// use strum::EnumMessage;
+/// use strum_macros;
+///
+/// #[derive(strum_macros::EnumMessage, Debug)]
+/// #[allow(dead_code)]
+/// enum Color {
+///     /// Danger color.
+///     #[strum(message = "Red", detailed_message = "This is very red")]
+///     Red,
+///     #[strum(message = "Simply Green")]
+///     Green { range: usize },
+///     #[strum(serialize = "b", serialize = "blue")]
+///     Blue(usize),
+/// }
+///
+/// // Generated code looks like more or less like this:
+/// /*
+/// impl ::strum::EnumMessage for Color {
+///     fn get_message(&self) -> ::core::option::Option<&'static str> {
+///         match self {
+///             &Color::Red => ::core::option::Option::Some("Red"),
+///             &Color::Green {..} => ::core::option::Option::Some("Simply Green"),
+///             _ => None
+///         }
+///     }
+///
+///     fn get_detailed_message(&self) -> ::core::option::Option<&'static str> {
+///         match self {
+///             &Color::Red => ::core::option::Option::Some("This is very red"),
+///             &Color::Green {..}=> ::core::option::Option::Some("Simply Green"),
+///             _ => None
+///         }
+///     }
+///
+///     fn get_documentation(&self) -> ::std::option::Option<&'static str> {
+///         match self {
+///             &Color::Red => ::std::option::Option::Some("Danger color."),
+///             _ => None
+///         }
+///     }
+///
+///     fn get_serializations(&self) -> &'static [&'static str] {
+///         match self {
+///             &Color::Red => {
+///                 static ARR: [&'static str; 1] = ["Red"];
+///                 &ARR
+///             },
+///             &Color::Green {..}=> {
+///                 static ARR: [&'static str; 1] = ["Green"];
+///                 &ARR
+///             },
+///             &Color::Blue (..) => {
+///                 static ARR: [&'static str; 2] = ["b", "blue"];
+///                 &ARR
+///             },
+///         }
+///     }
+/// }
+/// */
+///
+/// let c = Color::Red;
+/// assert_eq!("Red", c.get_message().unwrap());
+/// assert_eq!("This is very red", c.get_detailed_message().unwrap());
+/// assert_eq!("Danger color.", c.get_documentation().unwrap());
+/// assert_eq!(["Red"], c.get_serializations());
+/// ```
+#[proc_macro_derive(EnumMessage, attributes(strum))]
+pub fn enum_messages(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
+    let ast = syn::parse_macro_input!(input as DeriveInput);
+
+    let toks = macros::enum_messages::enum_message_inner(&ast)
+        .unwrap_or_else(|err| err.to_compile_error());
+    debug_print_generated(&ast, &toks);
+    toks.into()
+}
+
+/// Add custom properties to enum variants.
+///
+/// Enables the encoding of arbitary constants into enum variants. This method
+/// currently only supports adding additional string values. Other types of literals are still
+/// experimental in the rustc compiler. The generated code works by nesting match statements.
+/// The first match statement matches on the type of the enum, and the inner match statement
+/// matches on the name of the property requested. This design works well for enums with a small
+/// number of variants and properties, but scales linearly with the number of variants so may not
+/// be the best choice in all situations.
+///
+/// ```
+///
+/// use strum_macros;
+/// // bring the trait into scope
+/// use strum::EnumProperty;
+///
+/// #[derive(strum_macros::EnumProperty, Debug)]
+/// #[allow(dead_code)]
+/// enum Color {
+///     #[strum(props(Red = "255", Blue = "255", Green = "255"))]
+///     White,
+///     #[strum(props(Red = "0", Blue = "0", Green = "0"))]
+///     Black,
+///     #[strum(props(Red = "0", Blue = "255", Green = "0"))]
+///     Blue,
+///     #[strum(props(Red = "255", Blue = "0", Green = "0"))]
+///     Red,
+///     #[strum(props(Red = "0", Blue = "0", Green = "255"))]
+///     Green,
+/// }
+///
+/// let my_color = Color::Red;
+/// let display = format!(
+///     "My color is {:?}. It's RGB is {},{},{}",
+///     my_color,
+///     my_color.get_str("Red").unwrap(),
+///     my_color.get_str("Green").unwrap(),
+///     my_color.get_str("Blue").unwrap()
+/// );
+/// assert_eq!("My color is Red. It\'s RGB is 255,0,0", &display);
+/// ```
+
+#[proc_macro_derive(EnumProperty, attributes(strum))]
+pub fn enum_properties(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
+    let ast = syn::parse_macro_input!(input as DeriveInput);
+
+    let toks = macros::enum_properties::enum_properties_inner(&ast)
+        .unwrap_or_else(|err| err.to_compile_error());
+    debug_print_generated(&ast, &toks);
+    toks.into()
+}
+
+/// Generate a new type with only the discriminant names.
+///
+/// Given an enum named `MyEnum`, generates another enum called `MyEnumDiscriminants` with the same
+/// variants but without any data fields. This is useful when you wish to determine the variant of
+/// an `enum` but one or more of the variants contains a non-`Default` field. `From`
+/// implementations are generated so that you can easily convert from `MyEnum` to
+/// `MyEnumDiscriminants`.
+///
+/// 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.
+///
+/// Note, the variant attributes passed to the discriminant enum are filtered to avoid compilation
+/// errors due to the derives mismatches, thus only `#[doc]`, `#[cfg]`, `#[allow]`, and `#[deny]`
+/// are passed through by default. If you want to specify a custom attribute on the discriminant
+/// variant, wrap it with `#[strum_discriminants(...)]` attribute.
+///
+/// ```
+/// // Bring trait into scope
+/// use std::str::FromStr;
+/// use strum::{IntoEnumIterator, EnumMessage};
+/// use strum_macros::{EnumDiscriminants, EnumIter, EnumString, EnumMessage};
+///
+/// #[derive(Debug)]
+/// struct NonDefault;
+///
+/// // simple example
+/// # #[allow(dead_code)]
+/// #[derive(Debug, EnumDiscriminants)]
+/// #[strum_discriminants(derive(EnumString, EnumMessage))]
+/// enum MyEnum {
+///     #[strum_discriminants(strum(message = "Variant zero"))]
+///     Variant0(NonDefault),
+///     Variant1 { a: NonDefault },
+/// }
+///
+/// // You can rename the generated enum using the `#[strum_discriminants(name(OtherName))]` attribute:
+/// # #[allow(dead_code)]
+/// #[derive(Debug, EnumDiscriminants)]
+/// #[strum_discriminants(derive(EnumIter))]
+/// #[strum_discriminants(name(MyVariants))]
+/// enum MyEnumR {
+///     Variant0(bool),
+///     Variant1 { a: bool },
+/// }
+///
+/// // test simple example
+/// assert_eq!(
+///     MyEnumDiscriminants::Variant0,
+///     MyEnumDiscriminants::from_str("Variant0").unwrap()
+/// );
+/// // test rename example combined with EnumIter
+/// assert_eq!(
+///     vec![MyVariants::Variant0, MyVariants::Variant1],
+///     MyVariants::iter().collect::<Vec<_>>()
+/// );
+///
+/// // Make use of the auto-From conversion to check whether an instance of `MyEnum` matches a
+/// // `MyEnumDiscriminants` discriminant.
+/// assert_eq!(
+///     MyEnumDiscriminants::Variant0,
+///     MyEnum::Variant0(NonDefault).into()
+/// );
+/// assert_eq!(
+///     MyEnumDiscriminants::Variant0,
+///     MyEnumDiscriminants::from(MyEnum::Variant0(NonDefault))
+/// );
+///
+/// // Make use of the EnumMessage on the `MyEnumDiscriminants` discriminant.
+/// assert_eq!(
+///     MyEnumDiscriminants::Variant0.get_message(),
+///     Some("Variant zero")
+/// );
+/// ```
+///
+/// It is also possible to specify the visibility (e.g. `pub`/`pub(crate)`/etc.)
+/// of the generated enum. By default, the generated enum inherits the
+/// visibility of the parent enum it was generated from.
+///
+/// ```
+/// use strum_macros::EnumDiscriminants;
+///
+/// // You can set the visibility of the generated enum using the `#[strum_discriminants(vis(..))]` attribute:
+/// mod inner {
+///     use strum_macros::EnumDiscriminants;
+///
+///     # #[allow(dead_code)]
+///     #[derive(Debug, EnumDiscriminants)]
+///     #[strum_discriminants(vis(pub))]
+///     #[strum_discriminants(name(PubDiscriminants))]
+///     enum PrivateEnum {
+///         Variant0(bool),
+///         Variant1 { a: bool },
+///     }
+/// }
+///
+/// // test visibility example, `PrivateEnum` should not be accessible here
+/// assert_ne!(
+///     inner::PubDiscriminants::Variant0,
+///     inner::PubDiscriminants::Variant1,
+/// );
+/// ```
+#[proc_macro_derive(EnumDiscriminants, attributes(strum, strum_discriminants))]
+pub fn enum_discriminants(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
+    let ast = syn::parse_macro_input!(input as DeriveInput);
+
+    let toks = macros::enum_discriminants::enum_discriminants_inner(&ast)
+        .unwrap_or_else(|err| err.to_compile_error());
+    debug_print_generated(&ast, &toks);
+    toks.into()
+}
+
+/// Add a constant `usize` equal to the number of variants.
+///
+/// For a given enum generates implementation of `strum::EnumCount`,
+/// which adds a static property `COUNT` of type usize that holds the number of variants.
+///
+/// ```
+/// use strum::{EnumCount, IntoEnumIterator};
+/// use strum_macros::{EnumCount as EnumCountMacro, EnumIter};
+///
+/// #[derive(Debug, EnumCountMacro, EnumIter)]
+/// enum Week {
+///     Sunday,
+///     Monday,
+///     Tuesday,
+///     Wednesday,
+///     Thursday,
+///     Friday,
+///     Saturday,
+/// }
+///
+/// assert_eq!(7, Week::COUNT);
+/// assert_eq!(Week::iter().count(), Week::COUNT);
+///
+/// ```
+#[proc_macro_derive(EnumCount, attributes(strum))]
+pub fn enum_count(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
+    let ast = syn::parse_macro_input!(input as DeriveInput);
+    let toks =
+        macros::enum_count::enum_count_inner(&ast).unwrap_or_else(|err| err.to_compile_error());
+    debug_print_generated(&ast, &toks);
+    toks.into()
+}
diff --git a/src/macros/enum_count.rs b/src/macros/enum_count.rs
new file mode 100644
index 0000000..b9e6aaa
--- /dev/null
+++ b/src/macros/enum_count.rs
@@ -0,0 +1,34 @@
+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.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()?;
+    let strum_module_path = type_properties.crate_module_path();
+
+    // Used in the quasi-quotation below as `#name`
+    let name = &ast.ident;
+
+    // Helper is provided for handling complex generic types correctly and effortlessly
+    let (impl_generics, ty_generics, where_clause) = ast.generics.split_for_impl();
+
+    Ok(quote! {
+        // Implementation
+        impl #impl_generics #strum_module_path::EnumCount for #name #ty_generics #where_clause {
+            const COUNT: usize = #n;
+        }
+    })
+}
diff --git a/src/macros/enum_discriminants.rs b/src/macros/enum_discriminants.rs
new file mode 100644
index 0000000..4e54a30
--- /dev/null
+++ b/src/macros/enum_discriminants.rs
@@ -0,0 +1,164 @@
+use proc_macro2::{Span, TokenStream, TokenTree};
+use quote::{quote, ToTokens};
+use syn::parse_quote;
+use syn::{Data, DeriveInput, Fields};
+
+use crate::helpers::{non_enum_error, strum_discriminants_passthrough_error, HasTypeProperties};
+
+/// Attributes to copy from the main enum's variants to the discriminant enum's variants.
+///
+/// Attributes not in this list may be for other `proc_macro`s on the main enum, and may cause
+/// compilation problems when copied across.
+const ATTRIBUTES_TO_COPY: &[&str] = &["doc", "cfg", "allow", "deny", "strum_discriminants"];
+
+pub fn enum_discriminants_inner(ast: &DeriveInput) -> syn::Result<TokenStream> {
+    let name = &ast.ident;
+    let vis = &ast.vis;
+
+    let variants = match &ast.data {
+        Data::Enum(v) => &v.variants,
+        _ => return Err(non_enum_error()),
+    };
+
+    // Derives for the generated enum
+    let type_properties = ast.get_type_properties()?;
+
+    let derives = type_properties.discriminant_derives;
+
+    let derives = quote! {
+        #[derive(Clone, Copy, Debug, PartialEq, Eq, #(#derives),*)]
+    };
+
+    // Work out the name
+    let default_name = syn::Ident::new(&format!("{}Discriminants", name), Span::call_site());
+
+    let discriminants_name = type_properties.discriminant_name.unwrap_or(default_name);
+    let discriminants_vis = type_properties
+        .discriminant_vis
+        .unwrap_or_else(|| vis.clone());
+
+    // Pass through all other attributes
+    let pass_though_attributes = type_properties.discriminant_others;
+
+    // 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. Only passthrough the whitelisted
+        // attributes and proxy `#[strum_discriminants(...)]` attributes
+        let attrs = variant
+            .attrs
+            .iter()
+            .filter(|attr| {
+                ATTRIBUTES_TO_COPY
+                    .iter()
+                    .any(|attr_whitelisted| attr.path().is_ident(attr_whitelisted))
+            })
+            .map(|attr| {
+                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 {
+                        TokenTree::Group(ref group) => group.stream(),
+                        _ => {
+                            return Err(strum_discriminants_passthrough_error(&passthrough_group));
+                        }
+                    };
+                    if passthrough_attribute.is_empty() {
+                        return Err(strum_discriminants_passthrough_error(&passthrough_group));
+                    }
+                    Ok(quote! { #[#passthrough_attribute] })
+                } else {
+                    Ok(attr.to_token_stream())
+                }
+            })
+            .collect::<Result<Vec<_>, _>>()?;
+
+        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;
+            let params = match &variant.fields {
+                Fields::Unit => quote! {},
+                Fields::Unnamed(_fields) => {
+                    quote! { (..) }
+                }
+                Fields::Named(_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 ::core::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 ::core::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
+                }
+            }
+        }
+    };
+
+    Ok(quote! {
+        /// Auto-generated discriminant enum variants
+        #derives
+        #(#[ #pass_though_attributes ])*
+        #discriminants_vis enum #discriminants_name {
+            #(#discriminants),*
+        }
+
+        #impl_from
+        #impl_from_ref
+    })
+}
diff --git a/src/macros/enum_is.rs b/src/macros/enum_is.rs
new file mode 100644
index 0000000..ecada45
--- /dev/null
+++ b/src/macros/enum_is.rs
@@ -0,0 +1,44 @@
+use crate::helpers::{non_enum_error, snakify, HasStrumVariantProperties};
+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 (impl_generics, ty_generics, where_clause) = ast.generics.split_for_impl();
+
+    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()));
+            let doc_comment = format!("Returns [true] if the enum is [{}::{}] otherwise [false]", enum_name, variant_name);
+            Some(quote! {
+                #[must_use]
+                #[inline]
+                #[doc = #doc_comment]
+                pub const fn #fn_name(&self) -> bool {
+                    match self {
+                        &#enum_name::#variant_name { .. } => true,
+                        _ => false
+                    }
+                }
+            })
+        })
+        .collect();
+
+    Ok(quote! {
+        impl #impl_generics #enum_name  #ty_generics #where_clause {
+            #(#variants)*
+        }
+    }
+    .into())
+}
diff --git a/src/macros/enum_iter.rs b/src/macros/enum_iter.rs
new file mode 100644
index 0000000..0e700aa
--- /dev/null
+++ b/src/macros/enum_iter.rs
@@ -0,0 +1,172 @@
+use proc_macro2::{Span, TokenStream};
+use quote::quote;
+use syn::{Data, DeriveInput, Fields, Ident};
+
+use crate::helpers::{non_enum_error, HasStrumVariantProperties, HasTypeProperties};
+
+pub fn enum_iter_inner(ast: &DeriveInput) -> syn::Result<TokenStream> {
+    let name = &ast.ident;
+    let gen = &ast.generics;
+    let (impl_generics, ty_generics, where_clause) = gen.split_for_impl();
+    let vis = &ast.vis;
+    let type_properties = ast.get_type_properties()?;
+    let strum_module_path = type_properties.crate_module_path();
+    let doc_comment = format!("An iterator over the variants of [{}]", name);
+
+    if gen.lifetimes().count() > 0 {
+        return Err(syn::Error::new(
+            Span::call_site(),
+            "This macro doesn't support enums with lifetimes. \
+             The resulting enums would be unbounded.",
+        ));
+    }
+
+    let phantom_data = if gen.type_params().count() > 0 {
+        let g = gen.type_params().map(|param| &param.ident);
+        quote! { < ( #(#g),* ) > }
+    } else {
+        quote! { < () > }
+    };
+
+    let variants = match &ast.data {
+        Data::Enum(v) => &v.variants,
+        _ => return Err(non_enum_error()),
+    };
+
+    let mut arms = Vec::new();
+    let mut idx = 0usize;
+    for variant in variants {
+        if variant.get_variant_properties()?.disabled.is_some() {
+            continue;
+        }
+
+        let ident = &variant.ident;
+        let params = match &variant.fields {
+            Fields::Unit => quote! {},
+            Fields::Unnamed(fields) => {
+                let defaults = ::core::iter::repeat(quote!(::core::default::Default::default()))
+                    .take(fields.unnamed.len());
+                quote! { (#(#defaults),*) }
+            }
+            Fields::Named(fields) => {
+                let fields = fields
+                    .named
+                    .iter()
+                    .map(|field| field.ident.as_ref().unwrap());
+                quote! { {#(#fields: ::core::default::Default::default()),*} }
+            }
+        };
+
+        arms.push(quote! {#idx => ::core::option::Option::Some(#name::#ident #params)});
+        idx += 1;
+    }
+
+    let variant_count = arms.len();
+    arms.push(quote! { _ => ::core::option::Option::None });
+    let iter_name = syn::parse_str::<Ident>(&format!("{}Iter", name)).unwrap();
+
+    // Create a string literal "MyEnumIter" to use in the debug impl.
+    let iter_name_debug_struct =
+        syn::parse_str::<syn::LitStr>(&format!("\"{}\"", iter_name)).unwrap();
+
+    Ok(quote! {
+        #[doc = #doc_comment]
+        #[allow(
+            missing_copy_implementations,
+        )]
+        #vis struct #iter_name #ty_generics {
+            idx: usize,
+            back_idx: usize,
+            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 {
+                // 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)
+                    .field("len", &self.len())
+                    .finish()
+            }
+        }
+
+        impl #impl_generics #iter_name #ty_generics #where_clause {
+            fn get(&self, idx: usize) -> ::core::option::Option<#name #ty_generics> {
+                match idx {
+                    #(#arms),*
+                }
+            }
+        }
+
+        impl #impl_generics #strum_module_path::IntoEnumIterator for #name #ty_generics #where_clause {
+            type Iterator = #iter_name #ty_generics;
+            fn iter() -> #iter_name #ty_generics {
+                #iter_name {
+                    idx: 0,
+                    back_idx: 0,
+                    marker: ::core::marker::PhantomData,
+                }
+            }
+        }
+
+        impl #impl_generics Iterator for #iter_name #ty_generics #where_clause {
+            type Item = #name #ty_generics;
+
+            fn next(&mut self) -> ::core::option::Option<<Self as Iterator>::Item> {
+                self.nth(0)
+            }
+
+            fn size_hint(&self) -> (usize, ::core::option::Option<usize>) {
+                let t = if self.idx + self.back_idx >= #variant_count { 0 } else { #variant_count - self.idx - self.back_idx };
+                (t, Some(t))
+            }
+
+            fn nth(&mut self, n: usize) -> ::core::option::Option<<Self as Iterator>::Item> {
+                let idx = self.idx + n + 1;
+                if idx + self.back_idx > #variant_count {
+                    // We went past the end of the iterator. Freeze idx at #variant_count
+                    // so that it doesn't overflow if the user calls this repeatedly.
+                    // See PR #76 for context.
+                    self.idx = #variant_count;
+                    ::core::option::Option::None
+                } else {
+                    self.idx = idx;
+                    self.get(idx - 1)
+                }
+            }
+        }
+
+        impl #impl_generics ExactSizeIterator for #iter_name #ty_generics #where_clause {
+            fn len(&self) -> usize {
+                self.size_hint().0
+            }
+        }
+
+        impl #impl_generics DoubleEndedIterator for #iter_name #ty_generics #where_clause {
+            fn next_back(&mut self) -> ::core::option::Option<<Self as Iterator>::Item> {
+                let back_idx = self.back_idx + 1;
+
+                if self.idx + back_idx > #variant_count {
+                    // We went past the end of the iterator. Freeze back_idx at #variant_count
+                    // so that it doesn't overflow if the user calls this repeatedly.
+                    // See PR #76 for context.
+                    self.back_idx = #variant_count;
+                    ::core::option::Option::None
+                } else {
+                    self.back_idx = back_idx;
+                    self.get(#variant_count - self.back_idx)
+                }
+            }
+        }
+
+        impl #impl_generics Clone for #iter_name #ty_generics #where_clause {
+            fn clone(&self) -> #iter_name #ty_generics {
+                #iter_name {
+                    idx: self.idx,
+                    back_idx: self.back_idx,
+                    marker: self.marker.clone(),
+                }
+            }
+        }
+    })
+}
diff --git a/src/macros/enum_messages.rs b/src/macros/enum_messages.rs
new file mode 100644
index 0000000..c056108
--- /dev/null
+++ b/src/macros/enum_messages.rs
@@ -0,0 +1,138 @@
+use proc_macro2::TokenStream;
+use quote::quote;
+use syn::{Data, DeriveInput, Fields, LitStr};
+
+use crate::helpers::{non_enum_error, HasStrumVariantProperties, HasTypeProperties};
+
+pub fn enum_message_inner(ast: &DeriveInput) -> syn::Result<TokenStream> {
+    let name = &ast.ident;
+    let (impl_generics, ty_generics, where_clause) = ast.generics.split_for_impl();
+    let variants = match &ast.data {
+        Data::Enum(v) => &v.variants,
+        _ => return Err(non_enum_error()),
+    };
+
+    let type_properties = ast.get_type_properties()?;
+    let strum_module_path = type_properties.crate_module_path();
+
+    let mut arms = Vec::new();
+    let mut detailed_arms = Vec::new();
+    let mut documentation_arms = Vec::new();
+    let mut serializations = Vec::new();
+
+    for variant in variants {
+        let variant_properties = variant.get_variant_properties()?;
+        let messages = variant_properties.message.as_ref();
+        let detailed_messages = variant_properties.detailed_message.as_ref();
+        let documentation = &variant_properties.documentation;
+        let ident = &variant.ident;
+
+        let params = match variant.fields {
+            Fields::Unit => quote! {},
+            Fields::Unnamed(..) => quote! { (..) },
+            Fields::Named(..) => quote! { {..} },
+        };
+
+        // You can't disable getting the serializations.
+        {
+            let serialization_variants =
+                variant_properties.get_serializations(type_properties.case_style);
+
+            let count = serialization_variants.len();
+            serializations.push(quote! {
+                &#name::#ident #params => {
+                    static ARR: [&'static str; #count] = [#(#serialization_variants),*];
+                    &ARR
+                }
+            });
+        }
+
+        // But you can disable the messages.
+        if variant_properties.disabled.is_some() {
+            continue;
+        }
+
+        if let Some(msg) = messages {
+            let params = params.clone();
+
+            // Push the simple message.
+            let tokens = quote! { &#name::#ident #params => ::core::option::Option::Some(#msg) };
+            arms.push(tokens.clone());
+
+            if detailed_messages.is_none() {
+                detailed_arms.push(tokens);
+            }
+        }
+
+        if let Some(msg) = detailed_messages {
+            let params = params.clone();
+            // Push the detailed message.
+            detailed_arms
+                .push(quote! { &#name::#ident #params => ::core::option::Option::Some(#msg) });
+        }
+
+        if !documentation.is_empty() {
+            let params = params.clone();
+            // Strip a single leading space from each documentation line.
+            let documentation: Vec<LitStr> = documentation.iter().map(|lit_str| {
+                let line = lit_str.value();
+                if line.starts_with(' ') {
+                    LitStr::new(&line.as_str()[1..], lit_str.span())
+                } else {
+                    lit_str.clone()
+                }
+            }).collect();
+            if documentation.len() == 1 {
+                let text = &documentation[0];
+                documentation_arms
+                    .push(quote! { &#name::#ident #params => ::core::option::Option::Some(#text) });
+            } else {
+                // Push the documentation.
+                documentation_arms
+                    .push(quote! {
+                        &#name::#ident #params => ::core::option::Option::Some(concat!(#(concat!(#documentation, "\n")),*))
+                    });
+            }
+        }
+    }
+
+    if arms.len() < variants.len() {
+        arms.push(quote! { _ => ::core::option::Option::None });
+    }
+
+    if detailed_arms.len() < variants.len() {
+        detailed_arms.push(quote! { _ => ::core::option::Option::None });
+    }
+
+    if documentation_arms.len() < variants.len() {
+        documentation_arms.push(quote! { _ => ::core::option::Option::None });
+    }
+
+    Ok(quote! {
+        impl #impl_generics #strum_module_path::EnumMessage for #name #ty_generics #where_clause {
+            fn get_message(&self) -> ::core::option::Option<&'static str> {
+                match self {
+                    #(#arms),*
+                }
+            }
+
+            fn get_detailed_message(&self) -> ::core::option::Option<&'static str> {
+                match self {
+                    #(#detailed_arms),*
+                }
+            }
+
+            fn get_documentation(&self) -> ::core::option::Option<&'static str> {
+                match self {
+                    #(#documentation_arms),*
+                }
+            }
+
+            fn get_serializations(&self) -> &'static [&'static str] {
+                match self {
+                    #(#serializations),*
+                }
+            }
+        }
+    })
+}
diff --git a/src/macros/enum_properties.rs b/src/macros/enum_properties.rs
new file mode 100644
index 0000000..2583096
--- /dev/null
+++ b/src/macros/enum_properties.rs
@@ -0,0 +1,61 @@
+use proc_macro2::TokenStream;
+use quote::quote;
+use syn::{Data, DeriveInput, Fields};
+
+use crate::helpers::{non_enum_error, HasStrumVariantProperties, HasTypeProperties};
+
+pub fn enum_properties_inner(ast: &DeriveInput) -> syn::Result<TokenStream> {
+    let name = &ast.ident;
+    let (impl_generics, ty_generics, where_clause) = ast.generics.split_for_impl();
+    let variants = match &ast.data {
+        Data::Enum(v) => &v.variants,
+        _ => return Err(non_enum_error()),
+    };
+    let type_properties = ast.get_type_properties()?;
+    let strum_module_path = type_properties.crate_module_path();
+
+    let mut arms = Vec::new();
+    for variant in variants {
+        let ident = &variant.ident;
+        let variant_properties = variant.get_variant_properties()?;
+        let mut string_arms = Vec::new();
+        // But you can disable the messages.
+        if variant_properties.disabled.is_some() {
+            continue;
+        }
+
+        let params = match variant.fields {
+            Fields::Unit => quote! {},
+            Fields::Unnamed(..) => quote! { (..) },
+            Fields::Named(..) => quote! { {..} },
+        };
+
+        for (key, value) in variant_properties.string_props {
+            string_arms.push(quote! { #key => ::core::option::Option::Some( #value )});
+        }
+
+        string_arms.push(quote! { _ => ::core::option::Option::None });
+
+        arms.push(quote! {
+            &#name::#ident #params => {
+                match prop {
+                    #(#string_arms),*
+                }
+            }
+        });
+    }
+
+    if arms.len() < variants.len() {
+        arms.push(quote! { _ => ::core::option::Option::None });
+    }
+
+    Ok(quote! {
+        impl #impl_generics #strum_module_path::EnumProperty for #name #ty_generics #where_clause {
+            fn get_str(&self, prop: &str) -> ::core::option::Option<&'static str> {
+                match self {
+                    #(#arms),*
+                }
+            }
+        }
+    })
+}
diff --git a/src/macros/enum_try_as.rs b/src/macros/enum_try_as.rs
new file mode 100644
index 0000000..088a1f1
--- /dev/null
+++ b/src/macros/enum_try_as.rs
@@ -0,0 +1,80 @@
+use crate::helpers::{non_enum_error, snakify, HasStrumVariantProperties};
+use proc_macro2::TokenStream;
+use quote::{format_ident, quote, ToTokens};
+use syn::{Data, DeriveInput};
+
+pub fn enum_try_as_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;
+            }
+
+            match &variant.fields {
+                syn::Fields::Unnamed(values) => {
+                    let variant_name = &variant.ident;
+                    let types: Vec<_> = values.unnamed.iter().map(|field| {
+                        field.to_token_stream()
+                    }).collect();
+                    let field_names: Vec<_> = values.unnamed.iter().enumerate().map(|(i, _)| {
+                        let name = "x".repeat(i + 1);
+                        let name = format_ident!("{}", name);
+                        quote! {#name}
+                    }).collect();
+
+                    let move_fn_name = format_ident!("try_as_{}", snakify(&variant_name.to_string()));
+                    let ref_fn_name = format_ident!("try_as_{}_ref", snakify(&variant_name.to_string()));
+                    let mut_fn_name = format_ident!("try_as_{}_mut", snakify(&variant_name.to_string()));
+
+                    Some(quote! {
+                        #[must_use]
+                        #[inline]
+                        pub fn #move_fn_name(self) -> ::core::option::Option<(#(#types),*)> {
+                            match self {
+                                #enum_name::#variant_name (#(#field_names),*) => Some((#(#field_names),*)),
+                                _ => None
+                            }
+                        }
+
+                        #[must_use]
+                        #[inline]
+                        pub const fn #ref_fn_name(&self) -> ::core::option::Option<(#(&#types),*)> {
+                            match self {
+                                #enum_name::#variant_name (#(#field_names),*) => Some((#(#field_names),*)),
+                                _ => None
+                            }
+                        }
+
+                        #[must_use]
+                        #[inline]
+                        pub fn #mut_fn_name(&mut self) -> ::core::option::Option<(#(&mut #types),*)> {
+                            match self {
+                                #enum_name::#variant_name (#(#field_names),*) => Some((#(#field_names),*)),
+                                _ => None
+                            }
+                        }
+                    })
+                },
+                _ => {
+                    return None;
+                }
+            }
+
+        })
+        .collect();
+
+    Ok(quote! {
+        impl #enum_name {
+            #(#variants)*
+        }
+    }
+    .into())
+}
diff --git a/src/macros/enum_variant_names.rs b/src/macros/enum_variant_names.rs
new file mode 100644
index 0000000..c54d45d
--- /dev/null
+++ b/src/macros/enum_variant_names.rs
@@ -0,0 +1,34 @@
+use proc_macro2::TokenStream;
+use quote::quote;
+use syn::{Data, DeriveInput};
+
+use crate::helpers::{non_enum_error, HasStrumVariantProperties, HasTypeProperties};
+
+pub fn enum_variant_names_inner(ast: &DeriveInput) -> syn::Result<TokenStream> {
+    let name = &ast.ident;
+    let gen = &ast.generics;
+    let (impl_generics, ty_generics, where_clause) = gen.split_for_impl();
+
+    let variants = match &ast.data {
+        Data::Enum(v) => &v.variants,
+        _ => return Err(non_enum_error()),
+    };
+
+    // Derives for the generated enum
+    let type_properties = ast.get_type_properties()?;
+    let strum_module_path = type_properties.crate_module_path();
+
+    let names = variants
+        .iter()
+        .map(|v| {
+            let props = v.get_variant_properties()?;
+            Ok(props.get_preferred_name(type_properties.case_style))
+        })
+        .collect::<syn::Result<Vec<_>>>()?;
+
+    Ok(quote! {
+        impl #impl_generics #strum_module_path::VariantNames for #name #ty_generics #where_clause {
+            const VARIANTS: &'static [&'static str] = &[ #(#names),* ];
+        }
+    })
+}
diff --git a/src/macros/from_repr.rs b/src/macros/from_repr.rs
new file mode 100644
index 0000000..92fd7ad
--- /dev/null
+++ b/src/macros/from_repr.rs
@@ -0,0 +1,152 @@
+use heck::ToShoutySnakeCase;
+use proc_macro2::{Span, TokenStream};
+use quote::{format_ident, quote, ToTokens};
+use syn::{Data, DeriveInput, Fields, PathArguments, Type, TypeParen};
+
+use crate::helpers::{non_enum_error, HasStrumVariantProperties};
+
+pub fn from_repr_inner(ast: &DeriveInput) -> syn::Result<TokenStream> {
+    let name = &ast.ident;
+    let gen = &ast.generics;
+    let (impl_generics, ty_generics, where_clause) = gen.split_for_impl();
+    let vis = &ast.vis;
+    let attrs = &ast.attrs;
+
+    let mut discriminant_type: Type = syn::parse("usize".parse().unwrap()).unwrap();
+    for attr in attrs {
+        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;
+        }
+        if path.segments.len() != 1 {
+            continue;
+        }
+        let segment = path.segments.first().unwrap();
+        if segment.ident != "repr" {
+            continue;
+        }
+        if segment.arguments != PathArguments::None {
+            continue;
+        }
+        let typ_paren = match syn::parse2::<Type>(tokens.clone()) {
+            Ok(Type::Paren(TypeParen { elem, .. })) => *elem,
+            _ => continue,
+        };
+        let inner_path = match &typ_paren {
+            Type::Path(t) => t,
+            _ => continue,
+        };
+        if let Some(seg) = inner_path.path.segments.last() {
+            for t in &[
+                "u8", "u16", "u32", "u64", "usize", "i8", "i16", "i32", "i64", "isize",
+            ] {
+                if seg.ident == t {
+                    discriminant_type = typ_paren;
+                    break;
+                }
+            }
+        }
+    }
+
+    if gen.lifetimes().count() > 0 {
+        return Err(syn::Error::new(
+            Span::call_site(),
+            "This macro doesn't support enums with lifetimes. \
+             The resulting enums would be unbounded.",
+        ));
+    }
+
+    let variants = match &ast.data {
+        Data::Enum(v) => &v.variants,
+        _ => return Err(non_enum_error()),
+    };
+
+    let mut arms = Vec::new();
+    let mut constant_defs = Vec::new();
+    let mut has_additional_data = false;
+    let mut prev_const_var_ident = None;
+    for variant in variants {
+        if variant.get_variant_properties()?.disabled.is_some() {
+            continue;
+        }
+
+        let ident = &variant.ident;
+        let params = match &variant.fields {
+            Fields::Unit => quote! {},
+            Fields::Unnamed(fields) => {
+                has_additional_data = true;
+                let defaults = ::core::iter::repeat(quote!(::core::default::Default::default()))
+                    .take(fields.unnamed.len());
+                quote! { (#(#defaults),*) }
+            }
+            Fields::Named(fields) => {
+                has_additional_data = true;
+                let fields = fields
+                    .named
+                    .iter()
+                    .map(|field| field.ident.as_ref().unwrap());
+                quote! { {#(#fields: ::core::default::Default::default()),*} }
+            }
+        };
+
+        let const_var_str = format!("{}_DISCRIMINANT", variant.ident).to_shouty_snake_case();
+        let const_var_ident = format_ident!("{}", const_var_str);
+
+        let const_val_expr = match &variant.discriminant {
+            Some((_, expr)) => quote! { #expr },
+            None => match &prev_const_var_ident {
+                Some(prev) => quote! { #prev + 1 },
+                None => quote! { 0 },
+            },
+        };
+
+        constant_defs.push(quote! {const #const_var_ident: #discriminant_type = #const_val_expr;});
+        arms.push(quote! {v if v == #const_var_ident => ::core::option::Option::Some(#name::#ident #params)});
+
+        prev_const_var_ident = Some(const_var_ident);
+    }
+
+    arms.push(quote! { _ => ::core::option::Option::None });
+
+    let const_if_possible = if has_additional_data {
+        quote! {}
+    } else {
+        #[rustversion::before(1.46)]
+        fn filter_by_rust_version(_: TokenStream) -> TokenStream {
+            quote! {}
+        }
+
+        #[rustversion::since(1.46)]
+        fn filter_by_rust_version(s: TokenStream) -> TokenStream {
+            s
+        }
+        filter_by_rust_version(quote! { const })
+    };
+
+    Ok(quote! {
+        #[allow(clippy::use_self)]
+        impl #impl_generics #name #ty_generics #where_clause {
+            #[doc = "Try to create [Self] from the raw representation"]
+            #vis #const_if_possible fn from_repr(discriminant: #discriminant_type) -> Option<#name #ty_generics> {
+                #(#constant_defs)*
+                match discriminant {
+                    #(#arms),*
+                }
+            }
+        }
+    })
+}
diff --git a/src/macros/mod.rs b/src/macros/mod.rs
new file mode 100644
index 0000000..8df8cd6
--- /dev/null
+++ b/src/macros/mod.rs
@@ -0,0 +1,16 @@
+pub mod enum_count;
+pub mod enum_discriminants;
+pub mod enum_is;
+pub mod enum_iter;
+pub mod enum_messages;
+pub mod enum_properties;
+pub mod enum_try_as;
+pub mod enum_variant_names;
+pub mod from_repr;
+
+mod strings;
+
+pub use self::strings::as_ref_str;
+pub use self::strings::display;
+pub use self::strings::from_string;
+pub use self::strings::to_string;
diff --git a/src/macros/strings/as_ref_str.rs b/src/macros/strings/as_ref_str.rs
new file mode 100644
index 0000000..617b887
--- /dev/null
+++ b/src/macros/strings/as_ref_str.rs
@@ -0,0 +1,117 @@
+use proc_macro2::TokenStream;
+use quote::quote;
+use syn::{parse_quote, Data, DeriveInput, Fields};
+
+use crate::helpers::{non_enum_error, HasStrumVariantProperties, HasTypeProperties};
+
+fn get_arms(ast: &DeriveInput) -> syn::Result<Vec<TokenStream>> {
+    let name = &ast.ident;
+    let mut arms = Vec::new();
+    let variants = match &ast.data {
+        Data::Enum(v) => &v.variants,
+        _ => return Err(non_enum_error()),
+    };
+
+    let type_properties = ast.get_type_properties()?;
+
+    for variant in variants {
+        let ident = &variant.ident;
+        let variant_properties = variant.get_variant_properties()?;
+
+        if variant_properties.disabled.is_some() {
+            continue;
+        }
+
+        // Look at all the serialize attributes.
+        // Use `to_string` attribute (not `as_ref_str` or something) to keep things consistent
+        // (i.e. always `enum.as_ref().to_string() == enum.to_string()`).
+        let output = variant_properties.get_preferred_name(type_properties.case_style);
+        let params = match variant.fields {
+            Fields::Unit => quote! {},
+            Fields::Unnamed(..) => quote! { (..) },
+            Fields::Named(..) => quote! { {..} },
+        };
+
+        arms.push(quote! { #name::#ident #params => #output });
+    }
+
+    if arms.len() < variants.len() {
+        arms.push(quote! {
+            _ => panic!(
+                "AsRef::<str>::as_ref() or AsStaticRef::<str>::as_static() \
+                 called on disabled variant.",
+            )
+        });
+    }
+
+    Ok(arms)
+}
+
+pub fn as_ref_str_inner(ast: &DeriveInput) -> syn::Result<TokenStream> {
+    let name = &ast.ident;
+    let (impl_generics, ty_generics, where_clause) = ast.generics.split_for_impl();
+    let arms = get_arms(ast)?;
+    Ok(quote! {
+        impl #impl_generics ::core::convert::AsRef<str> for #name #ty_generics #where_clause {
+            fn as_ref(&self) -> &str {
+                match *self {
+                    #(#arms),*
+                }
+            }
+        }
+    })
+}
+
+pub enum GenerateTraitVariant {
+    AsStaticStr,
+    From,
+}
+
+pub fn as_static_str_inner(
+    ast: &DeriveInput,
+    trait_variant: &GenerateTraitVariant,
+) -> syn::Result<TokenStream> {
+    let name = &ast.ident;
+    let (impl_generics, ty_generics, where_clause) = ast.generics.split_for_impl();
+    let arms = get_arms(ast)?;
+    let type_properties = ast.get_type_properties()?;
+    let strum_module_path = type_properties.crate_module_path();
+
+    let mut generics = ast.generics.clone();
+    generics
+        .params
+        .push(syn::GenericParam::Lifetime(syn::LifetimeParam::new(
+            parse_quote!('_derivative_strum),
+        )));
+    let (impl_generics2, _, _) = generics.split_for_impl();
+    let arms2 = arms.clone();
+    let arms3 = arms.clone();
+
+    Ok(match trait_variant {
+        GenerateTraitVariant::AsStaticStr => quote! {
+            impl #impl_generics #strum_module_path::AsStaticRef<str> for #name #ty_generics #where_clause {
+                fn as_static(&self) -> &'static str {
+                    match *self {
+                        #(#arms),*
+                    }
+                }
+            }
+        },
+        GenerateTraitVariant::From => quote! {
+            impl #impl_generics ::core::convert::From<#name #ty_generics> for &'static str #where_clause {
+                fn from(x: #name #ty_generics) -> &'static str {
+                    match x {
+                        #(#arms2),*
+                    }
+                }
+            }
+            impl #impl_generics2 ::core::convert::From<&'_derivative_strum #name #ty_generics> for &'static str #where_clause {
+                fn from(x: &'_derivative_strum #name #ty_generics) -> &'static str {
+                    match *x {
+                        #(#arms3),*
+                    }
+                }
+            }
+        },
+    })
+}
diff --git a/src/macros/strings/display.rs b/src/macros/strings/display.rs
new file mode 100644
index 0000000..fcc5936
--- /dev/null
+++ b/src/macros/strings/display.rs
@@ -0,0 +1,65 @@
+use proc_macro2::TokenStream;
+use quote::quote;
+use syn::{Data, DeriveInput, Fields};
+
+use crate::helpers::{non_enum_error, HasStrumVariantProperties, HasTypeProperties};
+
+pub fn display_inner(ast: &DeriveInput) -> syn::Result<TokenStream> {
+    let name = &ast.ident;
+    let (impl_generics, ty_generics, where_clause) = ast.generics.split_for_impl();
+    let variants = match &ast.data {
+        Data::Enum(v) => &v.variants,
+        _ => return Err(non_enum_error()),
+    };
+
+    let type_properties = ast.get_type_properties()?;
+
+    let mut arms = Vec::new();
+    for variant in variants {
+        let ident = &variant.ident;
+        let variant_properties = variant.get_variant_properties()?;
+
+        if variant_properties.disabled.is_some() {
+            continue;
+        }
+
+        // Look at all the serialize attributes.
+        let output = variant_properties.get_preferred_name(type_properties.case_style);
+
+        let params = match variant.fields {
+            Fields::Unit => quote! {},
+            Fields::Unnamed(..) => quote! { (..) },
+            Fields::Named(..) => quote! { {..} },
+        };
+
+        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() {
+        arms.push(quote! { _ => panic!("fmt() called on disabled variant.") });
+    }
+
+    Ok(quote! {
+        impl #impl_generics ::core::fmt::Display for #name #ty_generics #where_clause {
+            fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::result::Result<(), ::core::fmt::Error> {
+                match *self {
+                    #(#arms),*
+                }
+            }
+        }
+    })
+}
diff --git a/src/macros/strings/from_string.rs b/src/macros/strings/from_string.rs
new file mode 100644
index 0000000..2d25591
--- /dev/null
+++ b/src/macros/strings/from_string.rs
@@ -0,0 +1,180 @@
+use proc_macro2::TokenStream;
+use quote::quote;
+use syn::{Data, DeriveInput, Fields};
+
+use crate::helpers::{
+    non_enum_error, occurrence_error, HasStrumVariantProperties, HasTypeProperties,
+};
+
+pub fn from_string_inner(ast: &DeriveInput) -> syn::Result<TokenStream> {
+    let name = &ast.ident;
+    let (impl_generics, ty_generics, where_clause) = ast.generics.split_for_impl();
+    let variants = match &ast.data {
+        Data::Enum(v) => &v.variants,
+        _ => return Err(non_enum_error()),
+    };
+
+    let type_properties = ast.get_type_properties()?;
+    let strum_module_path = type_properties.crate_module_path();
+
+    let mut default_kw = None;
+    let mut default =
+        quote! { ::core::result::Result::Err(#strum_module_path::ParseError::VariantNotFound) };
+
+    let mut phf_exact_match_arms = Vec::new();
+    let mut standard_match_arms = Vec::new();
+    for variant in variants {
+        let ident = &variant.ident;
+        let variant_properties = variant.get_variant_properties()?;
+
+        if variant_properties.disabled.is_some() {
+            continue;
+        }
+
+        if let Some(kw) = variant_properties.default {
+            if let Some(fst_kw) = default_kw {
+                return Err(occurrence_error(fst_kw, kw, "default"));
+            }
+
+            match &variant.fields {
+                Fields::Unnamed(fields) if fields.unnamed.len() == 1 => {}
+                _ => {
+                    return Err(syn::Error::new_spanned(
+                        variant,
+                        "Default only works on newtype structs with a single String field",
+                    ))
+                }
+            }
+
+            default_kw = Some(kw);
+            default = quote! {
+                ::core::result::Result::Ok(#name::#ident(s.into()))
+            };
+            continue;
+        }
+
+        let params = match &variant.fields {
+            Fields::Unit => quote! {},
+            Fields::Unnamed(fields) => {
+                let defaults =
+                    ::core::iter::repeat(quote!(Default::default())).take(fields.unnamed.len());
+                quote! { (#(#defaults),*) }
+            }
+            Fields::Named(fields) => {
+                let fields = fields
+                    .named
+                    .iter()
+                    .map(|field| field.ident.as_ref().unwrap());
+                quote! { {#(#fields: Default::default()),*} }
+            }
+        };
+
+        let is_ascii_case_insensitive = variant_properties
+            .ascii_case_insensitive
+            .unwrap_or(type_properties.ascii_case_insensitive);
+
+        // If we don't have any custom variants, add the default serialized name.
+        for serialization in variant_properties.get_serializations(type_properties.case_style) {
+            if type_properties.use_phf {
+                phf_exact_match_arms.push(quote! { #serialization => #name::#ident #params, });
+
+                if is_ascii_case_insensitive {
+                    // Store the lowercase and UPPERCASE variants in the phf map to capture 
+                    let ser_string = serialization.value();
+
+                    let lower =
+                        syn::LitStr::new(&ser_string.to_ascii_lowercase(), serialization.span());
+                    let upper =
+                        syn::LitStr::new(&ser_string.to_ascii_uppercase(), serialization.span());
+                    phf_exact_match_arms.push(quote! { #lower => #name::#ident #params, });
+                    phf_exact_match_arms.push(quote! { #upper => #name::#ident #params, });
+                    standard_match_arms.push(quote! { s if s.eq_ignore_ascii_case(#serialization) => #name::#ident #params, });
+                }
+            } else {
+                standard_match_arms.push(if !is_ascii_case_insensitive {
+                    quote! { #serialization => #name::#ident #params, }
+                } else {
+                    quote! { s if s.eq_ignore_ascii_case(#serialization) => #name::#ident #params, }
+                });
+            }
+        }
+    }
+
+    let phf_body = if phf_exact_match_arms.is_empty() {
+        quote!()
+    } else {
+        quote! {
+            use #strum_module_path::_private_phf_reexport_for_macro_if_phf_feature as phf;
+            static PHF: phf::Map<&'static str, #name> = phf::phf_map! {
+                #(#phf_exact_match_arms)*
+            };
+            if let Some(value) = PHF.get(s).cloned() {
+                return ::core::result::Result::Ok(value);
+            }
+        }
+    };
+    let standard_match_body = if standard_match_arms.is_empty() {
+        default
+    } else {
+        quote! {
+            ::core::result::Result::Ok(match s {
+                #(#standard_match_arms)*
+                _ => return #default,
+            })
+        }
+    };
+
+    let from_str = quote! {
+        #[allow(clippy::use_self)]
+        impl #impl_generics ::core::str::FromStr for #name #ty_generics #where_clause {
+            type Err = #strum_module_path::ParseError;
+            fn from_str(s: &str) -> ::core::result::Result< #name #ty_generics , <Self as ::core::str::FromStr>::Err> {
+                #phf_body
+                #standard_match_body
+            }
+        }
+    };
+
+    let try_from_str = try_from_str(
+        name,
+        &impl_generics,
+        &ty_generics,
+        where_clause,
+        &strum_module_path,
+    );
+
+    Ok(quote! {
+        #from_str
+        #try_from_str
+    })
+}
+
+#[rustversion::before(1.34)]
+fn try_from_str(
+    _name: &proc_macro2::Ident,
+    _impl_generics: &syn::ImplGenerics,
+    _ty_generics: &syn::TypeGenerics,
+    _where_clause: Option<&syn::WhereClause>,
+    _strum_module_path: &syn::Path,
+) -> TokenStream {
+    Default::default()
+}
+
+#[rustversion::since(1.34)]
+fn try_from_str(
+    name: &proc_macro2::Ident,
+    impl_generics: &syn::ImplGenerics,
+    ty_generics: &syn::TypeGenerics,
+    where_clause: Option<&syn::WhereClause>,
+    strum_module_path: &syn::Path,
+) -> TokenStream {
+    quote! {
+        #[allow(clippy::use_self)]
+        impl #impl_generics ::core::convert::TryFrom<&str> for #name #ty_generics #where_clause {
+            type Error = #strum_module_path::ParseError;
+            fn try_from(s: &str) -> ::core::result::Result< #name #ty_generics , <Self as ::core::convert::TryFrom<&str>>::Error> {
+                ::core::str::FromStr::from_str(s)
+            }
+        }
+    }
+}
diff --git a/src/macros/strings/mod.rs b/src/macros/strings/mod.rs
new file mode 100644
index 0000000..e416f4b
--- /dev/null
+++ b/src/macros/strings/mod.rs
@@ -0,0 +1,4 @@
+pub mod as_ref_str;
+pub mod display;
+pub mod from_string;
+pub mod to_string;
diff --git a/src/macros/strings/to_string.rs b/src/macros/strings/to_string.rs
new file mode 100644
index 0000000..9a1e661
--- /dev/null
+++ b/src/macros/strings/to_string.rs
@@ -0,0 +1,67 @@
+use proc_macro2::TokenStream;
+use quote::quote;
+use syn::{Data, DeriveInput, Fields};
+
+use crate::helpers::{non_enum_error, HasStrumVariantProperties, HasTypeProperties};
+
+pub fn to_string_inner(ast: &DeriveInput) -> syn::Result<TokenStream> {
+    let name = &ast.ident;
+    let (impl_generics, ty_generics, where_clause) = ast.generics.split_for_impl();
+    let variants = match &ast.data {
+        Data::Enum(v) => &v.variants,
+        _ => return Err(non_enum_error()),
+    };
+
+    let type_properties = ast.get_type_properties()?;
+    let mut arms = Vec::new();
+    for variant in variants {
+        let ident = &variant.ident;
+        let variant_properties = variant.get_variant_properties()?;
+
+        if variant_properties.disabled.is_some() {
+            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);
+
+        let params = match variant.fields {
+            Fields::Unit => quote! {},
+            Fields::Unnamed(..) => quote! { (..) },
+            Fields::Named(..) => quote! { {..} },
+        };
+
+        arms.push(quote! { #name::#ident #params => ::std::string::String::from(#output) });
+    }
+
+    if arms.len() < variants.len() {
+        arms.push(quote! { _ => panic!("to_string() called on disabled variant.") });
+    }
+
+    Ok(quote! {
+        #[allow(clippy::use_self)]
+        impl #impl_generics ::std::string::ToString for #name #ty_generics #where_clause {
+            fn to_string(&self) -> ::std::string::String {
+                match *self {
+                    #(#arms),*
+                }
+            }
+        }
+    })
+}