Skip to content

Commit f9c8222

Browse files
authored
Expose from_expr option when deriving FromMeta (#370)
* Add from_expr option for FromMeta This rounds out the ability to make structs that accept custom short-forms without having to hand implement FromMeta. Fixes #369 * Fix: Don't consider skipped variants for from_expr validation
1 parent 59a46eb commit f9c8222

File tree

7 files changed

+149
-1
lines changed

7 files changed

+149
-1
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
- Keep parsing the body and type params even if there are errors from parsing attributes. [#7](https://github.com/TedDriggs/darling/issues/325)
66
- Support `#[darling(with = ...)]` on the `generics` field when deriving `FromDeriveInput`.
7+
- Add `#[darling(from_expr = ...)]` when deriving `FromMeta` to support overriding the key-value form [#369](https://github.com/TedDriggs/darling/issues/369)
78

89

910
## v0.21.1 (August 4, 2025)

core/src/codegen/from_meta_impl.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ pub struct FromMetaImpl<'a> {
1212
pub base: TraitImpl<'a>,
1313
pub from_word: Option<Cow<'a, Callable>>,
1414
pub from_none: Option<&'a Callable>,
15+
pub from_expr: Option<&'a Callable>,
1516
pub derive_syn_parse: bool,
1617
}
1718

@@ -35,6 +36,14 @@ impl ToTokens for FromMetaImpl<'_> {
3536
}
3637
});
3738

39+
let from_expr = self.from_expr.map(|body| {
40+
quote_spanned! {body.span()=>
41+
fn from_expr(expr: &::darling::export::syn::Expr) -> ::darling::Result<Self> {
42+
::darling::export::identity::<fn(&::darling::export::syn::Expr) -> ::darling::Result<Self>>(#body)(expr)
43+
}
44+
}
45+
});
46+
3847
let impl_block = match base.data {
3948
// Unit structs allow empty bodies only.
4049
Data::Struct(ref vd) if vd.style.is_unit() => {
@@ -82,6 +91,8 @@ impl ToTokens for FromMetaImpl<'_> {
8291

8392
#from_none
8493

94+
#from_expr
95+
8596
fn from_list(__items: &[::darling::export::NestedMeta]) -> ::darling::Result<Self> {
8697

8798
#decls
@@ -157,6 +168,8 @@ impl ToTokens for FromMetaImpl<'_> {
157168
#from_word
158169

159170
#from_none
171+
172+
#from_expr
160173
)
161174
}
162175
};

core/src/options/from_meta.rs

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ pub struct FromMetaOptions {
1717
from_word: Option<Callable>,
1818
/// Override for the default [`FromMeta::from_none`] method.
1919
from_none: Option<Callable>,
20+
/// Override for the default [`FromMeta::from_expr`] method.
21+
from_expr: Option<Callable>,
2022
/// Whether or not to derive [`syn::parse::Parse`] in addition to deriving [`FromMeta`].
2123
derive_syn_parse: Option<bool>,
2224
}
@@ -27,6 +29,7 @@ impl FromMetaOptions {
2729
base: Core::start(di)?,
2830
from_word: None,
2931
from_none: None,
32+
from_expr: None,
3033
derive_syn_parse: None,
3134
})
3235
.parse_attributes(&di.attrs)?
@@ -78,6 +81,12 @@ impl ParseAttribute for FromMetaOptions {
7881
}
7982

8083
self.from_none = FromMeta::from_meta(mi).map(Some)?;
84+
} else if path.is_ident("from_expr") {
85+
if self.from_expr.is_some() {
86+
return Err(Error::duplicate_field_path(path).with_span(path));
87+
}
88+
89+
self.from_expr = FromMeta::from_meta(mi).map(Some)?;
8190
} else if path.is_ident("derive_syn_parse") {
8291
if self.derive_syn_parse.is_some() {
8392
return Err(Error::duplicate_field_path(path).with_span(path));
@@ -113,6 +122,12 @@ impl ParseData for FromMetaOptions {
113122
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));
114123
}
115124
}
125+
126+
if let Some(from_expr) = &self.from_expr {
127+
if data.is_newtype() {
128+
errors.push(Error::custom("`from_expr` cannot be used on newtype structs because the implementation is entirely delegated to the inner type").with_span(from_expr));
129+
}
130+
}
116131
}
117132
Data::Enum(ref data) => {
118133
let word_variants: Vec<_> = data
@@ -140,6 +155,15 @@ impl ParseData for FromMetaOptions {
140155
);
141156
}
142157
}
158+
159+
if let Some(from_expr) = &self.from_expr {
160+
if data.iter().any(|v| v.is_unit_variant() && !v.is_skipped()) {
161+
errors.push(
162+
Error::custom("`from_expr` cannot be used on enums with non-skipped unit variants because it conflicts with the generated impl")
163+
.with_span(from_expr),
164+
);
165+
}
166+
}
143167
}
144168
}
145169
}
@@ -151,6 +175,7 @@ impl<'a> From<&'a FromMetaOptions> for FromMetaImpl<'a> {
151175
base: (&v.base).into(),
152176
from_word: v.from_word(),
153177
from_none: v.from_none.as_ref(),
178+
from_expr: v.from_expr.as_ref(),
154179
derive_syn_parse: v.derive_syn_parse.unwrap_or_default(),
155180
}
156181
}

core/src/options/input_variant.rs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,14 @@ pub struct InputVariant {
2020
}
2121

2222
impl InputVariant {
23+
pub fn is_unit_variant(&self) -> bool {
24+
self.data.is_unit()
25+
}
26+
27+
pub fn is_skipped(&self) -> bool {
28+
self.skip.unwrap_or_default()
29+
}
30+
2331
pub fn as_codegen_variant<'a>(&'a self, ty_ident: &'a syn::Ident) -> codegen::Variant<'a> {
2432
codegen::Variant {
2533
ty_ident,
@@ -29,7 +37,7 @@ impl InputVariant {
2937
.as_deref()
3038
.map_or_else(|| Cow::Owned(self.ident.to_string()), Cow::Borrowed),
3139
data: self.data.as_ref().map(InputField::as_codegen_field),
32-
skip: self.skip.unwrap_or_default(),
40+
skip: self.is_skipped(),
3341
allow_unknown_fields: self.allow_unknown_fields.unwrap_or_default(),
3442
}
3543
}

examples/from_word_and_expr.rs

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
use darling::{ast::Data, util::Flag, FromDeriveInput, FromMeta};
2+
use darling_macro::FromField;
3+
4+
#[derive(Default, FromMeta)]
5+
#[darling(from_word = || Ok(Default::default()), from_expr = |expr| Ok(ErrorPolicy::from(expr)))]
6+
struct ErrorPolicy {
7+
warn: Flag,
8+
value: Option<syn::Expr>,
9+
}
10+
11+
impl From<&'_ syn::Expr> for ErrorPolicy {
12+
fn from(expr: &'_ syn::Expr) -> Self {
13+
ErrorPolicy {
14+
warn: Flag::default(),
15+
value: Some(expr.clone()),
16+
}
17+
}
18+
}
19+
20+
#[derive(FromField)]
21+
#[darling(attributes(toml))]
22+
struct Field {
23+
default: Option<ErrorPolicy>,
24+
recover: Option<ErrorPolicy>,
25+
}
26+
27+
#[derive(FromDeriveInput)]
28+
#[darling(attributes(toml))]
29+
struct TomlConfig {
30+
data: Data<(), Field>,
31+
}
32+
33+
fn main() {
34+
let input = TomlConfig::from_derive_input(&syn::parse_quote! {
35+
struct Config {
36+
#[toml(default, recover(warn))]
37+
field1: String,
38+
#[toml(default = String::new())]
39+
field2: String,
40+
}
41+
})
42+
.unwrap();
43+
44+
assert!(input.data.is_struct());
45+
let fields = input.data.take_struct().expect("input is struct").fields;
46+
assert!(fields[0].default.is_some());
47+
assert!(fields[0]
48+
.recover
49+
.as_ref()
50+
.map(|r| r.warn.is_present())
51+
.unwrap_or(false));
52+
assert!(fields[1]
53+
.default
54+
.as_ref()
55+
.map(|d| d.value.is_some())
56+
.unwrap_or(false));
57+
assert!(fields[1].recover.is_none());
58+
}

tests/compile-fail/from_expr.rs

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
use darling::FromMeta;
2+
3+
/// This usage of `from_expr` is VALID because there are no unit variants that should conflict with the
4+
/// implementation.
5+
#[derive(FromMeta)]
6+
#[darling(from_expr = |expr| Ok(HasNoUnits::Variant2 { other: format!("{:?}", expr) }))]
7+
enum HasNoUnits {
8+
Variant1 { field: String },
9+
Variant2 { other: String },
10+
}
11+
12+
/// This usage of `from_expr` is invalid because unit variants already generate a from_expr
13+
/// method, and we don't allow using the from_expr override when it conflicts with the macro's
14+
/// "normal" operation.
15+
#[derive(FromMeta)]
16+
#[darling(from_expr = |expr| Ok(HasUnits::Variant2))]
17+
enum HasUnits {
18+
Variant1 { field: String },
19+
Variant2,
20+
}
21+
22+
fn newtype_from_expr(_expr: &syn::Expr) -> darling::Result<Newtype> {
23+
Ok(Newtype(true))
24+
}
25+
26+
// This usage of `from_expr` is invalid because newtype structs call the inner type's `from_meta`
27+
// directly from their `from_meta`, so the custom `from_expr` will never be called in normal usage.
28+
#[derive(FromMeta)]
29+
#[darling(from_expr = newtype_from_expr)]
30+
struct Newtype(bool);
31+
32+
fn main() {}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
error: `from_expr` cannot be used on enums with non-skipped unit variants because it conflicts with the generated impl
2+
--> tests/compile-fail/from_expr.rs:16:23
3+
|
4+
16 | #[darling(from_expr = |expr| Ok(HasUnits::Variant2))]
5+
| ^
6+
7+
error: `from_expr` cannot be used on newtype structs because the implementation is entirely delegated to the inner type
8+
--> tests/compile-fail/from_expr.rs:29:23
9+
|
10+
29 | #[darling(from_expr = newtype_from_expr)]
11+
| ^^^^^^^^^^^^^^^^^

0 commit comments

Comments
 (0)