Vendor dependencies for 0.3.0 release

This commit is contained in:
2025-09-27 10:29:08 -05:00
parent 0c8d39d483
commit 82ab7f317b
26803 changed files with 16134934 additions and 0 deletions

View File

@@ -0,0 +1,178 @@
use heck::{
ToKebabCase, ToLowerCamelCase, ToShoutySnakeCase, ToSnakeCase, ToTitleCase, ToTrainCase,
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,
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()
}

View File

@@ -0,0 +1,33 @@
use super::metadata::{InnerVariantExt, InnerVariantMeta};
use super::occurrence_error;
use syn::{Field, LitStr};
pub trait HasInnerVariantProperties {
fn get_variant_inner_properties(&self) -> syn::Result<StrumInnerVariantProperties>;
}
#[derive(Clone, Eq, PartialEq, Debug, Default)]
pub struct StrumInnerVariantProperties {
pub default_with: Option<LitStr>,
}
impl HasInnerVariantProperties for Field {
fn get_variant_inner_properties(&self) -> syn::Result<StrumInnerVariantProperties> {
let mut output = StrumInnerVariantProperties { default_with: None };
let mut default_with_kw = None;
for meta in self.get_named_metadata()? {
match meta {
InnerVariantMeta::DefaultWith { kw, value } => {
if let Some(fst_kw) = default_with_kw {
return Err(occurrence_error(fst_kw, kw, "default_with"));
}
default_with_kw = Some(kw);
output.default_with = Some(value);
}
}
}
Ok(output)
}
}

View File

@@ -0,0 +1,330 @@
use proc_macro2::TokenStream;
use syn::{
parenthesized,
parse::{Parse, ParseStream},
parse2, parse_str,
punctuated::Punctuated,
Attribute, DeriveInput, Expr, ExprLit, Field, 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);
custom_keyword!(prefix);
// 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!(default_with);
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),
Prefix {
kw: kw::prefix,
prefix: LitStr,
},
}
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 if lookahead.peek(kw::prefix) {
let kw = input.parse::<kw::prefix>()?;
input.parse::<Token![=]>()?;
let prefix = input.parse()?;
Ok(EnumMeta::Prefix { kw, prefix })
} 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),
DefaultWith {
kw: kw::default_with,
value: LitStr,
},
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::default_with) {
let kw = input.parse()?;
let _: Token![=] = input.parse()?;
let value = input.parse()?;
Ok(VariantMeta::DefaultWith { kw, value })
} 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.meta.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)
})
}
#[derive(Debug)]
pub enum InnerVariantMeta {
DefaultWith { kw: kw::default_with, value: LitStr },
}
impl Parse for InnerVariantMeta {
fn parse(input: ParseStream) -> syn::Result<Self> {
let lookahead = input.lookahead1();
if lookahead.peek(kw::default_with) {
let kw = input.parse()?;
let _: Token![=] = input.parse()?;
let value = input.parse()?;
Ok(InnerVariantMeta::DefaultWith { kw, value })
} else {
Err(lookahead.error())
}
}
}
pub trait InnerVariantExt {
/// Get all the metadata associated with an enum variant inner.
fn get_named_metadata(&self) -> syn::Result<Vec<InnerVariantMeta>>;
}
impl InnerVariantExt for Field {
fn get_named_metadata(&self) -> syn::Result<Vec<InnerVariantMeta>> {
let result = get_metadata_inner("strum", &self.attrs)?;
self.attrs
.iter()
.filter(|attr| attr.meta.path().is_ident("default_with"))
.try_fold(result, |vec, _attr| Ok(vec))
}
}

42
vendor/strum_macros/src/helpers/mod.rs vendored Normal file
View File

@@ -0,0 +1,42 @@
pub use self::case_style::snakify;
pub use self::inner_variant_props::HasInnerVariantProperties;
pub use self::type_props::HasTypeProperties;
pub use self::variant_props::HasStrumVariantProperties;
pub mod case_style;
pub mod inner_variant_props;
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 non_unit_variant_error() -> syn::Error {
syn::Error::new(
Span::call_site(),
"This macro only supports enums of strictly unit variants. Consider \
using it in conjunction with [`EnumDiscriminants`]",
)
}
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
}

View File

@@ -0,0 +1,138 @@
use proc_macro2::TokenStream;
use quote::quote;
use std::default::Default;
use syn::{parse_quote, DeriveInput, Ident, LitStr, 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,
pub prefix: Option<LitStr>,
pub enum_repr: Option<TokenStream>,
}
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;
let mut prefix_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);
}
EnumMeta::Prefix { prefix, kw } => {
if let Some(fst_kw) = prefix_kw {
return Err(occurrence_error(fst_kw, kw, "prefix"));
}
prefix_kw = Some(kw);
output.prefix = Some(prefix);
}
}
}
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) });
}
}
}
let attrs = &self.attrs;
for attr in attrs {
if let Ok(list) = attr.meta.require_list() {
if let Some(ident) = list.path.get_ident() {
if ident == "repr" {
output.enum_repr = Some(list.tokens.clone())
}
}
}
}
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))
}
}

View File

@@ -0,0 +1,153 @@
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 default_with: Option<LitStr>,
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>,
prefix: Option<&LitStr>,
) -> LitStr {
let mut output = 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))
});
if let Some(prefix) = prefix {
output = LitStr::new(&(prefix.value() + &output.value()), output.span());
}
output
}
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 disabled_kw = None;
let mut default_kw = None;
let mut default_with_kw = None;
let mut to_string_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::DefaultWith { kw, value } => {
if let Some(fst_kw) = default_with_kw {
return Err(occurrence_error(fst_kw, kw, "default_with"));
}
default_with_kw = Some(kw);
output.default_with = Some(value);
}
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)
}
}

956
vendor/strum_macros/src/lib.rs vendored Normal file
View File

@@ -0,0 +1,956 @@
//! # Strum
//!
//! Strum is a set of macros and traits for working with
//! enums and strings easier in Rust.
//!
//! This crate only contains derive macros for use with the
//! [`strum`](https://docs.rs/strum)
//! crate. The macros provied by this crate are also available by
//! enabling the `derive` feature in aforementioned `strum` crate.
#![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/latest/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 `&'a str`, where `'a` is the lifetime of the input enum reference.
///
/// 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.
///
/// If you require a `&'static str`, you can use
/// [`strum::IntoStaticStr`](crate::IntoStaticStr) instead.
///
/// ```
/// // 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()
/// );
///
/// // With prefix on all variants
/// #[derive(AsRefStr, Debug)]
/// #[strum(prefix = "/")]
/// enum ColorWithPrefix {
/// #[strum(serialize = "redred")]
/// Red,
/// Green,
/// }
///
/// assert_eq!("/redred", ColorWithPrefix::Red.as_ref());
/// assert_eq!("/Green", ColorWithPrefix::Green.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 a `'static` slice 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};
/// // You need to import the trait, to have access to VARIANTS
/// use strum::VariantNames;
///
/// #[derive(Debug, EnumString, strum_macros::VariantNames)]
/// #[strum(serialize_all = "kebab-case")]
/// enum Color {
/// Red,
/// Blue,
/// Yellow,
/// RebeccaPurple,
/// }
/// assert_eq!(["red", "blue", "yellow", "rebecca-purple"], Color::VARIANTS);
/// ```
#[proc_macro_derive(VariantNames, 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()
}
#[doc(hidden)]
#[proc_macro_derive(EnumVariantNames, attributes(strum))]
#[deprecated(
since = "0.26.0",
note = "please use `#[derive(VariantNames)]` instead"
)]
pub fn variant_names_deprecated(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()
}
/// Adds a `'static` slice with all of the Enum's variants.
///
/// Implements `strum::VariantArray` which adds an associated constant `VARIANTS`.
/// This constant contains an array with all the variants of the enumerator.
///
/// This trait can only be autoderived if the enumerator is composed only of unit-type variants,
/// meaning that the variants must not have any data.
///
/// ```
/// use strum::VariantArray;
///
/// #[derive(VariantArray, Debug, PartialEq, Eq)]
/// enum Op {
/// Add,
/// Sub,
/// Mul,
/// Div,
/// }
///
/// assert_eq!(Op::VARIANTS, &[Op::Add, Op::Sub, Op::Mul, Op::Div]);
/// ```
#[proc_macro_derive(VariantArray, attributes(strum))]
pub fn static_variants_array(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
let ast = syn::parse_macro_input!(input as DeriveInput);
let toks = macros::enum_variant_array::static_variants_array_inner(&ast)
.unwrap_or_else(|err| err.to_compile_error());
debug_print_generated(&ast, &toks);
toks.into()
}
#[proc_macro_derive(AsStaticStr, attributes(strum))]
#[doc(hidden)]
#[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"
)]
#[doc(hidden)]
#[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.
/// 2. Of the various `serialize` properties, the value with the longest length is chosen. If that
/// behavior isn't desired, you should use `to_string`.
/// 3. The name of the variant will be used if there are no `serialize` or `to_string` attributes.
/// 4. If the enum has a `strum(prefix = "some_value_")`, every variant will have that prefix prepended
/// to the serialization.
/// 5. Enums with fields support string interpolation.
/// Note this means the variant will not "round trip" if you then deserialize the string.
///
/// ```rust
/// #[derive(strum_macros::Display)]
/// pub enum Color {
/// #[strum(to_string = "saturation is {sat}")]
/// Red { sat: usize },
/// #[strum(to_string = "hue is {1}, saturation is {0}")]
/// Blue(usize, usize),
/// }
/// ```
///
/// ```
/// // 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,
/// #[strum(to_string = "purple with {sat} saturation")]
/// Purple {
/// sat: usize,
/// },
/// }
///
/// // 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 }
/// );
/// // you can also use named fields in message
/// let purple = Color::Purple { sat: 10 };
/// assert_eq!(String::from("purple with 10 saturation"), purple.to_string());
/// ```
#[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`](https://docs.rs/strum/latest/strum/trait.IntoEnumIterator.html) 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()
}
/// Creates a new type that maps all the variants of an enum to another generic value.
///
/// This macro only supports enums with unit type variants.A new type called `YourEnumTable<T>`. Essentially, it's a wrapper
/// `[T; YourEnum::Count]` where gets/sets are infallible. Some important caveats to note:
///
/// * The size of `YourEnumTable<T>` increases with the number of variants, not the number of values because it's always
/// fully populated. This means it may not be a good choice for sparsely populated maps.
///
/// * Lookups are basically constant time since it's functionally an array index.
///
/// * Your variants cannot have associated data. You can use `EnumDiscriminants` to generate an Enum with the same
/// names to work around this.
///
/// # Stability
///
/// Several people expressed interest in a data structure like this and pushed the PR through to completion, but the api
/// seems incomplete, and I reserve the right to deprecate it in the future if it becomes clear the design is flawed.
///
/// # Example
/// ```rust
/// use strum_macros::EnumTable;
///
/// #[derive(EnumTable)]
/// enum Color {
/// Red,
/// Yellow,
/// Green,
/// Blue,
/// }
///
/// assert_eq!(ColorTable::default(), ColorTable::new(0, 0, 0, 0));
/// assert_eq!(ColorTable::filled(2), ColorTable::new(2, 2, 2, 2));
/// assert_eq!(ColorTable::from_closure(|_| 3), ColorTable::new(3, 3, 3, 3));
/// assert_eq!(ColorTable::default().transform(|_, val| val + 2), ColorTable::new(2, 2, 2, 2));
///
/// let mut complex_map = ColorTable::from_closure(|color| match color {
/// Color::Red => 0,
/// _ => 3
/// });
///
/// complex_map[Color::Green] = complex_map[Color::Red];
/// assert_eq!(complex_map, ColorTable::new(0, 3, 0, 3));
/// ```
#[doc(hidden)]
#[proc_macro_derive(EnumTable, attributes(strum))]
pub fn enum_table(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
let ast = syn::parse_macro_input!(input as DeriveInput);
let toks =
macros::enum_table::enum_table_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 specified.
///
/// 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 separate 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 arbitrary 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};
///
/// #[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()
}

View File

@@ -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;
}
})
}

View File

@@ -0,0 +1,171 @@
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;
let repr = type_properties.enum_repr.map(|repr| quote!(#[repr(#repr)]));
// Add the variants without fields, but exclude the `strum` meta item
let mut discriminants = Vec::new();
for variant in variants {
let ident = &variant.ident;
let discriminant = variant
.discriminant
.as_ref()
.map(|(_, expr)| quote!( = #expr));
// 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 #discriminant});
}
// 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
#repr
#(#[ #pass_though_attributes ])*
#discriminants_vis enum #discriminants_name {
#(#discriminants),*
}
#impl_from
#impl_from_ref
})
}

View File

@@ -0,0 +1,47 @@
use crate::helpers::{case_style::snakify, non_enum_error, 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())
}

View File

@@ -0,0 +1,174 @@
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 #impl_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;
#iter_name::get(self, 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;
#iter_name::get(self, #variant_count - self.back_idx)
}
}
}
impl #impl_generics ::core::iter::FusedIterator for #iter_name #ty_generics #where_clause { }
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(),
}
}
}
})
}

View File

@@ -0,0 +1,141 @@
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),*
}
}
}
})
}

View File

@@ -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),*
}
}
}
})
}

View File

@@ -0,0 +1,204 @@
use proc_macro2::{Span, TokenStream};
use quote::{format_ident, quote};
use syn::{spanned::Spanned, Data, DeriveInput, Fields};
use crate::helpers::{non_enum_error, snakify, HasStrumVariantProperties};
pub fn enum_table_inner(ast: &DeriveInput) -> syn::Result<TokenStream> {
let name = &ast.ident;
let gen = &ast.generics;
let vis = &ast.vis;
let mut doc_comment = format!("A map over the variants of `{}`", name);
if gen.lifetimes().count() > 0 {
return Err(syn::Error::new(
Span::call_site(),
"`EnumTable` doesn't support enums with lifetimes.",
));
}
let variants = match &ast.data {
Data::Enum(v) => &v.variants,
_ => return Err(non_enum_error()),
};
let table_name = format_ident!("{}Table", name);
// the identifiers of each variant, in PascalCase
let mut pascal_idents = Vec::new();
// the identifiers of each struct field, in snake_case
let mut snake_idents = Vec::new();
// match arms in the form `MyEnumTable::Variant => &self.variant,`
let mut get_matches = Vec::new();
// match arms in the form `MyEnumTable::Variant => &mut self.variant,`
let mut get_matches_mut = Vec::new();
// match arms in the form `MyEnumTable::Variant => self.variant = new_value`
let mut set_matches = Vec::new();
// struct fields of the form `variant: func(MyEnum::Variant),*
let mut closure_fields = Vec::new();
// struct fields of the form `variant: func(MyEnum::Variant, self.variant),`
let mut transform_fields = Vec::new();
// identifiers for disabled variants
let mut disabled_variants = Vec::new();
// match arms for disabled variants
let mut disabled_matches = Vec::new();
for variant in variants {
// skip disabled variants
if variant.get_variant_properties()?.disabled.is_some() {
let disabled_ident = &variant.ident;
let panic_message = format!(
"Can't use `{}` with `{}` - variant is disabled for Strum features",
disabled_ident, table_name
);
disabled_variants.push(disabled_ident);
disabled_matches.push(quote!(#name::#disabled_ident => panic!(#panic_message),));
continue;
}
// Error on variants with data
if variant.fields != Fields::Unit {
return Err(syn::Error::new(
variant.fields.span(),
"`EnumTable` doesn't support enums with non-unit variants",
));
};
let pascal_case = &variant.ident;
let snake_case = format_ident!("_{}", snakify(&pascal_case.to_string()));
get_matches.push(quote! {#name::#pascal_case => &self.#snake_case,});
get_matches_mut.push(quote! {#name::#pascal_case => &mut self.#snake_case,});
set_matches.push(quote! {#name::#pascal_case => self.#snake_case = new_value,});
closure_fields.push(quote! {#snake_case: func(#name::#pascal_case),});
transform_fields.push(quote! {#snake_case: func(#name::#pascal_case, &self.#snake_case),});
pascal_idents.push(pascal_case);
snake_idents.push(snake_case);
}
// Error on empty enums
if pascal_idents.is_empty() {
return Err(syn::Error::new(
variants.span(),
"`EnumTable` requires at least one non-disabled variant",
));
}
// if the index operation can panic, add that to the documentation
if !disabled_variants.is_empty() {
doc_comment.push_str(&format!(
"\n# Panics\nIndexing `{}` with any of the following variants will cause a panic:",
table_name
));
for variant in disabled_variants {
doc_comment.push_str(&format!("\n\n- `{}::{}`", name, variant));
}
}
let doc_new = format!(
"Create a new {} with a value for each variant of {}",
table_name, name
);
let doc_closure = format!(
"Create a new {} by running a function on each variant of `{}`",
table_name, name
);
let doc_transform = format!("Create a new `{}` by running a function on each variant of `{}` and the corresponding value in the current `{0}`", table_name, name);
let doc_filled = format!(
"Create a new `{}` with the same value in each field.",
table_name
);
let doc_option_all = format!("Converts `{}<Option<T>>` into `Option<{0}<T>>`. Returns `Some` if all fields are `Some`, otherwise returns `None`.", table_name);
let doc_result_all_ok = format!("Converts `{}<Result<T, E>>` into `Result<{0}<T>, E>`. Returns `Ok` if all fields are `Ok`, otherwise returns `Err`.", table_name);
Ok(quote! {
#[doc = #doc_comment]
#[allow(
missing_copy_implementations,
)]
#[derive(Debug, Clone, Default, PartialEq, Eq, Hash)]
#vis struct #table_name<T> {
#(#snake_idents: T,)*
}
impl<T: Clone> #table_name<T> {
#[doc = #doc_filled]
#vis fn filled(value: T) -> #table_name<T> {
#table_name {
#(#snake_idents: value.clone(),)*
}
}
}
impl<T> #table_name<T> {
#[doc = #doc_new]
#vis fn new(
#(#snake_idents: T,)*
) -> #table_name<T> {
#table_name {
#(#snake_idents,)*
}
}
#[doc = #doc_closure]
#vis fn from_closure<F: Fn(#name)->T>(func: F) -> #table_name<T> {
#table_name {
#(#closure_fields)*
}
}
#[doc = #doc_transform]
#vis fn transform<U, F: Fn(#name, &T)->U>(&self, func: F) -> #table_name<U> {
#table_name {
#(#transform_fields)*
}
}
}
impl<T> ::core::ops::Index<#name> for #table_name<T> {
type Output = T;
fn index(&self, idx: #name) -> &T {
match idx {
#(#get_matches)*
#(#disabled_matches)*
}
}
}
impl<T> ::core::ops::IndexMut<#name> for #table_name<T> {
fn index_mut(&mut self, idx: #name) -> &mut T {
match idx {
#(#get_matches_mut)*
#(#disabled_matches)*
}
}
}
impl<T> #table_name<::core::option::Option<T>> {
#[doc = #doc_option_all]
#vis fn all(self) -> ::core::option::Option<#table_name<T>> {
if let #table_name {
#(#snake_idents: ::core::option::Option::Some(#snake_idents),)*
} = self {
::core::option::Option::Some(#table_name {
#(#snake_idents,)*
})
} else {
::core::option::Option::None
}
}
}
impl<T, E> #table_name<::core::result::Result<T, E>> {
#[doc = #doc_result_all_ok]
#vis fn all_ok(self) -> ::core::result::Result<#table_name<T>, E> {
::core::result::Result::Ok(#table_name {
#(#snake_idents: self.#snake_idents?,)*
})
}
}
})
}

View File

@@ -0,0 +1,80 @@
use crate::helpers::{case_style::snakify, non_enum_error, 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 (impl_generics, ty_generics, where_clause) = ast.generics.split_for_impl();
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 #impl_generics #enum_name #ty_generics #where_clause {
#(#variants)*
}
})
}

View File

@@ -0,0 +1,34 @@
use proc_macro2::TokenStream;
use quote::quote;
use syn::{Data, DeriveInput, Fields};
use crate::helpers::{non_enum_error, non_unit_variant_error, HasTypeProperties};
pub fn static_variants_array_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()),
};
let type_properties = ast.get_type_properties()?;
let strum_module_path = type_properties.crate_module_path();
let idents = variants
.iter()
.cloned()
.map(|v| match v.fields {
Fields::Unit => Ok(v.ident),
_ => Err(non_unit_variant_error()),
})
.collect::<syn::Result<Vec<_>>>()?;
Ok(quote! {
impl #impl_generics #strum_module_path::VariantArray for #name #ty_generics #where_clause {
const VARIANTS: &'static [Self] = &[ #(#name::#idents),* ];
}
})
}

View File

@@ -0,0 +1,35 @@
use proc_macro2::TokenStream;
use quote::quote;
use syn::{Data, DeriveInput, LitStr};
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, type_properties.prefix.as_ref()))
})
.collect::<syn::Result<Vec<LitStr>>>()?;
Ok(quote! {
impl #impl_generics #strum_module_path::VariantNames for #name #ty_generics #where_clause {
const VARIANTS: &'static [&'static str] = &[ #(#names),* ];
}
})
}

View File

@@ -0,0 +1,124 @@
use proc_macro2::{Span, TokenStream};
use quote::{format_ident, quote};
use syn::{Data, DeriveInput, Fields, Type};
use crate::helpers::{non_enum_error, HasStrumVariantProperties, HasTypeProperties};
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 mut discriminant_type: Type = syn::parse("usize".parse().unwrap()).unwrap();
if let Some(type_path) = ast
.get_type_properties()
.ok()
.and_then(|tp| tp.enum_repr)
.and_then(|repr_ts| syn::parse2::<Type>(repr_ts).ok())
{
if let Type::Path(path) = type_path.clone() {
if let Some(seg) = path.path.segments.last() {
for t in &[
"u8", "u16", "u32", "u64", "usize", "i8", "i16", "i32", "i64", "isize",
] {
if seg.ident == t {
discriminant_type = type_path;
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);
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! {
#[allow(non_upper_case_globals)]
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),*
}
}
}
})
}

18
vendor/strum_macros/src/macros/mod.rs vendored Normal file
View File

@@ -0,0 +1,18 @@
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_table;
pub mod enum_try_as;
pub mod enum_variant_array;
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;

View File

@@ -0,0 +1,119 @@
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, type_properties.prefix.as_ref());
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),*
}
}
}
},
})
}

View File

@@ -0,0 +1,202 @@
use proc_macro2::{Ident, TokenStream};
use quote::quote;
use syn::{punctuated::Punctuated, Data, DeriveInput, Fields, LitStr, Token};
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, type_properties.prefix.as_ref());
let params = match variant.fields {
Fields::Unit => quote! {},
Fields::Unnamed(ref unnamed_fields) => {
// Transform unnamed params '(String, u8)' to '(ref field0, ref field1)'
let names: Punctuated<_, Token!(,)> = unnamed_fields
.unnamed
.iter()
.enumerate()
.map(|(index, field)| {
assert!(field.ident.is_none());
let ident = syn::parse_str::<Ident>(format!("field{}", index).as_str()).unwrap();
quote! { ref #ident }
})
.collect();
quote! { (#names) }
}
Fields::Named(ref field_names) => {
// Transform named params '{ name: String, age: u8 }' to '{ ref name, ref age }'
let names: Punctuated<TokenStream, Token!(,)> = field_names
.named
.iter()
.map(|field| {
let ident = field.ident.as_ref().unwrap();
quote! { ref #ident }
})
.collect();
quote! { {#names} }
}
};
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) => ::core::fmt::Display::fmt(s, f) });
}
_ => {
return Err(syn::Error::new_spanned(
variant,
"Default only works on newtype structs with a single String field",
))
}
}
} else {
let arm = match variant.fields {
Fields::Named(ref field_names) => {
let used_vars = capture_format_string_idents(&output)?;
if used_vars.is_empty() {
quote! { #name::#ident #params => ::core::fmt::Display::fmt(#output, f) }
} else {
// Create args like 'name = name, age = age' for format macro
let args: Punctuated<_, Token!(,)> = field_names
.named
.iter()
.filter_map(|field| {
let ident = field.ident.as_ref().unwrap();
// Only contain variables that are used in format string
if !used_vars.contains(ident) {
None
} else {
Some(quote! { #ident = #ident })
}
})
.collect();
quote! {
#[allow(unused_variables)]
#name::#ident #params => ::core::fmt::Display::fmt(&format!(#output, #args), f)
}
}
},
Fields::Unnamed(ref unnamed_fields) => {
let used_vars = capture_format_strings(&output)?;
if used_vars.iter().any(String::is_empty) {
return Err(syn::Error::new_spanned(
&output,
"Empty {} is not allowed; Use manual numbering ({0})",
))
}
if used_vars.is_empty() {
quote! { #name::#ident #params => ::core::fmt::Display::fmt(#output, f) }
} else {
let args: Punctuated<_, Token!(,)> = unnamed_fields
.unnamed
.iter()
.enumerate()
.map(|(index, field)| {
assert!(field.ident.is_none());
syn::parse_str::<Ident>(format!("field{}", index).as_str()).unwrap()
})
.collect();
quote! {
#[allow(unused_variables)]
#name::#ident #params => ::core::fmt::Display::fmt(&format!(#output, #args), f)
}
}
}
Fields::Unit => {
let used_vars = capture_format_strings(&output)?;
if !used_vars.is_empty() {
return Err(syn::Error::new_spanned(
&output,
"Unit variants do not support interpolation",
));
}
quote! { #name::#ident #params => ::core::fmt::Display::fmt(#output, f) }
}
};
arms.push(arm);
}
}
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),*
}
}
}
})
}
fn capture_format_string_idents(string_literal: &LitStr) -> syn::Result<Vec<Ident>> {
capture_format_strings(string_literal)?.into_iter().map(|ident| {
syn::parse_str::<Ident>(ident.as_str()).map_err(|_| {
syn::Error::new_spanned(
string_literal,
"Invalid identifier inside format string bracket",
)
})
}).collect()
}
fn capture_format_strings(string_literal: &LitStr) -> syn::Result<Vec<String>> {
// Remove escaped brackets
let format_str = string_literal.value().replace("{{", "").replace("}}", "");
let mut new_var_start_index: Option<usize> = None;
let mut var_used = Vec::new();
for (i, chr) in format_str.bytes().enumerate() {
if chr == b'{' {
if new_var_start_index.is_some() {
return Err(syn::Error::new_spanned(
string_literal,
"Bracket opened without closing previous bracket",
));
}
new_var_start_index = Some(i);
continue;
}
if chr == b'}' {
let start_index = new_var_start_index.take().ok_or(syn::Error::new_spanned(
string_literal,
"Bracket closed without previous opened bracket",
))?;
let inside_brackets = &format_str[start_index + 1..i];
let ident_str = inside_brackets.split(":").next().unwrap().trim_end();
var_used.push(ident_str.to_owned());
}
}
Ok(var_used)
}

View File

@@ -0,0 +1,198 @@
use proc_macro2::TokenStream;
use quote::quote;
use syn::{Data, DeriveInput, Fields};
use crate::helpers::{
non_enum_error, occurrence_error, HasInnerVariantProperties, 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) => {
if let Some(ref value) = variant_properties.default_with {
let func = proc_macro2::Ident::new(&value.value(), value.span());
let defaults = vec![quote! { #func() }];
quote! { (#(#defaults),*) }
} else {
let defaults =
::core::iter::repeat(quote!(Default::default())).take(fields.unnamed.len());
quote! { (#(#defaults),*) }
}
}
Fields::Named(fields) => {
let mut defaults = vec![];
for field in &fields.named {
let meta = field.get_variant_inner_properties()?;
let field = field.ident.as_ref().unwrap();
if let Some(default_with) = meta.default_with {
let func =
proc_macro2::Ident::new(&default_with.value(), default_with.span());
defaults.push(quote! {
#field: #func()
});
} else {
defaults.push(quote! { #field: Default::default() });
}
}
quote! { {#(#defaults),*} }
}
};
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)
}
}
}
}

View File

@@ -0,0 +1,4 @@
pub mod as_ref_str;
pub mod display;
pub mod from_string;
pub mod to_string;

View File

@@ -0,0 +1,68 @@
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, type_properties.prefix.as_ref());
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),*
}
}
}
})
}