summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMaurice Lam <yukl@google.com>2024-01-17 21:55:39 +0000
committerMaurice Lam <yukl@google.com>2024-01-17 21:55:39 +0000
commit5281ac2cd1410e7193a07c801b8e1871a11cd937 (patch)
treedbab79a311a4921cd5049b284c05c0c2dd58a3d1
parent4b400acbf4a8b4775ae9509bf5293c56605ce4eb (diff)
downloadstrum_macros-5281ac2cd1410e7193a07c801b8e1871a11cd937.tar.gz
mport 'strum_macros' crateupstream
Bug: http://b/319324325 Request Document: go/android-rust-importing-crates For CL Reviewers: go/android3p#cl-review For Build Team: go/ab-third-party-imports Test: N/A (No build files yet) Change-Id: Id14e38e38e6b38191526b0504a0f31b0850de74c
-rw-r--r--.cargo_vcs_info.json6
-rw-r--r--Cargo.toml58
-rw-r--r--Cargo.toml.orig29
-rw-r--r--LICENSE21
-rw-r--r--METADATA19
-rw-r--r--MODULE_LICENSE_MIT0
-rw-r--r--OWNERS2
-rw-r--r--README.md82
-rw-r--r--src/helpers/case_style.rs177
-rw-r--r--src/helpers/metadata.rs276
-rw-r--r--src/helpers/mod.rs32
-rw-r--r--src/helpers/type_props.rs116
-rw-r--r--src/helpers/variant_props.rs133
-rw-r--r--src/lib.rs815
-rw-r--r--src/macros/enum_count.rs34
-rw-r--r--src/macros/enum_discriminants.rs164
-rw-r--r--src/macros/enum_is.rs44
-rw-r--r--src/macros/enum_iter.rs172
-rw-r--r--src/macros/enum_messages.rs138
-rw-r--r--src/macros/enum_properties.rs61
-rw-r--r--src/macros/enum_try_as.rs80
-rw-r--r--src/macros/enum_variant_names.rs34
-rw-r--r--src/macros/from_repr.rs152
-rw-r--r--src/macros/mod.rs16
-rw-r--r--src/macros/strings/as_ref_str.rs117
-rw-r--r--src/macros/strings/display.rs65
-rw-r--r--src/macros/strings/from_string.rs180
-rw-r--r--src/macros/strings/mod.rs4
-rw-r--r--src/macros/strings/to_string.rs67
29 files changed, 3094 insertions, 0 deletions
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),*
+ }
+ }
+ }
+ })
+}