Skip to main content

winnow_args_derive/
lib.rs

1//! The derives of winnow-args: `Args`, `Subcommand`, `ValueEnum` and `Occurrence`.
2//!
3//! Each is documented with every attribute it accepts. `#[winnow_args(...)]` is
4//! `#[arg(...)]` under another name, for a type whose other derives claim `arg`.
5//!
6//! What `#[derive(Args)]` generates is one loop:
7//!
8//! 1. read the next item of the command line (a flag, a word, `--`);
9//! 2. `match` its name against the struct's flags, and store the value in a
10//!    local variable for that field;
11//! 3. give a plain word to the next positional field, in declaration order,
12//!    unless it names a subcommand, which then parses the rest;
13//! 4. when the words run out, apply defaults, check the rules, build the struct.
14//!
15//! The names are patterns of a `match`, which rustc compiles: no list of
16//! parsers is walked, whatever the number of flags.
17
18use proc_macro::TokenStream;
19use proc_macro2::{Span, TokenStream as TokenStream2};
20use quote::{format_ident, quote};
21use syn::{
22    Data, DeriveInput, Fields, GenericArgument, Ident, LitByteStr, LitChar, LitStr, PathArguments,
23    Type, parse_macro_input, spanned::Spanned as _,
24};
25
26/// Derive `Args` for a struct with named fields: the generated loop parses the whole command line.
27///
28/// Every attribute is `#[arg(...)]` (or `#[winnow_args(...)]`); a field with none is `--field-name`.
29/// Doc comments are the help text.
30///
31/// ```ignore
32/// use winnow_args::Args;
33///
34/// /// Copy files.
35/// #[derive(Args, Debug)]
36/// #[arg(name = "cp", version)]
37/// struct Cp {
38///     /// Explain what is done; repeat for more.
39///     #[arg(short, long, count)]
40///     verbose: u8,
41///     /// Overwrite without asking.
42///     #[arg(short, long, negate)]
43///     force: bool,
44///     /// Attributes to keep.
45///     #[arg(long, delimiter = ',')]
46///     preserve: Vec<String>,
47///     /// Jobs to run.
48///     #[arg(short, long, env = "CP_JOBS", default = "1")]
49///     jobs: usize,
50///     #[arg(positional)]
51///     source: String,
52///     #[arg(positional)]
53///     dest: Option<String>,
54/// }
55///
56/// let cp = Cp::parse_from(["-vvf", "--preserve=mode,links", "a", "--no-force", "b"])?;
57/// assert_eq!((cp.verbose, cp.force, cp.jobs), (2, false, 1));
58/// assert_eq!(cp.preserve, ["mode", "links"]);
59/// assert_eq!((cp.source.as_str(), cp.dest.as_deref()), ("a", Some("b")));
60/// # Ok::<(), winnow_args::Error>(())
61/// ```
62///
63/// What a field's type means. A repeated single-value flag keeps the last value.
64/// `values` and `delimiter` change what a `Vec` takes.
65///
66/// | Type | Flag | Positional |
67/// |---|---|---|
68/// | `bool` | a switch | |
69/// | integer, with `count` | how many times it was given | |
70/// | `T` | one value; an error if absent and without `env` or `default` | a required word |
71/// | `Option<T>` | one value, or `None` | an optional word |
72/// | `Vec<T>` | one value per occurrence | every word left |
73/// | `Option<bool>`, with `plus` | `-x` is `Some(true)`, `+x` `Some(false)` | |
74///
75/// # Struct options
76///
77/// Naming and help:
78///
79/// - `name = "…"`: the program name in the usage line (`argv[0]` when empty) and in completion scripts, which need it.
80/// - `version`, `version = "…"`: supply `-V`/`--version`; bare, the crate's `CARGO_PKG_VERSION`.
81/// - `about`, `long_about`, `after_help`, `after_long_help`: help text; the first two default to the doc comment.
82/// - `disable_help_flag`, `disable_help_short` (`--help` only), `disable_version_flag`,
83///   `disable_help_subcommand`: do not supply that item.
84/// - `arg_required_else_help`: no arguments at all is a request for help.
85///
86/// Grammar:
87///
88/// - `unknown_flags = "value"`: a flag-like word naming no flag is a positional value; `"error"` is the default.
89/// - `long_only`: a long name may be spelled with one dash (GNU `getopt_long_only`).
90/// - `plus_options`: `+abc` is a bundle of `+` options, for fields with `plus`.
91/// - `restart_token = "…"`: a word that starts a new run of positionals; flags resume and keep their values.
92/// - `default_subcommand = "…"`: the subcommand for a line whose first word names none; needs a `subcommand` field.
93/// - `group("name", required, multiple)`: declare a group; fields join it with `group = "name"`.
94///   `required` means at least one member. Without `multiple`, a second member conflicts.
95///
96/// # Field roles
97///
98/// A field is a flag unless it is one of these. Most programs need `positional`, `subcommand` and
99/// `flatten`. `sequence` and `unknown` are for tools whose flags mean something by their order
100/// (see `Occurrence`). `flatten`, `sequence`, `unknown` and `skip` take no other option, except
101/// `sequence, unknown`.
102///
103/// - `positional`: a plain word. Required `T`, then `Option<T>`, then one `Vec<T>` last (it takes
104///   every word left). Any other order is a compile error.
105/// - `subcommand`: a `Subcommand` enum, `E` or `Option<E>`; takes no other option.
106/// - `flatten`: another flags-only `Args` struct, parsed as if declared here (nested too). A flag spelled in
107///   both is a compile error. The flattened struct has no positionals, subcommand, `sequence`, `unknown`,
108///   `keywords`, `global` flags or generics, nor `unknown_flags = "value"`; the parent no generics.
109/// - `sequence`: a `Vec` of an `Occurrence` enum: its flags and words, kept in order, its flags in help.
110///   One per struct; a spelling it shares with the struct is a compile error.
111/// - `sequence, unknown`: the same, with unknown flags offered to the enum's `unknown` variant.
112/// - `unknown`: a `Vec<T>` of the flag-like words no flag takes, whole. One per struct.
113/// - `skip`: left at `Default`, never parsed.
114///
115/// # Flag names
116///
117/// - `short`, `short = 'x'`: `-x`; bare, the field name's first letter. A second `short` is another letter.
118/// - `long`, `long = "name"`: `--name`, matched in full; bare, the field name in kebab-case. A second `long` is another name.
119/// - `alias = "…"`, `alias("…", …)`: more long names, not shown in help.
120/// - `negate`, `negate = "no-name"`: `--no-name` sets a `bool` false; bare, `--no-<long>`.
121/// - `two_dashes`: under `long_only`, the long name is never spelled with one dash (`--omagic`).
122/// - `prefix`: an ASCII `short` that always takes the rest of its word (`-lfoo`); a value flag.
123/// - `plus = 'c'`: also spelled `+c` in a `plus_options` struct; on an `Option<bool>` with `short = 'c'`,
124///   `-c` is `Some(true)` and `+c` `Some(false)`.
125/// - `global`: also accepted after a subcommand word, at any depth.
126///
127/// # Values
128///
129/// A value is any `T: FromArg`; the field's type says how many, as the table above has it.
130///
131/// - `count`: an integer counting occurrences.
132/// - `value_name = "…"`: the placeholder in help.
133/// - `delimiter = ','`: split each value of a `Vec` field.
134/// - `values = N`: each occurrence of a `Vec` flag takes `N` words, whatever they look like.
135/// - `values = 1..`, `values = 2..=3`: at least and at most that many words an occurrence; those after
136///   the first are taken while they are not flag-like. Positionals after such a flag need a flag or `--`
137///   before them.
138/// - `value_terminator = ";"`: the word that ends such a run, itself dropped.
139/// - `choices("a", "b")`: the only values accepted.
140/// - `env = "VAR"`, `default = "…"`: fallbacks after the command line, in that order; an `Option<T>`
141///   field then holds `Some`. A switch's variable is true unless empty, `0`, `false`, `no` or `off`.
142///   An unparseable `count` variable is left unset, not an error.
143/// - `default_fn = path`: a function returning the field's value (for a `Vec`, the list), called when
144///   nothing else gave one; `default_note = "…"` is what help shows for it.
145/// - `default_if("--flag", "v")`: the default when `--flag` was given; `default_if("--flag", "x", "v")`
146///   when its value is `x`, whose type must be `PartialEq`. Tried after `env` and before `default`,
147///   in order; the selector is as for the rules below.
148/// - `default_missing = "…"`: the value of a flag given without one, which makes its value optional.
149/// - `require_equals`: the value only attached (`--name=v`, `-nv`); with `default_missing`, a bare flag
150///   leaves the next word alone.
151/// - `keep_equals`: a short flag's attached value keeps a leading `=` (`-L=dir`).
152/// - `allow_hyphen_values`: the next word is the value, flag-like or `--` included.
153/// - `allow_negative_numbers`: a negative number is a value, for a flag or a positional.
154/// - `keywords`: on a flag of a type deriving `Args`, each value is `--value` of that type (`-z now`).
155///
156/// # Positionals
157///
158/// - `double_dash = "…"`: how `--` treats it: `"optional"` (default), `"required"` (only words after it),
159///   `"automatic"` (flags stop once it has a value), `"preserve"` (a `--` reaching it is a value).
160/// - `stop_flags`: once it has a value, flags stop, whatever its `double_dash`.
161///
162/// # Rules
163///
164/// Selectors name another field: `"--long"`, `"-s"`, a positional's name or the field's name.
165///
166/// - `required`: the field must hold a value once defaults are applied. A bare `Vec` may be empty without it.
167/// - `required_unless("…", …)`: required unless one of them has a value.
168/// - `conflicts("…", …)`: not supplied together with these.
169/// - `overrides("…", …)`: the last of this and these supplied wins.
170/// - `requires("…", …)`: these must have a value when this is supplied.
171/// - `group = "name"`: a member of a struct-level group.
172///
173/// # Help
174///
175/// - `help = "…"`, `long_help = "…"`: replace the doc comment's first paragraph and whole text.
176/// - `help_heading = "…"`: list it under that heading instead of "Options".
177/// - `hide`: leave it out of help and completion.
178#[proc_macro_derive(Args, attributes(arg, winnow_args))]
179pub fn derive_args(input: TokenStream) -> TokenStream {
180    let input = parse_macro_input!(input as DeriveInput);
181    expand(&input)
182        .unwrap_or_else(syn::Error::into_compile_error)
183        .into()
184}
185
186/// Derive `Subcommand` (and `Args`, so the enum can be the whole command line) for an enum of subcommands.
187///
188/// A variant is a unit, or holds one `Args` type, which may be `Box`ed; a parent struct holds the enum in
189/// a `subcommand` field. The doc comments are the help text. As a whole command line the enum has no
190/// name, version or about: wrap it in a struct for those, and for completion scripts.
191///
192/// ```ignore
193/// use winnow_args::{Args, Subcommand};
194///
195/// #[derive(Args, Debug, PartialEq)]
196/// struct Add {
197///     #[arg(positional)]
198///     paths: Vec<String>,
199/// }
200///
201/// #[derive(Subcommand, Debug, PartialEq)]
202/// enum Command {
203///     /// Stage files.
204///     Add(Add),
205///     /// Show what changed.
206///     #[arg(alias = "st")]
207///     Status,
208/// }
209///
210/// #[derive(Args, Debug)]
211/// struct Git {
212///     /// Say less.
213///     #[arg(short, long, global)]
214///     quiet: bool,
215///     #[arg(subcommand)]
216///     command: Command,
217/// }
218///
219/// let git = Git::parse_from(["add", "-q", "a", "b"])?;
220/// assert!(git.quiet);
221/// assert_eq!(git.command, Command::Add(Add { paths: vec!["a".into(), "b".into()] }));
222/// assert_eq!(Git::parse_from(["st"])?.command, Command::Status);
223///
224/// match git.command {
225///     Command::Add(Add { paths }) => println!("staging {paths:?}"),
226///     Command::Status => println!("on branch main"),
227/// }
228/// # Ok::<(), winnow_args::Error>(())
229/// ```
230///
231/// - Enum: `rename_all = "…"`: the case of variant names, one of `"kebab-case"` (default), `"lowercase"`,
232///   `"UPPERCASE"`, `"snake_case"` and `"verbatim"`.
233/// - Variant: `name = "…"`: the word that selects it.
234/// - Variant: `alias = "…"`, `alias("…", …)`: other words, shown in help; `alias_hidden` is the same, not shown.
235/// - Variant: `hide`: leave it out of the list in help and completion.
236/// - Variant: `help = "…"`: the one-line description; `long_help = "…"` is accepted and unused in the list.
237#[proc_macro_derive(Subcommand, attributes(arg, winnow_args))]
238pub fn derive_subcommand(input: TokenStream) -> TokenStream {
239    let input = parse_macro_input!(input as DeriveInput);
240    expand_subcommand(&input)
241        .unwrap_or_else(syn::Error::into_compile_error)
242        .into()
243}
244
245/// One `match` on the subcommand's name; the variant's `Args` parses the rest.
246fn expand_subcommand(input: &DeriveInput) -> syn::Result<TokenStream2> {
247    let Data::Enum(data) = &input.data else {
248        return Err(syn::Error::new(
249            input.span(),
250            "`Subcommand` can only be derived for enums",
251        ));
252    };
253    let case = rename_all(input)?;
254    let mut names: Vec<(String, &Ident)> = Vec::new();
255    let mut arms = Vec::new();
256    let mut patterns = Vec::new();
257    let mut subs = Vec::new();
258    for variant in &data.variants {
259        let ident = &variant.ident;
260        let info = variant_names(variant, &mut names, case)?;
261        let pattern = byte_patterns(&info.names);
262        let primary = LitStr::new(&info.names[0], Span::call_site());
263        let inner = match &variant.fields {
264            Fields::Unnamed(fields) if fields.unnamed.len() == 1 => {
265                let ty = &fields.unnamed[0].ty;
266                Some(boxed(ty).unwrap_or(ty).clone())
267            }
268            _ => None,
269        };
270        let command = match &inner {
271            Some(ty) => quote!(<#ty as ::winnow_args::Args>::HELP),
272            None => {
273                // The whole doc comment, when the description is its first paragraph.
274                let (doc, long_doc) = docs(&variant.attrs);
275                let long_about = text(if doc == info.about {
276                    &long_doc
277                } else {
278                    &info.about
279                });
280                let about = text(&info.about);
281                quote!(&::winnow_args::help::Command {
282                    name: "",
283                    about: #about,
284                    long_about: #long_about,
285                    after_help: "",
286                    after_long_help: "",
287                    items: &[],
288                    subcommands: &[],
289                    subcommand_required: false,
290                    help_flag: true,
291                    help_short: true,
292                    version: ::core::option::Option::None,
293                })
294            }
295        };
296        let about = match (&inner, info.about.is_empty()) {
297            (Some(ty), true) => quote!(<#ty as ::winnow_args::Args>::HELP.about),
298            _ => text(&info.about),
299        };
300        let shown = &info.names[1..=info.shown];
301        let all = &info.names;
302        let hide = info.hide;
303        subs.push(quote! {
304            ::winnow_args::help::Sub {
305                name: #primary,
306                aliases: &[#(#shown),*],
307                names: &[#(#all),*],
308                command: #command,
309                about: #about,
310                hide: #hide,
311            }
312        });
313        // Read here, so a payload only printed is not dead code.
314        let read_payload = quote! {
315            |__item| match __item {
316                Self::#ident(__read) => {
317                    let _ = __read;
318                }
319                #[allow(unreachable_patterns)]
320                _ => {}
321            }
322        };
323        let parse = match &variant.fields {
324            Fields::Unit => {
325                let index = subs.len() - 1;
326                quote! {
327                    __wa::finish_with(
328                        __input,
329                        __globals,
330                        <Self as ::winnow_args::Args>::HELP.subcommands[#index].command,
331                    )
332                    .map(|()| Self::#ident)
333                }
334            }
335            Fields::Unnamed(fields) if fields.unnamed.len() == 1 => {
336                let ty = &fields.unnamed[0].ty;
337                match boxed(ty) {
338                    Some(inner) => quote! {
339                        <#inner as ::winnow_args::Args>::parse_argv_with(__input, __globals)
340                            .map(|v| Self::#ident(::std::boxed::Box::new(v)))
341                            .inspect(#read_payload)
342                    },
343                    None => quote! {
344                        <#ty as ::winnow_args::Args>::parse_argv_with(__input, __globals)
345                            .map(Self::#ident)
346                            .inspect(#read_payload)
347                    },
348                }
349            }
350            _ => {
351                return Err(syn::Error::new(
352                    variant.span(),
353                    "a subcommand variant is a unit or holds one `Args` type",
354                ));
355            }
356        };
357        arms.push(quote!(#pattern => (#parse).map_err(|e| e.within(#primary)),));
358        patterns.push(pattern);
359    }
360    let (about, long_about) = docs(&input.attrs);
361    let (about, long_about) = (text(&about), text(&long_about));
362
363    let name = &input.ident;
364    let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
365    Ok(quote! {
366        impl #impl_generics ::winnow_args::Subcommand for #name #ty_generics #where_clause {
367            #[inline]
368            fn has(__name: &[u8]) -> bool {
369                matches!(__name, #(#patterns)|*)
370            }
371
372            fn parse_subcommand(
373                __name: &[u8],
374                __input: &mut ::winnow_args::Argv<'_>,
375                __globals: &mut dyn ::winnow_args::Globals,
376            ) -> ::core::result::Result<Self, ::winnow_args::Error> {
377                use ::winnow_args::__private as __wa;
378                match __name {
379                    #(#arms)*
380                    _ => ::core::result::Result::Err(__wa::Error::unexpected_arg(
381                        __input.offset(),
382                        ::std::string::String::from_utf8_lossy(__name),
383                    )),
384                }
385            }
386        }
387
388        impl #impl_generics ::winnow_args::Args for #name #ty_generics #where_clause {
389            const HELP: &'static ::winnow_args::help::Command = &::winnow_args::help::Command {
390                name: "",
391                about: #about,
392                long_about: #long_about,
393                after_help: "",
394                after_long_help: "",
395                items: &[],
396                subcommands: &[#(#subs),*],
397                subcommand_required: true,
398                help_flag: true,
399                help_short: true,
400                version: ::core::option::Option::None,
401            };
402
403            fn parse_argv_with(
404                __input: &mut ::winnow_args::Argv<'_>,
405                __globals: &mut dyn ::winnow_args::Globals,
406            ) -> ::core::result::Result<Self, ::winnow_args::Error> {
407                use ::winnow_args::__private as __wa;
408                let __help = <Self as ::winnow_args::Args>::HELP;
409                // Inherited global flags may come before the subcommand's name.
410                loop {
411                    if __input.is_empty() {
412                        return ::core::result::Result::Err(
413                            __wa::Error::missing_subcommand(__input.offset()),
414                        );
415                    }
416                    let __arg = __wa::arg(__input)?;
417                    match __arg {
418                        __wa::Arg::Word(__word) => {
419                            if <Self as __wa::Subcommand>::has(&**__word.value) {
420                                return <Self as __wa::Subcommand>::parse_subcommand(
421                                    &**__word.value, __input, __globals,
422                                );
423                            }
424                            if &**__word.value == b"help" {
425                                return ::core::result::Result::Err(__wa::help_word(__help, __input));
426                            }
427                        }
428                        __wa::Arg::Long(_) | __wa::Arg::Short(_)
429                            if __globals.bind(&__arg, __input)? =>
430                        {
431                            continue;
432                        }
433                        __wa::Arg::Long(__flag) if __flag.name == b"help" => {
434                            return ::core::result::Result::Err(__wa::Error::help(__help, true));
435                        }
436                        __wa::Arg::Short(__flag) if __flag.letter == 'h' => {
437                            return ::core::result::Result::Err(__wa::Error::help(__help, false));
438                        }
439                        _ => {}
440                    }
441                    return ::core::result::Result::Err(__arg.unexpected());
442                }
443            }
444        }
445    })
446}
447
448/// Derive `Occurrence` for an enum: one variant per flag or positional, kept
449/// in command-line order by an `#[arg(sequence)]` field of an `Args` struct.
450///
451/// A variant is a unit (a switch) or holds one value type; a `Spanned<T>` also records the offset and
452/// whether the value was attached. A variant with no `short` or `long` is `--variant-name`. Doc comments
453/// are the help text. A spelling used twice, here or in the parent, is a compile error.
454///
455/// ```ignore
456/// use winnow_args::{Args, Occurrence};
457///
458/// #[derive(Occurrence, Debug, PartialEq)]
459/// enum Item {
460///     /// Link what follows only if needed.
461///     #[arg(long)]
462///     AsNeeded,
463///     /// Search for a library.
464///     #[arg(short = 'l', prefix)]
465///     Library(String),
466///     #[arg(positional, value_name = "FILE")]
467///     Input(String),
468/// }
469///
470/// #[derive(Args, Debug)]
471/// struct Ld {
472///     #[arg(short, long)]
473///     output: Option<String>,
474///     #[arg(sequence)]
475///     items: Vec<Item>,
476/// }
477///
478/// let ld = Ld::parse_from(["a.o", "--as-needed", "-lm", "-o", "out", "b.o"])?;
479/// assert_eq!(ld.output.as_deref(), Some("out"));
480/// assert_eq!(
481///     ld.items,
482///     [
483///         Item::Input("a.o".into()),
484///         Item::AsNeeded,
485///         Item::Library("m".into()),
486///         Item::Input("b.o".into()),
487///     ]
488/// );
489/// # Ok::<(), winnow_args::Error>(())
490/// ```
491///
492/// Enum options, the default of every value variant:
493///
494/// - `allow_hyphen_values`, `keep_equals`: as for a field.
495///
496/// Variant names:
497///
498/// - `short = 'x'`, `long`, `long = "name"`: as for a field; a second `long` is another name.
499/// - `alias = "…"`, `alias("…", …)`: more long names.
500/// - `two_dashes`, `prefix`: as for a field.
501///
502/// Variant values, as for a field:
503///
504/// - `value_name = "…"`, `require_equals`, `keep_equals`, `allow_hyphen_values`, `allow_negative_numbers`,
505///   `default_missing = "…"`.
506///
507/// Variant roles, at most one of each in an enum:
508///
509/// - `positional`: each plain word is that variant; its `value_name` defaults to the variant's name.
510/// - `unknown`: takes a flag no variant names, whole, for a parent with `sequence, unknown`.
511/// - `bundle`: takes a word of several short flags (`-sS`), whole, ahead of its letters.
512/// - `skip`: built by the program, never parsed.
513#[proc_macro_derive(Occurrence, attributes(arg, winnow_args))]
514pub fn derive_occurrence(input: TokenStream) -> TokenStream {
515    let input = parse_macro_input!(input as DeriveInput);
516    expand_occurrence(&input)
517        .unwrap_or_else(syn::Error::into_compile_error)
518        .into()
519}
520
521/// Derive `FromArg` for an enum of unit variants, matched on the value's bytes; help lists them as possible values.
522///
523/// ```ignore
524/// use winnow_args::{Args, ErrorKind, ValueEnum};
525///
526/// #[derive(ValueEnum, Debug, PartialEq)]
527/// enum Color {
528///     Always,
529///     #[arg(alias = "no")]
530///     Never,
531///     #[arg(name = "auto")]
532///     WhenTerminal,
533/// }
534///
535/// #[derive(Args, Debug)]
536/// struct Cli {
537///     #[arg(long)]
538///     color: Option<Color>,
539/// }
540///
541/// assert_eq!(Cli::parse_from(["--color=auto"])?.color, Some(Color::WhenTerminal));
542/// assert_eq!(Cli::parse_from(["--color", "no"])?.color, Some(Color::Never));
543/// let error = Cli::parse_from(["--color=red"]).unwrap_err();
544/// assert_eq!(error.kind(), ErrorKind::InvalidChoice);
545/// # Ok::<(), winnow_args::Error>(())
546/// ```
547///
548/// - Enum: `rename_all = "…"`: the case of variant names, one of `"kebab-case"` (default), `"lowercase"`,
549///   `"UPPERCASE"`, `"snake_case"` and `"verbatim"`.
550/// - Variant: `name = "…"`: the value that selects it.
551/// - Variant: `alias = "…"`, `alias("…", …)`: other accepted values; `alias_hidden` is the same, not listed.
552/// - Variant: `hide`: accepted, but named neither in help nor in errors.
553/// - Variant: `help = "…"`, `long_help = "…"`: accepted and unused.
554#[proc_macro_derive(ValueEnum, attributes(arg, winnow_args))]
555pub fn derive_value_enum(input: TokenStream) -> TokenStream {
556    let input = parse_macro_input!(input as DeriveInput);
557    expand_value_enum(&input)
558        .unwrap_or_else(syn::Error::into_compile_error)
559        .into()
560}
561
562/// `from_arg`: a `match` on the long name and one on the letter, each arm
563/// building its variant; `from_word`: the positional variant, if any.
564fn expand_occurrence(input: &DeriveInput) -> syn::Result<TokenStream2> {
565    let Data::Enum(data) = &input.data else {
566        return Err(syn::Error::new(
567            input.span(),
568            "`Occurrence` can only be derived for enums",
569        ));
570    };
571    let (mut long_arms, mut short_arms) = (Vec::new(), Vec::new());
572    let (mut single_dash, mut prefixes) = (Vec::new(), Vec::new());
573    let mut letters = Vec::new();
574    let (mut word, mut unknown, mut bundle) = (None, None, None);
575    let mut seen = std::collections::HashMap::new();
576    // What help lists: one row a variant, in declaration order.
577    let mut help_items = Vec::new();
578    // `allow_hyphen_values` and `keep_equals` on the enum: every value
579    // variant's default.
580    let (mut enum_hyphen_values, mut enum_keep_equals) = (false, false);
581    for attr in input.attrs.iter().filter(|a| is_ours(a)) {
582        attr.parse_nested_meta(|meta| {
583            if meta.path.is_ident("allow_hyphen_values") {
584                enum_hyphen_values = true;
585                Ok(())
586            } else if meta.path.is_ident("keep_equals") {
587                enum_keep_equals = true;
588                Ok(())
589            } else {
590                Err(meta.error("expected `allow_hyphen_values` or `keep_equals`"))
591            }
592        })?;
593    }
594    for variant in &data.variants {
595        let ident = &variant.ident;
596        let (mut short, mut longs, mut positional) = (None, Vec::new(), false);
597        let (mut two_dashes, mut prefix, mut value_name) = (false, false, None);
598        let (mut hyphen_values, mut negative_numbers) = (enum_hyphen_values, false);
599        let (mut require_equals, mut default_missing) = (false, None);
600        let mut keep_equals = enum_keep_equals;
601        let (mut skip, mut is_unknown, mut is_bundle) = (false, false, false);
602        for attr in variant.attrs.iter().filter(|a| is_ours(a)) {
603            attr.parse_nested_meta(|meta| {
604                if meta.path.is_ident("allow_hyphen_values") {
605                    hyphen_values = true;
606                } else if meta.path.is_ident("allow_negative_numbers") {
607                    negative_numbers = true;
608                } else if meta.path.is_ident("require_equals") {
609                    require_equals = true;
610                } else if meta.path.is_ident("keep_equals") {
611                    keep_equals = true;
612                } else if meta.path.is_ident("default_missing") {
613                    default_missing = Some(meta.value()?.parse::<LitStr>()?.value());
614                } else if meta.path.is_ident("skip") {
615                    skip = true;
616                } else if meta.path.is_ident("bundle") {
617                    is_bundle = true;
618                } else if meta.path.is_ident("unknown") {
619                    is_unknown = true;
620                } else if meta.path.is_ident("short") {
621                    if short.is_some() {
622                        return Err(meta.error("an `Occurrence` variant has one `short`"));
623                    }
624                    short = Some(meta.value()?.parse::<LitChar>()?.value());
625                } else if meta.path.is_ident("long") {
626                    longs.push(if meta.input.peek(syn::Token![=]) {
627                        meta.value()?.parse::<LitStr>()?.value()
628                    } else {
629                        kebab_case(&ident.to_string())
630                    });
631                } else if meta.path.is_ident("alias") {
632                    longs.extend(aliases(&meta)?);
633                } else if meta.path.is_ident("positional") {
634                    positional = true;
635                } else if meta.path.is_ident("two_dashes") {
636                    two_dashes = true;
637                } else if meta.path.is_ident("prefix") {
638                    prefix = true;
639                } else if meta.path.is_ident("value_name") {
640                    value_name = Some(meta.value()?.parse::<LitStr>()?.value());
641                } else {
642                    return Err(meta.error(
643                        "expected `short`, `long`, `alias`, `positional`, `two_dashes`, `prefix`, \
644                         `value_name`, `allow_hyphen_values`, `allow_negative_numbers`, \
645                         `require_equals`, `keep_equals`, `default_missing`, `skip`, `unknown` or `bundle`",
646                    ));
647                }
648                Ok(())
649            })?;
650        }
651        let takes = match &variant.fields {
652            Fields::Unit => None,
653            Fields::Unnamed(f) if f.unnamed.len() == 1 => Some(&f.unnamed[0].ty),
654            _ => {
655                return Err(syn::Error::new(
656                    variant.span(),
657                    "an `Occurrence` variant is a unit (a switch) or holds one value",
658                ));
659            }
660        };
661        if skip {
662            // Built by the program itself (from a `-z` keyword, say), never parsed.
663            continue;
664        }
665        // A `Spanned<T>` value also records where it was and whether it was attached.
666        let spanned = takes.and_then(|ty| {
667            last_segment(ty)
668                .filter(|s| s.ident == "Spanned")
669                .and_then(inner)
670        });
671        let wrap = |value: TokenStream2, offset: TokenStream2, attached: TokenStream2| {
672            if spanned.is_some() {
673                quote! {
674                    ::winnow_args::value::Spanned {
675                        value: #value,
676                        offset: #offset,
677                        attached: #attached,
678                    }
679                }
680            } else {
681                value
682            }
683        };
684        if positional || is_unknown || is_bundle {
685            let Some(ty) = takes else {
686                return Err(syn::Error::new(
687                    variant.span(),
688                    "a positional, `unknown` or `bundle` variant holds its value",
689                ));
690            };
691            let ty = spanned.unwrap_or(ty);
692            let slot = if positional {
693                &mut word
694            } else if is_unknown {
695                &mut unknown
696            } else {
697                &mut bundle
698            };
699            if slot.is_some() {
700                return Err(syn::Error::new(
701                    variant.span(),
702                    "at most one positional, one `unknown` and one `bundle` variant",
703                ));
704            }
705            let name = value_name.unwrap_or_else(|| ident.to_string().to_uppercase());
706            if positional {
707                help_items.push(occurrence_item(
708                    variant,
709                    None,
710                    &[],
711                    Some(&name),
712                    quote!(<#ty as ::winnow_args::FromArg>::CHOICES),
713                    false,
714                    false,
715                ));
716            }
717            let value = wrap(
718                quote!(__word.convert::<#ty>(#name)?),
719                quote!(__word.offset),
720                quote!(false),
721            );
722            *slot = Some(quote!({
723                let __item = Self::#ident(#value);
724                // Read here, so a value only printed is not dead code.
725                match &__item {
726                    Self::#ident(__read) => {
727                        let _ = __read;
728                    }
729                    #[allow(unreachable_patterns)]
730                    _ => {}
731                }
732                ::core::result::Result::Ok(::core::option::Option::Some(__item))
733            }));
734            continue;
735        }
736        if short.is_none() && longs.is_empty() {
737            longs.push(kebab_case(&ident.to_string()));
738        }
739        let spelled = short
740            .map(|c| format!("-{c}"))
741            .into_iter()
742            .chain(longs.iter().map(|l| format!("--{l}")));
743        for name in spelled {
744            if let Some(other) = seen.insert(name.clone(), ident.clone()) {
745                return Err(syn::Error::new(
746                    variant.span(),
747                    format!("`{name}` is already used by `{other}`"),
748                ));
749            }
750        }
751        let choices = match takes {
752            Some(ty) => {
753                let ty = spanned.unwrap_or(ty);
754                quote!(<#ty as ::winnow_args::FromArg>::CHOICES)
755            }
756            None => quote!(&[]),
757        };
758        help_items.push(occurrence_item(
759            variant,
760            short,
761            &longs,
762            takes
763                .is_some()
764                .then(|| value_name.as_deref().unwrap_or("VALUE")),
765            choices,
766            require_equals,
767            default_missing.is_some(),
768        ));
769        let body = match takes {
770            None => quote! {
771                __arg.check_switch()?;
772                ::core::result::Result::Ok(::core::option::Option::Some(Self::#ident))
773            },
774            Some(ty) => {
775                let ty = spanned.unwrap_or(ty);
776                let options = quote! {
777                    ::winnow_args::token::ValueOptions {
778                        negative_numbers: #negative_numbers,
779                        hyphen_values: #hyphen_values,
780                        require_equals: #require_equals,
781                        keep_equals: #keep_equals,
782                    }
783                };
784                let read = match &default_missing {
785                    Some(missing) => {
786                        let bytes = LitByteStr::new(missing.as_bytes(), Span::call_site());
787                        quote! {
788                            __arg.read_value_or_with(
789                                __input,
790                                #options,
791                                ::winnow_args::__private::BStr::new(#bytes),
792                            )
793                        }
794                    }
795                    None => quote!(__arg.read_value_with(__input, #options)?),
796                };
797                let value = wrap(
798                    quote!(__arg.convert::<#ty>(__value)?),
799                    quote!(__arg.offset()),
800                    quote!(__attached),
801                );
802                let attached = spanned
803                    .is_some()
804                    .then(|| quote!(let __attached = __arg.has_attached_value(__input);));
805                quote! {
806                    #attached
807                    let __value = #read;
808                    {
809                    let __item = Self::#ident(#value);
810                    // Read here, so a value only printed is not dead code.
811                    match &__item {
812                        Self::#ident(__read) => {
813                            let _ = __read;
814                        }
815                        #[allow(unreachable_patterns)]
816                        _ => {}
817                    }
818                    ::core::result::Result::Ok(::core::option::Option::Some(__item))
819                }
820                }
821            }
822        };
823        if !longs.is_empty() {
824            let pattern = byte_patterns(&longs);
825            long_arms.push(quote!(#pattern => { #body }));
826            if !two_dashes {
827                single_dash.extend(longs.iter().cloned());
828            }
829        }
830        if let Some(c) = short {
831            if prefix {
832                if !c.is_ascii() || takes.is_none() {
833                    return Err(syn::Error::new(
834                        variant.span(),
835                        "`prefix` is for a value-taking variant with an ASCII `short` letter",
836                    ));
837                }
838                prefixes.push(c as u8);
839            }
840            let letter = LitChar::new(c, Span::call_site());
841            let takes_value = takes.is_some();
842            letters.push(quote!(#letter => ::core::option::Option::Some(#takes_value),));
843            short_arms.push(quote!(#letter => { #body }));
844        }
845    }
846    let has_positional = word.is_some();
847    let word =
848        word.unwrap_or_else(|| quote!(::core::result::Result::Ok(::core::option::Option::None)));
849    let bundle = bundle.map(|body| {
850        quote! {
851            const BUNDLES: bool = true;
852
853            fn from_bundle(
854                __word: &::winnow_args::token::Word<'_>,
855            ) -> ::core::result::Result<::core::option::Option<Self>, ::winnow_args::Error> {
856                #body
857            }
858        }
859    });
860    let unknown = unknown.map(|body| {
861        quote! {
862            fn from_unknown(
863                __word: &::winnow_args::token::Word<'_>,
864            ) -> ::core::result::Result<::core::option::Option<Self>, ::winnow_args::Error> {
865                #body
866            }
867        }
868    });
869    let is_long = matches_name(&single_dash);
870    let name = &input.ident;
871    let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
872    Ok(quote! {
873        impl #impl_generics ::winnow_args::Occurrence for #name #ty_generics #where_clause {
874            const PREFIXES: &'static [u8] = &[#(#prefixes),*];
875
876            const POSITIONAL: bool = #has_positional;
877
878            const ITEMS: &'static [::winnow_args::help::Item] = &[#(#help_items),*];
879
880            fn from_arg<'__i>(
881                __arg: &::winnow_args::Arg<'__i>,
882                __input: &mut ::winnow_args::Argv<'__i>,
883            ) -> ::core::result::Result<::core::option::Option<Self>, ::winnow_args::Error> {
884                let _ = &__input;
885                match __arg {
886                    ::winnow_args::Arg::Long(__flag) => match __flag.name {
887                        #(#long_arms)*
888                        _ => ::core::result::Result::Ok(::core::option::Option::None),
889                    },
890                    ::winnow_args::Arg::Short(__flag) if !__flag.plus => match __flag.letter {
891                        #(#short_arms)*
892                        _ => ::core::result::Result::Ok(::core::option::Option::None),
893                    },
894                    _ => ::core::result::Result::Ok(::core::option::Option::None),
895                }
896            }
897
898            fn from_word(
899                __word: &::winnow_args::token::Word<'_>,
900            ) -> ::core::result::Result<::core::option::Option<Self>, ::winnow_args::Error> {
901                let _ = __word;
902                #word
903            }
904
905            fn is_long(__name: &[u8]) -> bool {
906                #is_long
907            }
908
909            #unknown
910
911            #bundle
912
913            fn short(__letter: char) -> ::core::option::Option<bool> {
914                match __letter {
915                    #(#letters)*
916                    _ => ::core::option::Option::None,
917                }
918            }
919        }
920    })
921}
922
923/// An `Occurrence` variant's row in help: a flag by `short` and `longs`, or
924/// the positional when it has neither.
925fn occurrence_item(
926    variant: &syn::Variant,
927    short: Option<char>,
928    longs: &[String],
929    value_name: Option<&str>,
930    choices: TokenStream2,
931    require_equals: bool,
932    optional_value: bool,
933) -> TokenStream2 {
934    let (doc, long_doc) = docs(&variant.attrs);
935    let (help, long_help) = (text(&doc), text(&long_doc));
936    let positional = short.is_none() && longs.is_empty();
937    let short = opt_char(short);
938    let long = opt_str(longs.first().map(String::as_str));
939    let aliases = longs.iter().skip(1);
940    let value_name = opt_str(value_name);
941    quote!(::winnow_args::help::Item {
942        short: #short,
943        long: #long,
944        aliases: &[#(#aliases),*],
945        negate: ::core::option::Option::None,
946        value_name: #value_name,
947        help: #help,
948        long_help: #long_help,
949        heading: ::core::option::Option::None,
950        hide: false,
951        positional: #positional,
952        required: false,
953        multiple: #positional,
954        trailing: false,
955        default: ::core::option::Option::None,
956        env: ::core::option::Option::None,
957        choices: #choices,
958        require_equals: #require_equals,
959        global: false,
960        more_shorts: &[],
961        plus: ::core::option::Option::None,
962        optional_value: #optional_value,
963        values: 1,
964        more_values: false,
965    })
966}
967
968/// `FromArg` as one `match` on the value's bytes.
969fn expand_value_enum(input: &DeriveInput) -> syn::Result<TokenStream2> {
970    let Data::Enum(data) = &input.data else {
971        return Err(syn::Error::new(
972            input.span(),
973            "`ValueEnum` can only be derived for enums",
974        ));
975    };
976    let case = rename_all(input)?;
977    let mut names: Vec<(String, &Ident)> = Vec::new();
978    let mut arms = Vec::new();
979    let mut visible = Vec::new();
980    for variant in &data.variants {
981        if !matches!(variant.fields, Fields::Unit) {
982            return Err(syn::Error::new(
983                variant.span(),
984                "a `ValueEnum` variant holds no data",
985            ));
986        }
987        let Variant {
988            names: spellings,
989            hide,
990            ..
991        } = variant_names(variant, &mut names, case)?;
992        let name = LitStr::new(&spellings[0], Span::call_site());
993        if !hide {
994            visible.push(name);
995        }
996        let pattern = byte_patterns(&spellings);
997        let ident = &variant.ident;
998        arms.push(quote!(#pattern => ::core::result::Result::Ok(Self::#ident),));
999    }
1000    let name = &input.ident;
1001    let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
1002    Ok(quote! {
1003        impl #impl_generics ::winnow_args::FromArg for #name #ty_generics #where_clause {
1004            const CHOICES: &'static [&'static str] = &[#(#visible),*];
1005
1006            fn from_arg(
1007                __value: &::winnow_args::__private::BStr,
1008            ) -> ::core::result::Result<Self, ::winnow_args::__private::BoxError> {
1009                match &**__value {
1010                    #(#arms)*
1011                    _ => ::core::result::Result::Err(::std::boxed::Box::new(
1012                        ::winnow_args::ChoiceError { choices: Self::CHOICES },
1013                    )),
1014                }
1015            }
1016        }
1017    })
1018}
1019
1020/// Help prose, left out of binaries built without winnow-args' `help-text`.
1021fn text(prose: &str) -> TokenStream2 {
1022    if prose.is_empty() {
1023        quote!("")
1024    } else {
1025        quote!(::winnow_args::__text!(#prose))
1026    }
1027}
1028
1029/// A variant's spellings and help.
1030struct Variant {
1031    /// `name` (or kebab-case) first, then shown aliases, then hidden ones.
1032    names: Vec<String>,
1033    /// How many of `names` after the first are shown in help.
1034    shown: usize,
1035    hide: bool,
1036    about: String,
1037}
1038
1039/// How a variant's name is spelled when it gives none: an enum's
1040/// `#[arg(rename_all = "…")]`.
1041#[derive(Clone, Copy)]
1042enum Case {
1043    /// `DryRun` → `dry-run` (the default).
1044    Kebab,
1045    /// `DryRun` → `dryrun`.
1046    Lower,
1047    /// `DryRun` → `DRYRUN`.
1048    Upper,
1049    /// `DryRun` → `dry_run`.
1050    Snake,
1051    /// `DryRun` → `DryRun`.
1052    Verbatim,
1053}
1054
1055impl Case {
1056    fn apply(self, ident: &str) -> String {
1057        let ident = ident.trim_start_matches("r#");
1058        match self {
1059            Case::Kebab => kebab_case(ident),
1060            Case::Lower => ident.to_lowercase(),
1061            Case::Upper => ident.to_uppercase(),
1062            Case::Snake => kebab_case(ident).replace('-', "_"),
1063            Case::Verbatim => ident.to_owned(),
1064        }
1065    }
1066}
1067
1068/// An enum's `#[arg(rename_all = "…")]`, kebab-case if absent.
1069fn rename_all(input: &DeriveInput) -> syn::Result<Case> {
1070    let mut case = Case::Kebab;
1071    for attr in input.attrs.iter().filter(|a| is_ours(a)) {
1072        attr.parse_nested_meta(|meta| {
1073            if !meta.path.is_ident("rename_all") {
1074                return Err(meta.error("expected `rename_all`"));
1075            }
1076            let lit = meta.value()?.parse::<LitStr>()?;
1077            case = match lit.value().as_str() {
1078                "kebab-case" => Case::Kebab,
1079                "lowercase" => Case::Lower,
1080                "UPPERCASE" => Case::Upper,
1081                "snake_case" => Case::Snake,
1082                "verbatim" => Case::Verbatim,
1083                _ => {
1084                    return Err(syn::Error::new(
1085                        lit.span(),
1086                        "expected \"kebab-case\", \"lowercase\", \"UPPERCASE\", \"snake_case\" or \"verbatim\"",
1087                    ));
1088                }
1089            };
1090            Ok(())
1091        })?;
1092    }
1093    Ok(case)
1094}
1095
1096/// A variant's spellings, checked against `seen` for duplicates, and its help.
1097fn variant_names<'a>(
1098    variant: &'a syn::Variant,
1099    seen: &mut Vec<(String, &'a Ident)>,
1100    case: Case,
1101) -> syn::Result<Variant> {
1102    let (mut name, mut alias, mut hidden, mut hide) = (None, Vec::new(), Vec::new(), false);
1103    let mut help: Option<String> = None;
1104    for attr in variant.attrs.iter().filter(|a| is_ours(a)) {
1105        attr.parse_nested_meta(|meta| {
1106            if meta.path.is_ident("name") {
1107                name = Some(meta.value()?.parse::<LitStr>()?.value());
1108            } else if meta.path.is_ident("alias") {
1109                alias.extend(aliases(&meta)?);
1110            } else if meta.path.is_ident("alias_hidden") {
1111                hidden.extend(aliases(&meta)?);
1112            } else if meta.path.is_ident("hide") {
1113                hide = true;
1114            } else if meta.path.is_ident("help") {
1115                help = Some(meta.value()?.parse::<LitStr>()?.value());
1116            } else if meta.path.is_ident("long_help") {
1117                // The listing shows one line; the subcommand's own help has the rest.
1118                meta.value()?.parse::<LitStr>()?;
1119            } else {
1120                return Err(meta.error(
1121                    "expected `name`, `alias`, `alias_hidden`, `hide`, `help` or `long_help`",
1122                ));
1123            }
1124            Ok(())
1125        })?;
1126    }
1127    let ident = &variant.ident;
1128    let shown = alias.len();
1129    let names: Vec<String> =
1130        std::iter::once(name.unwrap_or_else(|| case.apply(&ident.to_string())))
1131            .chain(alias)
1132            .chain(hidden)
1133            .collect();
1134    for spelling in &names {
1135        if let Some((_, other)) = seen.iter().find(|(n, _)| n == spelling) {
1136            return Err(syn::Error::new(
1137                ident.span(),
1138                format!("`{spelling}` is already used by `{other}`"),
1139            ));
1140        }
1141        seen.push((spelling.clone(), ident));
1142    }
1143    Ok(Variant {
1144        names,
1145        shown,
1146        hide,
1147        about: help
1148            .map(|h| {
1149                h.split("\n\n")
1150                    .next()
1151                    .unwrap_or_default()
1152                    .replace('\n', " ")
1153            })
1154            .unwrap_or_else(|| docs(&variant.attrs).0),
1155    })
1156}
1157
1158/// A doc comment as help: its first paragraph, and the whole of it.
1159fn docs(attrs: &[syn::Attribute]) -> (String, String) {
1160    let lines: Vec<String> = attrs
1161        .iter()
1162        .filter(|a| a.path().is_ident("doc"))
1163        .filter_map(|a| match &a.meta {
1164            syn::Meta::NameValue(nv) => match &nv.value {
1165                syn::Expr::Lit(syn::ExprLit {
1166                    lit: syn::Lit::Str(s),
1167                    ..
1168                }) => Some(s.value()),
1169                _ => None,
1170            },
1171            _ => None,
1172        })
1173        .map(|line| {
1174            line.strip_prefix(' ')
1175                .unwrap_or(&line)
1176                .trim_end()
1177                .to_owned()
1178        })
1179        .collect();
1180    let long = lines.join("\n").trim().to_owned();
1181    let short = long
1182        .split("\n\n")
1183        .next()
1184        .unwrap_or_default()
1185        .replace('\n', " ");
1186    (short, long)
1187}
1188
1189/// `Some("s")` or `None`, spelled out.
1190fn opt_str(s: Option<&str>) -> TokenStream2 {
1191    match s {
1192        Some(s) => quote!(::core::option::Option::Some(#s)),
1193        None => quote!(::core::option::Option::None),
1194    }
1195}
1196
1197/// `Some('c')` or `None`, spelled out.
1198fn opt_char(c: Option<char>) -> TokenStream2 {
1199    match c {
1200        Some(c) => quote!(::core::option::Option::Some(#c)),
1201        None => quote!(::core::option::Option::None),
1202    }
1203}
1204
1205/// `b"a" | b"b"`.
1206fn byte_patterns<S: AsRef<str>>(names: &[S]) -> TokenStream2 {
1207    let literals = names
1208        .iter()
1209        .map(|n| LitByteStr::new(n.as_ref().as_bytes(), Span::call_site()));
1210    quote!(#(#literals)|*)
1211}
1212
1213/// Whether `__name` is one of `names`: `matches!(__name, b"a" | b"b")`.
1214fn matches_name(names: &[String]) -> TokenStream2 {
1215    if names.is_empty() {
1216        quote!(false)
1217    } else {
1218        let names = byte_patterns(names);
1219        quote!(matches!(__name, #names))
1220    }
1221}
1222
1223/// `alias = "x"` or `alias("x", "y")`.
1224fn aliases(meta: &syn::meta::ParseNestedMeta<'_>) -> syn::Result<Vec<String>> {
1225    if meta.input.peek(syn::Token![=]) {
1226        return Ok(vec![meta.value()?.parse::<LitStr>()?.value()]);
1227    }
1228    let content;
1229    syn::parenthesized!(content in meta.input);
1230    let names = syn::punctuated::Punctuated::<LitStr, syn::Token![,]>::parse_terminated(&content)?;
1231    Ok(names.iter().map(LitStr::value).collect())
1232}
1233
1234/// `DryRun` → `dry-run`.
1235fn kebab_case(ident: &str) -> String {
1236    let mut out = String::new();
1237    for (i, c) in ident.trim_start_matches("r#").chars().enumerate() {
1238        if c.is_uppercase() {
1239            if i > 0 {
1240                out.push('-');
1241            }
1242            out.extend(c.to_lowercase());
1243        } else {
1244            out.push(c);
1245        }
1246    }
1247    out
1248}
1249
1250/// An attribute for these derives: `#[arg(…)]`, or `#[winnow_args(…)]` where
1251/// another derive claims `arg`.
1252fn is_ours(attr: &syn::Attribute) -> bool {
1253    attr.path().is_ident("arg") || attr.path().is_ident("winnow_args")
1254}
1255
1256/// Whether `ty` is `bool`.
1257fn is_bool(ty: &Type) -> bool {
1258    last_segment(ty).is_some_and(|s| s.ident == "bool" && s.arguments.is_none())
1259}
1260
1261/// Whether a field is `#[arg(sequence)]`: occurrences of an `Occurrence` enum.
1262fn is_sequence(f: &syn::Field) -> bool {
1263    has_flag(f, "sequence")
1264}
1265
1266/// Whether a field is `#[arg(flatten)]`: another struct's flags, parsed as ours.
1267fn is_flatten(f: &syn::Field) -> bool {
1268    has_flag(f, "flatten")
1269}
1270
1271/// Whether a field is `#[arg(unknown)]`: the flags no one declared, whole.
1272fn is_unknown(f: &syn::Field) -> bool {
1273    has_flag(f, "unknown") && !is_sequence(f)
1274}
1275
1276/// Whether a field is `#[arg(skip)]`: left at its default, not parsed.
1277fn is_skipped(f: &syn::Field) -> bool {
1278    has_flag(f, "skip")
1279}
1280
1281/// Whether one of the field's attributes is the bare word `name`.
1282fn has_flag(f: &syn::Field, name: &str) -> bool {
1283    f.attrs.iter().filter(|a| is_ours(a)).any(|a| {
1284        let mut found = false;
1285        let _ = a.parse_nested_meta(|meta| {
1286            if meta.path.is_ident(name) {
1287                found = true;
1288            } else if meta.input.peek(syn::Token![=]) {
1289                meta.value()?.parse::<syn::Expr>()?;
1290            } else if meta.input.peek(syn::token::Paren) {
1291                let _content;
1292                syn::parenthesized!(_content in meta.input);
1293            }
1294            Ok(())
1295        });
1296        found
1297    })
1298}
1299
1300/// A `skip`, `flatten`, `sequence` or `unknown` field takes no other option
1301/// (`sequence, unknown` aside): none of them would be read.
1302fn role_alone(f: &syn::Field) -> syn::Result<()> {
1303    let role = ["skip", "flatten", "sequence", "unknown"]
1304        .into_iter()
1305        .find(|role| has_flag(f, role));
1306    let Some(role) = role else {
1307        return Ok(());
1308    };
1309    for attr in f.attrs.iter().filter(|a| is_ours(a)) {
1310        attr.parse_nested_meta(|meta| {
1311            let alone =
1312                meta.path.is_ident(role) || (role == "sequence" && meta.path.is_ident("unknown"));
1313            if alone {
1314                Ok(())
1315            } else {
1316                Err(meta.error(format!("a `{role}` field takes no other `arg` options")))
1317            }
1318        })?;
1319    }
1320    Ok(())
1321}
1322
1323/// `T` in `Box<T>`.
1324fn boxed(ty: &Type) -> Option<&Type> {
1325    let last = last_segment(ty)?;
1326    if last.ident == "Box" {
1327        inner(last)
1328    } else {
1329        None
1330    }
1331}
1332
1333enum Kind {
1334    /// `bool`: present or not.
1335    Switch,
1336    /// `#[arg(count)]` on an integer: how many times it was given.
1337    Count(Type),
1338    /// `Option<T>`: one value, may be absent.
1339    Optional(Type),
1340    /// `T`: one value, required.
1341    Required(Type),
1342    /// `Vec<T>`: a repeatable flag's values, or every remaining word.
1343    Many(Type),
1344}
1345
1346enum Role {
1347    Flag {
1348        short: Option<char>,
1349        long: Option<String>,
1350        /// More long names, matched like `long`.
1351        aliases: Vec<String>,
1352        /// Also accepted after a subcommand word, at any depth.
1353        global: bool,
1354    },
1355    Positional {
1356        name: String,
1357        double_dash: DoubleDash,
1358    },
1359    Subcommand,
1360}
1361
1362/// A positional's relation to `--`.
1363#[derive(Clone, Copy, PartialEq, Eq)]
1364enum DoubleDash {
1365    /// Words fill it on either side of `--`.
1366    Optional,
1367    /// Only words after `--` fill it, and they all do.
1368    Required,
1369    /// Once it has a value, flags stop as if `--` had been typed.
1370    Automatic,
1371    /// A `--` reaching it is one of its values, and stops nothing.
1372    Preserve,
1373}
1374
1375struct Field {
1376    ident: Ident,
1377    kind: Kind,
1378    role: Role,
1379    /// Splits each value of a `Vec` field.
1380    delimiter: Option<u8>,
1381    /// The only values accepted, checked before conversion.
1382    choices: Option<Vec<String>>,
1383    /// Environment variable consulted when the command line gave nothing.
1384    env: Option<String>,
1385    /// Value used when neither the command line nor the environment did.
1386    default: Option<String>,
1387    /// Selectors that may not be supplied together with this field.
1388    conflicts: Vec<String>,
1389    /// Selectors this field replaces, and that replace it: the last one given wins.
1390    overrides: Vec<String>,
1391    /// Selectors that must have a value when this field is supplied.
1392    requires: Vec<String>,
1393    /// Must end up with a value (for `Option` and `Vec` fields).
1394    required: bool,
1395    /// Required unless one of these selectors has a value.
1396    required_unless: Vec<String>,
1397    /// The struct-level group this field belongs to.
1398    group: Option<String>,
1399    /// The value of a flag given without one.
1400    default_missing: Option<String>,
1401    /// Help: first paragraph, whole text, section, hidden.
1402    help: String,
1403    long_help: String,
1404    heading: Option<String>,
1405    hide: bool,
1406    /// A flag's value placeholder.
1407    value_name: Option<String>,
1408    /// A negative number is a value: a flag's detached value, or a positional's word.
1409    negative_numbers: bool,
1410    /// A flag's detached value may be any word, flag-like or `--`.
1411    hyphen_values: bool,
1412    /// A flag's value must be attached.
1413    require_equals: bool,
1414    /// A short option's attached value keeps a leading `=` (GNU ld's `-L=dir`).
1415    keep_equals: bool,
1416    /// The long spelling that sets a `bool` false; the slot is then an
1417    /// `Option<bool>` until the end, so a default fills only what was not given.
1418    negate: Option<String>,
1419    /// Never spelled with one dash under `long_only`: `--omagic` (ld).
1420    two_dashes: bool,
1421    /// Words each occurrence takes (a `Vec` flag): `-platform_version macos 11.0 12.0`.
1422    values: usize,
1423    /// `values = a..` or `a..=b`: up to this many words in all (`usize::MAX`:
1424    /// no limit), the ones after the first only while they are not flag-like.
1425    values_max: Option<usize>,
1426    /// The word that ends a variable run of values, itself dropped.
1427    value_terminator: Option<String>,
1428    /// A function giving the value when nothing else did.
1429    default_fn: Option<syn::ExprPath>,
1430    /// What help says of `default_fn`'s value.
1431    default_note: Option<String>,
1432    /// `(selector, the value it must have, this field's default then)`.
1433    default_ifs: Vec<(String, Option<String>, String)>,
1434    /// Once this positional has a value, flags stop (whatever its `double_dash`).
1435    stop_flags: bool,
1436    /// The letter of its `+c` spelling, in a `plus_options` struct.
1437    plus: Option<char>,
1438    /// An `Option<bool>` with `short` and the same `plus` letter: `-c` is
1439    /// `Some(true)`, `+c` is `Some(false)`.
1440    tristate: bool,
1441    /// Its values are words of a vocabulary of their own, the field's type
1442    /// (`Args`): `-z now -z max-page-size=4096` (ld).
1443    keywords: bool,
1444    /// More letters it answers to, from a second `short` (`kill -l`/`-L`).
1445    short_aliases: Vec<char>,
1446    /// A short letter that always takes the rest of its word (`-lfoo`), so
1447    /// under `long_only` no long name starting with it is tried with one dash.
1448    prefix: bool,
1449}
1450
1451impl Field {
1452    /// This field's entry in `help::Command::items`.
1453    fn help_item(&self) -> TokenStream2 {
1454        let short = opt_char(self.short());
1455        let long = opt_str(self.longs().first().copied());
1456        let aliases = self.longs().into_iter().skip(1);
1457        let negate = opt_str(self.negate.as_deref());
1458        let require_equals = self.require_equals;
1459        let global = self.is_global();
1460        let positional = self.is_positional();
1461        let trailing = self.double_dash() == DoubleDash::Required;
1462        let display = self.display();
1463        let value_name = if positional {
1464            opt_str(Some(&display))
1465        } else {
1466            opt_str(self.value_name.as_deref())
1467        };
1468        let (help, long_help) = (text(&self.help), text(&self.long_help));
1469        let heading = opt_str(self.heading.as_deref());
1470        let hide = self.hide;
1471        let more_shorts = self.short_aliases.iter();
1472        let plus = opt_char(self.plus);
1473        let optional_value = self.default_missing.is_some();
1474        let values = self.values;
1475        let more_values = self.values_max.is_some();
1476        let required = matches!(self.kind, Kind::Required(_))
1477            && self.default.is_none()
1478            && self.default_fn.is_none()
1479            && self.env.is_none()
1480            && !self.keywords
1481            || self.required;
1482        let multiple = matches!(self.kind, Kind::Many(_) | Kind::Count(_));
1483        let default = opt_str(self.default.as_deref().or(self.default_note.as_deref()));
1484        let env = opt_str(self.env.as_deref());
1485        // Declared `choices`, else whatever fixed set the value type has.
1486        let choices = match (&self.choices, &self.kind, &self.role) {
1487            (Some(choices), _, _) => quote!(&[#(#choices),*]),
1488            (None, _, Role::Subcommand) | (None, Kind::Switch | Kind::Count(_), _) => quote!(&[]),
1489            (None, _, _) if self.keywords || self.tristate => quote!(&[]),
1490            (None, Kind::Optional(ty) | Kind::Required(ty) | Kind::Many(ty), _) => {
1491                quote!(<#ty as ::winnow_args::FromArg>::CHOICES)
1492            }
1493        };
1494        quote! {
1495            ::winnow_args::help::Item {
1496                short: #short,
1497                long: #long,
1498                aliases: &[#(#aliases),*],
1499                negate: #negate,
1500                value_name: #value_name,
1501                help: #help,
1502                long_help: #long_help,
1503                heading: #heading,
1504                hide: #hide,
1505                positional: #positional,
1506                required: #required,
1507                multiple: #multiple,
1508                trailing: #trailing,
1509                default: #default,
1510                env: #env,
1511                choices: #choices,
1512                require_equals: #require_equals,
1513                global: #global,
1514                more_shorts: &[#(#more_shorts),*],
1515                plus: #plus,
1516                optional_value: #optional_value,
1517                values: #values,
1518                more_values: #more_values,
1519            }
1520        }
1521    }
1522
1523    /// How this flag takes a detached value.
1524    fn value_options(&self) -> TokenStream2 {
1525        let (negative_numbers, hyphen_values, require_equals, keep_equals) = (
1526            self.negative_numbers,
1527            self.hyphen_values,
1528            self.require_equals,
1529            self.keep_equals,
1530        );
1531        quote! {
1532            __wa::ValueOptions {
1533                negative_numbers: #negative_numbers,
1534                hyphen_values: #hyphen_values,
1535                require_equals: #require_equals,
1536                keep_equals: #keep_equals,
1537            }
1538        }
1539    }
1540
1541    /// Reading this flag's value: `read_value`, or `read_value_or` its `default_missing`.
1542    fn read(&self) -> TokenStream2 {
1543        let (negative_numbers, hyphen_values, require_equals, keep_equals) = (
1544            self.negative_numbers,
1545            self.hyphen_values,
1546            self.require_equals,
1547            self.keep_equals,
1548        );
1549        let options = self.value_options();
1550        match &self.default_missing {
1551            None if !negative_numbers && !hyphen_values && !require_equals && !keep_equals => {
1552                quote!(__arg.read_value(__input)?)
1553            }
1554            None => quote!(__arg.read_value_with(__input, #options)?),
1555            Some(missing) => {
1556                let bytes = LitByteStr::new(missing.as_bytes(), Span::call_site());
1557                quote!(__arg.read_value_or_with(__input, #options, __wa::BStr::new(#bytes)))
1558            }
1559        }
1560    }
1561
1562    /// Reject `value` (a `&BStr`) outside `choices`, reported by `error(cause)`.
1563    fn check(
1564        &self,
1565        value: TokenStream2,
1566        error: impl FnOnce(TokenStream2) -> TokenStream2,
1567    ) -> TokenStream2 {
1568        let Some(choices) = &self.choices else {
1569            return quote!();
1570        };
1571        let pattern = byte_patterns(choices);
1572        let listed = choices.iter().map(|c| LitStr::new(c, Span::call_site()));
1573        let cause = quote!(::winnow_args::ChoiceError { choices: &[#(#listed),*] });
1574        let error = error(cause);
1575        quote! {
1576            if !matches!(&**#value, #pattern) {
1577                return ::core::result::Result::Err(#error);
1578            }
1579        }
1580    }
1581
1582    /// A flag's value converted to `ty`, checked against `choices` first.
1583    fn flag_value(&self, ty: &Type, value: TokenStream2) -> TokenStream2 {
1584        let check = self.check(value.clone(), |cause| {
1585            quote!(__wa::Error::invalid_value(__arg.offset(), __arg.spelling(), #value, #cause))
1586        });
1587        quote!({ #check __arg.convert::<#ty>(#value)? })
1588    }
1589
1590    /// `value` (a `&BStr` from the environment or a default) converted to `ty`;
1591    /// errors name it by `source`.
1592    fn source_value(&self, ty: &Type, value: TokenStream2, source: &str) -> TokenStream2 {
1593        let source = LitStr::new(source, Span::call_site());
1594        let check = self.check(
1595            value.clone(),
1596            |cause| quote!(__wa::Error::invalid_value(__input.offset(), #source, #value, #cause)),
1597        );
1598        quote!({
1599            #check
1600            <#ty as __wa::FromArg>::from_arg(#value).map_err(|cause| {
1601                __wa::Error::invalid_value(__input.offset(), #source, #value, cause)
1602            })?
1603        })
1604    }
1605
1606    /// Whether the field holds a value. Read before defaults this is "supplied"
1607    /// (command line or environment); after them it is "has a value".
1608    fn has(&self) -> TokenStream2 {
1609        let slot = slot(&self.ident);
1610        if self.keywords {
1611            return quote!((!#slot.is_empty()));
1612        }
1613        match &self.kind {
1614            Kind::Switch if self.negate.is_some() => {
1615                quote!((#slot == ::core::option::Option::Some(true)))
1616            }
1617            Kind::Switch => quote!(#slot),
1618            Kind::Count(_) => quote!((#slot != 0)),
1619            Kind::Optional(_) | Kind::Required(_) => quote!(#slot.is_some()),
1620            Kind::Many(_) => quote!((!#slot.is_empty())),
1621        }
1622    }
1623
1624    /// Store `value` (a `&BStr`), converted, in the slot of a field that takes
1625    /// values; `source` names where it came from in an error.
1626    fn assign(&self, value: TokenStream2, source: &str) -> TokenStream2 {
1627        let slot = slot(&self.ident);
1628        match &self.kind {
1629            Kind::Optional(ty) | Kind::Required(ty) => {
1630                let value = self.source_value(ty, value, source);
1631                quote!(#slot = ::core::option::Option::Some(#value);)
1632            }
1633            Kind::Many(ty) => match self.delimiter {
1634                None => {
1635                    let value = self.source_value(ty, value, source);
1636                    quote!(#slot.push(#value);)
1637                }
1638                Some(d) => {
1639                    let piece = self.source_value(ty, quote!(__piece), source);
1640                    quote!(for __piece in __wa::split(#value, #d) { #slot.push(#piece); })
1641                }
1642            },
1643            Kind::Switch | Kind::Count(_) => unreachable!("a switch or a count takes no value"),
1644        }
1645    }
1646
1647    /// Whether the field was not given, for a fallback to fill it.
1648    fn unset(&self, displaced: bool) -> TokenStream2 {
1649        let slot = slot(&self.ident);
1650        if self.negate.is_some() {
1651            return quote!(#slot.is_none());
1652        }
1653        let has = self.has();
1654        let not_displaced = displaced.then(|| {
1655            let displaced = displaced_flag(&self.ident);
1656            quote!(&& !#displaced)
1657        });
1658        quote!(!#has #not_displaced)
1659    }
1660
1661    /// Fill the field from its environment variable (`env`) or its default when
1662    /// the command line left it unset and no `overrides` displaced it.
1663    fn fallback(&self, env: bool, displaced: bool) -> TokenStream2 {
1664        let slot = slot(&self.ident);
1665        if self.negate.is_some() {
1666            return self.negatable_fallback(env);
1667        }
1668        let unset = self.unset(displaced);
1669        let assign = |value: TokenStream2, source: &str| self.assign(value, source);
1670        if !env {
1671            if let Some(function) = &self.default_fn {
1672                let set = match &self.kind {
1673                    Kind::Many(_) => quote!(#slot = #function();),
1674                    _ => quote!(#slot = ::core::option::Option::Some(#function());),
1675                };
1676                return quote!(if #unset { #set });
1677            }
1678            return self
1679                .default
1680                .as_ref()
1681                .map(|value| {
1682                    let bytes = LitByteStr::new(value.as_bytes(), Span::call_site());
1683                    let assign = assign(quote!(__wa::BStr::new(#bytes)), &self.display());
1684                    quote!(if #unset { #assign })
1685                })
1686                .unwrap_or_default();
1687        }
1688        self.env
1689            .as_ref()
1690            .map(|var| {
1691                let name = LitStr::new(var, Span::call_site());
1692                let apply = match &self.kind {
1693                    Kind::Switch => quote!(#slot = __wa::env::truthy(&__raw);),
1694                    Kind::Count(ty) => quote! {
1695                        if let ::core::option::Option::Some(::core::result::Result::Ok(__n)) =
1696                            __raw.to_str().map(str::parse::<#ty>)
1697                        {
1698                            #slot = __n;
1699                        }
1700                    },
1701                    _ => {
1702                        let assign = assign(quote!(__value), &format!("${var}"));
1703                        quote! {
1704                            let __value = __wa::BStr::new(__raw.as_encoded_bytes());
1705                            #assign
1706                        }
1707                    }
1708                };
1709                quote! {
1710                    if #unset {
1711                        if let ::core::option::Option::Some(__raw) = __wa::env::var(#name) {
1712                            #apply
1713                        }
1714                    }
1715                }
1716            })
1717            .unwrap_or_default()
1718    }
1719
1720    /// A negatable switch's fallback: only when neither spelling was given.
1721    fn negatable_fallback(&self, env: bool) -> TokenStream2 {
1722        let slot = slot(&self.ident);
1723        let value = if env {
1724            let Some(var) = &self.env else {
1725                return quote!();
1726            };
1727            let name = LitStr::new(var, Span::call_site());
1728            quote! {
1729                if let ::core::option::Option::Some(__raw) = __wa::env::var(#name) {
1730                    #slot = ::core::option::Option::Some(__wa::env::truthy(&__raw));
1731                }
1732            }
1733        } else {
1734            match (&self.default_fn, &self.default) {
1735                (Some(function), _) => quote!(#slot = ::core::option::Option::Some(#function());),
1736                (None, Some(default)) => {
1737                    let default = default == "true";
1738                    quote!(#slot = ::core::option::Option::Some(#default);)
1739                }
1740                (None, None) => return quote!(),
1741            }
1742        };
1743        quote!(if #slot.is_none() { #value })
1744    }
1745
1746    /// A positional word converted to `ty`, checked against `choices` first.
1747    fn word_value(&self, ty: &Type, word: TokenStream2, display: &LitStr) -> TokenStream2 {
1748        let check = self.check(
1749            quote!(#word.value),
1750            |cause| quote!(__wa::Error::invalid_value(#word.offset, #display, #word.value, #cause)),
1751        );
1752        quote!({ #check #word.convert::<#ty>(#display)? })
1753    }
1754
1755    /// How errors name the field: its long flag, else its short one, else its value name.
1756    fn display(&self) -> String {
1757        match &self.role {
1758            Role::Flag { long: Some(l), .. } => format!("--{l}"),
1759            Role::Flag { short: Some(c), .. } => format!("-{c}"),
1760            Role::Flag { .. } => match self.plus {
1761                Some(c) => format!("+{c}"),
1762                None => unreachable!("every flag has a name"),
1763            },
1764            Role::Positional { name, .. } => name.clone(),
1765            Role::Subcommand => "<COMMAND>".to_owned(),
1766        }
1767    }
1768
1769    /// The pattern of every letter it answers to: `'l' | 'L'`.
1770    fn short_pattern(&self) -> Option<TokenStream2> {
1771        let first = self.short()?;
1772        let letters = std::iter::once(first)
1773            .chain(self.short_aliases.iter().copied())
1774            .map(|c| LitChar::new(c, Span::call_site()));
1775        Some(quote!(#(#letters)|*))
1776    }
1777
1778    /// Every letter it answers to.
1779    fn shorts(&self) -> Vec<char> {
1780        self.short()
1781            .into_iter()
1782            .chain(self.short_aliases.iter().copied())
1783            .collect()
1784    }
1785
1786    fn short(&self) -> Option<char> {
1787        match self.role {
1788            Role::Flag { short, .. } => short,
1789            Role::Positional { .. } | Role::Subcommand => None,
1790        }
1791    }
1792
1793    /// Every long spelling: the name, then its aliases.
1794    fn longs(&self) -> Vec<&str> {
1795        match &self.role {
1796            Role::Flag { long, aliases, .. } => {
1797                long.iter().chain(aliases).map(String::as_str).collect()
1798            }
1799            Role::Positional { .. } | Role::Subcommand => Vec::new(),
1800        }
1801    }
1802
1803    /// The pattern matching any long spelling, if there is one.
1804    fn long_pattern(&self) -> Option<TokenStream2> {
1805        let longs = self.longs();
1806        (!longs.is_empty()).then(|| byte_patterns(&longs))
1807    }
1808
1809    /// Whether a flag reads a value (rather than being a switch or a count).
1810    fn takes_value(&self) -> bool {
1811        !self.tristate && !matches!(self.kind, Kind::Switch | Kind::Count(_))
1812    }
1813
1814    fn is_positional(&self) -> bool {
1815        matches!(self.role, Role::Positional { .. })
1816    }
1817
1818    fn is_global(&self) -> bool {
1819        matches!(self.role, Role::Flag { global: true, .. })
1820    }
1821
1822    /// A positional's relation to `--`; `Optional` for anything else.
1823    fn double_dash(&self) -> DoubleDash {
1824        match self.role {
1825            Role::Positional { double_dash, .. } => double_dash,
1826            _ => DoubleDash::Optional,
1827        }
1828    }
1829}
1830
1831fn expand(input: &DeriveInput) -> syn::Result<TokenStream2> {
1832    let Data::Struct(data) = &input.data else {
1833        return Err(syn::Error::new(
1834            input.span(),
1835            "`Args` can only be derived for structs",
1836        ));
1837    };
1838    let Fields::Named(named) = &data.fields else {
1839        return Err(syn::Error::new(
1840            data.fields.span(),
1841            "`Args` needs a struct with named fields",
1842        ));
1843    };
1844    for f in &named.named {
1845        role_alone(f)?;
1846    }
1847    let fields = named
1848        .named
1849        .iter()
1850        .filter(|f| !is_skipped(f) && !is_sequence(f) && !is_unknown(f) && !is_flatten(f))
1851        .map(field)
1852        .collect::<syn::Result<Vec<_>>>()?;
1853    let skipped: Vec<&Ident> = named
1854        .named
1855        .iter()
1856        .filter(|f| is_skipped(f) && !is_sequence(f) && !is_unknown(f) && !is_flatten(f))
1857        .filter_map(|f| f.ident.as_ref())
1858        .collect();
1859    // `#[arg(sequence)] items: Vec<T>`: occurrences of `T`'s flags and
1860    // positional, in order.
1861    let sequences: Vec<&syn::Field> = named.named.iter().filter(|f| is_sequence(f)).collect();
1862    if let Some(extra) = sequences.get(1) {
1863        return Err(syn::Error::new(
1864            extra.span(),
1865            "a struct has at most one `sequence` field",
1866        ));
1867    }
1868    // `#[arg(sequence, unknown)]`: unknown flags join the sequence, through
1869    // the enum's `#[arg(unknown)]` variant.
1870    let sequence_unknown = sequences.first().is_some_and(|f| has_flag(f, "unknown"));
1871    let sequence = vec_field(
1872        sequences.first().copied(),
1873        "a `sequence` field is a `Vec<T>` of an `Occurrence` enum",
1874    )?;
1875    let sequence_ty = sequence.as_ref().map(|(_, ty)| ty);
1876    // `#[arg(unknown)] unknown: Vec<T>`: flag-like words naming no flag.
1877    let unknowns: Vec<&syn::Field> = named.named.iter().filter(|f| is_unknown(f)).collect();
1878    if let Some(extra) = unknowns.get(1) {
1879        return Err(syn::Error::new(
1880            extra.span(),
1881            "a struct has at most one `unknown` field",
1882        ));
1883    }
1884    let unknown = vec_field(
1885        unknowns.first().copied(),
1886        "an `unknown` field is a `Vec<T>`",
1887    )?;
1888    // `#[arg(flatten)] common: T`: `T`'s flags, parsed as if declared here.
1889    let flattens: Vec<(Ident, Type)> = named
1890        .named
1891        .iter()
1892        .filter(|f| is_flatten(f))
1893        .map(|f| (f.ident.clone().expect("named field"), f.ty.clone()))
1894        .collect();
1895    if !flattens.is_empty() && !input.generics.params.is_empty() {
1896        return Err(syn::Error::new(
1897            input.generics.span(),
1898            "`flatten` in a generic struct is not supported yet",
1899        ));
1900    }
1901    check_duplicates(&fields)?;
1902    // A `double_dash = "required"` positional is outside the ordinary sequence:
1903    // every word after `--` goes to it, and no word before.
1904    let positionals: Vec<&Field> = fields
1905        .iter()
1906        .filter(|f| f.is_positional() && f.double_dash() != DoubleDash::Required)
1907        .collect();
1908    check_positional_order(&positionals)?;
1909    let trailing: Vec<&Field> = fields
1910        .iter()
1911        .filter(|f| f.double_dash() == DoubleDash::Required)
1912        .collect();
1913    if let Some(extra) = trailing.get(1) {
1914        return Err(syn::Error::new(
1915            extra.ident.span(),
1916            "a struct has at most one `double_dash = \"required\"` positional",
1917        ));
1918    }
1919    let trailing = trailing.first().copied();
1920    // The positionals a `--` fills while they are next, by position.
1921    let preserving: Vec<usize> = positionals
1922        .iter()
1923        .enumerate()
1924        .filter(|(_, f)| f.double_dash() == DoubleDash::Preserve)
1925        .map(|(i, _)| i)
1926        .collect();
1927    if let (Some(t), false) = (trailing, preserving.is_empty()) {
1928        return Err(syn::Error::new(
1929            t.ident.span(),
1930            "a `double_dash = \"preserve\"` positional keeps the `--` this one waits for",
1931        ));
1932    }
1933    let StructOptions {
1934        groups,
1935        restart_token,
1936        default_subcommand,
1937        arg_required_else_help,
1938        name: program_name,
1939        version,
1940        about,
1941        long_about,
1942        after_help,
1943        after_long_help,
1944        disable_help_flag,
1945        disable_help_short,
1946        disable_version_flag,
1947        disable_help_subcommand,
1948        unknown_flags_value,
1949        long_only,
1950        plus_options,
1951    } = struct_options(input)?;
1952    let (doc_about, doc_long_about) = docs(&input.attrs);
1953    let about = about.unwrap_or(doc_about);
1954    let long_about = long_about.unwrap_or(doc_long_about);
1955    let (about, long_about) = (text(&about), text(&long_about));
1956    let (after_help, after_long_help) = (text(&after_help), text(&after_long_help));
1957    let rules = Rules::new(&fields, &groups)?;
1958
1959    let name = &input.ident;
1960    let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
1961
1962    // Each field's slot: its name, type and starting value.
1963    let slot_parts: Vec<(Ident, TokenStream2, TokenStream2)> = fields
1964        .iter()
1965        .map(|f| {
1966            let none = quote!(::core::option::Option::None);
1967            let new = quote!(::std::vec::Vec::new());
1968            let (ty, init) = match &f.kind {
1969                // Borrows the command line, so never in a `Flatten` slot struct.
1970                _ if f.keywords => (quote!(::std::vec::Vec<&__wa::BStr>), new),
1971                Kind::Switch if f.negate.is_some() => (quote!(::core::option::Option<bool>), none),
1972                Kind::Switch => (quote!(bool), quote!(false)),
1973                Kind::Count(ty) => (quote!(#ty), quote!(0)),
1974                Kind::Optional(ty) | Kind::Required(ty) => {
1975                    (quote!(::core::option::Option<#ty>), none)
1976                }
1977                Kind::Many(ty) => (quote!(::std::vec::Vec<#ty>), new),
1978            };
1979            (slot(&f.ident), ty, init)
1980        })
1981        .collect();
1982    let slots = slot_parts
1983        .iter()
1984        .map(|(ident, ty, init)| quote!(let mut #ident: #ty = #init;));
1985
1986    // What storing one flag occurrence looks like; the same for both spellings.
1987    let store = |f: &Field| {
1988        let ident = slot(&f.ident);
1989        let displace = rules.displace(&fields, f);
1990        let stored = match &f.kind {
1991            _ if f.keywords => {
1992                let read = f.read();
1993                quote! {
1994                    let __value = #read;
1995                    #ident.push(__value);
1996                }
1997            }
1998            _ if f.tristate || f.negate.is_some() => quote! {
1999                __arg.check_switch()?;
2000                #ident = ::core::option::Option::Some(true);
2001            },
2002            Kind::Switch => quote! {
2003                __arg.check_switch()?;
2004                #ident = true;
2005            },
2006            Kind::Count(_) => quote! {
2007                __arg.check_switch()?;
2008                #ident = #ident.saturating_add(1);
2009            },
2010            Kind::Optional(ty) | Kind::Required(ty) => {
2011                let value = f.flag_value(ty, quote!(__value));
2012                let read = f.read();
2013                quote! {
2014                    let __value = #read;
2015                    #ident = ::core::option::Option::Some(#value);
2016                }
2017            }
2018            Kind::Many(ty) => match f.delimiter {
2019                None => {
2020                    let value = f.flag_value(ty, quote!(__value));
2021                    let read = f.read();
2022                    let more = match f.values_max {
2023                        // `values = N`: the words after the first, whatever they look like.
2024                        None => (f.values > 1).then(|| {
2025                            let more = f.values - 1;
2026                            quote! {
2027                                for _ in 0..#more {
2028                                    let __value = __arg.read_next(__input)?;
2029                                    #ident.push(#value);
2030                                }
2031                            }
2032                        }),
2033                        // `values = a..=b`: more words until one is flag-like or the terminator.
2034                        Some(max) => {
2035                            let (min, options) = (f.values, f.value_options());
2036                            let terminator = match &f.value_terminator {
2037                                Some(word) => {
2038                                    let word = LitByteStr::new(word.as_bytes(), Span::call_site());
2039                                    quote!(::core::option::Option::Some(__wa::BStr::new(#word)))
2040                                }
2041                                None => quote!(::core::option::Option::None),
2042                            };
2043                            Some(quote! {
2044                                let mut __taken = 1usize;
2045                                while __taken < #max {
2046                                    let ::core::option::Option::Some(__value) =
2047                                        __arg.read_more(__input, #options, #terminator)
2048                                    else {
2049                                        break;
2050                                    };
2051                                    #ident.push(#value);
2052                                    __taken += 1;
2053                                }
2054                                __arg.check_values(__taken, #min)?;
2055                            })
2056                        }
2057                    };
2058                    quote! {
2059                        let __value = #read;
2060                        #ident.push(#value);
2061                        #more
2062                    }
2063                }
2064                Some(d) => {
2065                    let value = f.flag_value(ty, quote!(__piece));
2066                    let read = f.read();
2067                    quote! {
2068                        for __piece in __wa::split(#read, #d) {
2069                            #ident.push(#value);
2070                        }
2071                    }
2072                }
2073            },
2074        };
2075        quote!(#stored #displace)
2076    };
2077
2078    let negated = |f: &Field| {
2079        let no = LitByteStr::new(f.negate.as_ref()?.as_bytes(), Span::call_site());
2080        let ident = slot(&f.ident);
2081        let displace = rules.displace(&fields, f);
2082        Some((
2083            quote!(#no),
2084            quote! {
2085                __arg.check_switch()?;
2086                #ident = ::core::option::Option::Some(false);
2087                #displace
2088            },
2089        ))
2090    };
2091    // A `+c` occurrence: a tristate's `Some(false)`, else what `-c` stores.
2092    let plus = |f: &Field| {
2093        let letter = LitChar::new(f.plus?, Span::call_site());
2094        let body = if f.tristate {
2095            let ident = slot(&f.ident);
2096            let displace = rules.displace(&fields, f);
2097            quote!(#ident = ::core::option::Option::Some(false); #displace)
2098        } else {
2099            store(f)
2100        };
2101        Some((quote!(#letter), body))
2102    };
2103    // The long (negations last), short and `+` arms of the fields `keep`
2104    // selects, each body ending in `tail`.
2105    type Part<'a> = &'a dyn Fn(&Field) -> Option<(TokenStream2, TokenStream2)>;
2106    let arms = |keep: &dyn Fn(&Field) -> bool, tail: TokenStream2| {
2107        let of = |part: Part<'_>| -> Vec<TokenStream2> {
2108            fields
2109                .iter()
2110                .filter(|f| keep(f))
2111                .filter_map(part)
2112                .map(|(pattern, body)| quote!(#pattern => { #body #tail }))
2113                .collect()
2114        };
2115        let mut long = of(&|f| Some((f.long_pattern()?, store(f))));
2116        long.extend(of(&negated));
2117        let short = of(&|f| Some((f.short_pattern()?, store(f))));
2118        (long, short, of(&plus))
2119    };
2120    let (long_arms, short_arms, plus_arms) = arms(&|_| true, quote!());
2121
2122    let unexpected = quote!(return ::core::result::Result::Err(__arg.unexpected()));
2123    let subcommands: Vec<&Field> = fields
2124        .iter()
2125        .filter(|f| matches!(f.role, Role::Subcommand))
2126        .collect();
2127    if let Some(extra) = subcommands.get(1) {
2128        return Err(syn::Error::new(
2129            extra.ident.span(),
2130            "a struct has at most one subcommand field",
2131        ));
2132    }
2133    let subcommand = subcommands.first();
2134    // A word routes to a subcommand only before any positional is filled.
2135    let track_filled = subcommand.is_some() && !positionals.is_empty();
2136    let filled = track_filled.then(|| quote!(__filled = true;));
2137
2138    // A word no ordinary positional takes: before `--`, it would have reached
2139    // the `double_dash = "required"` positional if there is one.
2140    let overflow = match trailing {
2141        Some(t) => {
2142            let display = LitStr::new(&t.display(), Span::call_site());
2143            quote! {
2144                return ::core::result::Result::Err(
2145                    __wa::Error::requires_double_dash(__word.offset, #display),
2146                )
2147            }
2148        }
2149        None => unexpected.clone(),
2150    };
2151    let positional_match = if positionals.is_empty() {
2152        quote!({ #overflow; })
2153    } else {
2154        let arms = positionals.iter().enumerate().map(|(i, f)| {
2155            let ident = slot(&f.ident);
2156            let display = LitStr::new(&f.display(), Span::call_site());
2157            let stop = (f.double_dash() == DoubleDash::Automatic || f.stop_flags)
2158                .then(|| quote!(__input.stop_flags();));
2159            match &f.kind {
2160                Kind::Optional(ty) | Kind::Required(ty) => {
2161                    let value = f.word_value(ty, quote!(__word), &display);
2162                    quote! {
2163                        #i => {
2164                            #ident = ::core::option::Option::Some(#value);
2165                            __position += 1;
2166                            #filled
2167                            #stop
2168                        }
2169                    }
2170                }
2171                Kind::Many(ty) => {
2172                    let push = match f.delimiter {
2173                        None => {
2174                            let value = f.word_value(ty, quote!(__word), &display);
2175                            // Sized once, for at most every word left: the
2176                            // usual tail of operands then needs no regrowth.
2177                            quote! {
2178                                if #ident.capacity() == 0 {
2179                                    #ident.reserve_exact(__input.words_left() + 1);
2180                                }
2181                                #ident.push(#value);
2182                            }
2183                        }
2184                        Some(d) => {
2185                            let value = f.word_value(ty, quote!(__piece), &display);
2186                            quote! {
2187                                for __piece in __word.split(#d) {
2188                                    #ident.push(#value);
2189                                }
2190                            }
2191                        }
2192                    };
2193                    quote! {
2194                        #i => {
2195                            #push
2196                            #filled
2197                            #stop
2198                        }
2199                    }
2200                }
2201                Kind::Switch | Kind::Count(_) => {
2202                    unreachable!("rejected for positionals in `field`")
2203                }
2204            }
2205        });
2206        quote! {
2207            match __position {
2208                #(#arms)*
2209                _ => { #overflow; }
2210            }
2211        }
2212    };
2213    let positional_match = match trailing {
2214        None => positional_match,
2215        Some(t) => {
2216            let ident = slot(&t.ident);
2217            let display = LitStr::new(&t.display(), Span::call_site());
2218            let store = match &t.kind {
2219                Kind::Many(ty) => match t.delimiter {
2220                    None => {
2221                        let value = t.word_value(ty, quote!(__word), &display);
2222                        quote!(#ident.push(#value);)
2223                    }
2224                    Some(d) => {
2225                        let value = t.word_value(ty, quote!(__piece), &display);
2226                        quote!(for __piece in __word.split(#d) { #ident.push(#value); })
2227                    }
2228                },
2229                Kind::Optional(ty) | Kind::Required(ty) => {
2230                    let value = t.word_value(ty, quote!(__word), &display);
2231                    quote! {
2232                        if #ident.is_some() { #unexpected; }
2233                        #ident = ::core::option::Option::Some(#value);
2234                    }
2235                }
2236                Kind::Switch | Kind::Count(_) => {
2237                    unreachable!("rejected for positionals in `field`")
2238                }
2239            };
2240            quote! {
2241                if __word.after_separator {
2242                    #store
2243                } else {
2244                    #positional_match
2245                }
2246            }
2247        }
2248    };
2249    let route = subcommand.map(|f| {
2250        let ident = slot(&f.ident);
2251        let ty = subcommand_type(f);
2252        let not_filled = track_filled.then(|| quote!(!__filled &&));
2253        let (global_longs, global_shorts, global_plus) = arms(
2254            &Field::is_global,
2255            quote!(return ::core::result::Result::Ok(true);),
2256        );
2257        // The subcommand sees this struct's globals first, then its ancestors'.
2258        let (inherit, handler) = if fields.iter().any(Field::is_global) {
2259            let shorts = fields.iter().filter(|f| f.is_global()).flat_map(|f| {
2260                let takes_value = f.takes_value();
2261                f.shorts().into_iter().map(move |c| {
2262                    let c = LitChar::new(c, Span::call_site());
2263                    quote!((#c, #takes_value))
2264                })
2265            });
2266            let setup = quote! {
2267                let mut __inherit = __wa::inherit(&[#(#shorts),*], __globals, |__arg, __input, __parent| {
2268                    match __arg {
2269                        __wa::Arg::Long(__flag) => match __flag.name {
2270                            #(#global_longs)*
2271                            _ => {}
2272                        },
2273                        __wa::Arg::Short(__flag) if __flag.plus => match __flag.letter {
2274                            #(#global_plus)*
2275                            _ => {}
2276                        },
2277                        __wa::Arg::Short(__flag) => match __flag.letter {
2278                            #(#global_shorts)*
2279                            _ => {}
2280                        },
2281                        _ => {}
2282                    }
2283                    __parent.bind(__arg, __input)
2284                });
2285            };
2286            (setup, quote!(&mut __inherit))
2287        } else {
2288            (quote!(), quote!(&mut *__globals))
2289        };
2290        // `help a b`: the help of a subcommand, unless one is actually named `help`.
2291        let help_word = (!disable_help_subcommand).then(|| {
2292            quote! {
2293                if &**__word.value == b"help" {
2294                    return ::core::result::Result::Err(
2295                        __wa::help_word(<Self as ::winnow_args::Args>::HELP, __input),
2296                    );
2297                }
2298            }
2299        });
2300        // Any other word selects the default subcommand, which reads it again as its own.
2301        let fallback = default_subcommand.as_ref().map(|name| {
2302            let name = LitByteStr::new(name.as_bytes(), Span::call_site());
2303            quote! {
2304                let __sub = {
2305                    #inherit
2306                    *__input = __start;
2307                    <#ty as __wa::Subcommand>::parse_subcommand(#name, __input, #handler)?
2308                };
2309                #ident = ::core::option::Option::Some(__sub);
2310                break;
2311            }
2312        });
2313        quote! {
2314            if #not_filled !__word.after_separator {
2315                if <#ty as __wa::Subcommand>::has(&**__word.value) {
2316                    let __sub = {
2317                        #inherit
2318                        <#ty as __wa::Subcommand>::parse_subcommand(
2319                            &**__word.value, __input, #handler,
2320                        )?
2321                    };
2322                    #ident = ::core::option::Option::Some(__sub);
2323                    break;
2324                }
2325                #help_word
2326                #fallback
2327            }
2328        }
2329    });
2330    // A new invocation of the same command: positionals start over, flags keep theirs.
2331    let restart = restart_token.map(|token| {
2332        let token = LitByteStr::new(token.as_bytes(), Span::call_site());
2333        let clears = fields.iter().filter(|f| f.is_positional()).map(|f| {
2334            let slot = slot(&f.ident);
2335            match f.kind {
2336                Kind::Many(_) => quote!(#slot.clear();),
2337                _ => quote!(#slot = ::core::option::Option::None;),
2338            }
2339        });
2340        let position = (!positionals.is_empty()).then(|| quote!(__position = 0;));
2341        let filled = track_filled.then(|| quote!(__filled = false;));
2342        quote! {
2343            if &**__word.value == #token {
2344                #(#clears)*
2345                #position
2346                #filled
2347                __input.resume_flags();
2348                continue;
2349            }
2350        }
2351    });
2352    let sequence_word = sequence_ty.map(|ty| {
2353        quote! {
2354            if let ::core::option::Option::Some(__item) =
2355                <#ty as ::winnow_args::Occurrence>::from_word(&__word)?
2356            {
2357                __sequence.push(__item);
2358                continue;
2359            }
2360        }
2361    });
2362    let word_arm = quote! {
2363        __wa::Arg::Word(__word) => {
2364            #restart
2365            #route
2366            #sequence_word
2367            #positional_match
2368        }
2369    };
2370    let separator_arm = if preserving.is_empty() {
2371        quote!(__wa::Arg::Separator { .. } => {})
2372    } else {
2373        quote! {
2374            __wa::Arg::Separator { offset: __offset } => {
2375                if matches!(__position, #(#preserving)|*) {
2376                    __input.resume_flags();
2377                    let __word = __wa::Word {
2378                        value: __wa::BStr::new(b"--"),
2379                        offset: __offset,
2380                        after_separator: false,
2381                    };
2382                    #positional_match
2383                }
2384            }
2385        }
2386    };
2387    let position = (!positionals.is_empty()).then(|| quote!(let mut __position: usize = 0;));
2388    let filled_slot = track_filled.then(|| quote!(let mut __filled = false;));
2389
2390    // A negative number is a word where the positional next to fill opts in,
2391    // unless it spells a declared digit short exactly (`-0` for fd's `--print0`).
2392    let opted: Vec<usize> = positionals
2393        .iter()
2394        .enumerate()
2395        .filter(|(_, f)| f.negative_numbers)
2396        .map(|(i, _)| i)
2397        .collect();
2398    let (unknown_slot, unknown_build) = match &unknown {
2399        Some((ident, ty)) => (
2400            quote!(let mut __unknown: ::std::vec::Vec<#ty> = ::std::vec::Vec::new();),
2401            quote!(#ident: __unknown,),
2402        ),
2403        None => (quote!(), quote!()),
2404    };
2405    let (sequence_slot, sequence_arg, sequence_build) = match &sequence {
2406        Some((ident, ty)) => (
2407            // Room for one item a word: a linker's line is mostly inputs.
2408            quote! {
2409                let mut __sequence: ::std::vec::Vec<#ty> =
2410                    ::std::vec::Vec::with_capacity(__input.words_left());
2411            },
2412            quote! {
2413                if let ::core::option::Option::Some(__item) =
2414                    <#ty as ::winnow_args::Occurrence>::from_arg(&__arg, __input)?
2415                {
2416                    __sequence.push(__item);
2417                } else
2418            },
2419            quote!(#ident: __sequence,),
2420        ),
2421        None => (quote!(), quote!(), quote!()),
2422    };
2423    // A word of several short flags (`-sS`), where the enum wants to know:
2424    // once its first letter is read, whoever took it, the `bundle` item goes
2425    // before the items of its letters.
2426    let bundle_start = sequence_ty.map(|_| {
2427        quote! {
2428            let __bundle_word = __input.front();
2429            let __bundle_start = __input.mode() == ::winnow_args::stream::Mode::Word;
2430            let __bundle_at = __sequence.len();
2431        }
2432    });
2433    let bundle_end = sequence_ty.map(|ty| {
2434        quote! {
2435            if <#ty as ::winnow_args::Occurrence>::BUNDLES
2436                && __bundle_start
2437                && matches!(__arg, __wa::Arg::Short(__flag) if !__flag.plus)
2438                && __input.mode() == ::winnow_args::stream::Mode::Bundle
2439            {
2440                let __word = __wa::Word {
2441                    value: __wa::BStr::new(__bundle_word),
2442                    offset: __arg.offset(),
2443                    after_separator: false,
2444                };
2445                if let ::core::option::Option::Some(__bundle) =
2446                    <#ty as ::winnow_args::Occurrence>::from_bundle(&__word)?
2447                {
2448                    __sequence.insert(__bundle_at, __bundle);
2449                }
2450            }
2451        }
2452    });
2453    // `#[arg(flatten)]` fields: their slots, the flags offered to them after
2454    // ours, and their values built at the end.
2455    let flat_slot = |ident: &Ident| format_ident!("__flat_{}", ident);
2456    let flatten_slots = flattens.iter().map(|(ident, ty)| {
2457        let slot = flat_slot(ident);
2458        quote! {
2459            let mut #slot = <<#ty as __wa::Flatten>::Slots as ::core::default::Default>::default();
2460        }
2461    });
2462    let flatten_arg: TokenStream2 = flattens
2463        .iter()
2464        .map(|(ident, ty)| {
2465            let slot = flat_slot(ident);
2466            quote!(if <#ty as __wa::Flatten>::bind(&mut #slot, &__arg, __input)? {} else)
2467        })
2468        .collect();
2469    let flatten_build = flattens.iter().map(|(ident, ty)| {
2470        let slot = flat_slot(ident);
2471        quote!(#ident: <#ty as __wa::Flatten>::finish(#slot, __input)?,)
2472    });
2473    let flatten_shorts: Vec<TokenStream2> = flattens
2474        .iter()
2475        .map(|(_, ty)| {
2476            quote! {
2477                __c if <#ty as __wa::Flatten>::short(__c).is_some() => {
2478                    <#ty as __wa::Flatten>::short(__c)
2479                }
2480            }
2481        })
2482        .collect();
2483    let flatten_longs: Vec<TokenStream2> = flattens
2484        .iter()
2485        .map(|(_, ty)| quote!(|| <#ty as __wa::Flatten>::is_long(__name)))
2486        .collect();
2487    // Whether each of our letters takes a value, as `short` answers.
2488    let own_shorts: Vec<TokenStream2> = fields
2489        .iter()
2490        .filter_map(|f| {
2491            let letters = f.short_pattern()?;
2492            let takes_value = f.takes_value();
2493            Some(quote!(#letters => ::core::option::Option::Some(#takes_value),))
2494        })
2495        .collect();
2496    // The long names one dash may spell under `long_only`, and the letters
2497    // that never start one.
2498    let own_longs: Vec<String> = fields
2499        .iter()
2500        .filter(|f| !f.two_dashes)
2501        .flat_map(|f| spellings(f).into_iter().map(str::to_owned))
2502        .collect();
2503    let prefixes: Vec<u8> = fields
2504        .iter()
2505        .filter(|f| f.prefix)
2506        .filter_map(|f| f.short().map(|c| c as u8))
2507        .collect();
2508    let flatten_prefixes = flattens.iter().map(|(_, ty)| {
2509        quote!(&& !__name.first().is_some_and(|__c| <#ty as __wa::Flatten>::is_prefix(*__c)))
2510    });
2511    let lexer = if plus_options {
2512        quote!(__wa::arg_plus)
2513    } else {
2514        quote!(__wa::arg)
2515    };
2516    if !plus_options {
2517        if let Some(f) = fields.iter().find(|f| f.plus.is_some()) {
2518            return Err(syn::Error::new(
2519                f.ident.span(),
2520                "`plus` needs `#[arg(plus_options)]` on the struct",
2521            ));
2522        }
2523    }
2524    let plus_match = plus_options.then(|| {
2525        quote! {
2526            __wa::Arg::Short(__flag) if __flag.plus => match __flag.letter {
2527                #(#plus_arms)*
2528                _ => #flatten_arg if !__globals.bind(&__arg, __input)? { #unexpected; }
2529            },
2530        }
2531    });
2532    let lex = if opted.is_empty() {
2533        quote!(#lexer(__input)?)
2534    } else {
2535        let digits: Vec<LitByteStr> = fields
2536            .iter()
2537            .filter_map(|f| f.short().filter(char::is_ascii_digit))
2538            .map(|c| LitByteStr::new(format!("-{c}").as_bytes(), Span::call_site()))
2539            .collect();
2540        let not_a_short =
2541            (!digits.is_empty()).then(|| quote!(&&!matches!(__input.front(), #(#digits)|*)));
2542        quote! {
2543            match matches!(__position, #(#opted)|*) #not_a_short {
2544                true => match __wa::number(__input) {
2545                    ::core::result::Result::Ok(__word) => __wa::Arg::Word(__word),
2546                    ::core::result::Result::Err(_) => #lexer(__input)?,
2547                },
2548                false => #lexer(__input)?,
2549            }
2550        }
2551    };
2552    let start = default_subcommand
2553        .is_some()
2554        .then(|| quote!(let __start = *__input;));
2555    let help_on_empty = arg_required_else_help.then(|| {
2556        quote! {
2557            if __input.is_empty() {
2558                return ::core::result::Result::Err(
2559                    __wa::Error::bare_help(<Self as ::winnow_args::Args>::HELP),
2560                );
2561            }
2562        }
2563    });
2564    if default_subcommand.is_some() && subcommand.is_none() {
2565        return Err(syn::Error::new(
2566            input.ident.span(),
2567            "`default_subcommand` needs an `#[arg(subcommand)]` field",
2568        ));
2569    }
2570    // `-h`/`--help` and `-V`/`--version`, unless declared or disabled: arms after
2571    // the struct's own, so a CLI naming its own `--help` keeps it.
2572    let declares_long = |name: &str| fields.iter().any(|f| f.longs().contains(&name));
2573    let declares_short = |c: char| fields.iter().any(|f| f.shorts().contains(&c));
2574    let help = quote!(<Self as ::winnow_args::Args>::HELP);
2575    let help_long = (!disable_help_flag && !declares_long("help")).then(
2576        || quote!(b"help" => return ::core::result::Result::Err(__wa::Error::help(#help, true)),),
2577    );
2578    let help_short = (!disable_help_flag && !disable_help_short && !declares_short('h')).then(
2579        || quote!('h' => return ::core::result::Result::Err(__wa::Error::help(#help, false)),),
2580    );
2581    let with_version = version.is_some() && !disable_version_flag;
2582    let version_long = (with_version && !declares_long("version")).then(
2583        || quote!(b"version" => return ::core::result::Result::Err(__wa::Error::version(#help)),),
2584    );
2585    let version_short = (with_version && !declares_short('V'))
2586        .then(|| quote!('V' => return ::core::result::Result::Err(__wa::Error::version(#help)),));
2587    // The row help prints is `--help`'s: a struct with its own has none.
2588    let help_flag = help_long.is_some();
2589    let help_short_flag = help_short.is_some();
2590    let version_short_flag = version_short.is_some();
2591    // A flattened struct or a sequence enum may declare `-h`, `--help`, `-V`
2592    // or `--version` itself (ld's `-h SONAME`): the built-in ones are then
2593    // tried after them, not as arms before.
2594    let late = !flattens.is_empty() || sequence.is_some();
2595    let late_if = |on: bool, test: TokenStream2, error: TokenStream2| {
2596        (late && on).then(|| quote!(if #test { return ::core::result::Result::Err(#error); } else))
2597    };
2598    let late_long: TokenStream2 = [
2599        late_if(
2600            help_long.is_some(),
2601            quote!(__flag.name == b"help"),
2602            quote!(__wa::Error::help(#help, true)),
2603        ),
2604        late_if(
2605            version_long.is_some(),
2606            quote!(__flag.name == b"version"),
2607            quote!(__wa::Error::version(#help)),
2608        ),
2609    ]
2610    .into_iter()
2611    .flatten()
2612    .collect();
2613    let late_short: TokenStream2 = [
2614        late_if(
2615            help_short.is_some(),
2616            quote!(__flag.letter == 'h'),
2617            quote!(__wa::Error::help(#help, false)),
2618        ),
2619        late_if(
2620            version_short.is_some(),
2621            quote!(__flag.letter == 'V'),
2622            quote!(__wa::Error::version(#help)),
2623        ),
2624    ]
2625    .into_iter()
2626    .flatten()
2627    .collect();
2628    let keep = |arm: Option<TokenStream2>| arm.filter(|_| !late);
2629    let (help_long, help_short) = (keep(help_long), keep(help_short));
2630    let (version_long, version_short) = (keep(version_long), keep(version_short));
2631
2632    // `long_only`: `-name` is `--name` for the long names that may take one
2633    // dash, tried before the word is read as short letters.
2634    // `unknown_flags = "value"`: a flag-like word naming no flag goes to the
2635    // positionals whole, never to a subcommand. A bundle of several letters is
2636    // checked before any binds; a long word or a single letter once no arm or
2637    // global took it.
2638    // An `unknown` field takes those words instead of the positionals.
2639    // Only the first letter of a word reaches the short arm's sink: the bundle
2640    // check vouched for the letters of a longer one.
2641    let (lenient_bundle, save_token, unknown_flag) = if unknown_flags_value
2642        || unknown.is_some()
2643        || sequence_unknown
2644    {
2645        // With nothing to take a word, `positional_match` always returns.
2646        let next = (!positionals.is_empty() || trailing.is_some()).then(|| quote!(continue;));
2647        let (sink, sink_next) = match (&unknown, sequence_ty.filter(|_| sequence_unknown)) {
2648            (_, Some(ty)) => (
2649                quote! {
2650                    match <#ty as ::winnow_args::Occurrence>::from_unknown(&__word)? {
2651                        ::core::option::Option::Some(__item) => __sequence.push(__item),
2652                        ::core::option::Option::None => {
2653                            return ::core::result::Result::Err(__wa::Error::unknown_flag(
2654                                __word.offset,
2655                                ::std::string::String::from_utf8_lossy(&__word.value),
2656                            ));
2657                        }
2658                    }
2659                },
2660                quote!(continue;),
2661            ),
2662            (Some((_, ty)), None) => (
2663                quote!(__unknown.push(__word.convert::<#ty>("<UNKNOWN>")?);),
2664                quote!(continue;),
2665            ),
2666            (None, None) => (
2667                quote! {
2668                    let __arg = __wa::Arg::Word(__word);
2669                    #positional_match
2670                },
2671                quote!(#next),
2672            ),
2673        };
2674        let theirs = match sequence_ty {
2675            Some(ty) => quote! {
2676                __c => match <#ty as ::winnow_args::Occurrence>::short(__c) {
2677                    ::core::option::Option::None => __globals.short(__c),
2678                    __known => __known,
2679                },
2680            },
2681            None => quote!(_ => __globals.short(__c),),
2682        };
2683        // Known letters whether their arms come first or late.
2684        let builtin = help_short_flag
2685            .then(|| quote!('h' => ::core::option::Option::Some(false),))
2686            .into_iter()
2687            .chain(version_short_flag.then(|| quote!('V' => ::core::option::Option::Some(false),)));
2688        (
2689            quote! {
2690                if let ::core::option::Option::Some(__word) = __wa::unknown_bundle(__input, |__c| match __c {
2691                    #(#own_shorts)*
2692                    #(#builtin)*
2693                    #(#flatten_shorts)*
2694                    #theirs
2695                }) {
2696                    #sink
2697                    #sink_next
2698                }
2699            },
2700            quote!(let __token = __input.front();),
2701            quote! {
2702                let __word = __wa::Word {
2703                    value: __wa::BStr::new(__token),
2704                    offset: __flag.offset,
2705                    after_separator: false,
2706                };
2707                #sink
2708            },
2709        )
2710    } else {
2711        (quote!(), quote!(), quote!(#unexpected;))
2712    };
2713    let mut names = Vec::new();
2714    if long_only {
2715        names.clone_from(&own_longs);
2716        names.extend(help_long.is_some().then(|| "help".to_owned()));
2717        names.extend(version_long.is_some().then(|| "version".to_owned()));
2718    }
2719    let long_only_lex =
2720        long_only && (!names.is_empty() || sequence_ty.is_some() || !flattens.is_empty());
2721    let lex = if long_only_lex {
2722        let own = matches_name(&names);
2723        let (their_names, their_prefixes) = match sequence_ty {
2724            Some(ty) => (
2725                quote!(|| <#ty as ::winnow_args::Occurrence>::is_long(__name)),
2726                quote! {
2727                    && !__name.first().is_some_and(|__c| {
2728                        <#ty as ::winnow_args::Occurrence>::PREFIXES.contains(__c)
2729                    })
2730                },
2731            ),
2732            None => (quote!(), quote!()),
2733        };
2734        let not_prefixed = (!prefixes.is_empty()).then(
2735            || quote!(!matches!(__name.first(), ::core::option::Option::Some(#(#prefixes)|*)) &&),
2736        );
2737        quote! {
2738            match __wa::long_only(__input, |__name| {
2739                #not_prefixed (#own #their_names #(#flatten_longs)*) #their_prefixes #(#flatten_prefixes)*
2740            }) {
2741                ::core::option::Option::Some(__arg) => __arg,
2742                ::core::option::Option::None => {
2743                    #lenient_bundle
2744                    #lex
2745                }
2746            }
2747        }
2748    } else {
2749        lex
2750    };
2751    let lenient_bundle = if long_only_lex {
2752        quote!()
2753    } else {
2754        lenient_bundle
2755    };
2756
2757    let help_version = match &version {
2758        Some(v) if with_version => quote!(::core::option::Option::Some(#v)),
2759        _ => quote!(::core::option::Option::None),
2760    };
2761    let help_items = fields
2762        .iter()
2763        .filter(|f| !matches!(f.role, Role::Subcommand))
2764        .map(|f| f.help_item());
2765    let (help_subcommands, subcommand_required) = match subcommand {
2766        Some(f) => {
2767            let ty = subcommand_type(f);
2768            (
2769                quote!(<#ty as ::winnow_args::Args>::HELP.subcommands),
2770                matches!(f.kind, Kind::Required(_)),
2771            )
2772        }
2773        None => (quote!(&[]), false),
2774    };
2775    let displaced = rules.displaced_slots(&fields);
2776    let env_fallbacks: Vec<TokenStream2> = fields
2777        .iter()
2778        .map(|f| f.fallback(true, rules.is_displaced(&fields, f)))
2779        .collect();
2780    let default_fallbacks: Vec<TokenStream2> = fields
2781        .iter()
2782        .map(|f| f.fallback(false, rules.is_displaced(&fields, f)))
2783        .collect();
2784    let exclusive = rules.exclusive(&fields, &groups);
2785    let supplied = rules.supplied(&fields);
2786    let default_ifs = rules.default_ifs(&fields);
2787    let required = rules.required(&fields, &groups);
2788    let build = fields.iter().map(|f| {
2789        let ident = &f.ident;
2790        let slot = slot(ident);
2791        match &f.kind {
2792            Kind::Required(ty) if f.keywords => {
2793                let display = LitStr::new(&f.display(), Span::call_site());
2794                quote!(#ident: __wa::keywords::<#ty>(&#slot, #display, __input.offset())?)
2795            }
2796            Kind::Switch if f.negate.is_some() => quote!(#ident: #slot.unwrap_or(false)),
2797            Kind::Switch | Kind::Count(_) | Kind::Optional(_) | Kind::Many(_) => {
2798                quote!(#ident: #slot)
2799            }
2800            Kind::Required(_) => {
2801                let display = LitStr::new(&f.display(), Span::call_site());
2802                let error = match f.role {
2803                    Role::Positional { .. } => {
2804                        quote!(__wa::Error::missing_argument(__input.offset(), #display))
2805                    }
2806                    Role::Flag { .. } => {
2807                        quote!(__wa::Error::missing_required(__input.offset(), #display))
2808                    }
2809                    Role::Subcommand => quote!(__wa::Error::missing_subcommand(__input.offset())),
2810                };
2811                quote! {
2812                    #ident: match #slot {
2813                        ::core::option::Option::Some(v) => v,
2814                        ::core::option::Option::None => {
2815                            return ::core::result::Result::Err(#error);
2816                        }
2817                    }
2818                }
2819            }
2820        }
2821    });
2822    let build: Vec<TokenStream2> = build.collect();
2823    // Every field read once after building, so a flag accepted and ignored on
2824    // purpose (`pwd -L`) is not dead code in the user's crate. No runtime work
2825    // after optimization.
2826    let field_reads: Vec<TokenStream2> = fields
2827        .iter()
2828        .map(|f| &f.ident)
2829        .chain(skipped.iter().copied())
2830        .chain(sequence.iter().chain(&unknown).map(|(ident, _)| ident))
2831        .chain(flattens.iter().map(|(ident, _)| ident))
2832        .map(|ident| quote!(let _ = &__built.#ident;))
2833        .collect();
2834    let flatten_build: Vec<TokenStream2> = flatten_build.collect();
2835    // Flags only: no positionals, subcommand, `sequence`, `unknown`,
2836    // keywords, `global` flags or generics.
2837    let flattenable = positionals.is_empty()
2838        && trailing.is_none()
2839        && subcommand.is_none()
2840        && sequence.is_none()
2841        && unknown.is_none()
2842        && !unknown_flags_value
2843        && input.generics.params.is_empty()
2844        && !fields.iter().any(|f| f.keywords || f.is_global());
2845    let flatten_impl = flattenable.then(|| {
2846        let vis = &input.vis;
2847        let slots_name = format_ident!("__{}WinnowArgsSlots", name);
2848        let displaced_idents: Vec<Ident> = fields
2849            .iter()
2850            .filter(|f| rules.is_displaced(&fields, f))
2851            .map(|f| displaced_flag(&f.ident))
2852            .collect();
2853        let nested: Vec<(Ident, &Type)> = flattens
2854            .iter()
2855            .map(|(ident, ty)| (flat_slot(ident), ty))
2856            .collect();
2857        let decls = slot_parts.iter().map(|(ident, ty, _)| quote!(#ident: #ty,));
2858        let inits = slot_parts
2859            .iter()
2860            .map(|(ident, _, init)| quote!(#ident: #init,));
2861        let all: Vec<&Ident> = slot_parts
2862            .iter()
2863            .map(|(ident, _, _)| ident)
2864            .chain(&displaced_idents)
2865            .chain(nested.iter().map(|(n, _)| n))
2866            .collect();
2867        let names: Vec<TokenStream2> = all.iter().map(|ident| quote!(#ident,)).collect();
2868        let mutable: Vec<TokenStream2> = all.iter().map(|ident| quote!(mut #ident,)).collect();
2869        let displaced_decls = displaced_idents.iter().map(|d| quote!(#d: bool,));
2870        let displaced_inits = displaced_idents.iter().map(|d| quote!(#d: false,));
2871        let nested_decls = nested
2872            .iter()
2873            .map(|(n, ty)| quote!(#n: <#ty as ::winnow_args::__private::Flatten>::Slots,));
2874        let nested_inits = nested
2875            .iter()
2876            .map(|(n, _)| quote!(#n: ::core::default::Default::default(),));
2877        let (bind_long, bind_short, bind_plus) = arms(&|_| true, quote!(true));
2878        let bind_nested: TokenStream2 = nested
2879            .iter()
2880            .map(|(n, ty)| {
2881                quote!(if <#ty as __wa::Flatten>::bind(&mut #n, &__arg, __input)? { true } else)
2882            })
2883            .collect();
2884        // A `+x` word is offered to the `+` arms only, here and below.
2885        let plus = if plus_options {
2886            quote! {
2887                __wa::Arg::Short(__flag) if __flag.plus => match __flag.letter {
2888                    #(#bind_plus)*
2889                    _ => #bind_nested { false }
2890                },
2891            }
2892        } else {
2893            quote!(__wa::Arg::Short(__flag) if __flag.plus => #bind_nested { false },)
2894        };
2895        let own_longs = matches_name(&own_longs);
2896        let own_prefixes = if prefixes.is_empty() {
2897            quote!(false)
2898        } else {
2899            quote!(matches!(__c, #(#prefixes)|*))
2900        };
2901        let nested_prefixes = nested
2902            .iter()
2903            .map(|(_, ty)| quote!(|| <#ty as __wa::Flatten>::is_prefix(__c)));
2904        quote! {
2905            #[doc(hidden)]
2906            #[allow(clippy::all, clippy::pedantic, clippy::nursery, clippy::restriction)]
2907            #vis struct #slots_name {
2908                #(#decls)*
2909                #(#displaced_decls)*
2910                #(#nested_decls)*
2911            }
2912
2913            #[automatically_derived]
2914            impl ::core::default::Default for #slots_name {
2915                fn default() -> Self {
2916                    Self {
2917                        #(#inits)*
2918                        #(#displaced_inits)*
2919                        #(#nested_inits)*
2920                    }
2921                }
2922            }
2923
2924            #[automatically_derived]
2925            #[allow(
2926                unused_mut,
2927                unused_variables,
2928                unreachable_code,
2929                clippy::all,
2930                clippy::pedantic,
2931                clippy::nursery,
2932                clippy::restriction
2933            )]
2934            impl ::winnow_args::__private::Flatten for #name {
2935                type Slots = #slots_name;
2936
2937                fn bind<'__i>(
2938                    __slots: &mut #slots_name,
2939                    __arg: &::winnow_args::Arg<'__i>,
2940                    __input: &mut ::winnow_args::Argv<'__i>,
2941                ) -> ::core::result::Result<bool, ::winnow_args::Error> {
2942                    use ::winnow_args::__private as __wa;
2943                    let __arg = *__arg;
2944                    let #slots_name { #(#mutable)* } = ::core::mem::take(__slots);
2945                    let __bound = match __arg {
2946                        __wa::Arg::Long(__flag) => match __flag.name {
2947                            #(#bind_long)*
2948                            _ => #bind_nested { false }
2949                        },
2950                        #plus
2951                        __wa::Arg::Short(__flag) => match __flag.letter {
2952                            #(#bind_short)*
2953                            _ => #bind_nested { false }
2954                        },
2955                        _ => false,
2956                    };
2957                    *__slots = #slots_name { #(#names)* };
2958                    ::core::result::Result::Ok(__bound)
2959                }
2960
2961                fn short(__c: char) -> ::core::option::Option<bool> {
2962                    use ::winnow_args::__private as __wa;
2963                    match __c {
2964                        #(#own_shorts)*
2965                        #(#flatten_shorts)*
2966                        _ => ::core::option::Option::None,
2967                    }
2968                }
2969
2970                fn is_prefix(__c: u8) -> bool {
2971                    use ::winnow_args::__private as __wa;
2972                    #own_prefixes #(#nested_prefixes)*
2973                }
2974
2975                fn is_long(__name: &[u8]) -> bool {
2976                    use ::winnow_args::__private as __wa;
2977                    #own_longs #(#flatten_longs)*
2978                }
2979
2980                fn finish(
2981                    __slots: #slots_name,
2982                    __input: &::winnow_args::Argv<'_>,
2983                ) -> ::core::result::Result<Self, ::winnow_args::Error> {
2984                    use ::winnow_args::__private as __wa;
2985                    let #slots_name { #(#mutable)* } = __slots;
2986                    #(#env_fallbacks)*
2987                    #exclusive
2988                    #supplied
2989                    #default_ifs
2990                    #(#default_fallbacks)*
2991                    #required
2992                    let __built = Self {
2993                        #(#build,)*
2994                        #(#skipped: ::core::default::Default::default(),)*
2995                        #(#flatten_build)*
2996                    };
2997                    #(#field_reads)*
2998                    ::core::result::Result::Ok(__built)
2999                }
3000            }
3001        }
3002    });
3003    let own_items = quote!(&[#(#help_items),*]);
3004    // A sequence enum with a positional variant takes every word.
3005    let positional_clash = sequence_ty
3006        .filter(|_| !positionals.is_empty() || trailing.is_some())
3007        .map(|ty| {
3008            quote! {
3009                const _: () = assert!(
3010                    !<#ty as ::winnow_args::Occurrence>::POSITIONAL,
3011                    "the sequence enum has a positional variant, which takes every word: this struct's positionals would never be filled",
3012                );
3013            }
3014        });
3015    // Ours, then the flattened structs', then the sequence enum's.
3016    let more: Vec<TokenStream2> = flattens
3017        .iter()
3018        .map(|(_, ty)| quote!(<#ty as ::winnow_args::Args>::HELP.items))
3019        .chain(
3020            sequence_ty
3021                .iter()
3022                .map(|ty| quote!(<#ty as ::winnow_args::Occurrence>::ITEMS)),
3023        )
3024        .collect();
3025    let items = if more.is_empty() {
3026        own_items
3027    } else {
3028        quote!({
3029            const __OWN: &[::winnow_args::help::Item] = #own_items;
3030            const _: () = assert!(
3031                !::winnow_args::help::items_clash(&[__OWN, #(#more),*]),
3032                "a flattened struct or the sequence enum declares a flag that this struct, or another of them, also declares",
3033            );
3034            #positional_clash
3035            const __N: usize = __OWN.len() #(+ #more.len())*;
3036            const __ALL: [::winnow_args::help::Item; __N] = ::winnow_args::__private::concat_items::<__N>(
3037                &[__OWN, #(#more),*],
3038            );
3039            &__ALL
3040        })
3041    };
3042
3043    Ok(quote! {
3044        impl #impl_generics ::winnow_args::Args for #name #ty_generics #where_clause {
3045            const HELP: &'static ::winnow_args::help::Command = &::winnow_args::help::Command {
3046                name: #program_name,
3047                about: #about,
3048                long_about: #long_about,
3049                after_help: #after_help,
3050                after_long_help: #after_long_help,
3051                items: #items,
3052                subcommands: #help_subcommands,
3053                subcommand_required: #subcommand_required,
3054                help_flag: #help_flag,
3055                help_short: #help_short_flag,
3056                version: #help_version,
3057            };
3058
3059            fn parse_argv_with(
3060                __input: &mut ::winnow_args::Argv<'_>,
3061                __globals: &mut dyn ::winnow_args::Globals,
3062            ) -> ::core::result::Result<Self, ::winnow_args::Error> {
3063                use ::winnow_args::__private as __wa;
3064                #(#slots)*
3065                #sequence_slot
3066                #unknown_slot
3067                #(#flatten_slots)*
3068                #(#displaced)*
3069                #position
3070                #filled_slot
3071                #help_on_empty
3072                while !__input.is_empty() {
3073                    #start
3074                    #lenient_bundle
3075                    #save_token
3076                    #bundle_start
3077                    let __arg = #lex;
3078                    match __arg {
3079                        __wa::Arg::Long(__flag) => match __flag.name {
3080                            #(#long_arms)*
3081                            #help_long
3082                            #version_long
3083                            _ => #sequence_arg #flatten_arg #late_long if !__globals.bind(&__arg, __input)? { #unknown_flag },
3084                        },
3085                        #plus_match
3086                        __wa::Arg::Short(__flag) => match __flag.letter {
3087                            #(#short_arms)*
3088                            #help_short
3089                            #version_short
3090                            _ => #sequence_arg #flatten_arg #late_short if !__globals.bind(&__arg, __input)? { #unknown_flag },
3091                        },
3092                        #separator_arm
3093                        #word_arm
3094                    }
3095                    #bundle_end
3096                }
3097                #(#env_fallbacks)*
3098                #exclusive
3099                #supplied
3100                #default_ifs
3101                #(#default_fallbacks)*
3102                #required
3103                let __built = Self {
3104                    #(#build,)*
3105                    #(#skipped: ::core::default::Default::default(),)*
3106                    #sequence_build
3107                    #unknown_build
3108                    #(#flatten_build)*
3109                };
3110                #(#field_reads)*
3111                ::core::result::Result::Ok(__built)
3112            }
3113        }
3114
3115        #flatten_impl
3116    })
3117}
3118
3119/// A struct-level `group("name", required, multiple)`.
3120struct Group {
3121    name: String,
3122    required: bool,
3123    multiple: bool,
3124}
3125
3126/// `#[arg(...)]` on the struct: options declared there rather than on a field.
3127#[derive(Default)]
3128struct StructOptions {
3129    groups: Vec<Group>,
3130    /// The word that starts a new invocation of this command.
3131    restart_token: Option<String>,
3132    /// The subcommand a word naming none selects.
3133    default_subcommand: Option<String>,
3134    /// A bare invocation asks for help.
3135    arg_required_else_help: bool,
3136    /// The program name for the usage line; `argv[0]` when empty.
3137    name: String,
3138    /// `-V`/`--version` text: a literal, or `CARGO_PKG_VERSION`.
3139    version: Option<TokenStream2>,
3140    /// Help text around the lists; `about`/`long_about` default to the doc comment.
3141    about: Option<String>,
3142    long_about: Option<String>,
3143    after_help: String,
3144    after_long_help: String,
3145    disable_help_flag: bool,
3146    /// Help is `--help` alone, `-h` left undeclared (bash's builtins).
3147    disable_help_short: bool,
3148    disable_version_flag: bool,
3149    disable_help_subcommand: bool,
3150    /// An unknown flag-like word is a positional value.
3151    unknown_flags_value: bool,
3152    /// GNU's `getopt_long_only`: a long name may be spelled with one dash.
3153    long_only: bool,
3154    /// `+abc` is a bundle of `+` options (`set +eu`), for fields with `plus`.
3155    plus_options: bool,
3156}
3157
3158fn struct_options(input: &DeriveInput) -> syn::Result<StructOptions> {
3159    let mut options = StructOptions::default();
3160    for attr in input.attrs.iter().filter(|a| is_ours(a)) {
3161        attr.parse_nested_meta(|meta| {
3162            if meta.path.is_ident("restart_token") {
3163                options.restart_token = Some(meta.value()?.parse::<LitStr>()?.value());
3164                return Ok(());
3165            }
3166            if meta.path.is_ident("default_subcommand") {
3167                options.default_subcommand = Some(meta.value()?.parse::<LitStr>()?.value());
3168                return Ok(());
3169            }
3170            if meta.path.is_ident("arg_required_else_help") {
3171                options.arg_required_else_help = true;
3172                return Ok(());
3173            }
3174            if meta.path.is_ident("long_only") {
3175                options.long_only = true;
3176                return Ok(());
3177            }
3178            if meta.path.is_ident("plus_options") {
3179                options.plus_options = true;
3180                return Ok(());
3181            }
3182            if meta.path.is_ident("unknown_flags") {
3183                let mode = meta.value()?.parse::<LitStr>()?;
3184                options.unknown_flags_value = match mode.value().as_str() {
3185                    "value" => true,
3186                    "error" => false,
3187                    _ => {
3188                        return Err(syn::Error::new(
3189                            mode.span(),
3190                            "expected \"value\" or \"error\"",
3191                        ));
3192                    }
3193                };
3194                return Ok(());
3195            }
3196            let text = |meta: &syn::meta::ParseNestedMeta<'_>| -> syn::Result<String> {
3197                Ok(meta.value()?.parse::<LitStr>()?.value())
3198            };
3199            if meta.path.is_ident("name") {
3200                options.name = text(&meta)?;
3201                return Ok(());
3202            }
3203            if meta.path.is_ident("version") {
3204                options.version = Some(if meta.input.peek(syn::Token![=]) {
3205                    let version = meta.value()?.parse::<LitStr>()?;
3206                    quote!(#version)
3207                } else {
3208                    quote!(::core::env!("CARGO_PKG_VERSION"))
3209                });
3210                return Ok(());
3211            }
3212            for (key, slot) in [
3213                ("about", &mut options.about),
3214                ("long_about", &mut options.long_about),
3215            ] {
3216                if meta.path.is_ident(key) {
3217                    *slot = Some(text(&meta)?);
3218                    return Ok(());
3219                }
3220            }
3221            for (key, slot) in [
3222                ("after_help", &mut options.after_help),
3223                ("after_long_help", &mut options.after_long_help),
3224            ] {
3225                if meta.path.is_ident(key) {
3226                    *slot = text(&meta)?;
3227                    return Ok(());
3228                }
3229            }
3230            for (key, slot) in [
3231                ("disable_help_flag", &mut options.disable_help_flag),
3232                ("disable_help_short", &mut options.disable_help_short),
3233                ("disable_version_flag", &mut options.disable_version_flag),
3234                (
3235                    "disable_help_subcommand",
3236                    &mut options.disable_help_subcommand,
3237                ),
3238            ] {
3239                if meta.path.is_ident(key) {
3240                    *slot = true;
3241                    return Ok(());
3242                }
3243            }
3244            if !meta.path.is_ident("group") {
3245                return Err(meta.error(
3246                    "expected `group(\"name\", ...)`, `restart_token`, `default_subcommand`, \
3247                     `arg_required_else_help`, `unknown_flags`, `long_only`, `plus_options`, `name`, `version`, `about`, \
3248                     `long_about`, `after_help`, `after_long_help` or a `disable_*` option",
3249                ));
3250            }
3251            let content;
3252            syn::parenthesized!(content in meta.input);
3253            let name = content.parse::<LitStr>()?.value();
3254            let (mut required, mut multiple) = (false, false);
3255            while !content.is_empty() {
3256                content.parse::<syn::Token![,]>()?;
3257                let flag = content.parse::<Ident>()?;
3258                match flag.to_string().as_str() {
3259                    "required" => required = true,
3260                    "multiple" => multiple = true,
3261                    _ => {
3262                        return Err(syn::Error::new(
3263                            flag.span(),
3264                            "expected `required` or `multiple`",
3265                        ));
3266                    }
3267                }
3268            }
3269            options.groups.push(Group {
3270                name,
3271                required,
3272                multiple,
3273            });
3274            Ok(())
3275        })?;
3276    }
3277    Ok(options)
3278}
3279
3280/// The relations between fields, resolved to field indices.
3281struct Rules {
3282    /// Pairs `(a, b)` that may not both be supplied; each unordered pair once.
3283    conflicts: Vec<(usize, usize)>,
3284    /// For each field, the fields it displaces when bound (symmetric).
3285    beats: Vec<Vec<usize>>,
3286    /// `(field, target)`: `field` supplied needs `target` to have a value.
3287    requires: Vec<(usize, usize)>,
3288    /// `(field, others)`: `field` needs a value unless one of `others` has one.
3289    required_unless: Vec<(usize, Vec<usize>)>,
3290    /// Members of each struct-level group, in `groups` order.
3291    members: Vec<Vec<usize>>,
3292    /// `(field, other, the value `other` must have, the field's default then)`.
3293    default_ifs: Vec<(usize, usize, Option<String>, String)>,
3294}
3295
3296impl Rules {
3297    fn new(fields: &[Field], groups: &[Group]) -> syn::Result<Self> {
3298        let resolve = |from: &Field, selector: &str| -> syn::Result<usize> {
3299            fields
3300                .iter()
3301                .position(
3302                    |f| match (selector.strip_prefix("--"), selector.strip_prefix('-')) {
3303                        (Some(long), _) => f.longs().contains(&long),
3304                        (None, Some(short)) => {
3305                            let mut chars = short.chars();
3306                            chars
3307                                .next()
3308                                .is_some_and(|c| chars.next().is_none() && f.shorts().contains(&c))
3309                        }
3310                        (None, None) => {
3311                            matches!(&f.role, Role::Positional { name, .. } if name == selector)
3312                                || f.ident == selector
3313                        }
3314                    },
3315                )
3316                .ok_or_else(|| {
3317                    // A rule is about a flag having a value, whichever way it was spelled.
3318                    let negated = selector
3319                        .strip_prefix("--")
3320                        .and_then(|long| fields.iter().find(|f| f.negate.as_deref() == Some(long)));
3321                    let message = match negated {
3322                        Some(f) => format!(
3323                            "`{selector}` is the negation of `{}`: name that flag instead",
3324                            f.display()
3325                        ),
3326                        None => format!("`{selector}` names no flag or positional of this struct"),
3327                    };
3328                    syn::Error::new(from.ident.span(), message)
3329                })
3330        };
3331        let mut rules = Rules {
3332            conflicts: Vec::new(),
3333            beats: vec![Vec::new(); fields.len()],
3334            requires: Vec::new(),
3335            required_unless: Vec::new(),
3336            members: vec![Vec::new(); groups.len()],
3337            default_ifs: Vec::new(),
3338        };
3339        for (i, f) in fields.iter().enumerate() {
3340            for selector in &f.conflicts {
3341                let j = resolve(f, selector)?;
3342                let pair = (i.min(j), i.max(j));
3343                if !rules.conflicts.contains(&pair) {
3344                    rules.conflicts.push(pair);
3345                }
3346            }
3347            for selector in &f.overrides {
3348                let j = resolve(f, selector)?;
3349                if !rules.beats[i].contains(&j) {
3350                    rules.beats[i].push(j);
3351                }
3352                if !rules.beats[j].contains(&i) {
3353                    rules.beats[j].push(i);
3354                }
3355            }
3356            for selector in &f.requires {
3357                rules.requires.push((i, resolve(f, selector)?));
3358            }
3359            for (selector, equals, value) in &f.default_ifs {
3360                let other = resolve(f, selector)?;
3361                let comparable = match &fields[other].kind {
3362                    Kind::Count(_) => false,
3363                    Kind::Switch => equals.as_deref().is_none_or(is_bool_word),
3364                    _ => !fields[other].keywords,
3365                };
3366                if equals.is_some() && !comparable {
3367                    return Err(syn::Error::new(
3368                        f.ident.span(),
3369                        format!(
3370                            "`{selector}` has no value to compare: use `default_if(\"{selector}\", \"…\")`"
3371                        ),
3372                    ));
3373                }
3374                rules
3375                    .default_ifs
3376                    .push((i, other, equals.clone(), value.clone()));
3377            }
3378            if !f.required_unless.is_empty() {
3379                let others = f
3380                    .required_unless
3381                    .iter()
3382                    .map(|selector| resolve(f, selector))
3383                    .collect::<syn::Result<_>>()?;
3384                rules.required_unless.push((i, others));
3385            }
3386            if let Some(group) = &f.group {
3387                let g = groups
3388                    .iter()
3389                    .position(|g| &g.name == group)
3390                    .ok_or_else(|| {
3391                        syn::Error::new(
3392                            f.ident.span(),
3393                            format!("no `#[arg(group(\"{group}\"))]` on the struct"),
3394                        )
3395                    })?;
3396                rules.members[g].push(i);
3397            }
3398        }
3399        if let Some((g, _)) = groups
3400            .iter()
3401            .zip(&rules.members)
3402            .find(|(g, members)| g.required && members.is_empty())
3403        {
3404            return Err(syn::Error::new(
3405                Span::call_site(),
3406                format!("group `{}` is required, but no field is in it", g.name),
3407            ));
3408        }
3409        Ok(rules)
3410    }
3411
3412    /// Whether an `overrides` can unset `f`, so its fallbacks must check.
3413    fn is_displaced(&self, fields: &[Field], f: &Field) -> bool {
3414        !self.beats[index_of(fields, f)].is_empty()
3415    }
3416
3417    /// `let mut __displaced_x = false;` for every field an `overrides` can unset.
3418    fn displaced_slots(&self, fields: &[Field]) -> Vec<TokenStream2> {
3419        fields
3420            .iter()
3421            .enumerate()
3422            .filter(|(i, _)| !self.beats[*i].is_empty())
3423            .map(|(_, f)| {
3424                let displaced = displaced_flag(&f.ident);
3425                quote!(let mut #displaced = false;)
3426            })
3427            .collect()
3428    }
3429
3430    /// After binding `f`: unset what it beats, and mark `f` as standing.
3431    fn displace(&self, fields: &[Field], f: &Field) -> TokenStream2 {
3432        let i = index_of(fields, f);
3433        if self.beats[i].is_empty() {
3434            return quote!();
3435        }
3436        let standing = displaced_flag(&f.ident);
3437        let clears = self.beats[i].iter().map(|&j| {
3438            let other = &fields[j];
3439            let slot = slot(&other.ident);
3440            let displaced = displaced_flag(&other.ident);
3441            let clear = match &other.kind {
3442                _ if other.keywords => quote!(#slot.clear();),
3443                Kind::Switch if other.negate.is_some() => {
3444                    quote!(#slot = ::core::option::Option::None;)
3445                }
3446                Kind::Switch => quote!(#slot = false;),
3447                Kind::Count(_) => quote!(#slot = 0;),
3448                Kind::Optional(_) | Kind::Required(_) => {
3449                    quote!(#slot = ::core::option::Option::None;)
3450                }
3451                Kind::Many(_) => quote!(#slot.clear();),
3452            };
3453            quote!(#clear #displaced = true;)
3454        });
3455        quote!(#standing = false; #(#clears)*)
3456    }
3457
3458    /// Conflicts and at-most-one groups, judged on what was supplied.
3459    fn exclusive(&self, fields: &[Field], groups: &[Group]) -> TokenStream2 {
3460        let pairs = self.conflicts.iter().map(|&(a, b)| {
3461            let (fa, fb) = (&fields[a], &fields[b]);
3462            let (has_a, has_b) = (fa.has(), fb.has());
3463            let (name_a, name_b) = (fa.display(), fb.display());
3464            quote! {
3465                if #has_a && #has_b {
3466                    return ::core::result::Result::Err(
3467                        __wa::Error::conflict(__input.offset(), #name_a, #name_b),
3468                    );
3469                }
3470            }
3471        });
3472        let at_most_one = groups.iter().zip(&self.members).filter(|(g, _)| !g.multiple).map(|(_, members)| {
3473            let checks = members.iter().map(|&m| {
3474                let has = fields[m].has();
3475                let name = fields[m].display();
3476                quote! {
3477                    if #has {
3478                        if let ::core::option::Option::Some(__first) = __first {
3479                            return ::core::result::Result::Err(
3480                                __wa::Error::conflict(__input.offset(), __first, #name),
3481                            );
3482                        }
3483                        __first = ::core::option::Option::Some(#name);
3484                    }
3485                }
3486            });
3487            quote! {{
3488                let mut __first: ::core::option::Option<&'static str> = ::core::option::Option::None;
3489                #(#checks)*
3490            }}
3491        });
3492        quote!(#(#pairs)* #(#at_most_one)*)
3493    }
3494
3495    /// `default_if`: each condition judged on what was supplied, then the first
3496    /// that holds for a field still without a value gives it one.
3497    fn default_ifs(&self, fields: &[Field]) -> TokenStream2 {
3498        let (mut conditions, mut fills) = (Vec::new(), Vec::new());
3499        for (n, (field, other, equals, value)) in self.default_ifs.iter().enumerate() {
3500            let (f, other) = (&fields[*field], &fields[*other]);
3501            let holds = format_ident!("__default_if_{n}");
3502            let other_slot = slot(&other.ident);
3503            let is = |ty: &Type, equals: &str| {
3504                let literal = LitByteStr::new(equals.as_bytes(), Span::call_site());
3505                quote! {
3506                    |__v| <#ty as ::winnow_args::FromArg>::from_arg(__wa::BStr::new(#literal))
3507                        .is_ok_and(|__literal| __literal == *__v)
3508                }
3509            };
3510            let condition = match (equals, &other.kind) {
3511                (None, _) => other.has(),
3512                (Some(equals), Kind::Switch) => {
3513                    let equals = equals == "true";
3514                    match other.negate {
3515                        Some(_) => quote!((#other_slot == ::core::option::Option::Some(#equals))),
3516                        None => quote!((#other_slot == #equals)),
3517                    }
3518                }
3519                (Some(equals), Kind::Optional(ty) | Kind::Required(ty)) => {
3520                    let is = is(ty, equals);
3521                    quote!(#other_slot.as_ref().is_some_and(#is))
3522                }
3523                (Some(equals), Kind::Many(ty)) => {
3524                    let is = is(ty, equals);
3525                    quote!(#other_slot.iter().any(#is))
3526                }
3527                (Some(_), Kind::Count(_)) => unreachable!("refused when the rules were read"),
3528            };
3529            conditions.push(quote!(let #holds = #condition;));
3530            let unset = f.unset(self.is_displaced(fields, f));
3531            let slot = slot(&f.ident);
3532            let fill = match &f.kind {
3533                Kind::Switch => {
3534                    let value = value == "true";
3535                    match f.negate {
3536                        Some(_) => quote!(#slot = ::core::option::Option::Some(#value);),
3537                        None => quote!(#slot = #value;),
3538                    }
3539                }
3540                _ => {
3541                    let bytes = LitByteStr::new(value.as_bytes(), Span::call_site());
3542                    f.assign(quote!(__wa::BStr::new(#bytes)), &f.display())
3543                }
3544            };
3545            fills.push(quote!(if #holds && #unset { #fill }));
3546        }
3547        quote!(#(#conditions)* #(#fills)*)
3548    }
3549
3550    /// Whether each `requires`-declaring field was supplied, before defaults fill it.
3551    fn supplied(&self, fields: &[Field]) -> TokenStream2 {
3552        let mut seen = Vec::new();
3553        let snapshots = self.requires.iter().filter_map(|&(i, _)| {
3554            if seen.contains(&i) {
3555                return None;
3556            }
3557            seen.push(i);
3558            let supplied = format_ident!("__supplied_{}", fields[i].ident);
3559            let has = fields[i].has();
3560            Some(quote!(let #supplied = #has;))
3561        });
3562        quote!(#(#snapshots)*)
3563    }
3564
3565    /// `required`, `required_unless`, required groups and `requires`, judged on
3566    /// what has a value, defaults included.
3567    fn required(&self, fields: &[Field], groups: &[Group]) -> TokenStream2 {
3568        let missing = |f: &Field| {
3569            let name = f.display();
3570            match f.role {
3571                Role::Positional { .. } => {
3572                    quote!(__wa::Error::missing_argument(__input.offset(), #name))
3573                }
3574                _ => quote!(__wa::Error::missing_required(__input.offset(), #name)),
3575            }
3576        };
3577        // A flag another one overrode was given its answer: it is not missing.
3578        let displaced = |f: &Field| {
3579            self.is_displaced(fields, f).then(|| {
3580                let flag = displaced_flag(&f.ident);
3581                quote!(&& !#flag)
3582            })
3583        };
3584        let plain = fields.iter().filter(|f| f.required).map(|f| {
3585            let (has, displaced) = (f.has(), displaced(f));
3586            let error = missing(f);
3587            quote!(if !#has #displaced { return ::core::result::Result::Err(#error); })
3588        });
3589        let unless = self.required_unless.iter().map(|(i, others)| {
3590            let (has, displaced) = (fields[*i].has(), displaced(&fields[*i]));
3591            let others = others.iter().map(|&j| fields[j].has());
3592            let error = missing(&fields[*i]);
3593            quote! {
3594                if !#has #displaced #(&& !#others)* {
3595                    return ::core::result::Result::Err(#error);
3596                }
3597            }
3598        });
3599        let one_of = groups
3600            .iter()
3601            .zip(&self.members)
3602            .filter(|(g, _)| g.required)
3603            .map(|(g, members)| {
3604                let name = &g.name;
3605                let has = members.iter().map(|&m| fields[m].has());
3606                let names = members.iter().map(|&m| fields[m].display());
3607                quote! {
3608                    if #(!#has)&&* {
3609                        return ::core::result::Result::Err(
3610                            __wa::Error::missing_one_of(__input.offset(), #name, &[#(#names),*]),
3611                        );
3612                    }
3613                }
3614            });
3615        let requires = self.requires.iter().map(|&(i, j)| {
3616            let supplied = format_ident!("__supplied_{}", fields[i].ident);
3617            let has = fields[j].has();
3618            let (by, target) = (fields[i].display(), fields[j].display());
3619            quote! {
3620                if #supplied && !#has {
3621                    return ::core::result::Result::Err(
3622                        __wa::Error::required_by(__input.offset(), #target, #by),
3623                    );
3624                }
3625            }
3626        });
3627        quote!(#(#plain)* #(#unless)* #(#one_of)* #(#requires)*)
3628    }
3629}
3630
3631/// A `Vec<T>` field's name and `T`; `shape` is the error for any other type.
3632fn vec_field(f: Option<&syn::Field>, shape: &str) -> syn::Result<Option<(Ident, Type)>> {
3633    let Some(f) = f else {
3634        return Ok(None);
3635    };
3636    let ty = last_segment(&f.ty)
3637        .filter(|s| s.ident == "Vec")
3638        .and_then(inner)
3639        .ok_or_else(|| syn::Error::new(f.ty.span(), shape))?;
3640    Ok(Some((f.ident.clone().expect("named field"), ty.clone())))
3641}
3642
3643/// The enum of a subcommand field.
3644fn subcommand_type(f: &Field) -> &Type {
3645    match &f.kind {
3646        Kind::Optional(ty) | Kind::Required(ty) => ty,
3647        _ => unreachable!("rejected for subcommands in `field`"),
3648    }
3649}
3650
3651fn displaced_flag(ident: &Ident) -> Ident {
3652    format_ident!("__displaced_{}", ident)
3653}
3654
3655fn index_of(fields: &[Field], f: &Field) -> usize {
3656    fields
3657        .iter()
3658        .position(|g| g.ident == f.ident)
3659        .expect("a field of this struct")
3660}
3661
3662fn slot(ident: &Ident) -> Ident {
3663    format_ident!("__slot_{}", ident)
3664}
3665
3666fn field(f: &syn::Field) -> syn::Result<Field> {
3667    let ident = f.ident.clone().expect("named field");
3668    let bare = ident.to_string().trim_start_matches("r#").to_owned();
3669
3670    let mut short = None;
3671    let mut long = None;
3672    let mut positional = false;
3673    let mut count = false;
3674    let mut subcommand = false;
3675    let mut global = false;
3676    let mut alias = Vec::new();
3677    let mut delimiter = None;
3678    let mut choices = None;
3679    let mut env = None;
3680    let mut default = None;
3681    let mut double_dash = None;
3682    let mut default_missing = None;
3683    let mut negative_numbers = false;
3684    let mut hyphen_values = false;
3685    let mut require_equals = false;
3686    let mut keep_equals = false;
3687    let (mut two_dashes, mut prefix) = (false, false);
3688    let mut values = 1;
3689    let (mut values_max, mut value_terminator) = (None, None);
3690    let (mut default_fn, mut default_note, mut default_ifs) = (None, None, Vec::new());
3691    let mut stop_flags = false;
3692    let mut plus: Option<char> = None;
3693    let mut keywords = false;
3694    let mut short_aliases = Vec::new();
3695    let mut negate: Option<Option<String>> = None;
3696    let (doc_help, doc_long_help) = docs(&f.attrs);
3697    let (mut help, mut long_help, mut heading, mut hide) = (None, None, None, false);
3698    let (mut conflicts, mut overrides, mut requires) = (Vec::new(), Vec::new(), Vec::new());
3699    let (mut required, mut required_unless, mut group) = (false, Vec::new(), None);
3700    let mut value_name = None;
3701    for attr in f.attrs.iter().filter(|a| is_ours(a)) {
3702        attr.parse_nested_meta(|meta| {
3703            if meta.path.is_ident("short") {
3704                let c = if meta.input.peek(syn::Token![=]) {
3705                    meta.value()?.parse::<LitChar>()?.value()
3706                } else {
3707                    bare.chars()
3708                        .next()
3709                        .ok_or_else(|| meta.error("cannot infer a short name"))?
3710                };
3711                // A second `short` is another letter, as `long` is another name.
3712                match short {
3713                    None => short = Some(c),
3714                    Some(_) => short_aliases.push(c),
3715                }
3716            } else if meta.path.is_ident("long") {
3717                let name = if meta.input.peek(syn::Token![=]) {
3718                    meta.value()?.parse::<LitStr>()?.value()
3719                } else {
3720                    bare.replace('_', "-")
3721                };
3722                // A second `long` is another spelling.
3723                match long {
3724                    None => long = Some(name),
3725                    Some(_) => alias.push(name),
3726                }
3727            } else if meta.path.is_ident("positional") {
3728                positional = true;
3729            } else if meta.path.is_ident("count") {
3730                count = true;
3731            } else if meta.path.is_ident("subcommand") {
3732                subcommand = true;
3733            } else if meta.path.is_ident("global") {
3734                global = true;
3735            } else if meta.path.is_ident("alias") {
3736                alias.extend(aliases(&meta)?);
3737            } else if meta.path.is_ident("help") {
3738                help = Some(meta.value()?.parse::<LitStr>()?.value());
3739            } else if meta.path.is_ident("long_help") {
3740                long_help = Some(meta.value()?.parse::<LitStr>()?.value());
3741            } else if meta.path.is_ident("help_heading") {
3742                heading = Some(meta.value()?.parse::<LitStr>()?.value());
3743            } else if meta.path.is_ident("hide") {
3744                hide = true;
3745            } else if meta.path.is_ident("negate") {
3746                negate = Some(if meta.input.peek(syn::Token![=]) {
3747                    let name = meta.value()?.parse::<LitStr>()?.value();
3748                    // A leading `--` is not part of the name.
3749                    Some(name.strip_prefix("--").unwrap_or(&name).to_owned())
3750                } else {
3751                    None
3752                });
3753            } else if meta.path.is_ident("keywords") {
3754                keywords = true;
3755            } else if meta.path.is_ident("plus") {
3756                plus = Some(meta.value()?.parse::<LitChar>()?.value());
3757            } else if meta.path.is_ident("stop_flags") {
3758                stop_flags = true;
3759            } else if meta.path.is_ident("values") {
3760                let count = meta.value()?.parse::<syn::Expr>()?;
3761                (values, values_max) = value_count(&count)?;
3762            } else if meta.path.is_ident("value_terminator") {
3763                value_terminator = Some(meta.value()?.parse::<LitStr>()?.value());
3764            } else if meta.path.is_ident("default_fn") {
3765                default_fn = Some(meta.value()?.parse::<syn::ExprPath>()?);
3766            } else if meta.path.is_ident("default_note") {
3767                default_note = Some(meta.value()?.parse::<LitStr>()?.value());
3768            } else if meta.path.is_ident("default_if") {
3769                let content;
3770                syn::parenthesized!(content in meta.input);
3771                let list =
3772                    syn::punctuated::Punctuated::<LitStr, syn::Token![,]>::parse_terminated(&content)?;
3773                let mut words = list.iter().map(LitStr::value);
3774                default_ifs.push(match (words.next(), words.next(), words.next(), words.next()) {
3775                    (Some(selector), Some(value), None, _) => (selector, None, value),
3776                    (Some(selector), Some(equals), Some(value), None) => {
3777                        (selector, Some(equals), value)
3778                    }
3779                    _ => {
3780                        return Err(meta.error(
3781                            "expected `default_if(\"--flag\", \"value\")` or \
3782                             `default_if(\"--flag\", \"its value\", \"value\")`",
3783                        ));
3784                    }
3785                });
3786            } else if meta.path.is_ident("two_dashes") {
3787                two_dashes = true;
3788            } else if meta.path.is_ident("prefix") {
3789                prefix = true;
3790            } else if meta.path.is_ident("require_equals") {
3791                require_equals = true;
3792            } else if meta.path.is_ident("keep_equals") {
3793                keep_equals = true;
3794            } else if meta.path.is_ident("allow_hyphen_values") {
3795                hyphen_values = true;
3796            } else if meta.path.is_ident("allow_negative_numbers") {
3797                negative_numbers = true;
3798            } else if meta.path.is_ident("default_missing") {
3799                default_missing = Some(meta.value()?.parse::<LitStr>()?.value());
3800            } else if meta.path.is_ident("double_dash") {
3801                let mode = meta.value()?.parse::<LitStr>()?;
3802                double_dash = Some(match mode.value().as_str() {
3803                    "required" => DoubleDash::Required,
3804                    "automatic" => DoubleDash::Automatic,
3805                    "optional" => DoubleDash::Optional,
3806                    "preserve" => DoubleDash::Preserve,
3807                    _ => {
3808                        return Err(syn::Error::new(
3809                            mode.span(),
3810                            "expected \"required\", \"automatic\", \"optional\" or \"preserve\"",
3811                        ));
3812                    }
3813                });
3814            } else if meta.path.is_ident("conflicts") {
3815                conflicts.extend(aliases(&meta)?);
3816            } else if meta.path.is_ident("overrides") {
3817                overrides.extend(aliases(&meta)?);
3818            } else if meta.path.is_ident("requires") {
3819                requires.extend(aliases(&meta)?);
3820            } else if meta.path.is_ident("required_unless") {
3821                required_unless.extend(aliases(&meta)?);
3822            } else if meta.path.is_ident("required") {
3823                required = true;
3824            } else if meta.path.is_ident("group") {
3825                group = Some(meta.value()?.parse::<LitStr>()?.value());
3826            } else if meta.path.is_ident("env") {
3827                env = Some(meta.value()?.parse::<LitStr>()?.value());
3828            } else if meta.path.is_ident("default") {
3829                default = Some(meta.value()?.parse::<LitStr>()?.value());
3830            } else if meta.path.is_ident("choices") {
3831                let content;
3832                syn::parenthesized!(content in meta.input);
3833                let list =
3834                    syn::punctuated::Punctuated::<LitStr, syn::Token![,]>::parse_terminated(&content)?;
3835                choices = Some(list.iter().map(LitStr::value).collect());
3836            } else if meta.path.is_ident("delimiter") {
3837                let c = meta.value()?.parse::<LitChar>()?;
3838                if !c.value().is_ascii() {
3839                    return Err(syn::Error::new(c.span(), "a delimiter is one ASCII character"));
3840                }
3841                delimiter = Some(c.value() as u8);
3842            } else if meta.path.is_ident("value_name") {
3843                value_name = Some(meta.value()?.parse::<LitStr>()?.value());
3844            } else {
3845                return Err(meta.error(
3846                    "unknown `arg` option; expected one of `short`, `long`, `alias`, `global`, `count`, \
3847                     `positional`, `value_name`, `double_dash`, `subcommand`, `flatten`, `sequence`, `unknown`, `skip`, \
3848                     `delimiter`, `choices`, `env`, `default`, `default_missing`, `allow_negative_numbers`, \
3849                     `allow_hyphen_values`, `require_equals`, `keep_equals`, `negate`, `two_dashes`, `prefix`, `values`, \
3850                     `value_terminator`, `default_fn`, `default_note`, `default_if`, \
3851                     `stop_flags`, `plus`, `keywords`, `conflicts`, `overrides`, `requires`, `required`, `required_unless`, \
3852                     `group`, `help`, `long_help`, `help_heading`, `hide`",
3853                ));
3854            }
3855            Ok(())
3856        })?;
3857    }
3858
3859    let error = |msg: String| Err(syn::Error::new(f.span(), msg));
3860    if delimiter.is_some() && (count || !matches!(kind(&f.ty), Kind::Many(_))) {
3861        return error("`delimiter` needs a `Vec<T>` field to put the pieces in".into());
3862    }
3863    let kind = if count {
3864        Kind::Count(f.ty.clone())
3865    } else {
3866        kind(&f.ty)
3867    };
3868    if default_missing.is_some()
3869        && (positional || subcommand || matches!(kind, Kind::Switch | Kind::Count(_)))
3870    {
3871        return error("`default_missing` is for flags that take a value".into());
3872    }
3873    if (hyphen_values || require_equals)
3874        && (positional || subcommand || matches!(kind, Kind::Switch | Kind::Count(_)))
3875    {
3876        return error(
3877            "`allow_hyphen_values` and `require_equals` are for flags that take a value".into(),
3878        );
3879    }
3880    if negative_numbers && (subcommand || matches!(kind, Kind::Switch | Kind::Count(_))) {
3881        return error("`allow_negative_numbers` is for fields that take a value".into());
3882    }
3883    let tristate =
3884        plus.is_some() && plus == short && matches!(&kind, Kind::Optional(ty) if is_bool(ty));
3885    if plus.is_some()
3886        && (positional || subcommand || !tristate && matches!(kind, Kind::Switch | Kind::Count(_)))
3887    {
3888        return error(
3889            "`plus` is for a flag taking a value, or an `Option<bool>` with the same `short` letter".into(),
3890        );
3891    }
3892    if keywords
3893        && (positional
3894            || subcommand
3895            || !matches!(kind, Kind::Required(_))
3896            || env.is_some()
3897            || default.is_some()
3898            || default_missing.is_some()
3899            || choices.is_some()
3900            || delimiter.is_some())
3901    {
3902        return error(
3903            "`keywords` is for a flag whose type derives `Args`, without `env`, `default`, \
3904             `default_missing`, `choices` or `delimiter`"
3905                .into(),
3906        );
3907    }
3908    if stop_flags && !positional {
3909        return error("`stop_flags` is for positional fields".into());
3910    }
3911    if double_dash.is_some() && !positional {
3912        return error("`double_dash` is for positional fields".into());
3913    }
3914    if (values > 1 || values_max.is_some())
3915        && (positional
3916            || subcommand
3917            || !matches!(kind, Kind::Many(_))
3918            || delimiter.is_some()
3919            || default_missing.is_some())
3920    {
3921        return error(
3922            "`values` is for a `Vec<T>` flag, without `delimiter` or `default_missing`".into(),
3923        );
3924    }
3925    if values_max.is_some() && require_equals {
3926        return error("a range of `values` takes detached words: not with `require_equals`".into());
3927    }
3928    if value_terminator.is_some() && values_max.is_none() {
3929        return error("`value_terminator` ends a range of `values` (`values = 1..`)".into());
3930    }
3931    let valueless = matches!(kind, Kind::Switch | Kind::Count(_)) && negate.is_none();
3932    if default_fn.is_some() && (default.is_some() || subcommand || keywords || valueless) {
3933        return error(
3934            "`default_fn` is for a field that takes a value, or `negate`, without `default`".into(),
3935        );
3936    }
3937    if default_note.is_some() && default_fn.is_none() {
3938        return error("`default_note` describes a `default_fn`".into());
3939    }
3940    if !default_ifs.is_empty() && (subcommand || keywords || matches!(kind, Kind::Count(_))) {
3941        return error("`default_if` is for a switch or a field that takes a value".into());
3942    }
3943    if let (Kind::Switch, Some((.., value))) =
3944        (&kind, default_ifs.iter().find(|d| !is_bool_word(&d.2)))
3945    {
3946        return error(format!(
3947            "a switch's `default_if` value is `true` or `false`, not `{value}`"
3948        ));
3949    }
3950    if two_dashes && (long.is_none() && alias.is_empty() || positional || subcommand) {
3951        return error("`two_dashes` is for flags with a long name".into());
3952    }
3953    if prefix
3954        && (short.is_none_or(|c| !c.is_ascii())
3955            || positional
3956            || subcommand
3957            || matches!(kind, Kind::Switch | Kind::Count(_)))
3958    {
3959        return error("`prefix` is for a value-taking flag with an ASCII `short` letter".into());
3960    }
3961    let flag_value_name =
3962        (!positional && !subcommand && !matches!(kind, Kind::Switch | Kind::Count(_)))
3963            .then(|| value_name.clone().unwrap_or_else(|| bare.to_uppercase()));
3964    let role = if subcommand {
3965        if positional || count || global || short.is_some() || long.is_some() || !alias.is_empty() {
3966            return error("a subcommand field takes no other `arg` options".into());
3967        }
3968        if !matches!(kind, Kind::Optional(_) | Kind::Required(_)) {
3969            return error("a subcommand field is `E` or `Option<E>`".into());
3970        }
3971        Role::Subcommand
3972    } else if positional {
3973        if short.is_some() || long.is_some() || global || !alias.is_empty() {
3974            return error("a positional field has no `short`, `long`, `alias` or `global`".into());
3975        }
3976        if matches!(kind, Kind::Switch | Kind::Count(_)) {
3977            return error("a positional field cannot be `bool` or `count`".into());
3978        }
3979        Role::Positional {
3980            double_dash: double_dash.unwrap_or(DoubleDash::Optional),
3981            name: value_name.unwrap_or_else(|| bare.to_uppercase()),
3982        }
3983    } else {
3984        // Like bpaf: a flag with no names is `--field-name` (unless it is
3985        // spelled only `+c`).
3986        if short.is_none() && long.is_none() && plus.is_none() {
3987            long = Some(bare.replace('_', "-"));
3988        }
3989        for l in long.iter().chain(&alias) {
3990            if l.is_empty() || l.starts_with('-') || l.contains('=') {
3991                return error(format!("`{l}` is not a usable long name"));
3992            }
3993        }
3994        if let Some(c) = short {
3995            if c == '-' || c == '=' {
3996                return error(format!("`{c}` is not a usable short name"));
3997            }
3998        }
3999        Role::Flag {
4000            short,
4001            long,
4002            aliases: alias,
4003            global,
4004        }
4005    };
4006
4007    if choices.is_some()
4008        && (matches!(kind, Kind::Switch | Kind::Count(_)) || matches!(role, Role::Subcommand))
4009    {
4010        return error("`choices` needs a field that takes a value".into());
4011    }
4012    if (env.is_some() || default.is_some()) && matches!(role, Role::Subcommand) {
4013        return error("a subcommand field takes no `env` or `default`".into());
4014    }
4015    let negate = match negate {
4016        None => None,
4017        Some(_) if !matches!(kind, Kind::Switch) || !matches!(role, Role::Flag { .. }) => {
4018            return error("`negate` is for `bool` flags".into());
4019        }
4020        Some(Some(name)) => Some(name),
4021        Some(None) => match &role {
4022            Role::Flag { long: Some(l), .. } => Some(format!("no-{l}")),
4023            _ => return error("`negate` without a name needs a `long` to prefix".into()),
4024        },
4025    };
4026    if let Some(no) = &negate {
4027        if no.is_empty() || no.starts_with('-') || no.contains('=') {
4028            return error(format!("`{no}` is not a usable long name"));
4029        }
4030    }
4031    match (&default, &negate) {
4032        (Some(d), Some(_)) if d != "true" && d != "false" => {
4033            return error("a negatable flag's `default` is \"true\" or \"false\"".into());
4034        }
4035        (Some(_), None) if matches!(kind, Kind::Switch | Kind::Count(_)) => {
4036            return error("`default` needs a field that takes a value, or `negate`".into());
4037        }
4038        _ => {}
4039    }
4040    Ok(Field {
4041        ident,
4042        kind,
4043        role,
4044        delimiter,
4045        choices,
4046        env,
4047        default,
4048        conflicts,
4049        overrides,
4050        requires,
4051        required,
4052        required_unless,
4053        group,
4054        default_missing,
4055        long_help: long_help.or_else(|| help.clone()).unwrap_or(doc_long_help),
4056        help: help.unwrap_or(doc_help),
4057        heading,
4058        hide,
4059        value_name: if tristate { None } else { flag_value_name },
4060        negative_numbers,
4061        hyphen_values,
4062        require_equals,
4063        keep_equals,
4064        negate,
4065        two_dashes,
4066        prefix,
4067        values,
4068        values_max,
4069        value_terminator,
4070        default_fn,
4071        default_note,
4072        default_ifs,
4073        stop_flags,
4074        plus,
4075        tristate,
4076        keywords,
4077        short_aliases,
4078    })
4079}
4080
4081fn is_bool_word(word: &str) -> bool {
4082    matches!(word, "true" | "false")
4083}
4084
4085/// `values = 3`, `values = 1..`, `values = 1..=3`: the least, and for a range
4086/// the most (`usize::MAX` when open).
4087fn value_count(count: &syn::Expr) -> syn::Result<(usize, Option<usize>)> {
4088    let number = |e: &syn::Expr| match e {
4089        syn::Expr::Lit(syn::ExprLit {
4090            lit: syn::Lit::Int(n),
4091            ..
4092        }) => n.base10_parse::<usize>(),
4093        _ => Err(syn::Error::new(e.span(), "expected a number")),
4094    };
4095    let (min, max) = match count {
4096        syn::Expr::Range(range) => {
4097            let min = range.start.as_deref().map(number).transpose()?.unwrap_or(1);
4098            let max = match (&range.end, &range.limits) {
4099                (None, _) => usize::MAX,
4100                (Some(end), syn::RangeLimits::Closed(_)) => number(end)?,
4101                (Some(end), syn::RangeLimits::HalfOpen(_)) => number(end)?.saturating_sub(1),
4102            };
4103            (min, Some(max))
4104        }
4105        exact => (number(exact)?, None),
4106    };
4107    if min == 0 || max.is_some_and(|max| max < min) {
4108        return Err(syn::Error::new(
4109            count.span(),
4110            "`values` is at least 1, and a range does not end below its start",
4111        ));
4112    }
4113    Ok((min, max))
4114}
4115
4116/// Every long spelling of `f`, its negation included.
4117fn spellings(f: &Field) -> Vec<&str> {
4118    let mut longs = f.longs();
4119    longs.extend(f.negate.as_deref());
4120    longs
4121}
4122
4123fn check_duplicates(fields: &[Field]) -> syn::Result<()> {
4124    for (i, a) in fields.iter().enumerate() {
4125        for b in &fields[..i] {
4126            let theirs = b.shorts();
4127            let clash = a
4128                .shorts()
4129                .into_iter()
4130                .find(|c| theirs.contains(c))
4131                .map(|c| format!("-{c}"))
4132                .or_else(|| {
4133                    let longs = spellings(b);
4134                    spellings(a)
4135                        .into_iter()
4136                        .find(|l| longs.contains(l))
4137                        .map(|l| format!("--{l}"))
4138                });
4139            if let Some(name) = clash {
4140                return Err(syn::Error::new(
4141                    a.ident.span(),
4142                    format!("`{name}` is already used by `{}`", b.ident),
4143                ));
4144            }
4145        }
4146    }
4147    Ok(())
4148}
4149
4150/// Required positionals come first, then optional ones, then at most one `Vec`,
4151/// last: otherwise which word fills which field would depend on lookahead.
4152fn check_positional_order(positionals: &[&Field]) -> syn::Result<()> {
4153    let rank = |f: &Field| match f.kind {
4154        Kind::Required(_) => 0,
4155        Kind::Optional(_) => 1,
4156        Kind::Many(_) | Kind::Switch | Kind::Count(_) => 2,
4157    };
4158    for pair in positionals.windows(2) {
4159        if rank(pair[1]) < rank(pair[0]) || rank(pair[0]) == 2 {
4160            return Err(syn::Error::new(
4161                pair[1].ident.span(),
4162                "positionals must be required, then optional, then one `Vec` last",
4163            ));
4164        }
4165    }
4166    Ok(())
4167}
4168
4169fn last_segment(ty: &Type) -> Option<&syn::PathSegment> {
4170    match ty {
4171        Type::Path(path) if path.qself.is_none() => path.path.segments.last(),
4172        _ => None,
4173    }
4174}
4175
4176fn inner(segment: &syn::PathSegment) -> Option<&Type> {
4177    match &segment.arguments {
4178        PathArguments::AngleBracketed(args) => match args.args.first()? {
4179            GenericArgument::Type(inner) => Some(inner),
4180            _ => None,
4181        },
4182        _ => None,
4183    }
4184}
4185
4186fn kind(ty: &Type) -> Kind {
4187    if is_bool(ty) {
4188        return Kind::Switch;
4189    }
4190    if let Some(last) = last_segment(ty) {
4191        if let Some(inner) = inner(last) {
4192            if last.ident == "Option" {
4193                return Kind::Optional(inner.clone());
4194            }
4195            if last.ident == "Vec" {
4196                return Kind::Many(inner.clone());
4197            }
4198        }
4199    }
4200    Kind::Required(ty.clone())
4201}