1
1
use std:: borrow:: Cow ;
2
2
3
+ use quote:: format_ident;
3
4
use syn:: { parse_quote_spanned, spanned:: Spanned } ;
4
5
5
6
use crate :: codegen;
@@ -13,7 +14,7 @@ pub struct InputField {
13
14
pub attr_name : Option < String > ,
14
15
pub ty : syn:: Type ,
15
16
pub default : Option < DefaultExpression > ,
16
- pub with : Option < syn :: Path > ,
17
+ pub with : Option < With > ,
17
18
18
19
/// If `true`, generated code will not look for this field in the input meta item,
19
20
/// instead always falling back to either `InputField::default` or `Default::default`.
@@ -34,7 +35,8 @@ impl InputField {
34
35
. map_or_else ( || Cow :: Owned ( self . ident . to_string ( ) ) , Cow :: Borrowed ) ,
35
36
ty : & self . ty ,
36
37
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 (
38
40
|| {
39
41
Cow :: Owned (
40
42
parse_quote_spanned ! ( self . ty. span( ) => :: darling:: FromMeta :: from_meta) ,
@@ -149,7 +151,7 @@ impl ParseAttribute for InputField {
149
151
return Err ( Error :: duplicate_field_path ( path) . with_span ( mi) ) ;
150
152
}
151
153
152
- self . with = Some ( FromMeta :: from_meta ( mi) ?) ;
154
+ self . with = Some ( With :: from_meta ( & self . ident , mi) ?) ;
153
155
154
156
if self . flatten . is_present ( ) {
155
157
return Err (
@@ -239,3 +241,56 @@ impl ParseAttribute for InputField {
239
241
Ok ( ( ) )
240
242
}
241
243
}
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
+ }
0 commit comments