Skip to content

Commit 5ecff50

Browse files
committed
Accept a closure for with in lieu of a path for fields
Fixes #309
1 parent c330c34 commit 5ecff50

File tree

4 files changed

+107
-5
lines changed

4 files changed

+107
-5
lines changed

core/src/codegen/field.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,9 @@ pub struct Field<'a> {
2222
/// The type of the field in the input.
2323
pub ty: &'a Type,
2424
pub default_expression: Option<DefaultExpression<'a>>,
25+
/// Initial declaration for `with`; this is used if `with` was a closure,
26+
/// to assign the closure to a local variable.
27+
pub with_initializer: Option<syn::Stmt>,
2528
pub with_path: Cow<'a, Path>,
2629
pub post_transform: Option<&'a PostfixTransform>,
2730
pub skip: bool,
@@ -107,6 +110,10 @@ impl<'a> ToTokens for Declaration<'a> {
107110
let mut __flatten: Vec<::darling::ast::NestedMeta> = vec![];
108111
});
109112
}
113+
114+
if let Some(stmt) = &self.0.with_initializer {
115+
stmt.to_tokens(tokens);
116+
}
110117
}
111118
}
112119

core/src/options/input_field.rs

Lines changed: 58 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use std::borrow::Cow;
22

3+
use quote::format_ident;
34
use syn::{parse_quote_spanned, spanned::Spanned};
45

56
use crate::codegen;
@@ -13,7 +14,7 @@ pub struct InputField {
1314
pub attr_name: Option<String>,
1415
pub ty: syn::Type,
1516
pub default: Option<DefaultExpression>,
16-
pub with: Option<syn::Path>,
17+
pub with: Option<With>,
1718

1819
/// If `true`, generated code will not look for this field in the input meta item,
1920
/// instead always falling back to either `InputField::default` or `Default::default`.
@@ -34,7 +35,8 @@ impl InputField {
3435
.map_or_else(|| Cow::Owned(self.ident.to_string()), Cow::Borrowed),
3536
ty: &self.ty,
3637
default_expression: self.as_codegen_default(),
37-
with_path: self.with.as_ref().map_or_else(
38+
with_initializer: self.with.as_ref().and_then(With::to_closure_declaration),
39+
with_path: self.with.as_ref().map(|w| &w.path).map_or_else(
3840
|| {
3941
Cow::Owned(
4042
parse_quote_spanned!(self.ty.span()=> ::darling::FromMeta::from_meta),
@@ -149,7 +151,7 @@ impl ParseAttribute for InputField {
149151
return Err(Error::duplicate_field_path(path).with_span(mi));
150152
}
151153

152-
self.with = Some(FromMeta::from_meta(mi)?);
154+
self.with = Some(With::from_meta(&self.ident, mi)?);
153155

154156
if self.flatten.is_present() {
155157
return Err(
@@ -239,3 +241,56 @@ impl ParseAttribute for InputField {
239241
Ok(())
240242
}
241243
}
244+
245+
#[derive(Debug, Clone)]
246+
pub struct With {
247+
/// The path that generated code should use when calling this.
248+
path: syn::Path,
249+
/// If set, the closure that should be assigned to `path` locally.
250+
closure: Option<syn::ExprClosure>,
251+
}
252+
253+
impl With {
254+
pub fn from_meta(field_name: &syn::Ident, meta: &syn::Meta) -> Result<Self> {
255+
if let syn::Meta::NameValue(nv) = meta {
256+
match &nv.value {
257+
syn::Expr::Path(path) => Ok(Self::from(path.path.clone())),
258+
syn::Expr::Closure(closure) => Ok(Self {
259+
path: format_ident!("__with_closure_for_{}", field_name).into(),
260+
closure: Some(closure.clone()),
261+
}),
262+
_ => Err(Error::unexpected_expr_type(&nv.value)),
263+
}
264+
} else {
265+
Err(Error::unsupported_format("non-value"))
266+
}
267+
}
268+
269+
/// Create the statement that declares the closure as a function pointer.
270+
fn to_closure_declaration(&self) -> Option<syn::Stmt> {
271+
self.closure.as_ref().map(|c| {
272+
let path = &self.path;
273+
// An explicit annotation that the input is borrowed is needed here,
274+
// or attempting to pass a closure will fail with an issue about a temporary
275+
// value being dropped while still borrowed in the extractor loop.
276+
//
277+
// Making the parameter type explicit here avoids errors if the closure doesn't
278+
// do enough to make the type clear to the compiler.
279+
//
280+
// The explicit return type is needed, or else using `Ok` and `?` in the closure
281+
// body will produce an error about needing type annotations due to uncertainty
282+
// about the error variant's type. `T` is left undefined so that postfix transforms
283+
// still work as expected
284+
parse_quote_spanned!(c.span()=> let #path: fn(&::syn::Meta) -> ::darling::Result<_> = #c;)
285+
})
286+
}
287+
}
288+
289+
impl From<syn::Path> for With {
290+
fn from(path: syn::Path) -> Self {
291+
Self {
292+
path,
293+
closure: None,
294+
}
295+
}
296+
}

examples/expr_with.rs

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,26 @@
1-
use darling::{util::parse_expr, FromDeriveInput};
1+
use darling::{util::parse_expr, FromDeriveInput, FromMeta};
22
use syn::{parse_quote, Expr};
33

44
#[derive(FromDeriveInput)]
55
#[darling(attributes(demo))]
66
pub struct Receiver {
77
#[darling(with = parse_expr::preserve_str_literal, map = Some)]
88
example1: Option<Expr>,
9+
#[darling(
10+
// A closure can be used in lieu of a path.
11+
with = |m| Ok(String::from_meta(m)?.to_uppercase()),
12+
default
13+
)]
14+
example2: String,
915
}
1016

1117
fn main() {
1218
let input = Receiver::from_derive_input(&parse_quote! {
13-
#[demo(example1 = test::path)]
19+
#[demo(example1 = test::path, example2 = "hello")]
1420
struct Example;
1521
})
1622
.unwrap();
1723

1824
assert_eq!(input.example1, Some(parse_quote!(test::path)));
25+
assert_eq!(input.example2, "HELLO".to_string());
1926
}

tests/meta_with.rs

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
use darling::{util::parse_expr, FromDeriveInput, FromMeta};
2+
use syn::{parse_quote, Expr};
3+
4+
#[derive(FromDeriveInput)]
5+
#[darling(attributes(demo))]
6+
pub struct Receiver {
7+
#[darling(with = parse_expr::preserve_str_literal, map = Some)]
8+
example1: Option<Expr>,
9+
#[darling(
10+
with = |m| Ok(String::from_meta(m)?.to_uppercase()),
11+
map = Some
12+
)]
13+
example2: Option<String>,
14+
// This is deliberately strange - it keeps the field name, and ignores
15+
// the rest of the attribute. In normal operation, this is strongly discouraged.
16+
// It's used here to verify that the parameter type is known even if it can't be
17+
// inferred from usage within the closure.
18+
#[darling(with = |m| Ok(m.path().clone()))]
19+
example3: syn::Path,
20+
}
21+
22+
#[test]
23+
fn handles_all_cases() {
24+
let input = Receiver::from_derive_input(&parse_quote! {
25+
#[demo(example1 = test::path, example2 = "hello", example3)]
26+
struct Example;
27+
})
28+
.unwrap();
29+
30+
assert_eq!(input.example1, Some(parse_quote!(test::path)));
31+
assert_eq!(input.example2, Some("HELLO".to_string()));
32+
assert_eq!(input.example3, parse_quote!(example3));
33+
}

0 commit comments

Comments
 (0)