Repository: TedDriggs/darling (rust)

Issue:
Syntax for expressing enums with mixed variants
Let's say I have the following:
```rust
#[derive(Debug, FromField)]
#[darling(attributes(my_attribute))]
struct StructFieldReceiver {
    ident: Option<syn::Ident>,
    vis: syn::Visibility,
    ty: syn::Type,
    name: String,
    sql_type: SqlType,
}

#[derive(Debug, FromMeta)]
#[darling(rename_all = "lowercase")]
enum SqlType {
// omitted variants
    BigInt, Int, SmallInt, TinyInt, Bit,
    Float(Option<u8>),
    Decimal{
        p: Option<u8>,
        s: Option<u8>
    },
// omitted variants
}
```
I can use the variants without type with `#[my_attribute(name = "name", sql_type = "int")]`, but I don't know the correct syntax for specifying the Float or Decimal variants. Could you please point me in the right direction?

Pull request description:
Allow word-form for newtype variants if the field type supports from_none
Fixes #249

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 931a8c0..38e0ac6 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,9 @@
 # Changelog
 
+## Unreleased
+
+-  Allow word-form for newtype enum variants whose only field produces a value when `from_none` is called on their type [#249](https://github.com/TedDriggs/darling/issues/249)
+
 ## v0.20.8 (February 23, 2024)
 
 -  Add `#[darling(with = ...)]` support to `attrs` magic field to allow using custom receiver types for `attrs` [#273](https://github.com/TedDriggs/darling/issues/273)
diff --git a/core/src/codegen/variant.rs b/core/src/codegen/variant.rs
index 2a55780..9ce643f 100644
--- a/core/src/codegen/variant.rs
+++ b/core/src/codegen/variant.rs
@@ -82,6 +82,12 @@ impl<'a> ToTokens for UnitMatchArm<'a> {
 
         let name_in_attr = &val.name_in_attr;
 
+        let unsupported_format_error = || {
+            quote!(::darling::export::Err(
+                ::darling::Error::unsupported_format("literal")
+            ))
+        };
+
         if val.data.is_unit() {
             let variant_ident = val.variant_ident;
             let ty_ident = val.ty_ident;
@@ -89,9 +95,29 @@ impl<'a> ToTokens for UnitMatchArm<'a> {
             tokens.append_all(quote!(
                 #name_in_attr => ::darling::export::Ok(#ty_ident::#variant_ident),
             ));
+        } else if val.data.is_newtype() {
+            let field = val
+                .data
+                .fields
+                .first()
+                .expect("Newtype should have exactly one field");
+            let field_ty = field.ty;
+            let ty_ident = val.ty_ident;
+            let variant_ident = val.variant_ident;
+            let unsupported_format = unsupported_format_error();
+
+            tokens.append_all(quote!{
+                #name_in_attr => {
+                    match <#field_ty as ::darling::FromMeta>::from_none() {
+                        ::darling::export::Some(__value) => ::darling::export::Ok(#ty_ident::#variant_ident(__value)),
+                        ::darling::export::None => #unsupported_format,
+                    }
+                }
+            })
         } else {
+            let unsupported_format = unsupported_format_error();
             tokens.append_all(quote!(
-                #name_in_attr => ::darling::export::Err(::darling::Error::unsupported_format("literal")),
+                #name_in_attr => #unsupported_format,
             ));
         }
     }


Test patch:
diff --git a/tests/enums_newtype.rs b/tests/enums_newtype.rs
index c2c4ec9..658661b 100644
--- a/tests/enums_newtype.rs
+++ b/tests/enums_newtype.rs
@@ -13,6 +13,7 @@ pub struct Amet {
 pub enum Lorem {
     Ipsum(bool),
     Dolor(String),
+    OptDolor(Option<String>),
     Sit(Amet),
 }
 
@@ -61,6 +62,42 @@ fn string_literal() {
     assert_eq!(pr, Lorem::Dolor("Hello".to_string()));
 }
 
+#[test]
+fn option_literal() {
+    let holder = Holder::from_derive_input(&parse_quote! {
+        #[hello(lorem(opt_dolor = "Hello"))]
+        struct Bar;
+    })
+    .unwrap();
+
+    assert_eq!(holder.lorem, Lorem::OptDolor(Some("Hello".into())));
+}
+
+/// Make sure newtype variants whose field's type's `from_none` return
+/// a `Some` can be used in key-value form.
+#[test]
+fn option_word_only() {
+    let holder = Holder::from_derive_input(&parse_quote! {
+        #[hello(lorem = "opt_dolor")]
+        struct Bar;
+    })
+    .unwrap();
+
+    assert_eq!(holder.lorem, Lorem::OptDolor(None));
+}
+
+/// Make sure that newtype variants which don't produce a from_none value
+/// do not allow the word form.
+#[test]
+#[should_panic]
+fn word_only_fails_for_non_option() {
+    Holder::from_derive_input(&parse_quote! {
+        #[hello(lorem = "dolor")]
+        struct Bar;
+    })
+    .unwrap();
+}
+
 #[test]
 fn struct_nested() {
     let di = parse_quote! {