Skip to content

Commit

Permalink
Merge branch 'ericmcbride-ISSUE-245'
Browse files Browse the repository at this point in the history
  • Loading branch information
Peter Glotfelty committed Jan 20, 2024
2 parents a155e81 + c3021f7 commit 565b33e
Show file tree
Hide file tree
Showing 8 changed files with 180 additions and 19 deletions.
33 changes: 33 additions & 0 deletions strum_macros/src/helpers/inner_variant_props.rs
Original file line number Diff line number Diff line change
@@ -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)
}
}
50 changes: 47 additions & 3 deletions strum_macros/src/helpers/metadata.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ use syn::{
parse::{Parse, ParseStream},
parse2, parse_str,
punctuated::Punctuated,
Attribute, DeriveInput, Expr, ExprLit, Ident, Lit, LitBool, LitStr, Meta, MetaNameValue, Path,
Token, Variant, Visibility,
Attribute, DeriveInput, Expr, ExprLit, Field, Ident, Lit, LitBool, LitStr, Meta, MetaNameValue,
Path, Token, Variant, Visibility,
};

use super::case_style::CaseStyle;
Expand All @@ -31,6 +31,7 @@ pub mod kw {
custom_keyword!(to_string);
custom_keyword!(disabled);
custom_keyword!(default);
custom_keyword!(default_with);
custom_keyword!(props);
custom_keyword!(ascii_case_insensitive);
}
Expand Down Expand Up @@ -165,6 +166,10 @@ pub enum VariantMeta {
},
Disabled(kw::disabled),
Default(kw::default),
DefaultWith {
kw: kw::default_with,
value: LitStr,
},
AsciiCaseInsensitive {
kw: kw::ascii_case_insensitive,
value: bool,
Expand Down Expand Up @@ -202,6 +207,11 @@ impl Parse for VariantMeta {
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![=]) {
Expand Down Expand Up @@ -253,7 +263,7 @@ impl VariantExt for Variant {
let result = get_metadata_inner("strum", &self.attrs)?;
self.attrs
.iter()
.filter(|attr| attr.path().is_ident("doc"))
.filter(|attr| attr.meta.path().is_ident("doc"))
.try_fold(result, |mut vec, attr| {
if let Meta::NameValue(MetaNameValue {
value:
Expand Down Expand Up @@ -284,3 +294,37 @@ fn get_metadata_inner<'a, T: Parse>(
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))
}
}
3 changes: 2 additions & 1 deletion strum_macros/src/helpers/mod.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
pub use self::case_style::{snakify, CaseStyleHelpers};
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;
Expand Down
12 changes: 11 additions & 1 deletion strum_macros/src/helpers/variant_props.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ pub trait HasStrumVariantProperties {
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>,
Expand Down Expand Up @@ -62,9 +63,10 @@ impl HasStrumVariantProperties for Variant {

let mut message_kw = None;
let mut detailed_message_kw = None;
let mut to_string_kw = None;
let mut disabled_kw = None;
let mut default_kw = None;
let mut 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 {
Expand Down Expand Up @@ -114,6 +116,14 @@ impl HasStrumVariantProperties for Variant {
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"));
Expand Down
7 changes: 5 additions & 2 deletions strum_macros/src/macros/enum_is.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use crate::helpers::{non_enum_error, snakify, HasStrumVariantProperties};
use crate::helpers::{case_style::snakify, non_enum_error, HasStrumVariantProperties};
use proc_macro2::TokenStream;
use quote::{format_ident, quote};
use syn::{Data, DeriveInput};
Expand All @@ -20,7 +20,10 @@ pub fn enum_is_inner(ast: &DeriveInput) -> syn::Result<TokenStream> {

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);
let doc_comment = format!(
"Returns [true] if the enum is [{}::{}] otherwise [false]",
enum_name, variant_name
);
Some(quote! {
#[must_use]
#[inline]
Expand Down
2 changes: 1 addition & 1 deletion strum_macros/src/macros/enum_try_as.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use crate::helpers::{non_enum_error, snakify, HasStrumVariantProperties};
use crate::helpers::{case_style::snakify, non_enum_error, HasStrumVariantProperties};
use proc_macro2::TokenStream;
use quote::{format_ident, quote, ToTokens};
use syn::{Data, DeriveInput};
Expand Down
40 changes: 29 additions & 11 deletions strum_macros/src/macros/strings/from_string.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ use quote::quote;
use syn::{Data, DeriveInput, Fields};

use crate::helpers::{
non_enum_error, occurrence_error, HasStrumVariantProperties, HasTypeProperties,
non_enum_error, occurrence_error, HasInnerVariantProperties, HasStrumVariantProperties,
HasTypeProperties,
};

pub fn from_string_inner(ast: &DeriveInput) -> syn::Result<TokenStream> {
Expand Down Expand Up @@ -45,7 +46,6 @@ pub fn from_string_inner(ast: &DeriveInput) -> syn::Result<TokenStream> {
))
}
}

default_kw = Some(kw);
default = quote! {
::core::result::Result::Ok(#name::#ident(s.into()))
Expand All @@ -56,16 +56,34 @@ pub fn from_string_inner(ast: &DeriveInput) -> syn::Result<TokenStream> {
let params = match &variant.fields {
Fields::Unit => quote! {},
Fields::Unnamed(fields) => {
let defaults =
::core::iter::repeat(quote!(Default::default())).take(fields.unnamed.len());
quote! { (#(#defaults),*) }
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 fields = fields
.named
.iter()
.map(|field| field.ident.as_ref().unwrap());
quote! { {#(#fields: Default::default()),*} }
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),*} }
}
};

Expand Down Expand Up @@ -113,6 +131,7 @@ pub fn from_string_inner(ast: &DeriveInput) -> syn::Result<TokenStream> {
}
}
};

let standard_match_body = if standard_match_arms.is_empty() {
default
} else {
Expand All @@ -134,7 +153,6 @@ pub fn from_string_inner(ast: &DeriveInput) -> syn::Result<TokenStream> {
}
}
};

let try_from_str = try_from_str(
name,
&impl_generics,
Expand Down
52 changes: 52 additions & 0 deletions strum_tests/tests/from_str.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,15 @@ enum Color {
Purple,
#[strum(serialize = "blk", serialize = "Black", ascii_case_insensitive)]
Black,
Pink {
#[strum(default_with = "test_default")]
test_no_default: NoDefault,

#[strum(default_with = "string_test")]
string_test: String,
},
#[strum(default_with = "to_white")]
White(String),
}

#[rustversion::since(1.34)]
Expand Down Expand Up @@ -177,3 +186,46 @@ fn case_insensitive_enum_case_insensitive() {
assert_from_str(CaseInsensitiveEnum::CaseInsensitive, "CaseInsensitive");
assert_from_str(CaseInsensitiveEnum::CaseInsensitive, "caseinsensitive");
}

#[derive(Eq, PartialEq, Debug)]
struct NoDefault(String);

fn test_default() -> NoDefault {
NoDefault(String::from("test"))
}

fn to_white() -> String {
String::from("white-test")
}

fn string_test() -> String {
String::from("This is a string test")
}

#[test]
fn color_default_with() {
match Color::from_str("Pink").unwrap() {
Color::Pink {
test_no_default,
string_test,
} => {
assert_eq!(test_no_default, test_default());
assert_eq!(string_test, String::from("This is a string test"));
}
other => {
panic!("Failed to get correct enum value {:?}", other);
}
}
}

#[test]
fn color_default_with_white() {
match Color::from_str("White").unwrap() {
Color::White(inner) => {
assert_eq!(inner, String::from("white-test"));
}
other => {
panic!("Failed t o get correct enum value {:?}", other);
}
}
}

0 comments on commit 565b33e

Please sign in to comment.