clap_derive/derives/
args.rs

1// Copyright 2018 Guillaume Pinot (@TeXitoi) <texitoi@texitoi.eu>,
2// Kevin Knapp (@kbknapp) <kbknapp@gmail.com>, and
3// Ana Hobden (@hoverbear) <operator@hoverbear.org>
4//
5// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
8// option. This file may not be copied, modified, or distributed
9// except according to those terms.
10//
11// This work was derived from Structopt (https://github.com/TeXitoi/structopt)
12// commit#ea76fa1b1b273e65e3b0b1046643715b49bec51f which is licensed under the
13// MIT/Apache 2.0 license.
14
15use proc_macro2::{Ident, Span, TokenStream};
16use quote::{format_ident, quote, quote_spanned};
17use syn::{
18    punctuated::Punctuated, spanned::Spanned, token::Comma, Data, DataStruct, DeriveInput, Field,
19    Fields, FieldsNamed, Generics,
20};
21
22use crate::item::{Item, Kind, Name};
23use crate::utils::{inner_type, sub_type, Sp, Ty};
24
25pub(crate) fn derive_args(input: &DeriveInput) -> Result<TokenStream, syn::Error> {
26    let ident = &input.ident;
27
28    match input.data {
29        Data::Struct(DataStruct {
30            fields: Fields::Named(ref fields),
31            ..
32        }) => {
33            let name = Name::Derived(ident.clone());
34            let item = Item::from_args_struct(input, name)?;
35            let fields = collect_args_fields(&item, fields)?;
36            gen_for_struct(&item, ident, &input.generics, &fields)
37        }
38        Data::Struct(DataStruct {
39            fields: Fields::Unit,
40            ..
41        }) => {
42            let name = Name::Derived(ident.clone());
43            let item = Item::from_args_struct(input, name)?;
44            let fields = Punctuated::<Field, Comma>::new();
45            let fields = fields
46                .iter()
47                .map(|field| {
48                    let item = Item::from_args_field(field, item.casing(), item.env_casing())?;
49                    Ok((field, item))
50                })
51                .collect::<Result<Vec<_>, syn::Error>>()?;
52            gen_for_struct(&item, ident, &input.generics, &fields)
53        }
54        _ => abort_call_site!("`#[derive(Args)]` only supports non-tuple structs"),
55    }
56}
57
58pub(crate) fn gen_for_struct(
59    item: &Item,
60    item_name: &Ident,
61    generics: &Generics,
62    fields: &[(&Field, Item)],
63) -> Result<TokenStream, syn::Error> {
64    if !matches!(&*item.kind(), Kind::Command(_)) {
65        abort! { item.kind().span(),
66            "`{}` cannot be used with `command`",
67            item.kind().name(),
68        }
69    }
70
71    let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
72
73    let constructor = gen_constructor(fields)?;
74    let updater = gen_updater(fields, true)?;
75    let raw_deprecated = raw_deprecated();
76
77    let app_var = Ident::new("__clap_app", Span::call_site());
78    let augmentation = gen_augment(fields, &app_var, item, false)?;
79    let augmentation_update = gen_augment(fields, &app_var, item, true)?;
80
81    let group_id = if item.skip_group() {
82        quote!(None)
83    } else {
84        let group_id = item.group_id();
85        quote!(Some(clap::Id::from(#group_id)))
86    };
87
88    Ok(quote! {
89        #[allow(
90            dead_code,
91            unreachable_code,
92            unused_variables,
93            unused_braces,
94            unused_qualifications,
95        )]
96        #[allow(
97            clippy::style,
98            clippy::complexity,
99            clippy::pedantic,
100            clippy::restriction,
101            clippy::perf,
102            clippy::deprecated,
103            clippy::nursery,
104            clippy::cargo,
105            clippy::suspicious_else_formatting,
106            clippy::almost_swapped,
107            clippy::redundant_locals,
108        )]
109        #[automatically_derived]
110        impl #impl_generics clap::FromArgMatches for #item_name #ty_generics #where_clause {
111            fn from_arg_matches(__clap_arg_matches: &clap::ArgMatches) -> ::std::result::Result<Self, clap::Error> {
112                Self::from_arg_matches_mut(&mut __clap_arg_matches.clone())
113            }
114
115            fn from_arg_matches_mut(__clap_arg_matches: &mut clap::ArgMatches) -> ::std::result::Result<Self, clap::Error> {
116                #raw_deprecated
117                let v = #item_name #constructor;
118                ::std::result::Result::Ok(v)
119            }
120
121            fn update_from_arg_matches(&mut self, __clap_arg_matches: &clap::ArgMatches) -> ::std::result::Result<(), clap::Error> {
122                self.update_from_arg_matches_mut(&mut __clap_arg_matches.clone())
123            }
124
125            fn update_from_arg_matches_mut(&mut self, __clap_arg_matches: &mut clap::ArgMatches) -> ::std::result::Result<(), clap::Error> {
126                #raw_deprecated
127                #updater
128                ::std::result::Result::Ok(())
129            }
130        }
131
132        #[allow(
133            dead_code,
134            unreachable_code,
135            unused_variables,
136            unused_braces,
137            unused_qualifications,
138        )]
139        #[allow(
140            clippy::style,
141            clippy::complexity,
142            clippy::pedantic,
143            clippy::restriction,
144            clippy::perf,
145            clippy::deprecated,
146            clippy::nursery,
147            clippy::cargo,
148            clippy::suspicious_else_formatting,
149            clippy::almost_swapped,
150            clippy::redundant_locals,
151        )]
152        #[automatically_derived]
153        impl #impl_generics clap::Args for #item_name #ty_generics #where_clause {
154            fn group_id() -> Option<clap::Id> {
155                #group_id
156            }
157            fn augment_args<'b>(#app_var: clap::Command) -> clap::Command {
158                #augmentation
159            }
160            fn augment_args_for_update<'b>(#app_var: clap::Command) -> clap::Command {
161                #augmentation_update
162            }
163        }
164    })
165}
166
167/// Generate a block of code to add arguments/subcommands corresponding to
168/// the `fields` to an cmd.
169pub(crate) fn gen_augment(
170    fields: &[(&Field, Item)],
171    app_var: &Ident,
172    parent_item: &Item,
173    override_required: bool,
174) -> Result<TokenStream, syn::Error> {
175    let mut subcommand_specified = false;
176    let mut args = Vec::new();
177    for (field, item) in fields {
178        let kind = item.kind();
179        let genned = match &*kind {
180            Kind::Command(_)
181            | Kind::Value
182            | Kind::Skip(_, _)
183            | Kind::FromGlobal(_)
184            | Kind::ExternalSubcommand => None,
185            Kind::Subcommand(ty) => {
186                if subcommand_specified {
187                    abort!(
188                        field.span(),
189                        "`#[command(subcommand)]` can only be used once per container"
190                    );
191                }
192                subcommand_specified = true;
193
194                let subcmd_type = match (**ty, sub_type(&field.ty)) {
195                    (Ty::Option, Some(sub_type)) => sub_type,
196                    _ => &field.ty,
197                };
198                let implicit_methods = if **ty == Ty::Option {
199                    quote!()
200                } else {
201                    quote_spanned! { kind.span()=>
202                        .subcommand_required(true)
203                        .arg_required_else_help(true)
204                    }
205                };
206
207                let override_methods = if override_required {
208                    quote_spanned! { kind.span()=>
209                        .subcommand_required(false)
210                        .arg_required_else_help(false)
211                    }
212                } else {
213                    quote!()
214                };
215
216                Some(quote! {
217                    let #app_var = <#subcmd_type as clap::Subcommand>::augment_subcommands( #app_var );
218                    let #app_var = #app_var
219                        #implicit_methods
220                        #override_methods;
221                })
222            }
223            Kind::Flatten(ty) => {
224                let inner_type = match (**ty, sub_type(&field.ty)) {
225                    (Ty::Option, Some(sub_type)) => sub_type,
226                    _ => &field.ty,
227                };
228
229                let next_help_heading = item.next_help_heading();
230                let next_display_order = item.next_display_order();
231                let flatten_group_assert = if matches!(**ty, Ty::Option) {
232                    quote_spanned! { kind.span()=>
233                        <#inner_type as clap::Args>::group_id().expect("cannot `#[flatten]` an `Option<Args>` with `#[group(skip)]`");
234                    }
235                } else {
236                    quote! {}
237                };
238                if override_required {
239                    Some(quote_spanned! { kind.span()=>
240                        #flatten_group_assert
241                        let #app_var = #app_var
242                            #next_help_heading
243                            #next_display_order;
244                        let #app_var = <#inner_type as clap::Args>::augment_args_for_update(#app_var);
245                    })
246                } else {
247                    Some(quote_spanned! { kind.span()=>
248                        #flatten_group_assert
249                        let #app_var = #app_var
250                            #next_help_heading
251                            #next_display_order;
252                        let #app_var = <#inner_type as clap::Args>::augment_args(#app_var);
253                    })
254                }
255            }
256            Kind::Arg(ty) => {
257                let value_parser = item.value_parser(&field.ty);
258                let action = item.action(&field.ty);
259                let value_name = item.value_name();
260
261                let implicit_methods = match **ty {
262                    Ty::Unit => {
263                        // Leaving out `value_parser` as it will always fail
264                        quote_spanned! { ty.span()=>
265                            .value_name(#value_name)
266                            #action
267                        }
268                    }
269                    Ty::Option => {
270                        quote_spanned! { ty.span()=>
271                            .value_name(#value_name)
272                            #value_parser
273                            #action
274                        }
275                    }
276
277                    Ty::OptionOption => quote_spanned! { ty.span()=>
278                        .value_name(#value_name)
279                        .num_args(0..=1)
280                        #value_parser
281                        #action
282                    },
283
284                    Ty::OptionVec => {
285                        if item.is_positional() {
286                            quote_spanned! { ty.span()=>
287                                .value_name(#value_name)
288                                .num_args(1..)  // action won't be sufficient for getting multiple
289                                #value_parser
290                                #action
291                            }
292                        } else {
293                            quote_spanned! { ty.span()=>
294                                .value_name(#value_name)
295                                #value_parser
296                                #action
297                            }
298                        }
299                    }
300
301                    Ty::Vec => {
302                        if item.is_positional() {
303                            quote_spanned! { ty.span()=>
304                                .value_name(#value_name)
305                                .num_args(1..)  // action won't be sufficient for getting multiple
306                                #value_parser
307                                #action
308                            }
309                        } else {
310                            quote_spanned! { ty.span()=>
311                                .value_name(#value_name)
312                                #value_parser
313                                #action
314                            }
315                        }
316                    }
317
318                    Ty::VecVec | Ty::OptionVecVec => {
319                        quote_spanned! { ty.span() =>
320                            .value_name(#value_name)
321                            #value_parser
322                            #action
323                        }
324                    }
325
326                    Ty::Other => {
327                        let required = item.find_default_method().is_none();
328                        // `ArgAction::takes_values` is assuming `ArgAction::default_value` will be
329                        // set though that won't always be true but this should be good enough,
330                        // otherwise we'll report an "arg required" error when unwrapping.
331                        let action_value = action.args();
332                        quote_spanned! { ty.span()=>
333                            .value_name(#value_name)
334                            .required(#required && #action_value.takes_values())
335                            #value_parser
336                            #action
337                        }
338                    }
339                };
340
341                let id = item.id();
342                let explicit_methods = item.field_methods();
343                let deprecations = if !override_required {
344                    item.deprecations()
345                } else {
346                    quote!()
347                };
348                let override_methods = if override_required {
349                    quote_spanned! { kind.span()=>
350                        .required(false)
351                    }
352                } else {
353                    quote!()
354                };
355
356                Some(quote_spanned! { field.span()=>
357                    let #app_var = #app_var.arg({
358                        #deprecations
359
360                        #[allow(deprecated)]
361                        let arg = clap::Arg::new(#id)
362                            #implicit_methods;
363
364                        let arg = arg
365                            #explicit_methods;
366
367                        let arg = arg
368                            #override_methods;
369
370                        arg
371                    });
372                })
373            }
374        };
375        args.push(genned);
376    }
377
378    let deprecations = if !override_required {
379        parent_item.deprecations()
380    } else {
381        quote!()
382    };
383    let initial_app_methods = parent_item.initial_top_level_methods();
384    let final_app_methods = parent_item.final_top_level_methods();
385    let group_app_methods = if parent_item.skip_group() {
386        quote!()
387    } else {
388        let group_id = parent_item.group_id();
389        let literal_group_members = fields
390            .iter()
391            .filter_map(|(_field, item)| {
392                let kind = item.kind();
393                if matches!(*kind, Kind::Arg(_)) {
394                    Some(item.id())
395                } else {
396                    None
397                }
398            })
399            .collect::<Vec<_>>();
400        let literal_group_members_len = literal_group_members.len();
401        let mut literal_group_members = quote! {{
402            let members: [clap::Id; #literal_group_members_len] = [#( clap::Id::from(#literal_group_members) ),* ];
403            members
404        }};
405        // HACK: Validation isn't ready yet for nested arg groups, so just don't populate the group in
406        // that situation
407        let possible_group_members_len = fields
408            .iter()
409            .filter(|(_field, item)| {
410                let kind = item.kind();
411                matches!(*kind, Kind::Flatten(_))
412            })
413            .count();
414        if 0 < possible_group_members_len {
415            literal_group_members = quote! {{
416                let members: [clap::Id; 0] = [];
417                members
418            }};
419        }
420
421        let group_methods = parent_item.group_methods();
422
423        quote!(
424            .group(
425                clap::ArgGroup::new(#group_id)
426                    .multiple(true)
427                    #group_methods
428                    .args(#literal_group_members)
429            )
430        )
431    };
432    Ok(quote! {{
433        #deprecations
434        let #app_var = #app_var
435            #initial_app_methods
436            #group_app_methods
437            ;
438        #( #args )*
439        #app_var #final_app_methods
440    }})
441}
442
443pub(crate) fn gen_constructor(fields: &[(&Field, Item)]) -> Result<TokenStream, syn::Error> {
444    let fields = fields.iter().map(|(field, item)| {
445        let field_name = field.ident.as_ref().unwrap();
446        let kind = item.kind();
447        let arg_matches = format_ident!("__clap_arg_matches");
448        let genned = match &*kind {
449            Kind::Command(_)
450            | Kind::Value
451            | Kind::ExternalSubcommand => {
452                abort! { kind.span(),
453                    "`{}` cannot be used with `arg`",
454                    kind.name(),
455                }
456            }
457            Kind::Subcommand(ty) => {
458                let subcmd_type = match (**ty, sub_type(&field.ty)) {
459                    (Ty::Option, Some(sub_type)) => sub_type,
460                    _ => &field.ty,
461                };
462                match **ty {
463                    Ty::Option => {
464                        quote_spanned! { kind.span()=>
465                            #field_name: {
466                                if #arg_matches.subcommand_name().map(<#subcmd_type as clap::Subcommand>::has_subcommand).unwrap_or(false) {
467                                    Some(<#subcmd_type as clap::FromArgMatches>::from_arg_matches_mut(#arg_matches)?)
468                                } else {
469                                    None
470                                }
471                            }
472                        }
473                    },
474                    Ty::Other => {
475                        quote_spanned! { kind.span()=>
476                            #field_name: {
477                                <#subcmd_type as clap::FromArgMatches>::from_arg_matches_mut(#arg_matches)?
478                            }
479                        }
480                    },
481                    Ty::Unit |
482                    Ty::Vec |
483                    Ty::OptionOption |
484                    Ty::OptionVec |
485                    Ty::VecVec |
486                    Ty::OptionVecVec => {
487                        abort!(
488                            ty.span(),
489                            "{} types are not supported for subcommand",
490                            ty.as_str()
491                        );
492                    }
493                }
494            }
495
496            Kind::Flatten(ty) => {
497                let inner_type = match (**ty, sub_type(&field.ty)) {
498                    (Ty::Option, Some(sub_type)) => sub_type,
499                    _ => &field.ty,
500                };
501                match **ty {
502                    Ty::Other => {
503                        quote_spanned! { kind.span()=>
504                            #field_name: <#inner_type as clap::FromArgMatches>::from_arg_matches_mut(#arg_matches)?
505                        }
506                    },
507                    Ty::Option => {
508                        quote_spanned! { kind.span()=>
509                            #field_name: {
510                                let group_id = <#inner_type as clap::Args>::group_id()
511                                    .expect("asserted during `Arg` creation");
512                                if #arg_matches.contains_id(group_id.as_str()) {
513                                    Some(
514                                        <#inner_type as clap::FromArgMatches>::from_arg_matches_mut(#arg_matches)?
515                                    )
516                                } else {
517                                    None
518                                }
519                            }
520                        }
521                    },
522                    Ty::Unit |
523                    Ty::Vec |
524                    Ty::OptionOption |
525                    Ty::OptionVec |
526                    Ty::VecVec |
527                    Ty::OptionVecVec => {
528                        abort!(
529                            ty.span(),
530                            "{} types are not supported for flatten",
531                            ty.as_str()
532                        );
533                    }
534                }
535            },
536
537            Kind::Skip(val, _) => match val {
538                None => quote_spanned!(kind.span()=> #field_name: Default::default()),
539                Some(val) => quote_spanned!(kind.span()=> #field_name: (#val).into()),
540            },
541
542            Kind::Arg(ty) | Kind::FromGlobal(ty) => {
543                gen_parsers(item, ty, field_name, field, None)?
544            }
545        };
546        Ok(genned)
547    }).collect::<Result<Vec<_>, syn::Error>>()?;
548
549    Ok(quote! {{
550        #( #fields ),*
551    }})
552}
553
554pub(crate) fn gen_updater(
555    fields: &[(&Field, Item)],
556    use_self: bool,
557) -> Result<TokenStream, syn::Error> {
558    let mut genned_fields = Vec::new();
559    for (field, item) in fields {
560        let field_name = field.ident.as_ref().unwrap();
561        let kind = item.kind();
562
563        let access = if use_self {
564            quote! {
565                #[allow(non_snake_case)]
566                let #field_name = &mut self.#field_name;
567            }
568        } else {
569            quote!()
570        };
571        let arg_matches = format_ident!("__clap_arg_matches");
572
573        let genned = match &*kind {
574            Kind::Command(_) | Kind::Value | Kind::ExternalSubcommand => {
575                abort! { kind.span(),
576                    "`{}` cannot be used with `arg`",
577                    kind.name(),
578                }
579            }
580            Kind::Subcommand(ty) => {
581                let subcmd_type = match (**ty, sub_type(&field.ty)) {
582                    (Ty::Option, Some(sub_type)) => sub_type,
583                    _ => &field.ty,
584                };
585
586                let updater = quote_spanned! { ty.span()=>
587                    <#subcmd_type as clap::FromArgMatches>::update_from_arg_matches_mut(#field_name, #arg_matches)?;
588                };
589
590                let updater = match **ty {
591                    Ty::Option => quote_spanned! { kind.span()=>
592                        if let Some(#field_name) = #field_name.as_mut() {
593                            #updater
594                        } else {
595                            *#field_name = Some(<#subcmd_type as clap::FromArgMatches>::from_arg_matches_mut(
596                                #arg_matches
597                            )?);
598                        }
599                    },
600                    _ => quote_spanned! { kind.span()=>
601                        #updater
602                    },
603                };
604
605                quote_spanned! { kind.span()=>
606                    {
607                        #access
608                        #updater
609                    }
610                }
611            }
612
613            Kind::Flatten(ty) => {
614                let inner_type = match (**ty, sub_type(&field.ty)) {
615                    (Ty::Option, Some(sub_type)) => sub_type,
616                    _ => &field.ty,
617                };
618
619                let updater = quote_spanned! { ty.span()=>
620                    <#inner_type as clap::FromArgMatches>::update_from_arg_matches_mut(#field_name, #arg_matches)?;
621                };
622
623                let updater = match **ty {
624                    Ty::Option => quote_spanned! { kind.span()=>
625                        if let Some(#field_name) = #field_name.as_mut() {
626                            #updater
627                        } else {
628                            *#field_name = Some(<#inner_type as clap::FromArgMatches>::from_arg_matches_mut(
629                                #arg_matches
630                            )?);
631                        }
632                    },
633                    _ => quote_spanned! { kind.span()=>
634                        #updater
635                    },
636                };
637
638                quote_spanned! { kind.span()=>
639                    {
640                        #access
641                        #updater
642                    }
643                }
644            }
645
646            Kind::Skip(_, _) => quote!(),
647
648            Kind::Arg(ty) | Kind::FromGlobal(ty) => {
649                gen_parsers(item, ty, field_name, field, Some(&access))?
650            }
651        };
652        genned_fields.push(genned);
653    }
654
655    Ok(quote! {
656        #( #genned_fields )*
657    })
658}
659
660fn gen_parsers(
661    item: &Item,
662    ty: &Sp<Ty>,
663    field_name: &Ident,
664    field: &Field,
665    update: Option<&TokenStream>,
666) -> Result<TokenStream, syn::Error> {
667    let span = ty.span();
668    let convert_type = inner_type(&field.ty);
669    let id = item.id();
670    let get_one = quote_spanned!(span=> remove_one::<#convert_type>);
671    let get_many = quote_spanned!(span=> remove_many::<#convert_type>);
672    let get_occurrences = quote_spanned!(span=> remove_occurrences::<#convert_type>);
673
674    // Give this identifier the same hygiene
675    // as the `arg_matches` parameter definition. This
676    // allows us to refer to `arg_matches` within a `quote_spanned` block
677    let arg_matches = format_ident!("__clap_arg_matches");
678
679    let field_value = match **ty {
680        Ty::Unit => {
681            quote_spanned! { ty.span()=>
682                ()
683            }
684        }
685
686        Ty::Option => {
687            quote_spanned! { ty.span()=>
688                #arg_matches.#get_one(#id)
689            }
690        }
691
692        Ty::OptionOption => quote_spanned! { ty.span()=>
693            if #arg_matches.contains_id(#id) {
694                Some(
695                    #arg_matches.#get_one(#id)
696                )
697            } else {
698                None
699            }
700        },
701
702        Ty::OptionVec => quote_spanned! { ty.span()=>
703            if #arg_matches.contains_id(#id) {
704                Some(#arg_matches.#get_many(#id)
705                    .map(|v| v.collect::<Vec<_>>())
706                    .unwrap_or_else(Vec::new))
707            } else {
708                None
709            }
710        },
711
712        Ty::Vec => {
713            quote_spanned! { ty.span()=>
714                #arg_matches.#get_many(#id)
715                    .map(|v| v.collect::<Vec<_>>())
716                    .unwrap_or_else(Vec::new)
717            }
718        }
719
720        Ty::VecVec => quote_spanned! { ty.span()=>
721            #arg_matches.#get_occurrences(#id)
722                .map(|g| g.map(::std::iter::Iterator::collect).collect::<Vec<Vec<_>>>())
723                .unwrap_or_else(Vec::new)
724        },
725
726        Ty::OptionVecVec => quote_spanned! { ty.span()=>
727            #arg_matches.#get_occurrences(#id)
728                .map(|g| g.map(::std::iter::Iterator::collect).collect::<Vec<Vec<_>>>())
729        },
730
731        Ty::Other => {
732            // Prefer `concat` where possible for reduced code size but fallback to `format!` to
733            // allow non-literal `id`s
734            match id {
735                Name::Assigned(_) => {
736                    quote_spanned! { ty.span()=>
737                        #arg_matches.#get_one(#id)
738                            .ok_or_else(|| clap::Error::raw(clap::error::ErrorKind::MissingRequiredArgument, format!("The following required argument was not provided: {}", #id)))?
739                    }
740                }
741                Name::Derived(_) => {
742                    quote_spanned! { ty.span()=>
743                        #arg_matches.#get_one(#id)
744                            .ok_or_else(|| clap::Error::raw(clap::error::ErrorKind::MissingRequiredArgument, concat!("The following required argument was not provided: ", #id)))?
745                    }
746                }
747            }
748        }
749    };
750
751    let genned = if let Some(access) = update {
752        quote_spanned! { field.span()=>
753            if #arg_matches.contains_id(#id) {
754                #access
755                *#field_name = #field_value
756            }
757        }
758    } else {
759        quote_spanned!(field.span()=> #field_name: #field_value )
760    };
761    Ok(genned)
762}
763
764#[cfg(feature = "raw-deprecated")]
765pub(crate) fn raw_deprecated() -> TokenStream {
766    quote! {}
767}
768
769#[cfg(not(feature = "raw-deprecated"))]
770pub(crate) fn raw_deprecated() -> TokenStream {
771    quote! {
772        #![allow(deprecated)]  // Assuming any deprecation in here will be related to a deprecation in `Args`
773
774    }
775}
776
777pub(crate) fn collect_args_fields<'a>(
778    item: &'a Item,
779    fields: &'a FieldsNamed,
780) -> Result<Vec<(&'a Field, Item)>, syn::Error> {
781    fields
782        .named
783        .iter()
784        .map(|field| {
785            let item = Item::from_args_field(field, item.casing(), item.env_casing())?;
786            Ok((field, item))
787        })
788        .collect()
789}