Repository: TedDriggs/darling (rust)

Issue:
Accept from_* methods as #[darling(...)] fields
It's currently impossible to have a struct with a derived `FromMeta` impl that has custom behavior in the `from_word` or `from_none` cases. The only workaround is to use `with` on every field of that type, which is tiresome.

Based on the discussion in #313, this proposal would allow the deriving struct to specify a path (or closure) that replaces the body of the corresponding method.

This will require some validation to ensure these options aren't conflicting with generated impls; for example, `from_word` cannot be used with enums where `word` is in use.

Additionally, this will require new documentation and examples.

Pull request description:
Allow custom from_word and from_none behaviors in derived FromMeta impls
Fixes #320 

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 5add02c..09c3937 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,6 +5,8 @@
 -  Support `#[darling(with = ...)]` on the `data` field when deriving `FromDeriveInput`. This allows the use of simpler receiver types, such as a `Vec` of enum variants.
 -  Bump version of `proc-macro2` to 1.0.86.
 -  Accept closures for `#[darling(with = ...)]` on fields in `FromDeriveInput`, `FromMeta`, `FromField`, etc. [#309](https://github.com/TedDriggs/darling/issues/309)
+-  Add `darling::util::Callable` to accept a path or closure as a meta-item expression
+-  Add `#[darling(from_word = ...)]` and `#[darling(from_none = ...)]` to control shorthand and fallback behaviors for structs and enums deriving `FromMeta` [#320](https://github.com/TedDriggs/darling/issues/320)
 
 ## v0.20.10 (July 9, 2024)
 
diff --git a/README.md b/README.md
index 5e4aa2a..c489fd8 100644
--- a/README.md
+++ b/README.md
@@ -123,6 +123,8 @@ Darling's features are built to work well for real-world projects.
 -   **Span access**: Use `darling::util::SpannedValue` in a struct to get access to that meta item's source code span. This can be used to emit warnings that point at a specific field from your proc macro. In addition, you can use `darling::Error::write_errors` to automatically get precise error location details in most cases.
 -   **"Did you mean" suggestions**: Compile errors from derived darling trait impls include suggestions for misspelled fields.
 -   **Struct flattening**: Use `#[darling(flatten)]` to remove one level of structure when presenting your meta item to users. Fields that are not known to the parent struct will be forwarded to the `flatten` field.
+-   **Custom shorthand**: Use `#[darling(from_word = ...)]` on a struct or enum to override how a simple word is interpreted. By default, it is an error for your macro's user to fail to specify the fields of your struct, but with this you can choose to instead produce a set of default values. This takes either a path or a closure whose signature matches `FromMeta::from_word`.
+-   **Custom handling for missing fields**: When a field is not present and `#[darling(default)]` is not used, derived impls will call `FromMeta::from_none` on that field's type to try and get the fallback value for the field. Usually, there is not a fallback value, so a missing field error is generated. `Option<T: FromMeta>` uses this to make options optional without requiring `#[darling(default)]` declarations, and structs and enums can use this themselves with `#[darling(from_none = ...)]`. This takes either a path or a closure whose signature matches `FromMeta::from_none`.
 
 ## Shape Validation
 
diff --git a/core/src/codegen/from_meta_impl.rs b/core/src/codegen/from_meta_impl.rs
index d96e639..4c59627 100644
--- a/core/src/codegen/from_meta_impl.rs
+++ b/core/src/codegen/from_meta_impl.rs
@@ -1,17 +1,39 @@
+use std::borrow::Cow;
+
 use proc_macro2::TokenStream;
-use quote::{quote, ToTokens};
+use quote::{quote, quote_spanned, ToTokens};
+use syn::spanned::Spanned;
 
 use crate::ast::{Data, Fields, Style};
 use crate::codegen::{Field, OuterFromImpl, TraitImpl, Variant};
+use crate::util::Callable;
 
 pub struct FromMetaImpl<'a> {
     pub base: TraitImpl<'a>,
+    pub from_word: Option<Cow<'a, Callable>>,
+    pub from_none: Option<&'a Callable>,
 }
 
 impl ToTokens for FromMetaImpl<'_> {
     fn to_tokens(&self, tokens: &mut TokenStream) {
         let base = &self.base;
 
+        let from_word = self.from_word.as_ref().map(|body| {
+            quote_spanned! {body.span()=>
+                fn from_word() -> ::darling::Result<Self> {
+                    ::darling::export::identity::<fn() -> ::darling::Result<Self>>(#body)()
+                }
+            }
+        });
+
+        let from_none = self.from_none.map(|body| {
+            quote_spanned! {body.span()=>
+                fn from_none() -> ::darling::export::Option<Self> {
+                    ::darling::export::identity::<fn() -> ::darling::export::Option<Self>>(#body)()
+                }
+            }
+        });
+
         let impl_block = match base.data {
             // Unit structs allow empty bodies only.
             Data::Struct(ref vd) if vd.style.is_unit() => {
@@ -55,6 +77,10 @@ impl ToTokens for FromMetaImpl<'_> {
                 let post_transform = base.post_transform_call();
 
                 quote!(
+                    #from_word
+
+                    #from_none
+
                     fn from_list(__items: &[::darling::export::NestedMeta]) -> ::darling::Result<Self> {
 
                         #decls
@@ -89,23 +115,6 @@ impl ToTokens for FromMetaImpl<'_> {
                     }
                 };
 
-                let word_or_err = variants
-                    .iter()
-                    .find_map(|variant| {
-                        if variant.word {
-                            let ty_ident = variant.ty_ident;
-                            let variant_ident = variant.variant_ident;
-                            Some(quote!(::darling::export::Ok(#ty_ident::#variant_ident)))
-                        } else {
-                            None
-                        }
-                    })
-                    .unwrap_or_else(|| {
-                        quote!(::darling::export::Err(
-                            ::darling::Error::unsupported_format("word")
-                        ))
-                    });
-
                 let data_variants = variants.iter().map(Variant::as_data_match_arm);
 
                 quote!(
@@ -135,9 +144,9 @@ impl ToTokens for FromMetaImpl<'_> {
                         }
                     }
 
-                    fn from_word() -> ::darling::Result<Self> {
-                        #word_or_err
-                    }
+                    #from_word
+
+                    #from_none
                 )
             }
         };
diff --git a/core/src/codegen/variant.rs b/core/src/codegen/variant.rs
index 8fd742a..9c1f6e5 100644
--- a/core/src/codegen/variant.rs
+++ b/core/src/codegen/variant.rs
@@ -26,10 +26,6 @@ pub struct Variant<'a> {
     /// Whether or not the variant should be skipped in the generated code.
     pub skip: bool,
 
-    /// Whether or not the variant should be used to create an instance for
-    /// `FromMeta::from_word`.
-    pub word: bool,
-
     pub allow_unknown_fields: bool,
 }
 
diff --git a/core/src/options/from_meta.rs b/core/src/options/from_meta.rs
index cc527e8..cfb7bd8 100644
--- a/core/src/options/from_meta.rs
+++ b/core/src/options/from_meta.rs
@@ -1,29 +1,85 @@
+use std::borrow::Cow;
+
 use proc_macro2::TokenStream;
 use quote::ToTokens;
+use syn::parse_quote;
 
 use crate::ast::Data;
 use crate::codegen::FromMetaImpl;
 use crate::error::Accumulator;
 use crate::options::{Core, ParseAttribute, ParseData};
-use crate::{Error, Result};
+use crate::util::Callable;
+use crate::{Error, FromMeta, Result};
 
 pub struct FromMetaOptions {
     base: Core,
+    /// Override for the default [`FromMeta::from_word`] method.
+    from_word: Option<Callable>,
+    /// Override for the default [`FromMeta::from_none`] method.
+    from_none: Option<Callable>,
 }
 
 impl FromMetaOptions {
     pub fn new(di: &syn::DeriveInput) -> Result<Self> {
         (FromMetaOptions {
             base: Core::start(di)?,
+            from_word: None,
+            from_none: None,
         })
         .parse_attributes(&di.attrs)?
         .parse_body(&di.data)
     }
+
+    /// Get the `from_word` method body, if one exists. This can come from direct use of
+    /// `#[darling(from_word = ...)]` on the container or from use of `#[darling(word)]` on
+    /// a unit variant.
+    #[allow(
+        clippy::wrong_self_convention,
+        // The reason is commented out due to MSRV issues.
+        // reason = "This matches the name of the input option and output method"
+    )]
+    fn from_word(&self) -> Option<Cow<'_, Callable>> {
+        self.from_word.as_ref().map(Cow::Borrowed).or_else(|| {
+            if let Data::Enum(ref variants) = self.base.data {
+                // The first variant which has `word` set to `true`.
+                // This assumes that validation has prevented multiple variants
+                // from claiming `word`.
+                let variant = variants
+                    .iter()
+                    .find(|v| v.word.map(|x| *x).unwrap_or_default())?;
+                let variant_ident = &variant.ident;
+                let closure: syn::ExprClosure = parse_quote! {
+                    || ::darling::export::Ok(Self::#variant_ident)
+                };
+                Some(Cow::Owned(Callable::from(closure)))
+            } else {
+                None
+            }
+        })
+    }
 }
 
 impl ParseAttribute for FromMetaOptions {
     fn parse_nested(&mut self, mi: &syn::Meta) -> Result<()> {
-        self.base.parse_nested(mi)
+        let path = mi.path();
+
+        if path.is_ident("from_word") {
+            if self.from_word.is_some() {
+                return Err(Error::duplicate_field_path(path).with_span(path));
+            }
+
+            self.from_word = FromMeta::from_meta(mi).map(Some)?;
+        } else if path.is_ident("from_none") {
+            if self.from_none.is_some() {
+                return Err(Error::duplicate_field_path(path).with_span(path));
+            }
+
+            self.from_none = FromMeta::from_meta(mi).map(Some)?;
+        } else {
+            self.base.parse_nested(mi)?;
+        }
+
+        Ok(())
     }
 }
 
@@ -39,18 +95,41 @@ impl ParseData for FromMetaOptions {
     fn validate_body(&self, errors: &mut Accumulator) {
         self.base.validate_body(errors);
 
-        if let Data::Enum(ref data) = self.base.data {
-            // Adds errors for duplicate `#[darling(word)]` annotations across all variants.
-            let word_variants: Vec<_> = data
-                .iter()
-                .filter_map(|variant| variant.word.as_ref())
-                .collect();
-            if word_variants.len() > 1 {
-                for word in word_variants {
-                    errors.push(
-                        Error::custom("`#[darling(word)]` can only be applied to one variant")
-                            .with_span(&word.span()),
-                    );
+        match self.base.data {
+            Data::Struct(ref data) => {
+                if let Some(from_word) = &self.from_word {
+                    if data.is_unit() {
+                        errors.push(Error::custom("`from_word` cannot be used on unit structs because it conflicts with the generated impl").with_span(from_word));
+                    } else if data.is_newtype() {
+                        errors.push(Error::custom("`from_word` cannot be used on newtype structs because the implementation is entirely delegated to the inner type").with_span(from_word));
+                    }
+                }
+            }
+            Data::Enum(ref data) => {
+                let word_variants: Vec<_> = data
+                    .iter()
+                    .filter_map(|variant| variant.word.as_ref())
+                    .collect();
+
+                if !word_variants.is_empty() {
+                    if let Some(from_word) = &self.from_word {
+                        errors.push(
+                            Error::custom(
+                                "`from_word` cannot be used with an enum that also uses `word`",
+                            )
+                            .with_span(from_word),
+                        )
+                    }
+                }
+
+                // Adds errors for duplicate `#[darling(word)]` annotations across all variants.
+                if word_variants.len() > 1 {
+                    for word in word_variants {
+                        errors.push(
+                            Error::custom("`#[darling(word)]` can only be applied to one variant")
+                                .with_span(&word.span()),
+                        );
+                    }
                 }
             }
         }
@@ -61,6 +140,8 @@ impl<'a> From<&'a FromMetaOptions> for FromMetaImpl<'a> {
     fn from(v: &'a FromMetaOptions) -> Self {
         FromMetaImpl {
             base: (&v.base).into(),
+            from_word: v.from_word(),
+            from_none: v.from_none.as_ref(),
         }
     }
 }
diff --git a/core/src/options/input_field.rs b/core/src/options/input_field.rs
index 6569b83..71e843a 100644
--- a/core/src/options/input_field.rs
+++ b/core/src/options/input_field.rs
@@ -4,7 +4,7 @@ use syn::{parse_quote_spanned, spanned::Spanned};
 
 use crate::codegen;
 use crate::options::{Core, DefaultExpression, ParseAttribute};
-use crate::util::{Flag, SpannedValue};
+use crate::util::{Callable, Flag, SpannedValue};
 use crate::{Error, FromMeta, Result};
 
 #[derive(Debug, Clone)]
@@ -13,7 +13,7 @@ pub struct InputField {
     pub attr_name: Option<String>,
     pub ty: syn::Type,
     pub default: Option<DefaultExpression>,
-    pub with: Option<With>,
+    pub with: Option<Callable>,
 
     /// If `true`, generated code will not look for this field in the input meta item,
     /// instead always falling back to either `InputField::default` or `Default::default`.
@@ -34,7 +34,7 @@ impl InputField {
                 .map_or_else(|| Cow::Owned(self.ident.to_string()), Cow::Borrowed),
             ty: &self.ty,
             default_expression: self.as_codegen_default(),
-            with_callable: self.with.as_ref().map(|w| &w.call).map_or_else(
+            with_callable: self.with.as_ref().map(|w| w.as_ref()).map_or_else(
                 || {
                     Cow::Owned(
                         parse_quote_spanned!(self.ty.span()=> ::darling::FromMeta::from_meta),
@@ -149,7 +149,7 @@ impl ParseAttribute for InputField {
                 return Err(Error::duplicate_field_path(path).with_span(mi));
             }
 
-            self.with = Some(With::from_meta(mi)?);
+            self.with = Some(FromMeta::from_meta(mi)?);
 
             if self.flatten.is_present() {
                 return Err(
@@ -239,24 +239,3 @@ impl ParseAttribute for InputField {
         Ok(())
     }
 }
-
-#[derive(Debug, Clone)]
-pub struct With {
-    /// The callable
-    call: syn::Expr,
-}
-
-impl With {
-    pub fn from_meta(meta: &syn::Meta) -> Result<Self> {
-        if let syn::Meta::NameValue(nv) = meta {
-            match &nv.value {
-                syn::Expr::Path(_) | syn::Expr::Closure(_) => Ok(Self {
-                    call: nv.value.clone(),
-                }),
-                _ => Err(Error::unexpected_expr_type(&nv.value)),
-            }
-        } else {
-            Err(Error::unsupported_format("non-value"))
-        }
-    }
-}
diff --git a/core/src/options/input_variant.rs b/core/src/options/input_variant.rs
index 05b9e27..443de7e 100644
--- a/core/src/options/input_variant.rs
+++ b/core/src/options/input_variant.rs
@@ -8,7 +8,7 @@ use crate::{Error, FromMeta, Result};
 
 #[derive(Debug, Clone)]
 pub struct InputVariant {
-    ident: syn::Ident,
+    pub ident: syn::Ident,
     attr_name: Option<String>,
     data: Fields<InputField>,
     skip: Option<bool>,
@@ -30,7 +30,6 @@ impl InputVariant {
                 .map_or_else(|| Cow::Owned(self.ident.to_string()), Cow::Borrowed),
             data: self.data.as_ref().map(InputField::as_codegen_field),
             skip: self.skip.unwrap_or_default(),
-            word: *self.word.unwrap_or_default(),
             allow_unknown_fields: self.allow_unknown_fields.unwrap_or_default(),
         }
     }
diff --git a/core/src/util/callable.rs b/core/src/util/callable.rs
new file mode 100644
index 0000000..c726679
--- /dev/null
+++ b/core/src/util/callable.rs
@@ -0,0 +1,62 @@
+use quote::ToTokens;
+
+use crate::{Error, FromMeta, Result};
+
+/// Either a path or a closure.
+///
+/// This type is useful for options that historically took a path,
+/// e.g. `#[darling(with = ...)]` or `#[serde(skip_serializing_if = ...)]`
+/// and now want to also allow using a closure to avoid needing a separate
+/// function declaration.
+///
+/// In `darling`, this value is wrapped in [`core::convert::identity`] before usage;
+/// this allows treatment of the closure and path cases as equivalent, and prevents
+/// a closure from accessing locals in the generated code.
+#[derive(Debug, Clone)]
+pub struct Callable {
+    /// The callable
+    call: syn::Expr,
+}
+
+impl AsRef<syn::Expr> for Callable {
+    fn as_ref(&self) -> &syn::Expr {
+        &self.call
+    }
+}
+
+impl From<syn::ExprPath> for Callable {
+    fn from(value: syn::ExprPath) -> Self {
+        Self {
+            call: syn::Expr::Path(value),
+        }
+    }
+}
+
+impl From<syn::ExprClosure> for Callable {
+    fn from(value: syn::ExprClosure) -> Self {
+        Self {
+            call: syn::Expr::Closure(value),
+        }
+    }
+}
+
+impl From<Callable> for syn::Expr {
+    fn from(value: Callable) -> Self {
+        value.call
+    }
+}
+
+impl FromMeta for Callable {
+    fn from_expr(expr: &syn::Expr) -> Result<Self> {
+        match expr {
+            syn::Expr::Path(_) | syn::Expr::Closure(_) => Ok(Self { call: expr.clone() }),
+            _ => Err(Error::unexpected_expr_type(expr)),
+        }
+    }
+}
+
+impl ToTokens for Callable {
+    fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
+        self.call.to_tokens(tokens);
+    }
+}
diff --git a/core/src/util/mod.rs b/core/src/util/mod.rs
index cf761ed..107ecd8 100644
--- a/core/src/util/mod.rs
+++ b/core/src/util/mod.rs
@@ -1,5 +1,6 @@
 //! Utility types for attribute parsing.
 
+mod callable;
 mod flag;
 mod ident_string;
 mod ignored;
@@ -12,6 +13,7 @@ mod shape;
 mod spanned_value;
 mod with_original;
 
+pub use self::callable::Callable;
 pub use self::flag::Flag;
 pub use self::ident_string::IdentString;
 pub use self::ignored::Ignored;
diff --git a/src/lib.rs b/src/lib.rs
index f9ba0ba..d34b8a7 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -22,6 +22,14 @@
 //!   in values not specified by the caller.
 //! * **Skipped fields**: You can skip a variant or field using `#[darling(skip)]`. Fields marked with this will fall back to
 //!   `Default::default()` for their value, but you can override that with an explicit default or a value from the type-level default.
+//! * **Custom shorthand**: Use `#[darling(from_word = ...)]` on a struct or enum to override how a simple word is interpreted.
+//!   By default, it is an error for your macro's user to fail to specify the fields of your struct, but with this you can choose to
+//!   instead produce a set of default values. This takes either a path or a closure whose signature matches `FromMeta::from_word`.
+//! * **Custom handling for missing fields**: When a field is not present and `#[darling(default)]` is not used, derived impls will
+//!   call `FromMeta::from_none` on that field's type to try and get the fallback value for the field. Usually, there is not a fallback
+//!   value, so a missing field error is generated. `Option<T: FromMeta>` uses this to make options optional without requiring
+//!   `#[darling(default)]` declarations, and structs and enums can use this themselves with `#[darling(from_none = ...)]`.
+//!   This takes either a path or a closure whose signature matches `FromMeta::from_none`.
 //!
 //! ## Forwarded Fields
 //! All derivable traits except `FromMeta` support forwarding some fields from the input AST to the derived struct.


Test patch:
diff --git a/tests/compile-fail/from_word.rs b/tests/compile-fail/from_word.rs
new file mode 100644
index 0000000..0d6de5f
--- /dev/null
+++ b/tests/compile-fail/from_word.rs
@@ -0,0 +1,30 @@
+use darling::FromMeta;
+
+// This usage of `from_word` is invalid because unit structs already generate a from_word
+// method, and we don't allow using the from_word override when it conflicts with the macro's
+// "normal" operation.
+#[derive(FromMeta)]
+#[darling(from_word = || Ok(Unit))]
+struct Unit;
+
+fn newtype_from_word() -> darling::Result<Newtype> {
+    Ok(Newtype(true))
+}
+
+// This usage of `from_word` is invalid because newtype structs call the inner type's `from_meta`
+// directly from their `from_meta`, so the custom `from_word` will never be called in normal usage.
+#[derive(FromMeta)]
+#[darling(from_word = newtype_from_word)]
+struct Newtype(bool);
+
+#[derive(FromMeta)]
+#[darling(from_word = || Ok(Wordy::Options { thing: "Hello".to_string() }))]
+enum Wordy {
+    #[darling(word)]
+    Normal,
+    Options {
+        thing: String,
+    },
+}
+
+fn main() {}
diff --git a/tests/compile-fail/from_word.stderr b/tests/compile-fail/from_word.stderr
new file mode 100644
index 0000000..2437764
--- /dev/null
+++ b/tests/compile-fail/from_word.stderr
@@ -0,0 +1,17 @@
+error: `from_word` cannot be used on unit structs because it conflicts with the generated impl
+ --> tests/compile-fail/from_word.rs:7:23
+  |
+7 | #[darling(from_word = || Ok(Unit))]
+  |                       ^
+
+error: `from_word` cannot be used on newtype structs because the implementation is entirely delegated to the inner type
+  --> tests/compile-fail/from_word.rs:17:23
+   |
+17 | #[darling(from_word = newtype_from_word)]
+   |                       ^^^^^^^^^^^^^^^^^
+
+error: `from_word` cannot be used with an enum that also uses `word`
+  --> tests/compile-fail/from_word.rs:21:23
+   |
+21 | #[darling(from_word = || Ok(Wordy::Options { thing: "Hello".to_string() }))]
+   |                       ^
diff --git a/tests/from_meta.rs b/tests/from_meta.rs
index 886465c..8fa47cb 100644
--- a/tests/from_meta.rs
+++ b/tests/from_meta.rs
@@ -107,3 +107,198 @@ mod enum_impl {
         assert_eq!(err.to_string(), Error::too_many_items(1).to_string());
     }
 }
+
+mod from_none_struct_closure {
+    use darling::FromMeta;
+    use syn::parse_quote;
+
+    #[derive(Debug, FromMeta)]
+    struct Outer {
+        // Do NOT add `darling(default)` here; this is testing the `from_none` fallback
+        // invoked when a field is not declared and no `default` is specified.
+        speech: Example,
+    }
+
+    #[derive(Debug, FromMeta)]
+    #[darling(from_none = || Some(Default::default()))]
+    struct Example {
+        max_volume: u32,
+    }
+
+    impl Default for Example {
+        fn default() -> Self {
+            Example { max_volume: 3 }
+        }
+    }
+
+    #[test]
+    fn absent_gets_from_none() {
+        let thing = Outer::from_list(&[]).unwrap();
+        assert_eq!(thing.speech.max_volume, 3);
+    }
+
+    #[test]
+    fn word_errors() {
+        let error = Outer::from_list(&[parse_quote!(speech)])
+            .expect_err("speech should require its fields if declared");
+        assert_eq!(error.len(), 1);
+    }
+
+    #[test]
+    fn list_sets_field() {
+        let thing = Outer::from_list(&[parse_quote!(speech(max_volume = 5))]).unwrap();
+        assert_eq!(thing.speech.max_volume, 5);
+    }
+}
+
+mod from_none_struct_path {
+    use darling::FromMeta;
+    use syn::parse_quote;
+
+    #[derive(Debug, FromMeta)]
+    struct Outer {
+        // Do NOT add `darling(default)` here; this is testing the `from_none` fallback
+        // invoked when a field is not declared and no `default` is specified.
+        speech: Example,
+    }
+
+    fn from_none_fallback() -> Option<Example> {
+        Some(Example { max_volume: 3 })
+    }
+
+    #[derive(Debug, FromMeta)]
+    #[darling(from_none = from_none_fallback)]
+    struct Example {
+        max_volume: u32,
+    }
+
+    #[test]
+    fn absent_gets_from_none() {
+        let thing = Outer::from_list(&[]).unwrap();
+        assert_eq!(thing.speech.max_volume, 3);
+    }
+
+    #[test]
+    fn word_errors() {
+        let error = Outer::from_list(&[parse_quote!(speech)])
+            .expect_err("speech should require its fields if declared");
+        assert_eq!(error.len(), 1);
+    }
+
+    #[test]
+    fn list_sets_field() {
+        let thing = Outer::from_list(&[parse_quote!(speech(max_volume = 5))]).unwrap();
+        assert_eq!(thing.speech.max_volume, 5);
+    }
+}
+
+mod from_word_struct_closure {
+    use darling::FromMeta;
+    use syn::parse_quote;
+
+    #[derive(FromMeta)]
+    struct Outer {
+        #[darling(default)]
+        speech: Example,
+    }
+
+    #[derive(FromMeta, Default)]
+    #[darling(from_word = || Ok(Example { max_volume: 10 }))]
+    struct Example {
+        max_volume: u32,
+    }
+
+    #[test]
+    fn absent_gets_default() {
+        let thing = Outer::from_list(&[]).unwrap();
+        assert_eq!(thing.speech.max_volume, 0);
+    }
+
+    #[test]
+    fn word_gets_value() {
+        let thing = Outer::from_list(&[parse_quote!(speech)]).unwrap();
+        assert_eq!(thing.speech.max_volume, 10);
+    }
+
+    #[test]
+    fn list_sets_field() {
+        let thing = Outer::from_list(&[parse_quote!(speech(max_volume = 5))]).unwrap();
+        assert_eq!(thing.speech.max_volume, 5);
+    }
+}
+
+mod from_word_struct_path {
+    use darling::FromMeta;
+    use syn::parse_quote;
+
+    #[derive(FromMeta)]
+    struct Outer {
+        #[darling(default)]
+        speech: Example,
+    }
+
+    fn max_volume_10() -> darling::Result<Example> {
+        Ok(Example { max_volume: 10 })
+    }
+
+    #[derive(FromMeta, Default)]
+    #[darling(from_word = max_volume_10)]
+    struct Example {
+        max_volume: u32,
+    }
+
+    #[test]
+    fn absent_gets_default() {
+        let thing = Outer::from_list(&[]).unwrap();
+        assert_eq!(thing.speech.max_volume, 0);
+    }
+
+    #[test]
+    fn word_gets_value() {
+        let thing = Outer::from_list(&[parse_quote!(speech)]).unwrap();
+        assert_eq!(thing.speech.max_volume, 10);
+    }
+
+    #[test]
+    fn list_sets_field() {
+        let thing = Outer::from_list(&[parse_quote!(speech(max_volume = 5))]).unwrap();
+        assert_eq!(thing.speech.max_volume, 5);
+    }
+}
+
+mod from_word_enum_closure {
+    use darling::FromMeta;
+    use syn::parse_quote;
+
+    #[derive(Debug, FromMeta)]
+    struct Outer {
+        speech: Example,
+    }
+
+    #[derive(Debug, FromMeta, PartialEq, Eq)]
+    #[darling(from_word = || Ok(Example::Left { max_volume: 10 }))]
+    enum Example {
+        Left { max_volume: u32 },
+        Right { speed: u32 },
+    }
+
+    #[test]
+    fn word_gets_value() {
+        let thing = Outer::from_list(&[parse_quote!(speech)]).unwrap();
+        assert_eq!(thing.speech, Example::Left { max_volume: 10 });
+    }
+
+    #[test]
+    fn list_sets_field() {
+        let thing = Outer::from_list(&[parse_quote!(speech(left(max_volume = 5)))]).unwrap();
+        assert_eq!(thing.speech, Example::Left { max_volume: 5 });
+    }
+
+    #[test]
+    fn variant_word_fails() {
+        let thing = Outer::from_list(&[parse_quote!(speech(left))]).expect_err(
+            "A variant word is an error because from_word applies at the all-up enum level",
+        );
+        assert_eq!(thing.len(), 1);
+    }
+}