diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-17 12:18:32 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-17 12:18:32 +0000 |
commit | 4547b622d8d29df964fa2914213088b148c498fc (patch) | |
tree | 9fc6b25f3c3add6b745be9a2400a6e96140046e9 /vendor/strum_macros/src | |
parent | Releasing progress-linux version 1.66.0+dfsg1-1~progress7.99u1. (diff) | |
download | rustc-4547b622d8d29df964fa2914213088b148c498fc.tar.xz rustc-4547b622d8d29df964fa2914213088b148c498fc.zip |
Merging upstream version 1.67.1+dfsg1.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'vendor/strum_macros/src')
19 files changed, 2602 insertions, 0 deletions
diff --git a/vendor/strum_macros/src/helpers/case_style.rs b/vendor/strum_macros/src/helpers/case_style.rs new file mode 100644 index 000000000..425382606 --- /dev/null +++ b/vendor/strum_macros/src/helpers/case_style.rs @@ -0,0 +1,117 @@ +use heck::{ + ToKebabCase, ToLowerCamelCase, ToShoutySnakeCase, ToSnakeCase, ToTitleCase, ToUpperCamelCase, +}; +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, +} + +const VALID_CASE_STYLES: &[&str] = &[ + "camelCase", + "PascalCase", + "kebab-case", + "snake_case", + "SCREAMING_SNAKE_CASE", + "SCREAMING-KEBAB-CASE", + "lowercase", + "UPPERCASE", + "title_case", + "mixed_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" | "PascalCase" => CaseStyle::PascalCase, + "camelCase" => CaseStyle::CamelCase, + "snake_case" | "snek_case" => CaseStyle::SnakeCase, + "kebab_case" | "kebab-case" => CaseStyle::KebabCase, + "SCREAMING-KEBAB-CASE" => CaseStyle::ScreamingKebabCase, + "shouty_snake_case" | "shouty_snek_case" | "SCREAMING_SNAKE_CASE" => { + CaseStyle::ShoutySnakeCase + } + "title_case" => CaseStyle::TitleCase, + "mixed_case" => CaseStyle::MixedCase, + "lowercase" => CaseStyle::LowerCase, + "UPPERCASE" => CaseStyle::UpperCase, + _ => 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::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 + } + } +} + +#[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))); +} diff --git a/vendor/strum_macros/src/helpers/metadata.rs b/vendor/strum_macros/src/helpers/metadata.rs new file mode 100644 index 000000000..56e4c78bd --- /dev/null +++ b/vendor/strum_macros/src/helpers/metadata.rs @@ -0,0 +1,309 @@ +use proc_macro2::{Span, TokenStream}; +use syn::{ + parenthesized, + parse::{Parse, ParseStream}, + parse2, parse_str, + punctuated::Punctuated, + spanned::Spanned, + Attribute, DeriveInput, Ident, Lit, LitBool, LitStr, Meta, MetaNameValue, Path, Token, Variant, + Visibility, +}; + +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()) + } + } +} + +impl Spanned for EnumMeta { + fn span(&self) -> Span { + match self { + EnumMeta::SerializeAll { kw, .. } => kw.span(), + EnumMeta::AsciiCaseInsensitive(kw) => kw.span(), + EnumMeta::Crate { kw, .. } => kw.span(), + EnumMeta::UsePhf(use_phf) => use_phf.span(), + } + } +} + +pub enum EnumDiscriminantsMeta { + Derive { kw: kw::derive, paths: Vec<Path> }, + Name { kw: kw::name, name: Ident }, + 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::<_, Token![,]>(Path::parse)?; + 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 }) + } + } +} + +impl Spanned for EnumDiscriminantsMeta { + fn span(&self) -> Span { + match self { + EnumDiscriminantsMeta::Derive { kw, .. } => kw.span, + EnumDiscriminantsMeta::Name { kw, .. } => kw.span, + EnumDiscriminantsMeta::Vis { kw, .. } => kw.span, + EnumDiscriminantsMeta::Other { path, .. } => path.span(), + } + } +} + +pub trait DeriveInputExt { + /// Get all the strum metadata associated with an enum. + fn get_metadata(&self) -> syn::Result<Vec<EnumMeta>>; + + /// 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::<_, Token![,]>(Prop::parse)?; + 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)) + } +} + +impl Spanned for VariantMeta { + fn span(&self) -> Span { + match self { + VariantMeta::Message { kw, .. } => kw.span, + VariantMeta::DetailedMessage { kw, .. } => kw.span, + VariantMeta::Documentation { value } => value.span(), + VariantMeta::Serialize { kw, .. } => kw.span, + VariantMeta::ToString { kw, .. } => kw.span, + VariantMeta::Disabled(kw) => kw.span, + VariantMeta::Default(kw) => kw.span, + VariantMeta::AsciiCaseInsensitive { kw, .. } => kw.span, + VariantMeta::Props { kw, .. } => kw.span, + } + } +} + +pub trait VariantExt { + /// Get all the metadata associated with an enum variant. + fn get_metadata(&self) -> syn::Result<Vec<VariantMeta>>; +} + +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 { + lit: Lit::Str(value), + .. + }) = attr.parse_meta()? + { + vec.push(VariantMeta::Documentation { value }) + } + Ok(vec) + }) + } +} + +fn get_metadata_inner<'a, T: Parse + Spanned>( + 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/vendor/strum_macros/src/helpers/mod.rs b/vendor/strum_macros/src/helpers/mod.rs new file mode 100644 index 000000000..11aebc835 --- /dev/null +++ b/vendor/strum_macros/src/helpers/mod.rs @@ -0,0 +1,32 @@ +pub use self::case_style::CaseStyleHelpers; +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/vendor/strum_macros/src/helpers/type_props.rs b/vendor/strum_macros/src/helpers/type_props.rs new file mode 100644 index 000000000..0d49e04eb --- /dev/null +++ b/vendor/strum_macros/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/vendor/strum_macros/src/helpers/variant_props.rs b/vendor/strum_macros/src/helpers/variant_props.rs new file mode 100644 index 000000000..a39d3ea17 --- /dev/null +++ b/vendor/strum_macros/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>, + 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/vendor/strum_macros/src/lib.rs b/vendor/strum_macros/src/lib.rs new file mode 100644 index 000000000..de16c9c25 --- /dev/null +++ b/vendor/strum_macros/src/lib.rs @@ -0,0 +1,755 @@ +//! # 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, + ¯os::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, + ¯os::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 en 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::IntoEnumIter` 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() +} + +/// 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, expect 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. +/// +/// ```nocompile +/// 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/vendor/strum_macros/src/macros/enum_count.rs b/vendor/strum_macros/src/macros/enum_count.rs new file mode 100644 index 000000000..44c7f2e57 --- /dev/null +++ b/vendor/strum_macros/src/macros/enum_count.rs @@ -0,0 +1,27 @@ +use proc_macro2::TokenStream; +use quote::quote; +use syn::{Data, DeriveInput}; + +use crate::helpers::{non_enum_error, HasTypeProperties}; + +pub(crate) fn enum_count_inner(ast: &DeriveInput) -> syn::Result<TokenStream> { + let n = match &ast.data { + Data::Enum(v) => v.variants.len(), + _ => 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/vendor/strum_macros/src/macros/enum_discriminants.rs b/vendor/strum_macros/src/macros/enum_discriminants.rs new file mode 100644 index 000000000..66eee46b4 --- /dev/null +++ b/vendor/strum_macros/src/macros/enum_discriminants.rs @@ -0,0 +1,162 @@ +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 passthrough_group = attr + .tokens + .clone() + .into_iter() + .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/vendor/strum_macros/src/macros/enum_iter.rs b/vendor/strum_macros/src/macros/enum_iter.rs new file mode 100644 index 000000000..9c7017c00 --- /dev/null +++ b/vendor/strum_macros/src/macros/enum_iter.rs @@ -0,0 +1,158 @@ +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(); + + 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| ¶m.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(); + + Ok(quote! { + #[doc = "An iterator over the variants of [Self]"] + #[allow( + missing_copy_implementations, + missing_debug_implementations, + )] + #vis struct #iter_name #ty_generics { + idx: usize, + back_idx: usize, + marker: ::core::marker::PhantomData #phantom_data, + } + + impl #impl_generics #iter_name #ty_generics #where_clause { + fn get(&self, idx: usize) -> 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) -> Option<<Self as Iterator>::Item> { + self.nth(0) + } + + fn size_hint(&self) -> (usize, 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) -> 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) -> 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/vendor/strum_macros/src/macros/enum_messages.rs b/vendor/strum_macros/src/macros/enum_messages.rs new file mode 100644 index 000000000..c05610851 --- /dev/null +++ b/vendor/strum_macros/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/vendor/strum_macros/src/macros/enum_properties.rs b/vendor/strum_macros/src/macros/enum_properties.rs new file mode 100644 index 000000000..0fe389eff --- /dev/null +++ b/vendor/strum_macros/src/macros/enum_properties.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 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(); + let mut bool_arms = Vec::new(); + let mut num_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 }); + bool_arms.push(quote! { _ => ::core::option::Option::None }); + num_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/vendor/strum_macros/src/macros/enum_variant_names.rs b/vendor/strum_macros/src/macros/enum_variant_names.rs new file mode 100644 index 000000000..c54d45dc0 --- /dev/null +++ b/vendor/strum_macros/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/vendor/strum_macros/src/macros/from_repr.rs b/vendor/strum_macros/src/macros/from_repr.rs new file mode 100644 index 000000000..ec9f55779 --- /dev/null +++ b/vendor/strum_macros/src/macros/from_repr.rs @@ -0,0 +1,139 @@ +use heck::ToShoutySnakeCase; +use proc_macro2::{Span, TokenStream}; +use quote::{format_ident, quote}; +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 tokens = &attr.tokens; + 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/vendor/strum_macros/src/macros/mod.rs b/vendor/strum_macros/src/macros/mod.rs new file mode 100644 index 000000000..b4129697d --- /dev/null +++ b/vendor/strum_macros/src/macros/mod.rs @@ -0,0 +1,14 @@ +pub mod enum_count; +pub mod enum_discriminants; +pub mod enum_iter; +pub mod enum_messages; +pub mod enum_properties; +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/vendor/strum_macros/src/macros/strings/as_ref_str.rs b/vendor/strum_macros/src/macros/strings/as_ref_str.rs new file mode 100644 index 000000000..4dfdc4b80 --- /dev/null +++ b/vendor/strum_macros/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::LifetimeDef::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/vendor/strum_macros/src/macros/strings/display.rs b/vendor/strum_macros/src/macros/strings/display.rs new file mode 100644 index 000000000..82a34b0df --- /dev/null +++ b/vendor/strum_macros/src/macros/strings/display.rs @@ -0,0 +1,51 @@ +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! { {..} }, + }; + + 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/vendor/strum_macros/src/macros/strings/from_string.rs b/vendor/strum_macros/src/macros/strings/from_string.rs new file mode 100644 index 000000000..2d2559174 --- /dev/null +++ b/vendor/strum_macros/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/vendor/strum_macros/src/macros/strings/mod.rs b/vendor/strum_macros/src/macros/strings/mod.rs new file mode 100644 index 000000000..e416f4b3b --- /dev/null +++ b/vendor/strum_macros/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/vendor/strum_macros/src/macros/strings/to_string.rs b/vendor/strum_macros/src/macros/strings/to_string.rs new file mode 100644 index 000000000..7e4c48380 --- /dev/null +++ b/vendor/strum_macros/src/macros/strings/to_string.rs @@ -0,0 +1,51 @@ +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; + } + + // 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),* + } + } + } + }) +} |