Skip to content

Commit 50a814d

Browse files
committed
Enable #[darling(with = ...)] for generics field
1 parent 7f7dee7 commit 50a814d

File tree

4 files changed

+105
-19
lines changed

4 files changed

+105
-19
lines changed

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
# Changelog
22

3+
## Unreleased
4+
5+
- Keep parsing the body and type params even if there are errors from parsing attributes. [#7](https://github.com/TedDriggs/darling/issues/325)
6+
- Support `#[darling(with = ...)]` on the `generics` field when deriving `FromDeriveInput`.
7+
8+
39
## v0.21.1 (August 4, 2025)
410

511
- Track all alternate field names, and show them in error message if there aren't too many. [#325](https://github.com/TedDriggs/darling/issues/325)

core/src/codegen/from_derive_impl.rs

Lines changed: 20 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ use super::ForwardAttrs;
1313

1414
pub struct FromDeriveInputImpl<'a> {
1515
pub ident: Option<&'a Ident>,
16-
pub generics: Option<&'a Ident>,
16+
pub generics: Option<&'a ForwardedField>,
1717
pub vis: Option<&'a Ident>,
1818
pub data: Option<&'a ForwardedField>,
1919
pub base: TraitImpl<'a>,
@@ -53,17 +53,17 @@ impl ToTokens for FromDeriveInputImpl<'_> {
5353
let passed_vis = self.vis.as_ref().map(|i| quote!(#i: #input.vis.clone(),));
5454
let passed_attrs = self.forward_attrs.as_initializer();
5555

56-
let read_generics = self.generics.map(|_| {
56+
let read_generics = self.generics.map(|generics| {
57+
let ident = &generics.ident;
58+
let with = generics.with.as_ref().map(|p| quote!(#p)).unwrap_or_else(
59+
|| quote_spanned!(ident.span()=>::darling::FromGenerics::from_generics),
60+
);
5761
quote! {
58-
let __generics = __errors.handle(::darling::FromGenerics::from_generics(&#input.generics));
62+
let #ident = __errors.handle(#with(&#input.generics));
5963
}
6064
});
6165

62-
let pass_generics_to_receiver = self.generics.map(|generics| {
63-
quote! {
64-
#generics: __generics.expect("Parsing succeeded"),
65-
}
66-
});
66+
let pass_generics_to_receiver = self.generics.map(|g| g.as_initializer());
6767

6868
let check_shape = self
6969
.supports
@@ -80,17 +80,20 @@ impl ToTokens for FromDeriveInputImpl<'_> {
8080
.unwrap_or_else(|| quote!(::darling::export::Ok));
8181

8282
let supports = self.supports;
83-
let validate_and_read_data = quote! {
84-
#supports
85-
let __data = __errors.handle(#check_shape(&#input.data).and_then(#read_data));
86-
};
87-
88-
let pass_data_to_receiver = self.data.map(|data| {
89-
let data_ident = &data.ident;
83+
let validate_and_read_data = {
84+
// If the caller wants `data` read into a field, we can use `data` as the local variable name
85+
// because we know there are no other fields of that name.
86+
let let_binding = self.data.map(|d| {
87+
let ident = &d.ident;
88+
quote!(let #ident = )
89+
});
9090
quote! {
91-
#data_ident: __data.expect("Data parsed successfully"),
91+
#supports
92+
#let_binding __errors.handle(#check_shape(&#input.data).and_then(#read_data));
9293
}
93-
});
94+
};
95+
96+
let pass_data_to_receiver = self.data.map(|f| f.as_initializer());
9497

9598
let inits = self.base.initializers();
9699
let default = if self.from_ident {

core/src/options/from_derive.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ pub struct FdiOptions {
1616
pub vis: Option<Ident>,
1717

1818
/// The field on the target struct which should receive the type generics, if any.
19-
pub generics: Option<Ident>,
19+
pub generics: Option<ForwardedField>,
2020

2121
/// The field on the target struct which should receive the derive input body, if any.
2222
pub data: Option<ForwardedField>,
@@ -65,7 +65,7 @@ impl ParseData for FdiOptions {
6565
Ok(())
6666
}
6767
Some("generics") => {
68-
self.generics.clone_from(&field.ident);
68+
self.generics = ForwardedField::from_field(field).map(Some)?;
6969
Ok(())
7070
}
7171
_ => self.base.parse_field(field),

tests/generics_with.rs

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
use std::collections::BTreeSet;
2+
3+
use darling::{Error, FromDeriveInput, Result};
4+
use syn::{parse_quote, Ident};
5+
6+
fn check_ident(ident: &Ident) -> Result<String> {
7+
let s = ident.to_string();
8+
if s.len() < 2 {
9+
Err(Error::custom("generics must be at least 2 characters").with_span(ident))
10+
} else {
11+
Ok(s)
12+
}
13+
}
14+
15+
fn long_generic_names(generics: &syn::Generics) -> Result<BTreeSet<String>> {
16+
let mut errors = Error::accumulator();
17+
let valid = generics
18+
.type_params()
19+
.filter_map(|c| errors.handle(check_ident(&c.ident)))
20+
.collect();
21+
errors.finish_with(valid)
22+
}
23+
24+
#[derive(Debug, FromDeriveInput)]
25+
#[darling(attributes(a))]
26+
struct Receiver {
27+
#[darling(with = long_generic_names)]
28+
generics: BTreeSet<String>,
29+
surname: String,
30+
}
31+
32+
#[test]
33+
fn succeeds_on_no_generics() {
34+
let di = Receiver::from_derive_input(&parse_quote! {
35+
#[a(surname = "Smith")]
36+
struct Demo;
37+
})
38+
.unwrap();
39+
40+
assert!(di.generics.is_empty());
41+
}
42+
43+
#[test]
44+
fn succeeds_on_valid_generics() {
45+
let di = Receiver::from_derive_input(&parse_quote! {
46+
#[a(surname = "Smith")]
47+
struct Demo<Greeting> {
48+
hello: Greeting,
49+
world: String,
50+
}
51+
})
52+
.unwrap();
53+
54+
assert_eq!(di.generics.len(), 1);
55+
assert!(di.generics.contains("Greeting"));
56+
assert_eq!(di.surname, "Smith");
57+
}
58+
59+
#[test]
60+
fn rejects_invalid_input() {
61+
let err = Receiver::from_derive_input(&parse_quote! {
62+
struct Demo<G, S> {
63+
hello: G,
64+
world: S,
65+
}
66+
})
67+
.unwrap_err();
68+
69+
assert_eq!(
70+
err.len(),
71+
// 2 errors from short type param names
72+
2 +
73+
// error for missing field `surname`
74+
1,
75+
"errors should have accumulated, and body checking should have occurred"
76+
);
77+
}

0 commit comments

Comments
 (0)