Skip to content

Commit

Permalink
Improve FromMeta implementation for enums
Browse files Browse the repository at this point in the history
  • Loading branch information
davidsemakula committed Dec 28, 2023
1 parent 0c79e55 commit 233b60c
Show file tree
Hide file tree
Showing 3 changed files with 76 additions and 4 deletions.
18 changes: 14 additions & 4 deletions core/src/codegen/from_meta_impl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,6 @@ impl<'a> ToTokens for FromMetaImpl<'a> {
}
Data::Enum(ref variants) => {
let unit_arms = variants.iter().map(Variant::as_unit_match_arm);
let struct_arms = variants.iter().map(Variant::as_data_match_arm);

let unknown_variant_err = if !variants.is_empty() {
let names = variants.iter().map(Variant::as_name);
Expand All @@ -90,16 +89,26 @@ impl<'a> ToTokens for FromMetaImpl<'a> {
}
};

let default = base
.default
.as_ref()
.map(|default_expr| quote!(::darling::export::Ok(#default_expr)));
let unit_arm_default = default.as_ref().map(|default| quote!("" => #default,));
let list_default_or_err = default.unwrap_or(quote! {
::darling::export::Err(::darling::Error::too_few_items(1))
});

quote!(
fn from_list(__outer: &[::darling::export::NestedMeta]) -> ::darling::Result<Self> {
// An enum must have exactly one value inside the parentheses if it's not a unit
// match arm
// match arm.
// NOTE: The empty list case uses the annotated default value for the enum if any.
match __outer.len() {
0 => ::darling::export::Err(::darling::Error::too_few_items(1)),
0 => #list_default_or_err,
1 => {
if let ::darling::export::NestedMeta::Meta(ref __nested) = __outer[0] {
match ::darling::util::path_to_string(__nested.path()).as_ref() {
#(#struct_arms)*
#(#variants)*
__other => ::darling::export::Err(::darling::Error::#unknown_variant_err.with_span(__nested))
}
} else {
Expand All @@ -113,6 +122,7 @@ impl<'a> ToTokens for FromMetaImpl<'a> {
fn from_string(lit: &str) -> ::darling::Result<Self> {
match lit {
#(#unit_arms)*
#unit_arm_default
__other => ::darling::export::Err(::darling::Error::unknown_value(__other))
}
}
Expand Down
10 changes: 10 additions & 0 deletions core/src/codegen/variant.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,16 @@ impl<'a> UsesTypeParams for Variant<'a> {
}
}

impl<'a> ToTokens for Variant<'a> {
fn to_tokens(&self, tokens: &mut TokenStream) {
if self.data.is_unit() {
self.as_unit_match_arm().to_tokens(tokens);
} else {
self.as_data_match_arm().to_tokens(tokens)
}
}
}

/// Code generator for an enum variant in a unit match position.
/// This is placed in generated `from_string` calls for the parent enum.
/// Value-carrying variants wrapped in this type will emit code to produce an "unsupported format" error.
Expand Down
52 changes: 52 additions & 0 deletions tests/from_meta.rs
Original file line number Diff line number Diff line change
Expand Up @@ -64,3 +64,55 @@ fn nested_meta_lit_bool_errors() {
Error::unsupported_format("literal").to_string()
);
}

/// Tests behavior of FromMeta implementation for enums (including behavior of defaults).
mod enum_impl {
use darling::{Error, FromMeta};
use syn::parse_quote;

/// A playback volume.
/// Deriving `Default` will cause the "default" variant to be set when the meta-item is empty.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, FromMeta)]
#[darling(default)]
enum Volume {
#[default]
Normal,
Low,
High,
#[darling(rename = "dB")]
Decibels(u8),
}

#[test]
fn empty_list() {
let volume = Volume::from_list(&[]).unwrap();
assert_eq!(volume, Volume::Normal);
}

#[test]
fn empty_string() {
let volume = Volume::from_string("").unwrap();
assert_eq!(volume, Volume::Normal);
}

#[test]
fn single_value_list() {
let unit_variant = Volume::from_list(&[parse_quote!(high)]).unwrap();
assert_eq!(unit_variant, Volume::High);

let newtype_variant = Volume::from_list(&[parse_quote!(dB = 100)]).unwrap();
assert_eq!(newtype_variant, Volume::Decibels(100));
}

#[test]
fn string_for_unit_variant() {
let volume = Volume::from_string("low").unwrap();
assert_eq!(volume, Volume::Low);
}

#[test]
fn multiple_values_list_errors() {
let err = Volume::from_list(&[parse_quote!(low), parse_quote!(dB = 20)]).unwrap_err();
assert_eq!(err.to_string(), Error::too_many_items(1).to_string());
}
}

0 comments on commit 233b60c

Please sign in to comment.