feat: add display UI with splash screen, dashboard, and RSSI sparkline
Implement full SSD1306 OLED display support: - Splash screen on boot (1.5s) with project name, version, board - Dashboard showing radio state, config, counters, RSSI/SNR - RSSI history sparkline (64-sample ring buffer, 2px-wide bars) - DisplayOn/DisplayOff host commands routed via dedicated channel - Pure render functions against generic DrawTarget (no hardware coupling) - Display on/off control bypasses radio task entirely Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
17624 files changed
+4564928
-38
No files matched your search
+178
@@ -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()
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
+330
@@ -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
@@ -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
|
||||
}
|
||||
+138
@@ -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))
|
||||
}
|
||||
}
|
||||
+153
@@ -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)
|
||||
}
|
||||
}
|
||||
Vendored
+956
@@ -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,
|
||||
¯os::as_ref_str::GenerateTraitVariant::AsStaticStr,
|
||||
)
|
||||
.unwrap_or_else(|err| err.to_compile_error());
|
||||
debug_print_generated(&ast, &toks);
|
||||
toks.into()
|
||||
}
|
||||
|
||||
/// Implements `From<MyEnum> for &'static str` on an enum.
|
||||
///
|
||||
/// Implements `From<YourEnum>` and `From<&'a YourEnum>` for `&'static str`. This is
|
||||
/// useful for turning an enum variant into a static string.
|
||||
/// The Rust `std` provides a blanket impl of the reverse direction - i.e. `impl Into<&'static str> for YourEnum`.
|
||||
///
|
||||
/// ```
|
||||
/// use strum_macros::IntoStaticStr;
|
||||
///
|
||||
/// #[derive(IntoStaticStr)]
|
||||
/// enum State<'a> {
|
||||
/// Initial(&'a str),
|
||||
/// Finished,
|
||||
/// }
|
||||
///
|
||||
/// fn verify_state<'a>(s: &'a str) {
|
||||
/// let mut state = State::Initial(s);
|
||||
/// // The following won't work because the lifetime is incorrect:
|
||||
/// // let wrong: &'static str = state.as_ref();
|
||||
/// // using the trait implemented by the derive works however:
|
||||
/// let right: &'static str = state.into();
|
||||
/// assert_eq!("Initial", right);
|
||||
/// state = State::Finished;
|
||||
/// let done: &'static str = state.into();
|
||||
/// assert_eq!("Finished", done);
|
||||
/// }
|
||||
///
|
||||
/// verify_state(&"hello world".to_string());
|
||||
/// ```
|
||||
#[proc_macro_derive(IntoStaticStr, attributes(strum))]
|
||||
pub fn into_static_str(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
|
||||
let ast = syn::parse_macro_input!(input as DeriveInput);
|
||||
|
||||
let toks = macros::as_ref_str::as_static_str_inner(
|
||||
&ast,
|
||||
¯os::as_ref_str::GenerateTraitVariant::From,
|
||||
)
|
||||
.unwrap_or_else(|err| err.to_compile_error());
|
||||
debug_print_generated(&ast, &toks);
|
||||
toks.into()
|
||||
}
|
||||
|
||||
/// implements `std::string::ToString` on 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()
|
||||
}
|
||||
+34
@@ -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;
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -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
|
||||
})
|
||||
}
|
||||
+47
@@ -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())
|
||||
}
|
||||
+174
@@ -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| ¶m.ident);
|
||||
quote! { < ( #(#g),* ) > }
|
||||
} else {
|
||||
quote! { < () > }
|
||||
};
|
||||
|
||||
let variants = match &ast.data {
|
||||
Data::Enum(v) => &v.variants,
|
||||
_ => return Err(non_enum_error()),
|
||||
};
|
||||
|
||||
let mut arms = Vec::new();
|
||||
let mut idx = 0usize;
|
||||
for variant in variants {
|
||||
if variant.get_variant_properties()?.disabled.is_some() {
|
||||
continue;
|
||||
}
|
||||
|
||||
let ident = &variant.ident;
|
||||
let params = match &variant.fields {
|
||||
Fields::Unit => quote! {},
|
||||
Fields::Unnamed(fields) => {
|
||||
let defaults = ::core::iter::repeat(quote!(::core::default::Default::default()))
|
||||
.take(fields.unnamed.len());
|
||||
quote! { (#(#defaults),*) }
|
||||
}
|
||||
Fields::Named(fields) => {
|
||||
let fields = fields
|
||||
.named
|
||||
.iter()
|
||||
.map(|field| field.ident.as_ref().unwrap());
|
||||
quote! { {#(#fields: ::core::default::Default::default()),*} }
|
||||
}
|
||||
};
|
||||
|
||||
arms.push(quote! {#idx => ::core::option::Option::Some(#name::#ident #params)});
|
||||
idx += 1;
|
||||
}
|
||||
|
||||
let variant_count = arms.len();
|
||||
arms.push(quote! { _ => ::core::option::Option::None });
|
||||
let iter_name = syn::parse_str::<Ident>(&format!("{}Iter", name)).unwrap();
|
||||
|
||||
// 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(),
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
+141
@@ -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),*
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -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),*
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
+204
@@ -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?,)*
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
+80
@@ -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)*
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -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),* ];
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -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),* ];
|
||||
}
|
||||
})
|
||||
}
|
||||
+124
@@ -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
@@ -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;
|
||||
@@ -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),*
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
})
|
||||
}
|
||||
+202
@@ -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)
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
pub mod as_ref_str;
|
||||
pub mod display;
|
||||
pub mod from_string;
|
||||
pub mod to_string;
|
||||
@@ -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),*
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
Reference in new issue
Block a user