mirror of
https://github.com/danog/strum.git
synced 2024-11-30 04:28:59 +01:00
Added EnumTryAs
This commit is contained in:
parent
96c63e9f5c
commit
e0f81e9caa
@ -1,9 +1,11 @@
|
||||
pub use self::case_style::CaseStyleHelpers;
|
||||
pub use self::snakify::snakify;
|
||||
pub use self::type_props::HasTypeProperties;
|
||||
pub use self::variant_props::HasStrumVariantProperties;
|
||||
|
||||
pub mod case_style;
|
||||
mod metadata;
|
||||
mod snakify;
|
||||
pub mod type_props;
|
||||
pub mod variant_props;
|
||||
|
||||
|
18
strum_macros/src/helpers/snakify.rs
Normal file
18
strum_macros/src/helpers/snakify.rs
Normal file
@ -0,0 +1,18 @@
|
||||
use heck::ToSnakeCase;
|
||||
|
||||
/// 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()
|
||||
}
|
@ -409,6 +409,41 @@ pub fn enum_is(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
|
||||
toks.into()
|
||||
}
|
||||
|
||||
/// Generated `try_as_*()` methods for all unnamed variants.
|
||||
/// E.g. `Message.try_as_write()`.
|
||||
///
|
||||
/// These methods will only be generated for unnamed variants, not for named or unit variants.
|
||||
///
|
||||
/// ```
|
||||
/// use strum_macros::EnumTryAs;
|
||||
///
|
||||
/// #[derive(EnumTryAs, Debug)]
|
||||
/// enum Message {
|
||||
/// Quit,
|
||||
/// Move { x: i32, y: i32 },
|
||||
/// Write(String),
|
||||
/// ChangeColor(i32, i32, i32),
|
||||
/// }
|
||||
///
|
||||
/// assert_eq!(
|
||||
/// Message::Write(String::from("Hello")).try_as_write(),
|
||||
/// Some(String::from("Hello"))
|
||||
/// );
|
||||
/// assert_eq!(
|
||||
/// Message::ChangeColor(1, 2, 3).try_as_change_color(),
|
||||
/// Some((1, 2, 3))
|
||||
/// );
|
||||
/// ```
|
||||
#[proc_macro_derive(EnumTryAs, attributes(strum))]
|
||||
pub fn enum_try_as(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
|
||||
let ast = syn::parse_macro_input!(input as DeriveInput);
|
||||
|
||||
let toks =
|
||||
macros::enum_try_as::enum_try_as_inner(&ast).unwrap_or_else(|err| err.to_compile_error());
|
||||
debug_print_generated(&ast, &toks);
|
||||
toks.into()
|
||||
}
|
||||
|
||||
/// Add a function to enum that allows accessing variants by its discriminant
|
||||
///
|
||||
/// This macro adds a standalone function to obtain an enum variant by its discriminant. The macro adds
|
||||
|
@ -1,5 +1,4 @@
|
||||
use crate::helpers::{non_enum_error, HasStrumVariantProperties};
|
||||
use heck::ToSnakeCase;
|
||||
use crate::helpers::{non_enum_error, snakify, HasStrumVariantProperties};
|
||||
use proc_macro2::TokenStream;
|
||||
use quote::{format_ident, quote};
|
||||
use syn::{Data, DeriveInput};
|
||||
@ -42,20 +41,3 @@ pub fn enum_is_inner(ast: &DeriveInput) -> syn::Result<TokenStream> {
|
||||
}
|
||||
.into())
|
||||
}
|
||||
|
||||
/// 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`.
|
||||
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()
|
||||
}
|
||||
|
80
strum_macros/src/macros/enum_try_as.rs
Normal file
80
strum_macros/src/macros/enum_try_as.rs
Normal file
@ -0,0 +1,80 @@
|
||||
use crate::helpers::{non_enum_error, snakify, HasStrumVariantProperties};
|
||||
use proc_macro2::TokenStream;
|
||||
use quote::{format_ident, quote, ToTokens};
|
||||
use syn::{Data, DeriveInput};
|
||||
|
||||
pub fn enum_try_as_inner(ast: &DeriveInput) -> syn::Result<TokenStream> {
|
||||
let variants = match &ast.data {
|
||||
Data::Enum(v) => &v.variants,
|
||||
_ => return Err(non_enum_error()),
|
||||
};
|
||||
|
||||
let enum_name = &ast.ident;
|
||||
|
||||
let variants: Vec<_> = variants
|
||||
.iter()
|
||||
.filter_map(|variant| {
|
||||
if variant.get_variant_properties().ok()?.disabled.is_some() {
|
||||
return None;
|
||||
}
|
||||
|
||||
match &variant.fields {
|
||||
syn::Fields::Unnamed(values) => {
|
||||
let variant_name = &variant.ident;
|
||||
let types: Vec<_> = values.unnamed.iter().map(|field| {
|
||||
field.to_token_stream()
|
||||
}).collect();
|
||||
let field_names: Vec<_> = values.unnamed.iter().enumerate().map(|(i, _)| {
|
||||
let name = "x".repeat(i + 1);
|
||||
let name = format_ident!("{}", name);
|
||||
quote! {#name}
|
||||
}).collect();
|
||||
|
||||
let move_fn_name = format_ident!("try_as_{}", snakify(&variant_name.to_string()));
|
||||
let ref_fn_name = format_ident!("try_as_{}_ref", snakify(&variant_name.to_string()));
|
||||
let mut_fn_name = format_ident!("try_as_{}_mut", snakify(&variant_name.to_string()));
|
||||
|
||||
Some(quote! {
|
||||
#[must_use]
|
||||
#[inline]
|
||||
pub fn #move_fn_name(self) -> Option<(#(#types),*)> {
|
||||
match self {
|
||||
#enum_name::#variant_name (#(#field_names),*) => Some((#(#field_names),*)),
|
||||
_ => None
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
#[inline]
|
||||
pub const fn #ref_fn_name(&self) -> Option<(#(&#types),*)> {
|
||||
match self {
|
||||
#enum_name::#variant_name (#(#field_names),*) => Some((#(#field_names),*)),
|
||||
_ => None
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
#[inline]
|
||||
pub fn #mut_fn_name(&mut self) -> Option<(#(&mut #types),*)> {
|
||||
match self {
|
||||
#enum_name::#variant_name (#(#field_names),*) => Some((#(#field_names),*)),
|
||||
_ => None
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
_ => {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
|
||||
})
|
||||
.collect();
|
||||
|
||||
Ok(quote! {
|
||||
impl #enum_name {
|
||||
#(#variants)*
|
||||
}
|
||||
}
|
||||
.into())
|
||||
}
|
@ -4,6 +4,7 @@ pub mod enum_is;
|
||||
pub mod enum_iter;
|
||||
pub mod enum_messages;
|
||||
pub mod enum_properties;
|
||||
pub mod enum_try_as;
|
||||
pub mod enum_variant_names;
|
||||
pub mod from_repr;
|
||||
|
||||
|
44
strum_tests/tests/enum_try_as.rs
Normal file
44
strum_tests/tests/enum_try_as.rs
Normal file
@ -0,0 +1,44 @@
|
||||
use strum::EnumTryAs;
|
||||
|
||||
#[derive(EnumTryAs)]
|
||||
enum Foo {
|
||||
Unnamed0(),
|
||||
Unnamed1(u128),
|
||||
Unnamed2(bool, String),
|
||||
#[strum(disabled)]
|
||||
#[allow(dead_code)]
|
||||
Disabled(u32),
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unnamed_0() {
|
||||
let foo = Foo::Unnamed0();
|
||||
assert_eq!(Some(()), foo.try_as_unnamed_0());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unnamed_1() {
|
||||
let foo = Foo::Unnamed1(128);
|
||||
assert_eq!(Some(&128), foo.try_as_unnamed_1_ref());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unnamed_2() {
|
||||
let foo = Foo::Unnamed2(true, String::from("Hay"));
|
||||
assert_eq!(Some((true, String::from("Hay"))), foo.try_as_unnamed_2());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn can_mutate() {
|
||||
let mut foo = Foo::Unnamed1(128);
|
||||
if let Some(value) = foo.try_as_unnamed_1_mut() {
|
||||
*value = 44_u128;
|
||||
}
|
||||
assert_eq!(foo.try_as_unnamed_1(), Some(44));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn doesnt_match_other_variations() {
|
||||
let foo = Foo::Unnamed1(66);
|
||||
assert_eq!(None, foo.try_as_unnamed_0());
|
||||
}
|
Loading…
Reference in New Issue
Block a user