1use proc_macro::TokenStream;
53use proc_macro2::{Span, TokenStream as TokenStream2};
54use quote::{format_ident, quote};
55use syn::{
56 Data, DeriveInput, Fields, GenericArgument, Ident, LitByteStr, LitChar, LitStr, PathArguments,
57 Type, ext::IdentExt as _, parse_macro_input, spanned::Spanned as _,
58};
59
60mod spec;
61
62#[proc_macro_derive(Args, attributes(arg, winnow_args))]
226pub fn derive_args(input: TokenStream) -> TokenStream {
227 let input = parse_macro_input!(input as DeriveInput);
228 finish(expand(&input))
229}
230
231fn finish(expanded: syn::Result<TokenStream2>) -> TokenStream {
234 let expanded = expanded.unwrap_or_else(syn::Error::into_compile_error);
235 quote! {
236 #expanded
237 const _: ::core::option::Option<&str> = ::core::option_env!("WINNOW_ARGS_SPEC");
238 }
239 .into()
240}
241
242#[proc_macro_derive(Subcommand, attributes(arg, winnow_args))]
296pub fn derive_subcommand(input: TokenStream) -> TokenStream {
297 let input = parse_macro_input!(input as DeriveInput);
298 finish(expand_subcommand(&input))
299}
300
301fn expand_subcommand(input: &DeriveInput) -> syn::Result<TokenStream2> {
303 let Data::Enum(data) = &input.data else {
304 return Err(syn::Error::new(
305 input.span(),
306 "`Subcommand` can only be derived for enums",
307 ));
308 };
309 let case = rename_all(input)?;
310 let mut names: Vec<(String, &Ident)> = Vec::new();
311 let mut arms = Vec::new();
312 let mut patterns = Vec::new();
313 let mut subs = Vec::new();
314 let spec_on = spec::enabled();
315 let filed = filed(input)?;
316 let identity = spec::identity(input.ident.span(), &filed);
317 let mut spec_variants = Vec::new();
318 let mut spec_checks = Vec::new();
319 for variant in &data.variants {
320 let ident = &variant.ident;
321 let info = variant_names(variant, &mut names, case)?;
322 let pattern = byte_patterns(&info.names);
323 let primary = LitStr::new(&info.names[0], Span::call_site());
324 let inner = match &variant.fields {
325 Fields::Unnamed(fields) if fields.unnamed.len() == 1 => {
326 let ty = &fields.unnamed[0].ty;
327 Some(boxed(ty).unwrap_or(ty).clone())
328 }
329 _ => None,
330 };
331 let unit_long_about = inner.is_none().then(|| {
333 let (doc, long_doc) = docs(&variant.attrs);
334 if doc == info.about {
335 long_doc
336 } else {
337 info.about.clone()
338 }
339 });
340 let command = match (&inner, &unit_long_about) {
341 (Some(ty), _) => quote!(<#ty as ::winnow_args::Args>::HELP),
342 (None, long_about) => {
343 let long_about = text(long_about.as_deref().unwrap_or_default());
344 let about = text(&info.about);
345 quote!(&::winnow_args::help::Command {
346 name: "",
347 about: #about,
348 long_about: #long_about,
349 after_help: "",
350 after_long_help: "",
351 items: &[],
352 subcommands: &[],
353 subcommand_required: false,
354 help_flag: true,
355 help_short: true,
356 version: ::core::option::Option::None,
357 })
358 }
359 };
360 let about = match (&inner, info.about.is_empty()) {
361 (Some(ty), true) => quote!(<#ty as ::winnow_args::Args>::HELP.about),
362 _ => text(&info.about),
363 };
364 let shown = &info.names[1..=info.shown];
365 let all = &info.names;
366 let hide = info.hide;
367 if spec_on {
368 let ty = match &inner {
369 Some(ty) => {
370 let name = spec_type(spec_name(&variant.attrs)?, ty)?;
371 spec_checks.extend(spec_filed(&input.generics, quote!(Args), ty, &name));
372 Some(name)
373 }
374 None => None,
375 };
376 spec_variants.push(spec::VariantDoc {
377 name: info.names[0].clone(),
378 aliases: shown.to_vec(),
379 about: info.about.clone(),
380 long_about: unit_long_about.unwrap_or_default(),
381 hide: info.hide,
382 ty,
383 });
384 }
385 subs.push(quote! {
386 ::winnow_args::help::Sub {
387 name: #primary,
388 aliases: &[#(#shown),*],
389 names: &[#(#all),*],
390 command: #command,
391 about: #about,
392 hide: #hide,
393 }
394 });
395 let read_payload = quote! {
397 |__item| match __item {
398 Self::#ident(__read) => {
399 let _ = __read;
400 }
401 #[allow(unreachable_patterns)]
402 _ => {}
403 }
404 };
405 let parse = match &variant.fields {
406 Fields::Unit => {
407 let index = subs.len() - 1;
408 quote! {
409 __wa::finish_with(
410 __input,
411 __globals,
412 <Self as ::winnow_args::Args>::HELP.subcommands[#index].command,
413 )
414 .map(|()| Self::#ident)
415 }
416 }
417 Fields::Unnamed(fields) if fields.unnamed.len() == 1 => {
418 let ty = &fields.unnamed[0].ty;
419 match boxed(ty) {
420 Some(inner) => quote! {
421 <#inner as ::winnow_args::Args>::parse_argv_with(__input, __globals)
422 .map(|v| Self::#ident(::std::boxed::Box::new(v)))
423 .inspect(#read_payload)
424 },
425 None => quote! {
426 <#ty as ::winnow_args::Args>::parse_argv_with(__input, __globals)
427 .map(Self::#ident)
428 .inspect(#read_payload)
429 },
430 }
431 }
432 _ => {
433 return Err(syn::Error::new(
434 variant.span(),
435 "a subcommand variant is a unit or holds one `Args` type",
436 ));
437 }
438 };
439 arms.push(quote!(#pattern => (#parse).map_err(|e| e.within(#primary)),));
440 patterns.push(pattern);
441 }
442 let (about_text, long_about_text) = docs(&input.attrs);
443 let (about, long_about) = (text(&about_text), text(&long_about_text));
444 if spec_on {
445 spec::write_subcommands(
446 input.ident.span(),
447 &filed,
448 &about_text,
449 &long_about_text,
450 &spec_variants,
451 )?;
452 }
453
454 let name = &input.ident;
455 let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
456 Ok(quote! {
457 #(#spec_checks)*
458 impl #impl_generics ::winnow_args::Subcommand for #name #ty_generics #where_clause {
459 const SPEC: &'static str = #identity;
460
461 #[inline]
462 fn has(__name: &[u8]) -> bool {
463 matches!(__name, #(#patterns)|*)
464 }
465
466 fn parse_subcommand(
467 __name: &[u8],
468 __input: &mut ::winnow_args::Argv<'_>,
469 __globals: &mut dyn ::winnow_args::Globals,
470 ) -> ::core::result::Result<Self, ::winnow_args::Error> {
471 use ::winnow_args::__private as __wa;
472 match __name {
473 #(#arms)*
474 _ => ::core::result::Result::Err(__wa::Error::unexpected_arg(
475 __input.offset(),
476 ::std::string::String::from_utf8_lossy(__name),
477 )),
478 }
479 }
480 }
481
482 impl #impl_generics ::winnow_args::Args for #name #ty_generics #where_clause {
483 const SPEC: &'static str = #identity;
484
485 #[inline(never)]
488 fn help_data() -> &'static ::winnow_args::help::Command {
489 <Self as ::winnow_args::Args>::HELP
490 }
491
492 const HELP: &'static ::winnow_args::help::Command = &::winnow_args::help::Command {
493 name: "",
494 about: #about,
495 long_about: #long_about,
496 after_help: "",
497 after_long_help: "",
498 items: &[],
499 subcommands: &[#(#subs),*],
500 subcommand_required: true,
501 help_flag: true,
502 help_short: true,
503 version: ::core::option::Option::None,
504 };
505
506 fn parse_argv_with(
507 __input: &mut ::winnow_args::Argv<'_>,
508 __globals: &mut dyn ::winnow_args::Globals,
509 ) -> ::core::result::Result<Self, ::winnow_args::Error> {
510 use ::winnow_args::__private as __wa;
511 let __help = <Self as ::winnow_args::Args>::HELP;
512 loop {
514 if __input.is_empty() {
515 return ::core::result::Result::Err(
516 __wa::Error::missing_subcommand(__input.offset()),
517 );
518 }
519 let __arg = __wa::arg(__input)?;
520 match __arg {
521 __wa::Arg::Word(__word) => {
522 if <Self as __wa::Subcommand>::has(&**__word.value) {
523 return <Self as __wa::Subcommand>::parse_subcommand(
524 &**__word.value, __input, __globals,
525 );
526 }
527 if &**__word.value == b"help" {
528 return ::core::result::Result::Err(__wa::help_word(__help, __input));
529 }
530 }
531 __wa::Arg::Long(_) | __wa::Arg::Short(_)
532 if __globals.bind(&__arg, __input)? =>
533 {
534 continue;
535 }
536 __wa::Arg::Long(__flag) if __flag.name == b"help" => {
537 return ::core::result::Result::Err(__wa::Error::help(__help, true));
538 }
539 __wa::Arg::Short(__flag) if __flag.letter == 'h' => {
540 return ::core::result::Result::Err(__wa::Error::help(__help, false));
541 }
542 _ => {}
543 }
544 return ::core::result::Result::Err(__arg.unexpected());
545 }
546 }
547 }
548 })
549}
550
551#[proc_macro_derive(Occurrence, attributes(arg, winnow_args))]
617pub fn derive_occurrence(input: TokenStream) -> TokenStream {
618 let input = parse_macro_input!(input as DeriveInput);
619 finish(expand_occurrence(&input))
620}
621
622#[proc_macro_derive(ValueEnum, attributes(arg, winnow_args))]
657pub fn derive_value_enum(input: TokenStream) -> TokenStream {
658 let input = parse_macro_input!(input as DeriveInput);
659 finish(expand_value_enum(&input))
660}
661
662fn expand_occurrence(input: &DeriveInput) -> syn::Result<TokenStream2> {
665 let Data::Enum(data) = &input.data else {
666 return Err(syn::Error::new(
667 input.span(),
668 "`Occurrence` can only be derived for enums",
669 ));
670 };
671 let (mut long_arms, mut short_arms) = (Vec::new(), Vec::new());
672 let (mut single_dash, mut prefixes) = (Vec::new(), Vec::new());
673 let mut letters = Vec::new();
674 let (mut word, mut unknown, mut bundle) = (None, None, None);
675 let mut seen = std::collections::HashMap::new();
676 let mut help_items = Vec::new();
678 let spec_on = spec::enabled();
679 let filed = filed(input)?;
680 let identity = spec::identity(input.ident.span(), &filed);
681 let mut spec_rows = Vec::new();
682 let mut spec_checks = Vec::new();
683 let (mut enum_hyphen_values, mut enum_keep_equals) = (false, false);
686 for attr in input.attrs.iter().filter(|a| is_ours(a)) {
687 attr.parse_nested_meta(|meta| {
688 if meta.path.is_ident("allow_hyphen_values") {
689 enum_hyphen_values = true;
690 Ok(())
691 } else if meta.path.is_ident("keep_equals") {
692 enum_keep_equals = true;
693 Ok(())
694 } else if skip_spec(&meta)? {
695 Ok(())
696 } else {
697 Err(meta.error("expected `allow_hyphen_values`, `keep_equals` or `spec`"))
698 }
699 })?;
700 }
701 for variant in &data.variants {
702 let ident = &variant.ident;
703 let (mut short, mut longs, mut positional) = (None, Vec::new(), false);
704 let (mut two_dashes, mut prefix, mut value_name) = (false, false, None);
705 let (mut hyphen_values, mut negative_numbers) = (enum_hyphen_values, false);
706 let (mut require_equals, mut default_missing) = (false, None);
707 let mut keep_equals = enum_keep_equals;
708 let (mut skip, mut is_unknown, mut is_bundle) = (false, false, false);
709 let stated = spec_name(&variant.attrs)?;
710 for attr in variant.attrs.iter().filter(|a| is_ours(a)) {
711 attr.parse_nested_meta(|meta| {
712 if meta.path.is_ident("allow_hyphen_values") {
713 hyphen_values = true;
714 } else if meta.path.is_ident("allow_negative_numbers") {
715 negative_numbers = true;
716 } else if meta.path.is_ident("require_equals") {
717 require_equals = true;
718 } else if meta.path.is_ident("keep_equals") {
719 keep_equals = true;
720 } else if meta.path.is_ident("default_missing") {
721 default_missing = Some(meta.value()?.parse::<LitStr>()?.value());
722 } else if meta.path.is_ident("skip") {
723 skip = true;
724 } else if meta.path.is_ident("bundle") {
725 is_bundle = true;
726 } else if meta.path.is_ident("unknown") {
727 is_unknown = true;
728 } else if meta.path.is_ident("short") {
729 if short.is_some() {
730 return Err(meta.error("an `Occurrence` variant has one `short`"));
731 }
732 short = Some(meta.value()?.parse::<LitChar>()?.value());
733 } else if meta.path.is_ident("long") {
734 longs.push(if meta.input.peek(syn::Token![=]) {
735 meta.value()?.parse::<LitStr>()?.value()
736 } else {
737 kebab_case(&ident.to_string())
738 });
739 } else if meta.path.is_ident("alias") {
740 longs.extend(aliases(&meta)?);
741 } else if meta.path.is_ident("positional") {
742 positional = true;
743 } else if meta.path.is_ident("two_dashes") {
744 two_dashes = true;
745 } else if meta.path.is_ident("prefix") {
746 prefix = true;
747 } else if meta.path.is_ident("value_name") {
748 value_name = Some(meta.value()?.parse::<LitStr>()?.value());
749 } else if skip_spec(&meta)? {
750 } else {
751 return Err(meta.error(
752 "expected `short`, `long`, `alias`, `positional`, `two_dashes`, `prefix`, \
753 `value_name`, `allow_hyphen_values`, `allow_negative_numbers`, \
754 `require_equals`, `keep_equals`, `default_missing`, `skip`, `unknown` or `bundle`",
755 ));
756 }
757 Ok(())
758 })?;
759 }
760 let takes = match &variant.fields {
761 Fields::Unit => None,
762 Fields::Unnamed(f) if f.unnamed.len() == 1 => Some(&f.unnamed[0].ty),
763 _ => {
764 return Err(syn::Error::new(
765 variant.span(),
766 "an `Occurrence` variant is a unit (a switch) or holds one value",
767 ));
768 }
769 };
770 if skip {
771 continue;
773 }
774 let spanned = takes.and_then(|ty| {
776 last_segment(ty)
777 .filter(|s| s.ident == "Spanned")
778 .and_then(inner)
779 });
780 let optional = takes.and_then(|ty| {
783 last_segment(ty)
784 .filter(|s| s.ident == "Option")
785 .and_then(inner)
786 });
787 let wrap = |value: TokenStream2, offset: TokenStream2, attached: TokenStream2| {
788 if spanned.is_some() {
789 quote! {
790 ::winnow_args::value::Spanned {
791 value: #value,
792 offset: #offset,
793 attached: #attached,
794 }
795 }
796 } else {
797 value
798 }
799 };
800 if positional || is_unknown || is_bundle {
801 let Some(ty) = takes else {
802 return Err(syn::Error::new(
803 variant.span(),
804 "a positional, `unknown` or `bundle` variant holds its value",
805 ));
806 };
807 let ty = spanned.unwrap_or(ty);
808 let slot = if positional {
809 &mut word
810 } else if is_unknown {
811 &mut unknown
812 } else {
813 &mut bundle
814 };
815 if slot.is_some() {
816 return Err(syn::Error::new(
817 variant.span(),
818 "at most one positional, one `unknown` and one `bundle` variant",
819 ));
820 }
821 let name = value_name.unwrap_or_else(|| ident.to_string().to_uppercase());
822 if positional {
823 help_items.push(occurrence_item(
824 variant,
825 None,
826 &[],
827 Some(&name),
828 Some(ty),
829 false,
830 false,
831 ));
832 if spec_on {
833 spec_checks.extend(spec_check(&input.generics, Some(ty), stated.as_ref()));
834 spec_rows.push(occurrence_spec(
835 variant,
836 spec::ItemDoc {
837 value_name: Some(name.clone()),
838 choices_ty: choices_name(stated.as_ref(), ty),
839 ..spec::ItemDoc::default()
840 },
841 ));
842 }
843 }
844 let value = wrap(
845 quote!(__word.convert::<#ty>(#name)?),
846 quote!(__word.offset),
847 quote!(false),
848 );
849 *slot = Some(quote!({
850 let __item = Self::#ident(#value);
851 match &__item {
853 Self::#ident(__read) => {
854 let _ = __read;
855 }
856 #[allow(unreachable_patterns)]
857 _ => {}
858 }
859 ::core::result::Result::Ok(::core::option::Option::Some(__item))
860 }));
861 continue;
862 }
863 if short.is_none() && longs.is_empty() {
864 longs.push(kebab_case(&ident.to_string()));
865 }
866 let spelled = short
867 .map(|c| format!("-{c}"))
868 .into_iter()
869 .chain(longs.iter().map(|l| format!("--{l}")));
870 for name in spelled {
871 if let Some(other) = seen.insert(name.clone(), ident.clone()) {
872 return Err(syn::Error::new(
873 variant.span(),
874 format!("`{name}` is already used by `{other}`"),
875 ));
876 }
877 }
878 let value = takes.map(|ty| spanned.or(optional).unwrap_or(ty));
879 help_items.push(occurrence_item(
880 variant,
881 short,
882 &longs,
883 takes
884 .is_some()
885 .then(|| value_name.as_deref().unwrap_or("VALUE")),
886 value,
887 require_equals,
888 default_missing.is_some(),
889 ));
890 if spec_on {
891 let value_ty = takes.map(|ty| spanned.or(optional).unwrap_or(ty));
892 spec_checks.extend(spec_check(&input.generics, value_ty, stated.as_ref()));
893 spec_rows.push(occurrence_spec(
894 variant,
895 spec::ItemDoc {
896 short,
897 long: longs.first().cloned(),
898 aliases: longs.iter().skip(1).cloned().collect(),
899 value_name: value_ty
900 .map(|_| value_name.clone().unwrap_or_else(|| "VALUE".to_owned())),
901 choices_ty: value_ty.and_then(|ty| choices_name(stated.as_ref(), ty)),
902 require_equals,
903 optional_value: default_missing.is_some(),
904 two_dashes,
905 prefix,
906 ..spec::ItemDoc::default()
907 },
908 ));
909 }
910 let body = match takes {
911 None => quote! {
912 __arg.check_switch()?;
913 ::core::result::Result::Ok(::core::option::Option::Some(Self::#ident))
914 },
915 Some(ty) => {
916 let ty = spanned.or(optional).unwrap_or(ty);
917 let options = quote! {
918 ::winnow_args::token::ValueOptions {
919 negative_numbers: #negative_numbers,
920 hyphen_values: #hyphen_values,
921 require_equals: #require_equals,
922 keep_equals: #keep_equals,
923 }
924 };
925 let attached = spanned
926 .is_some()
927 .then(|| quote!(let __attached = __arg.has_attached_value(__input);));
928 let value = wrap(
929 quote!(__arg.convert::<#ty>(__value)?),
930 quote!(__arg.offset()),
931 quote!(__attached),
932 );
933 let item = if optional.is_some() && require_equals && default_missing.is_none() {
934 quote! {
935 if let ::core::option::Option::Some(__value) =
936 __arg.read_value_opt_with(__input, #options)
937 {
938 Self::#ident(::core::option::Option::Some(#value))
939 } else {
940 Self::#ident(::core::option::Option::None)
941 }
942 }
943 } else {
944 let read = match &default_missing {
945 Some(missing) => {
946 let bytes = LitByteStr::new(missing.as_bytes(), Span::call_site());
947 quote! {
948 __arg.read_value_or_with(
949 __input,
950 #options,
951 ::winnow_args::__private::BStr::new(#bytes),
952 )
953 }
954 }
955 None => quote!(__arg.read_value_with(__input, #options)?),
956 };
957 quote! {{
958 let __value = #read;
959 Self::#ident(#value)
960 }}
961 };
962 quote! {
963 #attached
964 let __item = #item;
965 match &__item {
967 Self::#ident(__read) => {
968 let _ = __read;
969 }
970 #[allow(unreachable_patterns)]
971 _ => {}
972 }
973 ::core::result::Result::Ok(::core::option::Option::Some(__item))
974 }
975 }
976 };
977 if !longs.is_empty() {
978 let pattern = byte_patterns(&longs);
979 long_arms.push(quote!(#pattern => { #body }));
980 if !two_dashes {
981 single_dash.extend(longs.iter().cloned());
982 }
983 }
984 if let Some(c) = short {
985 if prefix {
986 if !c.is_ascii() || takes.is_none() {
987 return Err(syn::Error::new(
988 variant.span(),
989 "`prefix` is for a value-taking variant with an ASCII `short` letter",
990 ));
991 }
992 prefixes.push(c as u8);
993 }
994 let letter = LitChar::new(c, Span::call_site());
995 let takes_value = takes.is_some();
996 letters.push(quote!(#letter => ::core::option::Option::Some(#takes_value),));
997 short_arms.push(quote!(#letter => { #body }));
998 }
999 }
1000 let has_positional = word.is_some();
1001 let word =
1002 word.unwrap_or_else(|| quote!(::core::result::Result::Ok(::core::option::Option::None)));
1003 let bundle = bundle.map(|body| {
1004 quote! {
1005 const BUNDLES: bool = true;
1006
1007 fn from_bundle(
1008 __word: &::winnow_args::token::Word<'_>,
1009 ) -> ::core::result::Result<::core::option::Option<Self>, ::winnow_args::Error> {
1010 #body
1011 }
1012 }
1013 });
1014 let unknown = unknown.map(|body| {
1015 quote! {
1016 fn from_unknown(
1017 __word: &::winnow_args::token::Word<'_>,
1018 ) -> ::core::result::Result<::core::option::Option<Self>, ::winnow_args::Error> {
1019 #body
1020 }
1021 }
1022 });
1023 let is_long = matches_name(&single_dash);
1024 let name = &input.ident;
1025 let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
1026 if spec_on {
1027 spec::write_occurrence(input.ident.span(), &filed, &spec_rows)?;
1028 }
1029 Ok(quote! {
1030 #(#spec_checks)*
1031 impl #impl_generics ::winnow_args::Occurrence for #name #ty_generics #where_clause {
1032 const SPEC: &'static str = #identity;
1033
1034 const PREFIXES: &'static [u8] = &[#(#prefixes),*];
1035
1036 const POSITIONAL: bool = #has_positional;
1037
1038 const ITEMS: &'static [::winnow_args::help::Item] = &[#(#help_items),*];
1039
1040 fn from_arg<'__i>(
1041 __arg: &::winnow_args::Arg<'__i>,
1042 __input: &mut ::winnow_args::Argv<'__i>,
1043 ) -> ::core::result::Result<::core::option::Option<Self>, ::winnow_args::Error> {
1044 let _ = &__input;
1045 match __arg {
1046 ::winnow_args::Arg::Long(__flag) => match __flag.name {
1047 #(#long_arms)*
1048 _ => ::core::result::Result::Ok(::core::option::Option::None),
1049 },
1050 ::winnow_args::Arg::Short(__flag) if !__flag.plus => match __flag.letter {
1051 #(#short_arms)*
1052 _ => ::core::result::Result::Ok(::core::option::Option::None),
1053 },
1054 _ => ::core::result::Result::Ok(::core::option::Option::None),
1055 }
1056 }
1057
1058 fn from_word(
1059 __word: &::winnow_args::token::Word<'_>,
1060 ) -> ::core::result::Result<::core::option::Option<Self>, ::winnow_args::Error> {
1061 let _ = __word;
1062 #word
1063 }
1064
1065 fn is_long(__name: &[u8]) -> bool {
1066 #is_long
1067 }
1068
1069 #unknown
1070
1071 #bundle
1072
1073 fn short(__letter: char) -> ::core::option::Option<bool> {
1074 match __letter {
1075 #(#letters)*
1076 _ => ::core::option::Option::None,
1077 }
1078 }
1079 }
1080 })
1081}
1082
1083fn occurrence_item(
1086 variant: &syn::Variant,
1087 short: Option<char>,
1088 longs: &[String],
1089 value_name: Option<&str>,
1090 value: Option<&Type>,
1091 require_equals: bool,
1092 optional_value: bool,
1093) -> TokenStream2 {
1094 let (doc, long_doc) = docs(&variant.attrs);
1095 let (help, long_help) = (text(&doc), text(&long_doc));
1096 let (choices, hint) = match value {
1098 Some(ty) => (
1099 quote!(<#ty as ::winnow_args::FromArg>::CHOICES),
1100 quote!(<#ty as ::winnow_args::FromArg>::HINT),
1101 ),
1102 None => (quote!(&[]), quote!(::winnow_args::complete::Hint::Nothing)),
1103 };
1104 let positional = short.is_none() && longs.is_empty();
1105 let short = opt_char(short);
1106 let long = opt_str(longs.first().map(String::as_str));
1107 let aliases = longs.iter().skip(1);
1108 let value_name = opt_str(value_name);
1109 quote!(::winnow_args::help::Item {
1110 short: #short,
1111 long: #long,
1112 aliases: &[#(#aliases),*],
1113 negate: ::core::option::Option::None,
1114 value_name: #value_name,
1115 help: #help,
1116 long_help: #long_help,
1117 heading: ::core::option::Option::None,
1118 hide: false,
1119 positional: #positional,
1120 required: false,
1121 multiple: #positional,
1122 trailing: false,
1123 default: ::core::option::Option::None,
1124 env: ::core::option::Option::None,
1125 choices: #choices,
1126 require_equals: #require_equals,
1127 global: false,
1128 more_shorts: &[],
1129 plus: ::core::option::Option::None,
1130 optional_value: #optional_value,
1131 values: 1,
1132 more_values: false,
1133 hint: #hint,
1134 complete: ::core::option::Option::None,
1135 })
1136}
1137
1138fn spec_name(attrs: &[syn::Attribute]) -> syn::Result<Option<String>> {
1141 let mut name = None;
1142 for attr in attrs.iter().filter(|a| is_ours(a)) {
1143 let _ = attr.parse_nested_meta(|meta| {
1145 if meta.path.is_ident("spec") {
1146 name = Some(meta.value()?.parse::<LitStr>()?);
1147 } else if meta.input.peek(syn::Token![=]) {
1148 meta.value()?.parse::<syn::Expr>()?;
1149 } else if meta.input.peek(syn::token::Paren) {
1150 let _content;
1151 syn::parenthesized!(_content in meta.input);
1152 _content.parse::<TokenStream2>()?;
1153 }
1154 Ok(())
1155 });
1156 }
1157 let Some(name) = name else {
1158 return Ok(None);
1159 };
1160 let text = name.value();
1161 if text.is_empty() || text.contains(['/', '\\', ':', '.']) {
1162 return Err(syn::Error::new(
1163 name.span(),
1164 "`spec` is a name as a type's is: a file is named after it",
1165 ));
1166 }
1167 Ok(Some(text))
1168}
1169
1170fn skip_spec(meta: &syn::meta::ParseNestedMeta<'_>) -> syn::Result<bool> {
1172 if !meta.path.is_ident("spec") {
1173 return Ok(false);
1174 }
1175 meta.value()?.parse::<LitStr>()?;
1176 Ok(true)
1177}
1178
1179fn filed(input: &DeriveInput) -> syn::Result<String> {
1181 Ok(spec_name(&input.attrs)?.unwrap_or_else(|| input.ident.unraw().to_string()))
1182}
1183
1184fn type_path(ty: &Type) -> Option<String> {
1193 let Type::Path(path) = boxed(ty).unwrap_or(ty) else {
1194 return None;
1195 };
1196 if path.qself.is_some() {
1197 return None;
1198 }
1199 let mut names: Vec<String> = path
1200 .path
1201 .segments
1202 .iter()
1203 .map(|segment| segment.ident.unraw().to_string())
1204 .collect();
1205 if let [name] = &names[..] {
1206 match spec::origin(ty.span(), name) {
1207 spec::Origin::Here => {}
1208 spec::Origin::Imported(from) => names = from,
1209 spec::Origin::Unknown => names.insert(0, "?".to_owned()),
1210 }
1211 }
1212 let rooted = names
1213 .iter()
1214 .take_while(|name| matches!(name.as_str(), "crate" | "self" | "super"))
1215 .count();
1216 if rooted > 0 {
1217 let supers = names[..rooted]
1218 .iter()
1219 .filter(|name| *name == "super")
1220 .count();
1221 let relative = names[0] != "crate";
1222 let modules = relative.then(|| spec::above(supers)).flatten();
1223 let root = ["crate".to_owned()].into_iter();
1224 names.splice(..rooted, root.chain(modules.unwrap_or_default()));
1225 }
1226 (names.len() > usize::from(rooted > 0)).then(|| names.join("::"))
1227}
1228
1229fn spec_type(stated: Option<String>, ty: &Type) -> syn::Result<String> {
1232 stated.or_else(|| type_path(ty)).ok_or_else(|| {
1233 syn::Error::new(
1234 ty.span(),
1235 "this type needs a name when `WINNOW_ARGS_SPEC` is set: state it, as `spec = \"Name\"`",
1236 )
1237 })
1238}
1239
1240fn spec_filed(
1244 generics: &syn::Generics,
1245 kind: TokenStream2,
1246 ty: &Type,
1247 name: &str,
1248) -> Option<TokenStream2> {
1249 if !generics.params.is_empty() {
1250 return None;
1251 }
1252 let ty = boxed(ty).unwrap_or(ty);
1253 let name = name.strip_prefix("?::").unwrap_or(name);
1254 Some(quote::quote_spanned! {ty.span()=>
1255 const _: () = ::core::assert!(
1256 ::winnow_args::__private::filed_as(<#ty as ::winnow_args::#kind>::SPEC, #name),
1257 "the documentation does not find this type by this path: it is named through an alias or a re-export, its module goes by another name here, or it states a `spec`. State the name it is filed under, as `spec = \"Name\"`",
1258 );
1259 })
1260}
1261
1262fn value_type(field: &Field) -> Option<&Type> {
1264 if !field.takes_value() || field.keywords || matches!(field.role, Role::Subcommand) {
1265 return None;
1266 }
1267 match &field.kind {
1268 Kind::Optional(ty) | Kind::Required(ty) | Kind::Many(ty) => Some(ty),
1269 Kind::Switch | Kind::Count(_) => None,
1270 }
1271}
1272
1273fn choices_type(field: &Field) -> Option<&Type> {
1276 value_type(field).filter(|_| field.choices.is_none())
1277}
1278
1279fn spec_check_stated(generics: &syn::Generics, field: &Field) -> Option<TokenStream2> {
1282 let stated = field.choices.as_ref()?;
1283 let ty = value_type(field).filter(|_| generics.params.is_empty())?;
1284 Some(quote::quote_spanned! {ty.span()=>
1285 const _: () = ::core::assert!(
1286 ::winnow_args::__private::stated_choices::<#ty>(&[#(#stated),*]),
1287 "the choices stated on this field are not those of its type, in number, spelling or order",
1288 );
1289 })
1290}
1291
1292fn choices_link(field: &Field) -> Option<String> {
1295 choices_name(field.spec.as_ref(), choices_type(field)?)
1296}
1297
1298fn choices_name(stated: Option<&String>, ty: &Type) -> Option<String> {
1299 stated.cloned().or_else(|| type_path(ty))
1300}
1301
1302fn spec_check(
1306 generics: &syn::Generics,
1307 ty: Option<&Type>,
1308 stated: Option<&String>,
1309) -> Option<TokenStream2> {
1310 let ty = ty.filter(|_| generics.params.is_empty())?;
1311 let name = choices_name(stated, ty)?;
1312 let name = name.strip_prefix("?::").unwrap_or(&name);
1313 Some(quote::quote_spanned! {ty.span()=>
1314 const _: () = ::core::assert!(
1315 ::winnow_args::__private::documented_choices::<#ty>(#name),
1316 "the documentation cannot find the choices of this value: its type is named through an alias or a re-export, states a `spec`, or implements `FromArg` by hand. State the name they are filed under, as `spec = \"Name\"`, or the choices, as `choices(\"a\", \"b\")`",
1317 );
1318 })
1319}
1320
1321fn package_version(version: &Option<TokenStream2>, enabled: bool) -> Option<String> {
1324 if !enabled {
1325 return None;
1326 }
1327 let tokens = version.as_ref()?;
1328 if let Ok(lit) = syn::parse2::<LitStr>(tokens.clone()) {
1329 let text = lit.value();
1330 return (!text.is_empty()).then_some(text);
1331 }
1332 std::env::var("CARGO_PKG_VERSION")
1333 .ok()
1334 .filter(|text| !text.is_empty())
1335}
1336
1337fn spec_item(field: &Field) -> syn::Result<spec::ItemDoc> {
1338 let longs = field.longs();
1339 let keywords = if field.keywords {
1340 match &field.kind {
1341 Kind::Required(ty) => Some(spec_type(field.spec.clone(), ty)?),
1342 _ => {
1343 return Err(syn::Error::new(
1344 field.ident.span(),
1345 "`keywords` needs a named type",
1346 ));
1347 }
1348 }
1349 } else {
1350 None
1351 };
1352 let required = (matches!(field.kind, Kind::Required(_))
1353 && field.default.is_none()
1354 && field.default_fn.is_none()
1355 && field.env.is_none()
1356 && !field.keywords)
1357 || field.required;
1358 Ok(spec::ItemDoc {
1359 short: field.short(),
1360 more_shorts: field.short_aliases.clone(),
1361 plus: field.plus,
1362 long: longs.first().map(|name| (*name).to_owned()),
1363 aliases: longs
1364 .iter()
1365 .skip(1)
1366 .map(|name| (*name).to_owned())
1367 .collect(),
1368 negate: field.negate.clone(),
1369 value_name: if field.is_positional() {
1370 Some(field.display())
1371 } else {
1372 field.value_name.clone()
1373 },
1374 help: field.help.clone(),
1375 long_help: field.long_help.clone(),
1376 heading: field.heading.clone(),
1377 hide: field.hide,
1378 positional: field.is_positional(),
1379 required,
1380 multiple: matches!(field.kind, Kind::Many(_) | Kind::Count(_)),
1381 trailing: field.double_dash() == DoubleDash::Required,
1382 default: field.default.clone().or_else(|| field.default_note.clone()),
1383 env: field.env.clone(),
1384 choices: field.choices.clone().unwrap_or_default(),
1385 choices_ty: choices_link(field),
1386 require_equals: field.require_equals,
1387 global: field.is_global(),
1388 optional_value: field.default_missing.is_some()
1389 || field.require_equals && matches!(field.kind, Kind::Optional(_)),
1390 values: field.values,
1391 more_values: field.values_max.is_some(),
1392 two_dashes: field.two_dashes,
1393 prefix: field.prefix,
1394 stop_flags: field.stop_flags,
1395 keywords,
1396 })
1397}
1398
1399fn occurrence_spec(variant: &syn::Variant, mut item: spec::ItemDoc) -> spec::ItemDoc {
1400 let (help, long_help) = docs(&variant.attrs);
1401 item.help = help;
1402 item.long_help = long_help;
1403 item.positional = item.short.is_none() && item.long.is_none();
1404 item.multiple = item.positional;
1405 item
1406}
1407
1408fn expand_value_enum(input: &DeriveInput) -> syn::Result<TokenStream2> {
1410 let Data::Enum(data) = &input.data else {
1411 return Err(syn::Error::new(
1412 input.span(),
1413 "`ValueEnum` can only be derived for enums",
1414 ));
1415 };
1416 let case = rename_all(input)?;
1417 let mut names: Vec<(String, &Ident)> = Vec::new();
1418 let mut arms = Vec::new();
1419 let mut visible = Vec::new();
1420 let spec_on = spec::enabled();
1421 let filed = filed(input)?;
1422 let identity = spec::identity(input.ident.span(), &filed);
1423 let mut spec_choices = Vec::new();
1424 for variant in &data.variants {
1425 if !matches!(variant.fields, Fields::Unit) {
1426 return Err(syn::Error::new(
1427 variant.span(),
1428 "a `ValueEnum` variant holds no data",
1429 ));
1430 }
1431 let Variant {
1432 names: spellings,
1433 hide,
1434 ..
1435 } = variant_names(variant, &mut names, case)?;
1436 let name = LitStr::new(&spellings[0], Span::call_site());
1437 if !hide {
1438 if spec_on {
1439 spec_choices.push(spellings[0].clone());
1440 }
1441 visible.push(name);
1442 }
1443 let pattern = byte_patterns(&spellings);
1444 let ident = &variant.ident;
1445 arms.push(quote!(#pattern => ::core::result::Result::Ok(Self::#ident),));
1446 }
1447 let name = &input.ident;
1448 let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
1449 if spec_on {
1450 spec::write_choices(input.ident.span(), &filed, &spec_choices)?;
1451 }
1452 Ok(quote! {
1453 impl #impl_generics ::winnow_args::FromArg for #name #ty_generics #where_clause {
1454 const CHOICES: &'static [&'static str] = &[#(#visible),*];
1455 const SPEC: &'static str = #identity;
1456
1457 fn from_arg(
1458 __value: &::winnow_args::__private::BStr,
1459 ) -> ::core::result::Result<Self, ::winnow_args::__private::BoxError> {
1460 match &**__value {
1461 #(#arms)*
1462 _ => ::core::result::Result::Err(::std::boxed::Box::new(
1463 ::winnow_args::ChoiceError { choices: Self::CHOICES },
1464 )),
1465 }
1466 }
1467 }
1468 })
1469}
1470
1471fn text(prose: &str) -> TokenStream2 {
1473 if prose.is_empty() {
1474 quote!("")
1475 } else {
1476 quote!(::winnow_args::__text!(#prose))
1477 }
1478}
1479
1480struct Variant {
1482 names: Vec<String>,
1484 shown: usize,
1486 hide: bool,
1487 about: String,
1488}
1489
1490#[derive(Clone, Copy)]
1493enum Case {
1494 Kebab,
1496 Lower,
1498 Upper,
1500 Snake,
1502 Verbatim,
1504}
1505
1506impl Case {
1507 fn apply(self, ident: &str) -> String {
1508 let ident = ident.trim_start_matches("r#");
1509 match self {
1510 Case::Kebab => kebab_case(ident),
1511 Case::Lower => ident.to_lowercase(),
1512 Case::Upper => ident.to_uppercase(),
1513 Case::Snake => kebab_case(ident).replace('-', "_"),
1514 Case::Verbatim => ident.to_owned(),
1515 }
1516 }
1517}
1518
1519fn rename_all(input: &DeriveInput) -> syn::Result<Case> {
1521 let mut case = Case::Kebab;
1522 for attr in input.attrs.iter().filter(|a| is_ours(a)) {
1523 attr.parse_nested_meta(|meta| {
1524 if skip_spec(&meta)? {
1525 return Ok(());
1526 }
1527 if !meta.path.is_ident("rename_all") {
1528 return Err(meta.error("expected `rename_all` or `spec`"));
1529 }
1530 let lit = meta.value()?.parse::<LitStr>()?;
1531 case = match lit.value().as_str() {
1532 "kebab-case" => Case::Kebab,
1533 "lowercase" => Case::Lower,
1534 "UPPERCASE" => Case::Upper,
1535 "snake_case" => Case::Snake,
1536 "verbatim" => Case::Verbatim,
1537 _ => {
1538 return Err(syn::Error::new(
1539 lit.span(),
1540 "expected \"kebab-case\", \"lowercase\", \"UPPERCASE\", \"snake_case\" or \"verbatim\"",
1541 ));
1542 }
1543 };
1544 Ok(())
1545 })?;
1546 }
1547 Ok(case)
1548}
1549
1550fn variant_names<'a>(
1552 variant: &'a syn::Variant,
1553 seen: &mut Vec<(String, &'a Ident)>,
1554 case: Case,
1555) -> syn::Result<Variant> {
1556 let holds_type = !matches!(variant.fields, Fields::Unit);
1557 let (mut name, mut alias, mut hidden, mut hide) = (None, Vec::new(), Vec::new(), false);
1558 let mut help: Option<String> = None;
1559 for attr in variant.attrs.iter().filter(|a| is_ours(a)) {
1560 attr.parse_nested_meta(|meta| {
1561 if meta.path.is_ident("name") {
1562 name = Some(meta.value()?.parse::<LitStr>()?.value());
1563 } else if meta.path.is_ident("alias") {
1564 alias.extend(aliases(&meta)?);
1565 } else if meta.path.is_ident("alias_hidden") {
1566 hidden.extend(aliases(&meta)?);
1567 } else if meta.path.is_ident("hide") {
1568 hide = true;
1569 } else if meta.path.is_ident("help") {
1570 help = Some(meta.value()?.parse::<LitStr>()?.value());
1571 } else if meta.path.is_ident("long_help") {
1572 meta.value()?.parse::<LitStr>()?;
1574 } else if skip_spec(&meta)? {
1575 if !holds_type {
1576 return Err(meta
1577 .error("`spec` names the type a variant holds, and this one holds none"));
1578 }
1579 } else {
1580 return Err(meta.error(
1581 "expected `name`, `alias`, `alias_hidden`, `hide`, `help` or `long_help`",
1582 ));
1583 }
1584 Ok(())
1585 })?;
1586 }
1587 let ident = &variant.ident;
1588 let shown = alias.len();
1589 let names: Vec<String> =
1590 std::iter::once(name.unwrap_or_else(|| case.apply(&ident.to_string())))
1591 .chain(alias)
1592 .chain(hidden)
1593 .collect();
1594 for spelling in &names {
1595 if let Some((_, other)) = seen.iter().find(|(n, _)| n == spelling) {
1596 return Err(syn::Error::new(
1597 ident.span(),
1598 format!("`{spelling}` is already used by `{other}`"),
1599 ));
1600 }
1601 seen.push((spelling.clone(), ident));
1602 }
1603 Ok(Variant {
1604 names,
1605 shown,
1606 hide,
1607 about: help
1608 .map(|h| {
1609 h.split("\n\n")
1610 .next()
1611 .unwrap_or_default()
1612 .replace('\n', " ")
1613 })
1614 .unwrap_or_else(|| docs(&variant.attrs).0),
1615 })
1616}
1617
1618fn docs(attrs: &[syn::Attribute]) -> (String, String) {
1620 let lines: Vec<String> = attrs
1621 .iter()
1622 .filter(|a| a.path().is_ident("doc"))
1623 .filter_map(|a| match &a.meta {
1624 syn::Meta::NameValue(nv) => match &nv.value {
1625 syn::Expr::Lit(syn::ExprLit {
1626 lit: syn::Lit::Str(s),
1627 ..
1628 }) => Some(s.value()),
1629 _ => None,
1630 },
1631 _ => None,
1632 })
1633 .map(|line| {
1634 line.strip_prefix(' ')
1635 .unwrap_or(&line)
1636 .trim_end()
1637 .to_owned()
1638 })
1639 .collect();
1640 let long = lines.join("\n").trim().to_owned();
1641 let short = long
1642 .split("\n\n")
1643 .next()
1644 .unwrap_or_default()
1645 .replace('\n', " ");
1646 (short, long)
1647}
1648
1649fn opt_str(s: Option<&str>) -> TokenStream2 {
1651 match s {
1652 Some(s) => quote!(::core::option::Option::Some(#s)),
1653 None => quote!(::core::option::Option::None),
1654 }
1655}
1656
1657fn opt_char(c: Option<char>) -> TokenStream2 {
1659 match c {
1660 Some(c) => quote!(::core::option::Option::Some(#c)),
1661 None => quote!(::core::option::Option::None),
1662 }
1663}
1664
1665fn byte_patterns<S: AsRef<str>>(names: &[S]) -> TokenStream2 {
1667 let literals = names
1668 .iter()
1669 .map(|n| LitByteStr::new(n.as_ref().as_bytes(), Span::call_site()));
1670 quote!(#(#literals)|*)
1671}
1672
1673fn matches_name(names: &[String]) -> TokenStream2 {
1675 if names.is_empty() {
1676 quote!(false)
1677 } else {
1678 let names = byte_patterns(names);
1679 quote!(matches!(__name, #names))
1680 }
1681}
1682
1683fn aliases(meta: &syn::meta::ParseNestedMeta<'_>) -> syn::Result<Vec<String>> {
1685 if meta.input.peek(syn::Token![=]) {
1686 return Ok(vec![meta.value()?.parse::<LitStr>()?.value()]);
1687 }
1688 let content;
1689 syn::parenthesized!(content in meta.input);
1690 let names = syn::punctuated::Punctuated::<LitStr, syn::Token![,]>::parse_terminated(&content)?;
1691 Ok(names.iter().map(LitStr::value).collect())
1692}
1693
1694fn kebab_case(ident: &str) -> String {
1696 let mut out = String::new();
1697 for (i, c) in ident.trim_start_matches("r#").chars().enumerate() {
1698 if c.is_uppercase() {
1699 if i > 0 {
1700 out.push('-');
1701 }
1702 out.extend(c.to_lowercase());
1703 } else {
1704 out.push(c);
1705 }
1706 }
1707 out
1708}
1709
1710fn is_ours(attr: &syn::Attribute) -> bool {
1713 attr.path().is_ident("arg") || attr.path().is_ident("winnow_args")
1714}
1715
1716fn is_bool(ty: &Type) -> bool {
1718 last_segment(ty).is_some_and(|s| s.ident == "bool" && s.arguments.is_none())
1719}
1720
1721fn is_sequence(f: &syn::Field) -> bool {
1723 has_flag(f, "sequence")
1724}
1725
1726fn is_flatten(f: &syn::Field) -> bool {
1728 has_flag(f, "flatten")
1729}
1730
1731fn is_unknown(f: &syn::Field) -> bool {
1733 has_flag(f, "unknown") && !is_sequence(f)
1734}
1735
1736fn is_skipped(f: &syn::Field) -> bool {
1738 has_flag(f, "skip")
1739}
1740
1741fn has_flag(f: &syn::Field, name: &str) -> bool {
1743 f.attrs.iter().filter(|a| is_ours(a)).any(|a| {
1744 let mut found = false;
1745 let _ = a.parse_nested_meta(|meta| {
1746 if meta.path.is_ident(name) {
1747 found = true;
1748 } else if meta.input.peek(syn::Token![=]) {
1749 meta.value()?.parse::<syn::Expr>()?;
1750 } else if meta.input.peek(syn::token::Paren) {
1751 let _content;
1752 syn::parenthesized!(_content in meta.input);
1753 }
1754 Ok(())
1755 });
1756 found
1757 })
1758}
1759
1760fn role_alone(f: &syn::Field) -> syn::Result<()> {
1763 let role = ["skip", "flatten", "sequence", "unknown"]
1764 .into_iter()
1765 .find(|role| has_flag(f, role));
1766 let Some(role) = role else {
1767 return Ok(());
1768 };
1769 for attr in f.attrs.iter().filter(|a| is_ours(a)) {
1770 attr.parse_nested_meta(|meta| {
1771 let alone =
1772 meta.path.is_ident(role) || (role == "sequence" && meta.path.is_ident("unknown"));
1773 let named = matches!(role, "flatten" | "sequence") && skip_spec(&meta)?;
1776 if alone || named {
1777 Ok(())
1778 } else {
1779 Err(meta.error(format!("a `{role}` field takes no other `arg` options")))
1780 }
1781 })?;
1782 }
1783 Ok(())
1784}
1785
1786fn boxed(ty: &Type) -> Option<&Type> {
1788 let last = last_segment(ty)?;
1789 if last.ident == "Box" {
1790 inner(last)
1791 } else {
1792 None
1793 }
1794}
1795
1796enum Kind {
1797 Switch,
1799 Count(Type),
1801 Optional(Type),
1803 Required(Type),
1805 Many(Type),
1807}
1808
1809enum Role {
1810 Flag {
1811 short: Option<char>,
1812 long: Option<String>,
1813 aliases: Vec<String>,
1815 global: bool,
1817 },
1818 Positional {
1819 name: String,
1820 double_dash: DoubleDash,
1821 },
1822 Subcommand,
1823}
1824
1825#[derive(Clone, Copy, PartialEq, Eq)]
1827enum DoubleDash {
1828 Optional,
1830 Required,
1832 Automatic,
1834 Preserve,
1836}
1837
1838struct Field {
1839 ident: Ident,
1840 spec: Option<String>,
1843 kind: Kind,
1844 role: Role,
1845 delimiter: Option<u8>,
1847 choices: Option<Vec<String>>,
1849 env: Option<String>,
1851 default: Option<String>,
1853 conflicts: Vec<String>,
1855 overrides: Vec<String>,
1857 requires: Vec<String>,
1859 required: bool,
1861 required_unless: Vec<String>,
1863 group: Option<String>,
1865 default_missing: Option<String>,
1867 help: String,
1869 long_help: String,
1870 heading: Option<String>,
1871 hide: bool,
1872 value_name: Option<String>,
1874 hint: Option<syn::Ident>,
1877 complete: Option<syn::Path>,
1879 negative_numbers: bool,
1881 hyphen_values: bool,
1883 require_equals: bool,
1885 keep_equals: bool,
1887 negate: Option<String>,
1890 two_dashes: bool,
1892 values: usize,
1894 values_max: Option<usize>,
1897 value_terminator: Option<String>,
1899 default_fn: Option<syn::ExprPath>,
1901 default_note: Option<String>,
1903 default_ifs: Vec<(String, Option<String>, String)>,
1905 stop_flags: bool,
1907 plus: Option<char>,
1909 tristate: bool,
1912 keywords: bool,
1915 short_aliases: Vec<char>,
1917 prefix: bool,
1920}
1921
1922impl Field {
1923 fn help_item(&self) -> TokenStream2 {
1925 let short = opt_char(self.short());
1926 let long = opt_str(self.longs().first().copied());
1927 let aliases = self.longs().into_iter().skip(1);
1928 let negate = opt_str(self.negate.as_deref());
1929 let require_equals = self.require_equals;
1930 let global = self.is_global();
1931 let positional = self.is_positional();
1932 let trailing = self.double_dash() == DoubleDash::Required;
1933 let display = self.display();
1934 let value_name = if positional {
1935 opt_str(Some(&display))
1936 } else {
1937 opt_str(self.value_name.as_deref())
1938 };
1939 let (help, long_help) = (text(&self.help), text(&self.long_help));
1940 let heading = opt_str(self.heading.as_deref());
1941 let hide = self.hide;
1942 let more_shorts = self.short_aliases.iter();
1943 let plus = opt_char(self.plus);
1944 let optional_value = self.default_missing.is_some()
1945 || require_equals && matches!(self.kind, Kind::Optional(_));
1946 let values = self.values;
1947 let more_values = self.values_max.is_some();
1948 let hint = match (&self.hint, &self.kind, &self.role) {
1949 (Some(hint), _, _) => quote!(::winnow_args::complete::Hint::#hint),
1950 (None, Kind::Optional(ty) | Kind::Required(ty) | Kind::Many(ty), role)
1951 if !matches!(role, Role::Subcommand) && !self.keywords && !self.tristate =>
1952 {
1953 quote!(<#ty as ::winnow_args::FromArg>::HINT)
1954 }
1955 (None, _, _) => quote!(::winnow_args::complete::Hint::Nothing),
1956 };
1957 let complete = match &self.complete {
1958 Some(function) => quote!(::core::option::Option::Some(#function)),
1959 None => quote!(::core::option::Option::None),
1960 };
1961 let required = matches!(self.kind, Kind::Required(_))
1962 && self.default.is_none()
1963 && self.default_fn.is_none()
1964 && self.env.is_none()
1965 && !self.keywords
1966 || self.required;
1967 let multiple = matches!(self.kind, Kind::Many(_) | Kind::Count(_));
1968 let default = opt_str(self.default.as_deref().or(self.default_note.as_deref()));
1969 let env = opt_str(self.env.as_deref());
1970 let choices = match (&self.choices, &self.kind, &self.role) {
1972 (Some(choices), _, _) => quote!(&[#(#choices),*]),
1973 (None, _, Role::Subcommand) | (None, Kind::Switch | Kind::Count(_), _) => quote!(&[]),
1974 (None, _, _) if self.keywords || self.tristate => quote!(&[]),
1975 (None, Kind::Optional(ty) | Kind::Required(ty) | Kind::Many(ty), _) => {
1976 quote!(<#ty as ::winnow_args::FromArg>::CHOICES)
1977 }
1978 };
1979 quote! {
1980 ::winnow_args::help::Item {
1981 short: #short,
1982 long: #long,
1983 aliases: &[#(#aliases),*],
1984 negate: #negate,
1985 value_name: #value_name,
1986 help: #help,
1987 long_help: #long_help,
1988 heading: #heading,
1989 hide: #hide,
1990 positional: #positional,
1991 required: #required,
1992 multiple: #multiple,
1993 trailing: #trailing,
1994 default: #default,
1995 env: #env,
1996 choices: #choices,
1997 require_equals: #require_equals,
1998 global: #global,
1999 more_shorts: &[#(#more_shorts),*],
2000 plus: #plus,
2001 optional_value: #optional_value,
2002 values: #values,
2003 more_values: #more_values,
2004 hint: #hint,
2005 complete: #complete,
2006 }
2007 }
2008 }
2009
2010 fn value_options(&self) -> TokenStream2 {
2012 let (negative_numbers, hyphen_values, require_equals, keep_equals) = (
2013 self.negative_numbers,
2014 self.hyphen_values,
2015 self.require_equals,
2016 self.keep_equals,
2017 );
2018 quote! {
2019 __wa::ValueOptions {
2020 negative_numbers: #negative_numbers,
2021 hyphen_values: #hyphen_values,
2022 require_equals: #require_equals,
2023 keep_equals: #keep_equals,
2024 }
2025 }
2026 }
2027
2028 fn read(&self) -> TokenStream2 {
2030 let (negative_numbers, hyphen_values, require_equals, keep_equals) = (
2031 self.negative_numbers,
2032 self.hyphen_values,
2033 self.require_equals,
2034 self.keep_equals,
2035 );
2036 let options = self.value_options();
2037 match &self.default_missing {
2038 None if !negative_numbers && !hyphen_values && !require_equals && !keep_equals => {
2039 quote!(__arg.read_value(__input)?)
2040 }
2041 None => quote!(__arg.read_value_with(__input, #options)?),
2042 Some(missing) => {
2043 let bytes = LitByteStr::new(missing.as_bytes(), Span::call_site());
2044 quote!(__arg.read_value_or_with(__input, #options, __wa::BStr::new(#bytes)))
2045 }
2046 }
2047 }
2048
2049 fn check(
2051 &self,
2052 value: TokenStream2,
2053 error: impl FnOnce(TokenStream2) -> TokenStream2,
2054 ) -> TokenStream2 {
2055 let Some(choices) = &self.choices else {
2056 return quote!();
2057 };
2058 let pattern = byte_patterns(choices);
2059 let listed = choices.iter().map(|c| LitStr::new(c, Span::call_site()));
2060 let cause = quote!(::winnow_args::ChoiceError { choices: &[#(#listed),*] });
2061 let error = error(cause);
2062 quote! {
2063 if !matches!(&**#value, #pattern) {
2064 return ::core::result::Result::Err(#error);
2065 }
2066 }
2067 }
2068
2069 fn flag_value(&self, ty: &Type, value: TokenStream2) -> TokenStream2 {
2071 let check = self.check(value.clone(), |cause| {
2072 quote!(__wa::Error::invalid_value(__arg.offset(), __arg.spelling(), #value, #cause))
2073 });
2074 quote!({ #check __arg.convert::<#ty>(#value)? })
2075 }
2076
2077 fn source_value(&self, ty: &Type, value: TokenStream2, source: &str) -> TokenStream2 {
2080 let source = LitStr::new(source, Span::call_site());
2081 let check = self.check(
2082 value.clone(),
2083 |cause| quote!(__wa::Error::invalid_value(__input.offset(), #source, #value, #cause)),
2084 );
2085 quote!({
2086 #check
2087 <#ty as __wa::FromArg>::from_arg(#value).map_err(|cause| {
2088 __wa::Error::invalid_value(__input.offset(), #source, #value, cause)
2089 })?
2090 })
2091 }
2092
2093 fn has(&self) -> TokenStream2 {
2096 let slot = slot(&self.ident);
2097 if self.keywords {
2098 return quote!((!#slot.is_empty()));
2099 }
2100 match &self.kind {
2101 Kind::Switch if self.negate.is_some() => {
2102 quote!((#slot == ::core::option::Option::Some(true)))
2103 }
2104 Kind::Switch => quote!(#slot),
2105 Kind::Count(_) => quote!((#slot != 0)),
2106 Kind::Optional(_) | Kind::Required(_) => quote!(#slot.is_some()),
2107 Kind::Many(_) => quote!((!#slot.is_empty())),
2108 }
2109 }
2110
2111 fn assign(&self, value: TokenStream2, source: &str) -> TokenStream2 {
2114 let slot = slot(&self.ident);
2115 match &self.kind {
2116 Kind::Optional(ty) | Kind::Required(ty) => {
2117 let value = self.source_value(ty, value, source);
2118 quote!(#slot = ::core::option::Option::Some(#value);)
2119 }
2120 Kind::Many(ty) => match self.delimiter {
2121 None => {
2122 let value = self.source_value(ty, value, source);
2123 quote!(#slot.push(#value);)
2124 }
2125 Some(d) => {
2126 let piece = self.source_value(ty, quote!(__piece), source);
2127 quote!(for __piece in __wa::split(#value, #d) { #slot.push(#piece); })
2128 }
2129 },
2130 Kind::Switch | Kind::Count(_) => unreachable!("a switch or a count takes no value"),
2131 }
2132 }
2133
2134 fn unset(&self, displaced: bool) -> TokenStream2 {
2136 let slot = slot(&self.ident);
2137 if self.negate.is_some() {
2138 return quote!(#slot.is_none());
2139 }
2140 let has = self.has();
2141 let not_displaced = displaced.then(|| {
2142 let displaced = displaced_flag(&self.ident);
2143 quote!(&& !#displaced)
2144 });
2145 quote!(!#has #not_displaced)
2146 }
2147
2148 fn fallback(&self, env: bool, displaced: bool) -> TokenStream2 {
2151 let slot = slot(&self.ident);
2152 if self.negate.is_some() {
2153 return self.negatable_fallback(env);
2154 }
2155 let unset = self.unset(displaced);
2156 let assign = |value: TokenStream2, source: &str| self.assign(value, source);
2157 if !env {
2158 if let Some(function) = &self.default_fn {
2159 let set = match &self.kind {
2160 Kind::Many(_) => quote!(#slot = #function();),
2161 _ => quote!(#slot = ::core::option::Option::Some(#function());),
2162 };
2163 return quote!(if #unset { #set });
2164 }
2165 return self
2166 .default
2167 .as_ref()
2168 .map(|value| {
2169 let bytes = LitByteStr::new(value.as_bytes(), Span::call_site());
2170 let assign = assign(quote!(__wa::BStr::new(#bytes)), &self.display());
2171 quote!(if #unset { #assign })
2172 })
2173 .unwrap_or_default();
2174 }
2175 self.env
2176 .as_ref()
2177 .map(|var| {
2178 let name = LitStr::new(var, Span::call_site());
2179 let apply = match &self.kind {
2180 Kind::Switch => quote!(#slot = __wa::env::truthy(&__raw);),
2181 Kind::Count(ty) => quote! {
2182 if let ::core::option::Option::Some(::core::result::Result::Ok(__n)) =
2183 __raw.to_str().map(str::parse::<#ty>)
2184 {
2185 #slot = __n;
2186 }
2187 },
2188 _ => {
2189 let assign = assign(quote!(__value), &format!("${var}"));
2190 quote! {
2191 let __value = __wa::BStr::new(__raw.as_encoded_bytes());
2192 #assign
2193 }
2194 }
2195 };
2196 quote! {
2197 if #unset {
2198 if let ::core::option::Option::Some(__raw) = __wa::env::var(#name) {
2199 #apply
2200 }
2201 }
2202 }
2203 })
2204 .unwrap_or_default()
2205 }
2206
2207 fn negatable_fallback(&self, env: bool) -> TokenStream2 {
2209 let slot = slot(&self.ident);
2210 let value = if env {
2211 let Some(var) = &self.env else {
2212 return quote!();
2213 };
2214 let name = LitStr::new(var, Span::call_site());
2215 quote! {
2216 if let ::core::option::Option::Some(__raw) = __wa::env::var(#name) {
2217 #slot = ::core::option::Option::Some(__wa::env::truthy(&__raw));
2218 }
2219 }
2220 } else {
2221 match (&self.default_fn, &self.default) {
2222 (Some(function), _) => quote!(#slot = ::core::option::Option::Some(#function());),
2223 (None, Some(default)) => {
2224 let default = default == "true";
2225 quote!(#slot = ::core::option::Option::Some(#default);)
2226 }
2227 (None, None) => return quote!(),
2228 }
2229 };
2230 quote!(if #slot.is_none() { #value })
2231 }
2232
2233 fn word_value(&self, ty: &Type, word: TokenStream2, display: &LitStr) -> TokenStream2 {
2235 let check = self.check(
2236 quote!(#word.value),
2237 |cause| quote!(__wa::Error::invalid_value(#word.offset, #display, #word.value, #cause)),
2238 );
2239 quote!({ #check #word.convert::<#ty>(#display)? })
2240 }
2241
2242 fn display(&self) -> String {
2244 match &self.role {
2245 Role::Flag { long: Some(l), .. } => format!("--{l}"),
2246 Role::Flag { short: Some(c), .. } => format!("-{c}"),
2247 Role::Flag { .. } => match self.plus {
2248 Some(c) => format!("+{c}"),
2249 None => unreachable!("every flag has a name"),
2250 },
2251 Role::Positional { name, .. } => name.clone(),
2252 Role::Subcommand => "<COMMAND>".to_owned(),
2253 }
2254 }
2255
2256 fn short_pattern(&self) -> Option<TokenStream2> {
2258 let first = self.short()?;
2259 let letters = std::iter::once(first)
2260 .chain(self.short_aliases.iter().copied())
2261 .map(|c| LitChar::new(c, Span::call_site()));
2262 Some(quote!(#(#letters)|*))
2263 }
2264
2265 fn shorts(&self) -> Vec<char> {
2267 self.short()
2268 .into_iter()
2269 .chain(self.short_aliases.iter().copied())
2270 .collect()
2271 }
2272
2273 fn short(&self) -> Option<char> {
2274 match self.role {
2275 Role::Flag { short, .. } => short,
2276 Role::Positional { .. } | Role::Subcommand => None,
2277 }
2278 }
2279
2280 fn longs(&self) -> Vec<&str> {
2282 match &self.role {
2283 Role::Flag { long, aliases, .. } => {
2284 long.iter().chain(aliases).map(String::as_str).collect()
2285 }
2286 Role::Positional { .. } | Role::Subcommand => Vec::new(),
2287 }
2288 }
2289
2290 fn long_pattern(&self) -> Option<TokenStream2> {
2292 let longs = self.longs();
2293 (!longs.is_empty()).then(|| byte_patterns(&longs))
2294 }
2295
2296 fn takes_value(&self) -> bool {
2298 !self.tristate && !matches!(self.kind, Kind::Switch | Kind::Count(_))
2299 }
2300
2301 fn is_positional(&self) -> bool {
2302 matches!(self.role, Role::Positional { .. })
2303 }
2304
2305 fn is_global(&self) -> bool {
2306 matches!(self.role, Role::Flag { global: true, .. })
2307 }
2308
2309 fn double_dash(&self) -> DoubleDash {
2311 match self.role {
2312 Role::Positional { double_dash, .. } => double_dash,
2313 _ => DoubleDash::Optional,
2314 }
2315 }
2316}
2317
2318fn expand(input: &DeriveInput) -> syn::Result<TokenStream2> {
2319 let Data::Struct(data) = &input.data else {
2320 return Err(syn::Error::new(
2321 input.span(),
2322 "`Args` can only be derived for structs",
2323 ));
2324 };
2325 let Fields::Named(named) = &data.fields else {
2326 return Err(syn::Error::new(
2327 data.fields.span(),
2328 "`Args` needs a struct with named fields",
2329 ));
2330 };
2331 for f in &named.named {
2332 role_alone(f)?;
2333 }
2334 let fields = named
2335 .named
2336 .iter()
2337 .filter(|f| !is_skipped(f) && !is_sequence(f) && !is_unknown(f) && !is_flatten(f))
2338 .map(field)
2339 .collect::<syn::Result<Vec<_>>>()?;
2340 let skipped: Vec<&Ident> = named
2341 .named
2342 .iter()
2343 .filter(|f| is_skipped(f) && !is_sequence(f) && !is_unknown(f) && !is_flatten(f))
2344 .filter_map(|f| f.ident.as_ref())
2345 .collect();
2346 let sequences: Vec<&syn::Field> = named.named.iter().filter(|f| is_sequence(f)).collect();
2349 if let Some(extra) = sequences.get(1) {
2350 return Err(syn::Error::new(
2351 extra.span(),
2352 "a struct has at most one `sequence` field",
2353 ));
2354 }
2355 let sequence_unknown = sequences.first().is_some_and(|f| has_flag(f, "unknown"));
2358 let sequence = vec_field(
2359 sequences.first().copied(),
2360 "a `sequence` field is a `Vec<T>` of an `Occurrence` enum",
2361 )?;
2362 let sequence_ty = sequence.as_ref().map(|(_, ty)| ty);
2363 let unknowns: Vec<&syn::Field> = named.named.iter().filter(|f| is_unknown(f)).collect();
2365 if let Some(extra) = unknowns.get(1) {
2366 return Err(syn::Error::new(
2367 extra.span(),
2368 "a struct has at most one `unknown` field",
2369 ));
2370 }
2371 let unknown = vec_field(
2372 unknowns.first().copied(),
2373 "an `unknown` field is a `Vec<T>`",
2374 )?;
2375 let flattens: Vec<(Ident, Type)> = named
2377 .named
2378 .iter()
2379 .filter(|f| is_flatten(f))
2380 .map(|f| (f.ident.clone().expect("named field"), f.ty.clone()))
2381 .collect();
2382 if !flattens.is_empty() && !input.generics.params.is_empty() {
2383 return Err(syn::Error::new(
2384 input.generics.span(),
2385 "`flatten` in a generic struct is not supported yet",
2386 ));
2387 }
2388 check_duplicates(&fields)?;
2389 let positionals: Vec<&Field> = fields
2392 .iter()
2393 .filter(|f| f.is_positional() && f.double_dash() != DoubleDash::Required)
2394 .collect();
2395 check_positional_order(&positionals)?;
2396 let trailing: Vec<&Field> = fields
2397 .iter()
2398 .filter(|f| f.double_dash() == DoubleDash::Required)
2399 .collect();
2400 if let Some(extra) = trailing.get(1) {
2401 return Err(syn::Error::new(
2402 extra.ident.span(),
2403 "a struct has at most one `double_dash = \"required\"` positional",
2404 ));
2405 }
2406 let trailing = trailing.first().copied();
2407 let preserving: Vec<usize> = positionals
2409 .iter()
2410 .enumerate()
2411 .filter(|(_, f)| f.double_dash() == DoubleDash::Preserve)
2412 .map(|(i, _)| i)
2413 .collect();
2414 if let (Some(t), false) = (trailing, preserving.is_empty()) {
2415 return Err(syn::Error::new(
2416 t.ident.span(),
2417 "a `double_dash = \"preserve\"` positional keeps the `--` this one waits for",
2418 ));
2419 }
2420 let StructOptions {
2421 groups,
2422 restart_token,
2423 default_subcommand,
2424 arg_required_else_help,
2425 name: program_name,
2426 version,
2427 about,
2428 long_about,
2429 after_help,
2430 after_long_help,
2431 disable_help_flag,
2432 disable_help_short,
2433 disable_version_flag,
2434 disable_help_subcommand,
2435 unknown_flags_value,
2436 long_only,
2437 plus_options,
2438 } = struct_options(input)?;
2439 let (doc_about, doc_long_about) = docs(&input.attrs);
2440 let about = about.unwrap_or(doc_about);
2441 let long_about = long_about.unwrap_or(doc_long_about);
2442 let (about_text, long_about_text) = (about, long_about);
2443 let (after_text, after_long_text) = (after_help, after_long_help);
2444 let (about, long_about) = (text(&about_text), text(&long_about_text));
2445 let (after_help, after_long_help) = (text(&after_text), text(&after_long_text));
2446 let rules = Rules::new(&fields, &groups)?;
2447
2448 let name = &input.ident;
2449 let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
2450
2451 let slot_parts: Vec<(Ident, TokenStream2, TokenStream2)> = fields
2453 .iter()
2454 .map(|f| {
2455 let none = quote!(::core::option::Option::None);
2456 let new = quote!(::std::vec::Vec::new());
2457 let (ty, init) = match &f.kind {
2458 _ if f.keywords => (quote!(::std::vec::Vec<&__wa::BStr>), new),
2460 Kind::Switch if f.negate.is_some() => (quote!(::core::option::Option<bool>), none),
2461 Kind::Switch => (quote!(bool), quote!(false)),
2462 Kind::Count(ty) => (quote!(#ty), quote!(0)),
2463 Kind::Optional(ty) | Kind::Required(ty) => {
2464 (quote!(::core::option::Option<#ty>), none)
2465 }
2466 Kind::Many(ty) => (quote!(::std::vec::Vec<#ty>), new),
2467 };
2468 (slot(&f.ident), ty, init)
2469 })
2470 .collect();
2471 let slots = slot_parts
2472 .iter()
2473 .map(|(ident, ty, init)| quote!(let mut #ident: #ty = #init;));
2474
2475 let store = |f: &Field| {
2477 let ident = slot(&f.ident);
2478 let displace = rules.displace(&fields, f);
2479 let stored = match &f.kind {
2480 _ if f.keywords => {
2481 let read = f.read();
2482 quote! {
2483 let __value = #read;
2484 #ident.push(__value);
2485 }
2486 }
2487 _ if f.tristate || f.negate.is_some() => quote! {
2488 __arg.check_switch()?;
2489 #ident = ::core::option::Option::Some(true);
2490 },
2491 Kind::Switch => quote! {
2492 __arg.check_switch()?;
2493 #ident = true;
2494 },
2495 Kind::Count(_) => quote! {
2496 __arg.check_switch()?;
2497 #ident = #ident.saturating_add(1);
2498 },
2499 Kind::Required(ty) => {
2500 let value = f.flag_value(ty, quote!(__value));
2501 let read = f.read();
2502 quote! {
2503 let __value = #read;
2504 #ident = ::core::option::Option::Some(#value);
2505 }
2506 }
2507 Kind::Optional(ty) => {
2508 let value = f.flag_value(ty, quote!(__value));
2509 match &f.default_missing {
2510 Some(_) => {
2514 let read = f.read();
2515 quote! {
2516 let __value = #read;
2517 #ident = ::core::option::Option::Some(#value);
2518 }
2519 }
2520 None if f.require_equals => {
2521 let options = f.value_options();
2522 quote! {
2523 if let ::core::option::Option::Some(__value) =
2524 __arg.read_value_opt_with(__input, #options)
2525 {
2526 #ident = ::core::option::Option::Some(#value);
2527 }
2528 }
2529 }
2530 None => {
2531 let read = f.read();
2532 quote! {
2533 let __value = #read;
2534 #ident = ::core::option::Option::Some(#value);
2535 }
2536 }
2537 }
2538 }
2539 Kind::Many(ty) => match f.delimiter {
2540 None => {
2541 let value = f.flag_value(ty, quote!(__value));
2542 let read = f.read();
2543 let more = match f.values_max {
2544 None => (f.values > 1).then(|| {
2546 let more = f.values - 1;
2547 quote! {
2548 for _ in 0..#more {
2549 let __value = __arg.read_next(__input)?;
2550 #ident.push(#value);
2551 }
2552 }
2553 }),
2554 Some(max) => {
2556 let (min, options) = (f.values, f.value_options());
2557 let terminator = match &f.value_terminator {
2558 Some(word) => {
2559 let word = LitByteStr::new(word.as_bytes(), Span::call_site());
2560 quote!(::core::option::Option::Some(__wa::BStr::new(#word)))
2561 }
2562 None => quote!(::core::option::Option::None),
2563 };
2564 Some(quote! {
2565 let mut __taken = 1usize;
2566 while __taken < #max {
2567 let ::core::option::Option::Some(__value) =
2568 __arg.read_more(__input, #options, #terminator)
2569 else {
2570 break;
2571 };
2572 #ident.push(#value);
2573 __taken += 1;
2574 }
2575 __arg.check_values(__taken, #min)?;
2576 })
2577 }
2578 };
2579 quote! {
2580 let __value = #read;
2581 #ident.push(#value);
2582 #more
2583 }
2584 }
2585 Some(d) => {
2586 let value = f.flag_value(ty, quote!(__piece));
2587 let read = f.read();
2588 quote! {
2589 for __piece in __wa::split(#read, #d) {
2590 #ident.push(#value);
2591 }
2592 }
2593 }
2594 },
2595 };
2596 quote!(#stored #displace)
2597 };
2598
2599 let negated = |f: &Field| {
2600 let no = LitByteStr::new(f.negate.as_ref()?.as_bytes(), Span::call_site());
2601 let ident = slot(&f.ident);
2602 let displace = rules.displace(&fields, f);
2603 Some((
2604 quote!(#no),
2605 quote! {
2606 __arg.check_switch()?;
2607 #ident = ::core::option::Option::Some(false);
2608 #displace
2609 },
2610 ))
2611 };
2612 let plus = |f: &Field| {
2614 let letter = LitChar::new(f.plus?, Span::call_site());
2615 let body = if f.tristate {
2616 let ident = slot(&f.ident);
2617 let displace = rules.displace(&fields, f);
2618 quote!(#ident = ::core::option::Option::Some(false); #displace)
2619 } else {
2620 store(f)
2621 };
2622 Some((quote!(#letter), body))
2623 };
2624 type Part<'a> = &'a dyn Fn(&Field) -> Option<(TokenStream2, TokenStream2)>;
2627 let arms = |keep: &dyn Fn(&Field) -> bool, tail: TokenStream2| {
2628 let of = |part: Part<'_>| -> Vec<TokenStream2> {
2629 fields
2630 .iter()
2631 .filter(|f| keep(f))
2632 .filter_map(part)
2633 .map(|(pattern, body)| quote!(#pattern => { #body #tail }))
2634 .collect()
2635 };
2636 let mut long = of(&|f| Some((f.long_pattern()?, store(f))));
2637 long.extend(of(&negated));
2638 let short = of(&|f| Some((f.short_pattern()?, store(f))));
2639 (long, short, of(&plus))
2640 };
2641 let (long_arms, short_arms, plus_arms) = arms(&|_| true, quote!());
2642
2643 let unexpected = quote!(return ::core::result::Result::Err(__arg.unexpected()));
2644 let subcommands: Vec<&Field> = fields
2645 .iter()
2646 .filter(|f| matches!(f.role, Role::Subcommand))
2647 .collect();
2648 if let Some(extra) = subcommands.get(1) {
2649 return Err(syn::Error::new(
2650 extra.ident.span(),
2651 "a struct has at most one subcommand field",
2652 ));
2653 }
2654 let subcommand = subcommands.first();
2655 let track_filled = subcommand.is_some() && !positionals.is_empty();
2657 let filled = track_filled.then(|| quote!(__filled = true;));
2658
2659 let overflow = match trailing {
2662 Some(t) => {
2663 let display = LitStr::new(&t.display(), Span::call_site());
2664 quote! {
2665 return ::core::result::Result::Err(
2666 __wa::Error::requires_double_dash(__word.offset, #display),
2667 )
2668 }
2669 }
2670 None => unexpected.clone(),
2671 };
2672 let positional_match = if positionals.is_empty() {
2673 quote!({ #overflow; })
2674 } else {
2675 let arms = positionals.iter().enumerate().map(|(i, f)| {
2676 let ident = slot(&f.ident);
2677 let display = LitStr::new(&f.display(), Span::call_site());
2678 let stop = (f.double_dash() == DoubleDash::Automatic || f.stop_flags)
2679 .then(|| quote!(__input.stop_flags();));
2680 match &f.kind {
2681 Kind::Optional(ty) | Kind::Required(ty) => {
2682 let value = f.word_value(ty, quote!(__word), &display);
2683 quote! {
2684 #i => {
2685 #ident = ::core::option::Option::Some(#value);
2686 __position += 1;
2687 #filled
2688 #stop
2689 }
2690 }
2691 }
2692 Kind::Many(ty) => {
2693 let push = match f.delimiter {
2694 None => {
2695 let value = f.word_value(ty, quote!(__word), &display);
2696 quote! {
2699 if #ident.capacity() == 0 {
2700 #ident.reserve_exact(__input.words_left() + 1);
2701 }
2702 #ident.push(#value);
2703 }
2704 }
2705 Some(d) => {
2706 let value = f.word_value(ty, quote!(__piece), &display);
2707 quote! {
2708 for __piece in __word.split(#d) {
2709 #ident.push(#value);
2710 }
2711 }
2712 }
2713 };
2714 quote! {
2715 #i => {
2716 #push
2717 #filled
2718 #stop
2719 }
2720 }
2721 }
2722 Kind::Switch | Kind::Count(_) => {
2723 unreachable!("rejected for positionals in `field`")
2724 }
2725 }
2726 });
2727 quote! {
2728 match __position {
2729 #(#arms)*
2730 _ => { #overflow; }
2731 }
2732 }
2733 };
2734 let positional_match = match trailing {
2735 None => positional_match,
2736 Some(t) => {
2737 let ident = slot(&t.ident);
2738 let display = LitStr::new(&t.display(), Span::call_site());
2739 let store = match &t.kind {
2740 Kind::Many(ty) => match t.delimiter {
2741 None => {
2742 let value = t.word_value(ty, quote!(__word), &display);
2743 quote!(#ident.push(#value);)
2744 }
2745 Some(d) => {
2746 let value = t.word_value(ty, quote!(__piece), &display);
2747 quote!(for __piece in __word.split(#d) { #ident.push(#value); })
2748 }
2749 },
2750 Kind::Optional(ty) | Kind::Required(ty) => {
2751 let value = t.word_value(ty, quote!(__word), &display);
2752 quote! {
2753 if #ident.is_some() { #unexpected; }
2754 #ident = ::core::option::Option::Some(#value);
2755 }
2756 }
2757 Kind::Switch | Kind::Count(_) => {
2758 unreachable!("rejected for positionals in `field`")
2759 }
2760 };
2761 quote! {
2762 if __word.after_separator {
2763 #store
2764 } else {
2765 #positional_match
2766 }
2767 }
2768 }
2769 };
2770 let route = subcommand.map(|f| {
2771 let ident = slot(&f.ident);
2772 let ty = subcommand_type(f);
2773 let not_filled = track_filled.then(|| quote!(!__filled &&));
2774 let (global_longs, global_shorts, global_plus) = arms(
2775 &Field::is_global,
2776 quote!(return ::core::result::Result::Ok(true);),
2777 );
2778 let (inherit, handler) = if fields.iter().any(Field::is_global) {
2780 let shorts = fields.iter().filter(|f| f.is_global()).flat_map(|f| {
2781 let takes_value = f.takes_value();
2782 f.shorts().into_iter().map(move |c| {
2783 let c = LitChar::new(c, Span::call_site());
2784 quote!((#c, #takes_value))
2785 })
2786 });
2787 let setup = quote! {
2788 let mut __inherit = __wa::inherit(&[#(#shorts),*], __globals, |__arg, __input, __parent| {
2789 match __arg {
2790 __wa::Arg::Long(__flag) => match __flag.name {
2791 #(#global_longs)*
2792 _ => {}
2793 },
2794 __wa::Arg::Short(__flag) if __flag.plus => match __flag.letter {
2795 #(#global_plus)*
2796 _ => {}
2797 },
2798 __wa::Arg::Short(__flag) => match __flag.letter {
2799 #(#global_shorts)*
2800 _ => {}
2801 },
2802 _ => {}
2803 }
2804 __parent.bind(__arg, __input)
2805 });
2806 };
2807 (setup, quote!(&mut __inherit))
2808 } else {
2809 (quote!(), quote!(&mut *__globals))
2810 };
2811 let help_word = (!disable_help_subcommand).then(|| {
2813 quote! {
2814 if &**__word.value == b"help" {
2815 return ::core::result::Result::Err(
2816 __wa::help_word(<Self as ::winnow_args::Args>::HELP, __input),
2817 );
2818 }
2819 }
2820 });
2821 let fallback = default_subcommand.as_ref().map(|name| {
2823 let name = LitByteStr::new(name.as_bytes(), Span::call_site());
2824 quote! {
2825 let __sub = {
2826 #inherit
2827 *__input = __start;
2828 <#ty as __wa::Subcommand>::parse_subcommand(#name, __input, #handler)?
2829 };
2830 #ident = ::core::option::Option::Some(__sub);
2831 break;
2832 }
2833 });
2834 quote! {
2835 if #not_filled !__word.after_separator {
2836 if <#ty as __wa::Subcommand>::has(&**__word.value) {
2837 let __sub = {
2838 #inherit
2839 <#ty as __wa::Subcommand>::parse_subcommand(
2840 &**__word.value, __input, #handler,
2841 )?
2842 };
2843 #ident = ::core::option::Option::Some(__sub);
2844 break;
2845 }
2846 #help_word
2847 #fallback
2848 }
2849 }
2850 });
2851 let restart = restart_token.map(|token| {
2853 let token = LitByteStr::new(token.as_bytes(), Span::call_site());
2854 let clears = fields.iter().filter(|f| f.is_positional()).map(|f| {
2855 let slot = slot(&f.ident);
2856 match f.kind {
2857 Kind::Many(_) => quote!(#slot.clear();),
2858 _ => quote!(#slot = ::core::option::Option::None;),
2859 }
2860 });
2861 let position = (!positionals.is_empty()).then(|| quote!(__position = 0;));
2862 let filled = track_filled.then(|| quote!(__filled = false;));
2863 quote! {
2864 if &**__word.value == #token {
2865 #(#clears)*
2866 #position
2867 #filled
2868 __input.resume_flags();
2869 continue;
2870 }
2871 }
2872 });
2873 let sequence_word = sequence_ty.map(|ty| {
2874 quote! {
2875 if let ::core::option::Option::Some(__item) =
2876 <#ty as ::winnow_args::Occurrence>::from_word(&__word)?
2877 {
2878 __sequence.push(__item);
2879 continue;
2880 }
2881 }
2882 });
2883 let word_arm = quote! {
2884 __wa::Arg::Word(__word) => {
2885 #restart
2886 #route
2887 #sequence_word
2888 #positional_match
2889 }
2890 };
2891 let separator_arm = if preserving.is_empty() {
2892 quote!(__wa::Arg::Separator { .. } => {})
2893 } else {
2894 quote! {
2895 __wa::Arg::Separator { offset: __offset } => {
2896 if matches!(__position, #(#preserving)|*) {
2897 __input.resume_flags();
2898 let __word = __wa::Word {
2899 value: __wa::BStr::new(b"--"),
2900 offset: __offset,
2901 after_separator: false,
2902 };
2903 #positional_match
2904 }
2905 }
2906 }
2907 };
2908 let position = (!positionals.is_empty()).then(|| quote!(let mut __position: usize = 0;));
2909 let filled_slot = track_filled.then(|| quote!(let mut __filled = false;));
2910
2911 let opted: Vec<usize> = positionals
2914 .iter()
2915 .enumerate()
2916 .filter(|(_, f)| f.negative_numbers)
2917 .map(|(i, _)| i)
2918 .collect();
2919 let (unknown_slot, unknown_build) = match &unknown {
2920 Some((ident, ty)) => (
2921 quote!(let mut __unknown: ::std::vec::Vec<#ty> = ::std::vec::Vec::new();),
2922 quote!(#ident: __unknown,),
2923 ),
2924 None => (quote!(), quote!()),
2925 };
2926 let (sequence_slot, sequence_arg, sequence_build) = match &sequence {
2927 Some((ident, ty)) => (
2928 quote! {
2930 let mut __sequence: ::std::vec::Vec<#ty> =
2931 ::std::vec::Vec::with_capacity(__input.words_left());
2932 },
2933 quote! {
2934 if let ::core::option::Option::Some(__item) =
2935 <#ty as ::winnow_args::Occurrence>::from_arg(&__arg, __input)?
2936 {
2937 __sequence.push(__item);
2938 } else
2939 },
2940 quote!(#ident: __sequence,),
2941 ),
2942 None => (quote!(), quote!(), quote!()),
2943 };
2944 let bundle_start = sequence_ty.map(|_| {
2948 quote! {
2949 let __bundle_word = __input.front();
2950 let __bundle_start = __input.mode() == ::winnow_args::stream::Mode::Word;
2951 let __bundle_at = __sequence.len();
2952 }
2953 });
2954 let bundle_end = sequence_ty.map(|ty| {
2955 quote! {
2956 if <#ty as ::winnow_args::Occurrence>::BUNDLES
2957 && __bundle_start
2958 && matches!(__arg, __wa::Arg::Short(__flag) if !__flag.plus)
2959 && __input.mode() == ::winnow_args::stream::Mode::Bundle
2960 {
2961 let __word = __wa::Word {
2962 value: __wa::BStr::new(__bundle_word),
2963 offset: __arg.offset(),
2964 after_separator: false,
2965 };
2966 if let ::core::option::Option::Some(__bundle) =
2967 <#ty as ::winnow_args::Occurrence>::from_bundle(&__word)?
2968 {
2969 __sequence.insert(__bundle_at, __bundle);
2970 }
2971 }
2972 }
2973 });
2974 let flat_slot = |ident: &Ident| format_ident!("__flat_{}", ident);
2977 let flatten_slots = flattens.iter().map(|(ident, ty)| {
2978 let slot = flat_slot(ident);
2979 quote! {
2980 let mut #slot = <<#ty as __wa::Flatten>::Slots as ::core::default::Default>::default();
2981 }
2982 });
2983 let flatten_arg: TokenStream2 = flattens
2984 .iter()
2985 .map(|(ident, ty)| {
2986 let slot = flat_slot(ident);
2987 quote!(if <#ty as __wa::Flatten>::bind(&mut #slot, &__arg, __input)? {} else)
2988 })
2989 .collect();
2990 let flatten_build = flattens.iter().map(|(ident, ty)| {
2991 let slot = flat_slot(ident);
2992 quote!(#ident: <#ty as __wa::Flatten>::finish(#slot, __input)?,)
2993 });
2994 let flatten_shorts: Vec<TokenStream2> = flattens
2995 .iter()
2996 .map(|(_, ty)| {
2997 quote! {
2998 __c if <#ty as __wa::Flatten>::short(__c).is_some() => {
2999 <#ty as __wa::Flatten>::short(__c)
3000 }
3001 }
3002 })
3003 .collect();
3004 let flatten_longs: Vec<TokenStream2> = flattens
3005 .iter()
3006 .map(|(_, ty)| quote!(|| <#ty as __wa::Flatten>::is_long(__name)))
3007 .collect();
3008 let own_shorts: Vec<TokenStream2> = fields
3010 .iter()
3011 .filter_map(|f| {
3012 let letters = f.short_pattern()?;
3013 let takes_value = f.takes_value();
3014 Some(quote!(#letters => ::core::option::Option::Some(#takes_value),))
3015 })
3016 .collect();
3017 let own_longs: Vec<String> = fields
3020 .iter()
3021 .filter(|f| !f.two_dashes)
3022 .flat_map(|f| spellings(f).into_iter().map(str::to_owned))
3023 .collect();
3024 let prefixes: Vec<u8> = fields
3025 .iter()
3026 .filter(|f| f.prefix)
3027 .filter_map(|f| f.short().map(|c| c as u8))
3028 .collect();
3029 let flatten_prefixes = flattens.iter().map(|(_, ty)| {
3030 quote!(&& !__name.first().is_some_and(|__c| <#ty as __wa::Flatten>::is_prefix(*__c)))
3031 });
3032 let lexer = if plus_options {
3033 quote!(__wa::arg_plus)
3034 } else {
3035 quote!(__wa::arg)
3036 };
3037 if !plus_options && let Some(f) = fields.iter().find(|f| f.plus.is_some()) {
3038 return Err(syn::Error::new(
3039 f.ident.span(),
3040 "`plus` needs `#[arg(plus_options)]` on the struct",
3041 ));
3042 }
3043 let plus_match = plus_options.then(|| {
3044 quote! {
3045 __wa::Arg::Short(__flag) if __flag.plus => match __flag.letter {
3046 #(#plus_arms)*
3047 _ => #flatten_arg if !__globals.bind(&__arg, __input)? { #unexpected; }
3048 },
3049 }
3050 });
3051 let lex = if opted.is_empty() {
3052 quote!(#lexer(__input)?)
3053 } else {
3054 let digits: Vec<LitByteStr> = fields
3055 .iter()
3056 .filter_map(|f| f.short().filter(char::is_ascii_digit))
3057 .map(|c| LitByteStr::new(format!("-{c}").as_bytes(), Span::call_site()))
3058 .collect();
3059 let not_a_short =
3060 (!digits.is_empty()).then(|| quote!(&&!matches!(__input.front(), #(#digits)|*)));
3061 quote! {
3062 match matches!(__position, #(#opted)|*) #not_a_short {
3063 true => match __wa::number(__input) {
3064 ::core::result::Result::Ok(__word) => __wa::Arg::Word(__word),
3065 ::core::result::Result::Err(_) => #lexer(__input)?,
3066 },
3067 false => #lexer(__input)?,
3068 }
3069 }
3070 };
3071 let start = default_subcommand
3072 .is_some()
3073 .then(|| quote!(let __start = *__input;));
3074 let help_on_empty = arg_required_else_help.then(|| {
3075 quote! {
3076 if __input.is_empty() {
3077 return ::core::result::Result::Err(
3078 __wa::Error::bare_help(<Self as ::winnow_args::Args>::HELP),
3079 );
3080 }
3081 }
3082 });
3083 if default_subcommand.is_some() && subcommand.is_none() {
3084 return Err(syn::Error::new(
3085 input.ident.span(),
3086 "`default_subcommand` needs an `#[arg(subcommand)]` field",
3087 ));
3088 }
3089 let declares_long = |name: &str| fields.iter().any(|f| f.longs().contains(&name));
3092 let declares_short = |c: char| fields.iter().any(|f| f.shorts().contains(&c));
3093 let help = quote!(<Self as ::winnow_args::Args>::HELP);
3094 let help_long = (!disable_help_flag && !declares_long("help")).then(
3095 || quote!(b"help" => return ::core::result::Result::Err(__wa::Error::help(#help, true)),),
3096 );
3097 let help_short = (!disable_help_flag && !disable_help_short && !declares_short('h')).then(
3098 || quote!('h' => return ::core::result::Result::Err(__wa::Error::help(#help, false)),),
3099 );
3100 let with_version = version.is_some() && !disable_version_flag;
3101 let version_long = (with_version && !declares_long("version")).then(
3102 || quote!(b"version" => return ::core::result::Result::Err(__wa::Error::version(#help)),),
3103 );
3104 let version_short = (with_version && !declares_short('V'))
3105 .then(|| quote!('V' => return ::core::result::Result::Err(__wa::Error::version(#help)),));
3106 let help_flag = help_long.is_some();
3108 let help_short_flag = help_short.is_some();
3109 let version_short_flag = version_short.is_some();
3110 let late = !flattens.is_empty() || sequence.is_some();
3114 let late_if = |on: bool, test: TokenStream2, error: TokenStream2| {
3115 (late && on).then(|| quote!(if #test { return ::core::result::Result::Err(#error); } else))
3116 };
3117 let late_long: TokenStream2 = [
3118 late_if(
3119 help_long.is_some(),
3120 quote!(__flag.name == b"help"),
3121 quote!(__wa::Error::help(#help, true)),
3122 ),
3123 late_if(
3124 version_long.is_some(),
3125 quote!(__flag.name == b"version"),
3126 quote!(__wa::Error::version(#help)),
3127 ),
3128 ]
3129 .into_iter()
3130 .flatten()
3131 .collect();
3132 let late_short: TokenStream2 = [
3133 late_if(
3134 help_short.is_some(),
3135 quote!(__flag.letter == 'h'),
3136 quote!(__wa::Error::help(#help, false)),
3137 ),
3138 late_if(
3139 version_short.is_some(),
3140 quote!(__flag.letter == 'V'),
3141 quote!(__wa::Error::version(#help)),
3142 ),
3143 ]
3144 .into_iter()
3145 .flatten()
3146 .collect();
3147 let keep = |arm: Option<TokenStream2>| arm.filter(|_| !late);
3148 let (help_long, help_short) = (keep(help_long), keep(help_short));
3149 let (version_long, version_short) = (keep(version_long), keep(version_short));
3150
3151 let (lenient_bundle, save_token, unknown_flag) = if unknown_flags_value
3161 || unknown.is_some()
3162 || sequence_unknown
3163 {
3164 let next = (!positionals.is_empty() || trailing.is_some()).then(|| quote!(continue;));
3166 let (sink, sink_next) = match (&unknown, sequence_ty.filter(|_| sequence_unknown)) {
3167 (_, Some(ty)) => (
3168 quote! {
3169 match <#ty as ::winnow_args::Occurrence>::from_unknown(&__word)? {
3170 ::core::option::Option::Some(__item) => __sequence.push(__item),
3171 ::core::option::Option::None => {
3172 return ::core::result::Result::Err(__wa::Error::unknown_flag(
3173 __word.offset,
3174 ::std::string::String::from_utf8_lossy(&__word.value),
3175 ));
3176 }
3177 }
3178 },
3179 quote!(continue;),
3180 ),
3181 (Some((_, ty)), None) => (
3182 quote!(__unknown.push(__word.convert::<#ty>("<UNKNOWN>")?);),
3183 quote!(continue;),
3184 ),
3185 (None, None) => (
3186 quote! {
3187 let __arg = __wa::Arg::Word(__word);
3188 #positional_match
3189 },
3190 quote!(#next),
3191 ),
3192 };
3193 let theirs = match sequence_ty {
3194 Some(ty) => quote! {
3195 __c => match <#ty as ::winnow_args::Occurrence>::short(__c) {
3196 ::core::option::Option::None => __globals.short(__c),
3197 __known => __known,
3198 },
3199 },
3200 None => quote!(_ => __globals.short(__c),),
3201 };
3202 let builtin = help_short_flag
3204 .then(|| quote!('h' => ::core::option::Option::Some(false),))
3205 .into_iter()
3206 .chain(version_short_flag.then(|| quote!('V' => ::core::option::Option::Some(false),)));
3207 (
3208 quote! {
3209 if let ::core::option::Option::Some(__word) = __wa::unknown_bundle(__input, |__c| match __c {
3210 #(#own_shorts)*
3211 #(#builtin)*
3212 #(#flatten_shorts)*
3213 #theirs
3214 }) {
3215 #sink
3216 #sink_next
3217 }
3218 },
3219 quote!(let __token = __input.front();),
3220 quote! {
3221 let __word = __wa::Word {
3222 value: __wa::BStr::new(__token),
3223 offset: __flag.offset,
3224 after_separator: false,
3225 };
3226 #sink
3227 },
3228 )
3229 } else {
3230 (quote!(), quote!(), quote!(#unexpected;))
3231 };
3232 let mut names = Vec::new();
3233 if long_only {
3234 names.clone_from(&own_longs);
3235 names.extend(help_long.is_some().then(|| "help".to_owned()));
3236 names.extend(version_long.is_some().then(|| "version".to_owned()));
3237 }
3238 let long_only_lex =
3239 long_only && (!names.is_empty() || sequence_ty.is_some() || !flattens.is_empty());
3240 let lex = if long_only_lex {
3241 let own = matches_name(&names);
3242 let (their_names, their_prefixes) = match sequence_ty {
3243 Some(ty) => (
3244 quote!(|| <#ty as ::winnow_args::Occurrence>::is_long(__name)),
3245 quote! {
3246 && !__name.first().is_some_and(|__c| {
3247 <#ty as ::winnow_args::Occurrence>::PREFIXES.contains(__c)
3248 })
3249 },
3250 ),
3251 None => (quote!(), quote!()),
3252 };
3253 let not_prefixed = (!prefixes.is_empty()).then(
3254 || quote!(!matches!(__name.first(), ::core::option::Option::Some(#(#prefixes)|*)) &&),
3255 );
3256 quote! {
3257 match __wa::long_only(__input, |__name| {
3258 #not_prefixed (#own #their_names #(#flatten_longs)*) #their_prefixes #(#flatten_prefixes)*
3259 }) {
3260 ::core::option::Option::Some(__arg) => __arg,
3261 ::core::option::Option::None => {
3262 #lenient_bundle
3263 #lex
3264 }
3265 }
3266 }
3267 } else {
3268 lex
3269 };
3270 let lenient_bundle = if long_only_lex {
3271 quote!()
3272 } else {
3273 lenient_bundle
3274 };
3275
3276 let help_version = match &version {
3277 Some(v) if with_version => quote!(::core::option::Option::Some(#v)),
3278 _ => quote!(::core::option::Option::None),
3279 };
3280 let help_items = fields
3281 .iter()
3282 .filter(|f| !matches!(f.role, Role::Subcommand))
3283 .map(|f| f.help_item());
3284 let (help_subcommands, subcommand_required) = match subcommand {
3285 Some(f) => {
3286 let ty = subcommand_type(f);
3287 (
3288 quote!(<#ty as ::winnow_args::Args>::HELP.subcommands),
3289 matches!(f.kind, Kind::Required(_)),
3290 )
3291 }
3292 None => (quote!(&[]), false),
3293 };
3294 let displaced = rules.displaced_slots(&fields);
3295 let env_fallbacks: Vec<TokenStream2> = fields
3296 .iter()
3297 .map(|f| f.fallback(true, rules.is_displaced(&fields, f)))
3298 .collect();
3299 let default_fallbacks: Vec<TokenStream2> = fields
3300 .iter()
3301 .map(|f| f.fallback(false, rules.is_displaced(&fields, f)))
3302 .collect();
3303 let exclusive = rules.exclusive(&fields, &groups);
3304 let supplied = rules.supplied(&fields);
3305 let default_ifs = rules.default_ifs(&fields);
3306 let required = rules.required(&fields, &groups);
3307 let build = fields.iter().map(|f| {
3308 let ident = &f.ident;
3309 let slot = slot(ident);
3310 match &f.kind {
3311 Kind::Required(ty) if f.keywords => {
3312 let display = LitStr::new(&f.display(), Span::call_site());
3313 quote!(#ident: __wa::keywords::<#ty>(&#slot, #display, __input.offset())?)
3314 }
3315 Kind::Switch if f.negate.is_some() => quote!(#ident: #slot.unwrap_or(false)),
3316 Kind::Switch | Kind::Count(_) | Kind::Optional(_) | Kind::Many(_) => {
3317 quote!(#ident: #slot)
3318 }
3319 Kind::Required(_) => {
3320 let display = LitStr::new(&f.display(), Span::call_site());
3321 let error = match f.role {
3322 Role::Positional { .. } => {
3323 quote!(__wa::Error::missing_argument(__input.offset(), #display))
3324 }
3325 Role::Flag { .. } => {
3326 quote!(__wa::Error::missing_required(__input.offset(), #display))
3327 }
3328 Role::Subcommand => quote!(__wa::Error::missing_subcommand(__input.offset())),
3329 };
3330 quote! {
3331 #ident: match #slot {
3332 ::core::option::Option::Some(v) => v,
3333 ::core::option::Option::None => {
3334 return ::core::result::Result::Err(#error);
3335 }
3336 }
3337 }
3338 }
3339 }
3340 });
3341 let build: Vec<TokenStream2> = build.collect();
3342 let field_reads: Vec<TokenStream2> = fields
3346 .iter()
3347 .map(|f| &f.ident)
3348 .chain(skipped.iter().copied())
3349 .chain(sequence.iter().chain(&unknown).map(|(ident, _)| ident))
3350 .chain(flattens.iter().map(|(ident, _)| ident))
3351 .map(|ident| quote!(let _ = &__built.#ident;))
3352 .collect();
3353 let flatten_build: Vec<TokenStream2> = flatten_build.collect();
3354 let flattenable = positionals.is_empty()
3357 && trailing.is_none()
3358 && subcommand.is_none()
3359 && sequence.is_none()
3360 && unknown.is_none()
3361 && !unknown_flags_value
3362 && input.generics.params.is_empty()
3363 && !fields.iter().any(|f| f.keywords || f.is_global());
3364 let flatten_impl = flattenable.then(|| {
3365 let vis = &input.vis;
3366 let slots_name = format_ident!("__{}WinnowArgsSlots", name);
3367 let displaced_idents: Vec<Ident> = fields
3368 .iter()
3369 .filter(|f| rules.is_displaced(&fields, f))
3370 .map(|f| displaced_flag(&f.ident))
3371 .collect();
3372 let nested: Vec<(Ident, &Type)> = flattens
3373 .iter()
3374 .map(|(ident, ty)| (flat_slot(ident), ty))
3375 .collect();
3376 let decls = slot_parts.iter().map(|(ident, ty, _)| quote!(#ident: #ty,));
3377 let inits = slot_parts
3378 .iter()
3379 .map(|(ident, _, init)| quote!(#ident: #init,));
3380 let all: Vec<&Ident> = slot_parts
3381 .iter()
3382 .map(|(ident, _, _)| ident)
3383 .chain(&displaced_idents)
3384 .chain(nested.iter().map(|(n, _)| n))
3385 .collect();
3386 let names: Vec<TokenStream2> = all.iter().map(|ident| quote!(#ident,)).collect();
3387 let mutable: Vec<TokenStream2> = all.iter().map(|ident| quote!(mut #ident,)).collect();
3388 let displaced_decls = displaced_idents.iter().map(|d| quote!(#d: bool,));
3389 let displaced_inits = displaced_idents.iter().map(|d| quote!(#d: false,));
3390 let nested_decls = nested
3391 .iter()
3392 .map(|(n, ty)| quote!(#n: <#ty as ::winnow_args::__private::Flatten>::Slots,));
3393 let nested_inits = nested
3394 .iter()
3395 .map(|(n, _)| quote!(#n: ::core::default::Default::default(),));
3396 let (bind_long, bind_short, bind_plus) = arms(&|_| true, quote!(true));
3397 let bind_nested: TokenStream2 = nested
3398 .iter()
3399 .map(|(n, ty)| {
3400 quote!(if <#ty as __wa::Flatten>::bind(&mut #n, &__arg, __input)? { true } else)
3401 })
3402 .collect();
3403 let plus = if plus_options {
3405 quote! {
3406 __wa::Arg::Short(__flag) if __flag.plus => match __flag.letter {
3407 #(#bind_plus)*
3408 _ => #bind_nested { false }
3409 },
3410 }
3411 } else {
3412 quote!(__wa::Arg::Short(__flag) if __flag.plus => #bind_nested { false },)
3413 };
3414 let own_longs = matches_name(&own_longs);
3415 let own_prefixes = if prefixes.is_empty() {
3416 quote!(false)
3417 } else {
3418 quote!(matches!(__c, #(#prefixes)|*))
3419 };
3420 let nested_prefixes = nested
3421 .iter()
3422 .map(|(_, ty)| quote!(|| <#ty as __wa::Flatten>::is_prefix(__c)));
3423 quote! {
3424 #[doc(hidden)]
3425 #[allow(clippy::all, clippy::pedantic, clippy::nursery, clippy::restriction)]
3426 #vis struct #slots_name {
3427 #(#decls)*
3428 #(#displaced_decls)*
3429 #(#nested_decls)*
3430 }
3431
3432 #[automatically_derived]
3433 impl ::core::default::Default for #slots_name {
3434 fn default() -> Self {
3435 Self {
3436 #(#inits)*
3437 #(#displaced_inits)*
3438 #(#nested_inits)*
3439 }
3440 }
3441 }
3442
3443 #[automatically_derived]
3444 #[allow(
3445 unused_mut,
3446 unused_variables,
3447 unreachable_code,
3448 clippy::all,
3449 clippy::pedantic,
3450 clippy::nursery,
3451 clippy::restriction
3452 )]
3453 impl ::winnow_args::__private::Flatten for #name {
3454 type Slots = #slots_name;
3455
3456 fn bind<'__i>(
3457 __slots: &mut #slots_name,
3458 __arg: &::winnow_args::Arg<'__i>,
3459 __input: &mut ::winnow_args::Argv<'__i>,
3460 ) -> ::core::result::Result<bool, ::winnow_args::Error> {
3461 use ::winnow_args::__private as __wa;
3462 let __arg = *__arg;
3463 let #slots_name { #(#mutable)* } = ::core::mem::take(__slots);
3464 let __bound = match __arg {
3465 __wa::Arg::Long(__flag) => match __flag.name {
3466 #(#bind_long)*
3467 _ => #bind_nested { false }
3468 },
3469 #plus
3470 __wa::Arg::Short(__flag) => match __flag.letter {
3471 #(#bind_short)*
3472 _ => #bind_nested { false }
3473 },
3474 _ => false,
3475 };
3476 *__slots = #slots_name { #(#names)* };
3477 ::core::result::Result::Ok(__bound)
3478 }
3479
3480 fn short(__c: char) -> ::core::option::Option<bool> {
3481 use ::winnow_args::__private as __wa;
3482 match __c {
3483 #(#own_shorts)*
3484 #(#flatten_shorts)*
3485 _ => ::core::option::Option::None,
3486 }
3487 }
3488
3489 fn is_prefix(__c: u8) -> bool {
3490 use ::winnow_args::__private as __wa;
3491 #own_prefixes #(#nested_prefixes)*
3492 }
3493
3494 fn is_long(__name: &[u8]) -> bool {
3495 use ::winnow_args::__private as __wa;
3496 #own_longs #(#flatten_longs)*
3497 }
3498
3499 fn finish(
3500 __slots: #slots_name,
3501 __input: &::winnow_args::Argv<'_>,
3502 ) -> ::core::result::Result<Self, ::winnow_args::Error> {
3503 use ::winnow_args::__private as __wa;
3504 let #slots_name { #(#mutable)* } = __slots;
3505 #(#env_fallbacks)*
3506 #exclusive
3507 #supplied
3508 #default_ifs
3509 #(#default_fallbacks)*
3510 #required
3511 let __built = Self {
3512 #(#build,)*
3513 #(#skipped: ::core::default::Default::default(),)*
3514 #(#flatten_build)*
3515 };
3516 #(#field_reads)*
3517 ::core::result::Result::Ok(__built)
3518 }
3519 }
3520 }
3521 });
3522 let own_items = quote!(&[#(#help_items),*]);
3523 let positional_clash = sequence_ty
3525 .filter(|_| !positionals.is_empty() || trailing.is_some())
3526 .map(|ty| {
3527 quote! {
3528 const _: () = assert!(
3529 !<#ty as ::winnow_args::Occurrence>::POSITIONAL,
3530 "the sequence enum has a positional variant, which takes every word: this struct's positionals would never be filled",
3531 );
3532 }
3533 });
3534 let more: Vec<TokenStream2> = flattens
3536 .iter()
3537 .map(|(_, ty)| quote!(<#ty as ::winnow_args::Args>::HELP.items))
3538 .chain(
3539 sequence_ty
3540 .iter()
3541 .map(|ty| quote!(<#ty as ::winnow_args::Occurrence>::ITEMS)),
3542 )
3543 .collect();
3544 let items = if more.is_empty() {
3545 own_items
3546 } else {
3547 quote!({
3548 const __OWN: &[::winnow_args::help::Item] = #own_items;
3549 const _: () = assert!(
3550 !::winnow_args::help::items_clash(&[__OWN, #(#more),*]),
3551 "a flattened struct or the sequence enum declares a flag that this struct, or another of them, also declares",
3552 );
3553 #positional_clash
3554 const __N: usize = __OWN.len() #(+ #more.len())*;
3555 const __ALL: [::winnow_args::help::Item; __N] = ::winnow_args::__private::concat_items::<__N>(
3556 &[__OWN, #(#more),*],
3557 );
3558 &__ALL
3559 })
3560 };
3561 let mut spec_checks = Vec::new();
3562 let filed = filed(input)?;
3563 let identity = spec::identity(input.ident.span(), &filed);
3564 if spec::enabled() {
3565 spec_checks.extend(fields.iter().filter_map(|field| {
3566 spec_check(&input.generics, choices_type(field), field.spec.as_ref())
3567 .or_else(|| spec_check_stated(&input.generics, field))
3568 }));
3569 let stated = |ident: &Ident| {
3572 let field = named.named.iter().find(|f| f.ident.as_ref() == Some(ident));
3573 field.map_or(Ok(None), |f| spec_name(&f.attrs))
3574 };
3575 let mut link = |kind: TokenStream2, stated: Option<String>, ty: &Type| {
3576 let name = spec_type(stated, ty)?;
3577 spec_checks.extend(spec_filed(&input.generics, kind, ty, &name));
3578 syn::Result::Ok(name)
3579 };
3580 for field in fields.iter().filter(|field| field.keywords) {
3581 if let Kind::Required(ty) = &field.kind {
3582 link(quote!(Args), field.spec.clone(), ty)?;
3583 }
3584 }
3585 let items = fields
3586 .iter()
3587 .filter(|field| !matches!(field.role, Role::Subcommand))
3588 .map(spec_item)
3589 .collect::<syn::Result<Vec<_>>>()?;
3590 let flatten = flattens
3591 .iter()
3592 .map(|(ident, ty)| link(quote!(Args), stated(ident)?, ty))
3593 .collect::<syn::Result<Vec<_>>>()?;
3594 let sequence = sequence
3595 .as_ref()
3596 .map(|(ident, ty)| link(quote!(Occurrence), stated(ident)?, ty))
3597 .transpose()?;
3598 let subcommand_ty = subcommand
3599 .map(|field| {
3600 link(
3601 quote!(Subcommand),
3602 field.spec.clone(),
3603 subcommand_type(field),
3604 )
3605 })
3606 .transpose()?;
3607 spec::write_args(
3608 input.ident.span(),
3609 &spec::ArgsDoc {
3610 ident: filed.clone(),
3611 name: program_name.clone(),
3612 about: about_text,
3613 long_about: long_about_text,
3614 after_help: after_text,
3615 after_long_help: after_long_text,
3616 package_version: package_version(&version, with_version),
3617 items,
3618 flatten,
3619 sequence,
3620 subcommand: subcommand_ty,
3621 subcommand_required,
3622 help_flag,
3623 help_short: help_short_flag,
3624 long_only,
3625 unknown_flags_value,
3626 },
3627 )?;
3628 }
3629
3630 Ok(quote! {
3631 #(#spec_checks)*
3632 impl #impl_generics ::winnow_args::Args for #name #ty_generics #where_clause {
3633 const SPEC: &'static str = #identity;
3634
3635 #[inline(never)]
3638 fn help_data() -> &'static ::winnow_args::help::Command {
3639 <Self as ::winnow_args::Args>::HELP
3640 }
3641
3642 const HELP: &'static ::winnow_args::help::Command = &::winnow_args::help::Command {
3643 name: #program_name,
3644 about: #about,
3645 long_about: #long_about,
3646 after_help: #after_help,
3647 after_long_help: #after_long_help,
3648 items: #items,
3649 subcommands: #help_subcommands,
3650 subcommand_required: #subcommand_required,
3651 help_flag: #help_flag,
3652 help_short: #help_short_flag,
3653 version: #help_version,
3654 };
3655
3656 fn parse_argv_with(
3657 __input: &mut ::winnow_args::Argv<'_>,
3658 __globals: &mut dyn ::winnow_args::Globals,
3659 ) -> ::core::result::Result<Self, ::winnow_args::Error> {
3660 use ::winnow_args::__private as __wa;
3661 #(#slots)*
3662 #sequence_slot
3663 #unknown_slot
3664 #(#flatten_slots)*
3665 #(#displaced)*
3666 #position
3667 #filled_slot
3668 #help_on_empty
3669 while !__input.is_empty() {
3670 #start
3671 #lenient_bundle
3672 #save_token
3673 #bundle_start
3674 let __arg = #lex;
3675 match __arg {
3676 __wa::Arg::Long(__flag) => match __flag.name {
3677 #(#long_arms)*
3678 #help_long
3679 #version_long
3680 _ => #sequence_arg #flatten_arg #late_long if !__globals.bind(&__arg, __input)? { #unknown_flag },
3681 },
3682 #plus_match
3683 __wa::Arg::Short(__flag) => match __flag.letter {
3684 #(#short_arms)*
3685 #help_short
3686 #version_short
3687 _ => #sequence_arg #flatten_arg #late_short if !__globals.bind(&__arg, __input)? { #unknown_flag },
3688 },
3689 #separator_arm
3690 #word_arm
3691 }
3692 #bundle_end
3693 }
3694 #(#env_fallbacks)*
3695 #exclusive
3696 #supplied
3697 #default_ifs
3698 #(#default_fallbacks)*
3699 #required
3700 let __built = Self {
3701 #(#build,)*
3702 #(#skipped: ::core::default::Default::default(),)*
3703 #sequence_build
3704 #unknown_build
3705 #(#flatten_build)*
3706 };
3707 #(#field_reads)*
3708 ::core::result::Result::Ok(__built)
3709 }
3710 }
3711
3712 #flatten_impl
3713 })
3714}
3715
3716struct Group {
3718 name: String,
3719 required: bool,
3720 multiple: bool,
3721}
3722
3723#[derive(Default)]
3725struct StructOptions {
3726 groups: Vec<Group>,
3727 restart_token: Option<String>,
3729 default_subcommand: Option<String>,
3731 arg_required_else_help: bool,
3733 name: String,
3735 version: Option<TokenStream2>,
3737 about: Option<String>,
3739 long_about: Option<String>,
3740 after_help: String,
3741 after_long_help: String,
3742 disable_help_flag: bool,
3743 disable_help_short: bool,
3745 disable_version_flag: bool,
3746 disable_help_subcommand: bool,
3747 unknown_flags_value: bool,
3749 long_only: bool,
3751 plus_options: bool,
3753}
3754
3755fn struct_options(input: &DeriveInput) -> syn::Result<StructOptions> {
3756 let mut options = StructOptions::default();
3757 for attr in input.attrs.iter().filter(|a| is_ours(a)) {
3758 attr.parse_nested_meta(|meta| {
3759 if skip_spec(&meta)? {
3760 return Ok(());
3761 }
3762 if meta.path.is_ident("restart_token") {
3763 options.restart_token = Some(meta.value()?.parse::<LitStr>()?.value());
3764 return Ok(());
3765 }
3766 if meta.path.is_ident("default_subcommand") {
3767 options.default_subcommand = Some(meta.value()?.parse::<LitStr>()?.value());
3768 return Ok(());
3769 }
3770 if meta.path.is_ident("arg_required_else_help") {
3771 options.arg_required_else_help = true;
3772 return Ok(());
3773 }
3774 if meta.path.is_ident("long_only") {
3775 options.long_only = true;
3776 return Ok(());
3777 }
3778 if meta.path.is_ident("plus_options") {
3779 options.plus_options = true;
3780 return Ok(());
3781 }
3782 if meta.path.is_ident("unknown_flags") {
3783 let mode = meta.value()?.parse::<LitStr>()?;
3784 options.unknown_flags_value = match mode.value().as_str() {
3785 "value" => true,
3786 "error" => false,
3787 _ => {
3788 return Err(syn::Error::new(
3789 mode.span(),
3790 "expected \"value\" or \"error\"",
3791 ));
3792 }
3793 };
3794 return Ok(());
3795 }
3796 let text = |meta: &syn::meta::ParseNestedMeta<'_>| -> syn::Result<String> {
3797 Ok(meta.value()?.parse::<LitStr>()?.value())
3798 };
3799 if meta.path.is_ident("name") {
3800 options.name = text(&meta)?;
3801 return Ok(());
3802 }
3803 if meta.path.is_ident("version") {
3804 options.version = Some(if meta.input.peek(syn::Token![=]) {
3805 let version = meta.value()?.parse::<LitStr>()?;
3806 quote!(#version)
3807 } else {
3808 quote!(::core::env!("CARGO_PKG_VERSION"))
3809 });
3810 return Ok(());
3811 }
3812 for (key, slot) in [
3813 ("about", &mut options.about),
3814 ("long_about", &mut options.long_about),
3815 ] {
3816 if meta.path.is_ident(key) {
3817 *slot = Some(text(&meta)?);
3818 return Ok(());
3819 }
3820 }
3821 for (key, slot) in [
3822 ("after_help", &mut options.after_help),
3823 ("after_long_help", &mut options.after_long_help),
3824 ] {
3825 if meta.path.is_ident(key) {
3826 *slot = text(&meta)?;
3827 return Ok(());
3828 }
3829 }
3830 for (key, slot) in [
3831 ("disable_help_flag", &mut options.disable_help_flag),
3832 ("disable_help_short", &mut options.disable_help_short),
3833 ("disable_version_flag", &mut options.disable_version_flag),
3834 (
3835 "disable_help_subcommand",
3836 &mut options.disable_help_subcommand,
3837 ),
3838 ] {
3839 if meta.path.is_ident(key) {
3840 *slot = true;
3841 return Ok(());
3842 }
3843 }
3844 if !meta.path.is_ident("group") {
3845 return Err(meta.error(
3846 "expected `group(\"name\", ...)`, `restart_token`, `default_subcommand`, \
3847 `arg_required_else_help`, `unknown_flags`, `long_only`, `plus_options`, `name`, `version`, `about`, \
3848 `long_about`, `after_help`, `after_long_help` or a `disable_*` option",
3849 ));
3850 }
3851 let content;
3852 syn::parenthesized!(content in meta.input);
3853 let name = content.parse::<LitStr>()?.value();
3854 let (mut required, mut multiple) = (false, false);
3855 while !content.is_empty() {
3856 content.parse::<syn::Token![,]>()?;
3857 let flag = content.parse::<Ident>()?;
3858 match flag.to_string().as_str() {
3859 "required" => required = true,
3860 "multiple" => multiple = true,
3861 _ => {
3862 return Err(syn::Error::new(
3863 flag.span(),
3864 "expected `required` or `multiple`",
3865 ));
3866 }
3867 }
3868 }
3869 options.groups.push(Group {
3870 name,
3871 required,
3872 multiple,
3873 });
3874 Ok(())
3875 })?;
3876 }
3877 Ok(options)
3878}
3879
3880struct Rules {
3882 conflicts: Vec<(usize, usize)>,
3884 beats: Vec<Vec<usize>>,
3886 requires: Vec<(usize, usize)>,
3888 required_unless: Vec<(usize, Vec<usize>)>,
3890 members: Vec<Vec<usize>>,
3892 default_ifs: Vec<(usize, usize, Option<String>, String)>,
3894}
3895
3896impl Rules {
3897 fn new(fields: &[Field], groups: &[Group]) -> syn::Result<Self> {
3898 let resolve = |from: &Field, selector: &str| -> syn::Result<usize> {
3899 fields
3900 .iter()
3901 .position(
3902 |f| match (selector.strip_prefix("--"), selector.strip_prefix('-')) {
3903 (Some(long), _) => f.longs().contains(&long),
3904 (None, Some(short)) => {
3905 let mut chars = short.chars();
3906 chars
3907 .next()
3908 .is_some_and(|c| chars.next().is_none() && f.shorts().contains(&c))
3909 }
3910 (None, None) => {
3911 matches!(&f.role, Role::Positional { name, .. } if name == selector)
3912 || f.ident == selector
3913 }
3914 },
3915 )
3916 .ok_or_else(|| {
3917 let negated = selector
3919 .strip_prefix("--")
3920 .and_then(|long| fields.iter().find(|f| f.negate.as_deref() == Some(long)));
3921 let message = match negated {
3922 Some(f) => format!(
3923 "`{selector}` is the negation of `{}`: name that flag instead",
3924 f.display()
3925 ),
3926 None => format!("`{selector}` names no flag or positional of this struct"),
3927 };
3928 syn::Error::new(from.ident.span(), message)
3929 })
3930 };
3931 let mut rules = Rules {
3932 conflicts: Vec::new(),
3933 beats: vec![Vec::new(); fields.len()],
3934 requires: Vec::new(),
3935 required_unless: Vec::new(),
3936 members: vec![Vec::new(); groups.len()],
3937 default_ifs: Vec::new(),
3938 };
3939 for (i, f) in fields.iter().enumerate() {
3940 for selector in &f.conflicts {
3941 let j = resolve(f, selector)?;
3942 let pair = (i.min(j), i.max(j));
3943 if !rules.conflicts.contains(&pair) {
3944 rules.conflicts.push(pair);
3945 }
3946 }
3947 for selector in &f.overrides {
3948 let j = resolve(f, selector)?;
3949 if !rules.beats[i].contains(&j) {
3950 rules.beats[i].push(j);
3951 }
3952 if !rules.beats[j].contains(&i) {
3953 rules.beats[j].push(i);
3954 }
3955 }
3956 for selector in &f.requires {
3957 rules.requires.push((i, resolve(f, selector)?));
3958 }
3959 for (selector, equals, value) in &f.default_ifs {
3960 let other = resolve(f, selector)?;
3961 let comparable = match &fields[other].kind {
3962 Kind::Count(_) => false,
3963 Kind::Switch => equals.as_deref().is_none_or(is_bool_word),
3964 _ => !fields[other].keywords,
3965 };
3966 if equals.is_some() && !comparable {
3967 return Err(syn::Error::new(
3968 f.ident.span(),
3969 format!(
3970 "`{selector}` has no value to compare: use `default_if(\"{selector}\", \"…\")`"
3971 ),
3972 ));
3973 }
3974 rules
3975 .default_ifs
3976 .push((i, other, equals.clone(), value.clone()));
3977 }
3978 if !f.required_unless.is_empty() {
3979 let others = f
3980 .required_unless
3981 .iter()
3982 .map(|selector| resolve(f, selector))
3983 .collect::<syn::Result<_>>()?;
3984 rules.required_unless.push((i, others));
3985 }
3986 if let Some(group) = &f.group {
3987 let g = groups
3988 .iter()
3989 .position(|g| &g.name == group)
3990 .ok_or_else(|| {
3991 syn::Error::new(
3992 f.ident.span(),
3993 format!("no `#[arg(group(\"{group}\"))]` on the struct"),
3994 )
3995 })?;
3996 rules.members[g].push(i);
3997 }
3998 }
3999 if let Some((g, _)) = groups
4000 .iter()
4001 .zip(&rules.members)
4002 .find(|(g, members)| g.required && members.is_empty())
4003 {
4004 return Err(syn::Error::new(
4005 Span::call_site(),
4006 format!("group `{}` is required, but no field is in it", g.name),
4007 ));
4008 }
4009 Ok(rules)
4010 }
4011
4012 fn is_displaced(&self, fields: &[Field], f: &Field) -> bool {
4014 !self.beats[index_of(fields, f)].is_empty()
4015 }
4016
4017 fn displaced_slots(&self, fields: &[Field]) -> Vec<TokenStream2> {
4019 fields
4020 .iter()
4021 .enumerate()
4022 .filter(|(i, _)| !self.beats[*i].is_empty())
4023 .map(|(_, f)| {
4024 let displaced = displaced_flag(&f.ident);
4025 quote!(let mut #displaced = false;)
4026 })
4027 .collect()
4028 }
4029
4030 fn displace(&self, fields: &[Field], f: &Field) -> TokenStream2 {
4032 let i = index_of(fields, f);
4033 if self.beats[i].is_empty() {
4034 return quote!();
4035 }
4036 let standing = displaced_flag(&f.ident);
4037 let clears = self.beats[i].iter().map(|&j| {
4038 let other = &fields[j];
4039 let slot = slot(&other.ident);
4040 let displaced = displaced_flag(&other.ident);
4041 let clear = match &other.kind {
4042 _ if other.keywords => quote!(#slot.clear();),
4043 Kind::Switch if other.negate.is_some() => {
4044 quote!(#slot = ::core::option::Option::None;)
4045 }
4046 Kind::Switch => quote!(#slot = false;),
4047 Kind::Count(_) => quote!(#slot = 0;),
4048 Kind::Optional(_) | Kind::Required(_) => {
4049 quote!(#slot = ::core::option::Option::None;)
4050 }
4051 Kind::Many(_) => quote!(#slot.clear();),
4052 };
4053 quote!(#clear #displaced = true;)
4054 });
4055 quote!(#standing = false; #(#clears)*)
4056 }
4057
4058 fn exclusive(&self, fields: &[Field], groups: &[Group]) -> TokenStream2 {
4060 let pairs = self.conflicts.iter().map(|&(a, b)| {
4061 let (fa, fb) = (&fields[a], &fields[b]);
4062 let (has_a, has_b) = (fa.has(), fb.has());
4063 let (name_a, name_b) = (fa.display(), fb.display());
4064 quote! {
4065 if #has_a && #has_b {
4066 return ::core::result::Result::Err(
4067 __wa::Error::conflict(__input.offset(), #name_a, #name_b),
4068 );
4069 }
4070 }
4071 });
4072 let at_most_one = groups.iter().zip(&self.members).filter(|(g, _)| !g.multiple).map(|(_, members)| {
4073 let checks = members.iter().map(|&m| {
4074 let has = fields[m].has();
4075 let name = fields[m].display();
4076 quote! {
4077 if #has {
4078 if let ::core::option::Option::Some(__first) = __first {
4079 return ::core::result::Result::Err(
4080 __wa::Error::conflict(__input.offset(), __first, #name),
4081 );
4082 }
4083 __first = ::core::option::Option::Some(#name);
4084 }
4085 }
4086 });
4087 quote! {{
4088 let mut __first: ::core::option::Option<&'static str> = ::core::option::Option::None;
4089 #(#checks)*
4090 }}
4091 });
4092 quote!(#(#pairs)* #(#at_most_one)*)
4093 }
4094
4095 fn default_ifs(&self, fields: &[Field]) -> TokenStream2 {
4098 let (mut conditions, mut fills) = (Vec::new(), Vec::new());
4099 for (n, (field, other, equals, value)) in self.default_ifs.iter().enumerate() {
4100 let (f, other) = (&fields[*field], &fields[*other]);
4101 let holds = format_ident!("__default_if_{n}");
4102 let other_slot = slot(&other.ident);
4103 let is = |ty: &Type, equals: &str| {
4104 let literal = LitByteStr::new(equals.as_bytes(), Span::call_site());
4105 quote! {
4106 |__v| <#ty as ::winnow_args::FromArg>::from_arg(__wa::BStr::new(#literal))
4107 .is_ok_and(|__literal| __literal == *__v)
4108 }
4109 };
4110 let condition = match (equals, &other.kind) {
4111 (None, _) => other.has(),
4112 (Some(equals), Kind::Switch) => {
4113 let equals = equals == "true";
4114 match other.negate {
4115 Some(_) => quote!((#other_slot == ::core::option::Option::Some(#equals))),
4116 None => quote!((#other_slot == #equals)),
4117 }
4118 }
4119 (Some(equals), Kind::Optional(ty) | Kind::Required(ty)) => {
4120 let is = is(ty, equals);
4121 quote!(#other_slot.as_ref().is_some_and(#is))
4122 }
4123 (Some(equals), Kind::Many(ty)) => {
4124 let is = is(ty, equals);
4125 quote!(#other_slot.iter().any(#is))
4126 }
4127 (Some(_), Kind::Count(_)) => unreachable!("refused when the rules were read"),
4128 };
4129 conditions.push(quote!(let #holds = #condition;));
4130 let unset = f.unset(self.is_displaced(fields, f));
4131 let slot = slot(&f.ident);
4132 let fill = match &f.kind {
4133 Kind::Switch => {
4134 let value = value == "true";
4135 match f.negate {
4136 Some(_) => quote!(#slot = ::core::option::Option::Some(#value);),
4137 None => quote!(#slot = #value;),
4138 }
4139 }
4140 _ => {
4141 let bytes = LitByteStr::new(value.as_bytes(), Span::call_site());
4142 f.assign(quote!(__wa::BStr::new(#bytes)), &f.display())
4143 }
4144 };
4145 fills.push(quote!(if #holds && #unset { #fill }));
4146 }
4147 quote!(#(#conditions)* #(#fills)*)
4148 }
4149
4150 fn supplied(&self, fields: &[Field]) -> TokenStream2 {
4152 let mut seen = Vec::new();
4153 let snapshots = self.requires.iter().filter_map(|&(i, _)| {
4154 if seen.contains(&i) {
4155 return None;
4156 }
4157 seen.push(i);
4158 let supplied = format_ident!("__supplied_{}", fields[i].ident);
4159 let has = fields[i].has();
4160 Some(quote!(let #supplied = #has;))
4161 });
4162 quote!(#(#snapshots)*)
4163 }
4164
4165 fn required(&self, fields: &[Field], groups: &[Group]) -> TokenStream2 {
4168 let missing = |f: &Field| {
4169 let name = f.display();
4170 match f.role {
4171 Role::Positional { .. } => {
4172 quote!(__wa::Error::missing_argument(__input.offset(), #name))
4173 }
4174 _ => quote!(__wa::Error::missing_required(__input.offset(), #name)),
4175 }
4176 };
4177 let displaced = |f: &Field| {
4179 self.is_displaced(fields, f).then(|| {
4180 let flag = displaced_flag(&f.ident);
4181 quote!(&& !#flag)
4182 })
4183 };
4184 let plain = fields.iter().filter(|f| f.required).map(|f| {
4185 let (has, displaced) = (f.has(), displaced(f));
4186 let error = missing(f);
4187 quote!(if !#has #displaced { return ::core::result::Result::Err(#error); })
4188 });
4189 let unless = self.required_unless.iter().map(|(i, others)| {
4190 let (has, displaced) = (fields[*i].has(), displaced(&fields[*i]));
4191 let others = others.iter().map(|&j| fields[j].has());
4192 let error = missing(&fields[*i]);
4193 quote! {
4194 if !#has #displaced #(&& !#others)* {
4195 return ::core::result::Result::Err(#error);
4196 }
4197 }
4198 });
4199 let one_of = groups
4200 .iter()
4201 .zip(&self.members)
4202 .filter(|(g, _)| g.required)
4203 .map(|(g, members)| {
4204 let name = &g.name;
4205 let has = members.iter().map(|&m| fields[m].has());
4206 let names = members.iter().map(|&m| fields[m].display());
4207 quote! {
4208 if #(!#has)&&* {
4209 return ::core::result::Result::Err(
4210 __wa::Error::missing_one_of(__input.offset(), #name, &[#(#names),*]),
4211 );
4212 }
4213 }
4214 });
4215 let requires = self.requires.iter().map(|&(i, j)| {
4216 let supplied = format_ident!("__supplied_{}", fields[i].ident);
4217 let has = fields[j].has();
4218 let (by, target) = (fields[i].display(), fields[j].display());
4219 quote! {
4220 if #supplied && !#has {
4221 return ::core::result::Result::Err(
4222 __wa::Error::required_by(__input.offset(), #target, #by),
4223 );
4224 }
4225 }
4226 });
4227 quote!(#(#plain)* #(#unless)* #(#one_of)* #(#requires)*)
4228 }
4229}
4230
4231fn vec_field(f: Option<&syn::Field>, shape: &str) -> syn::Result<Option<(Ident, Type)>> {
4233 let Some(f) = f else {
4234 return Ok(None);
4235 };
4236 let ty = last_segment(&f.ty)
4237 .filter(|s| s.ident == "Vec")
4238 .and_then(inner)
4239 .ok_or_else(|| syn::Error::new(f.ty.span(), shape))?;
4240 Ok(Some((f.ident.clone().expect("named field"), ty.clone())))
4241}
4242
4243fn subcommand_type(f: &Field) -> &Type {
4245 match &f.kind {
4246 Kind::Optional(ty) | Kind::Required(ty) => ty,
4247 _ => unreachable!("rejected for subcommands in `field`"),
4248 }
4249}
4250
4251fn displaced_flag(ident: &Ident) -> Ident {
4252 format_ident!("__displaced_{}", ident)
4253}
4254
4255fn index_of(fields: &[Field], f: &Field) -> usize {
4256 fields
4257 .iter()
4258 .position(|g| g.ident == f.ident)
4259 .expect("a field of this struct")
4260}
4261
4262fn slot(ident: &Ident) -> Ident {
4263 format_ident!("__slot_{}", ident)
4264}
4265
4266fn field(f: &syn::Field) -> syn::Result<Field> {
4267 let ident = f.ident.clone().expect("named field");
4268 let bare = ident.to_string().trim_start_matches("r#").to_owned();
4269
4270 let mut short = None;
4271 let mut long = None;
4272 let mut positional = false;
4273 let mut count = false;
4274 let mut subcommand = false;
4275 let mut global = false;
4276 let mut alias = Vec::new();
4277 let mut delimiter = None;
4278 let mut choices = None;
4279 let mut env = None;
4280 let mut default = None;
4281 let mut double_dash = None;
4282 let mut default_missing = None;
4283 let mut negative_numbers = false;
4284 let mut hyphen_values = false;
4285 let mut require_equals = false;
4286 let mut keep_equals = false;
4287 let (mut two_dashes, mut prefix) = (false, false);
4288 let mut values = 1;
4289 let (mut values_max, mut value_terminator) = (None, None);
4290 let (mut default_fn, mut default_note, mut default_ifs) = (None, None, Vec::new());
4291 let mut stop_flags = false;
4292 let mut plus: Option<char> = None;
4293 let mut keywords = false;
4294 let mut short_aliases = Vec::new();
4295 let mut negate: Option<Option<String>> = None;
4296 let (doc_help, doc_long_help) = docs(&f.attrs);
4297 let (mut help, mut long_help, mut heading, mut hide) = (None, None, None, false);
4298 let (mut conflicts, mut overrides, mut requires) = (Vec::new(), Vec::new(), Vec::new());
4299 let (mut required, mut required_unless, mut group) = (false, Vec::new(), None);
4300 let mut value_name = None;
4301 let (mut hint, mut complete) = (None, None);
4302 for attr in f.attrs.iter().filter(|a| is_ours(a)) {
4303 attr.parse_nested_meta(|meta| {
4304 if meta.path.is_ident("hint") {
4305 let name = meta.value()?.parse::<LitStr>()?;
4306 let variant = match name.value().as_str() {
4307 "file" => "Files",
4308 "dir" => "Dirs",
4309 "command" => "Commands",
4310 "none" => "Nothing",
4311 _ => {
4312 return Err(syn::Error::new(
4313 name.span(),
4314 "a hint is \"file\", \"dir\", \"command\" or \"none\"",
4315 ));
4316 }
4317 };
4318 hint = Some(syn::Ident::new(variant, name.span()));
4319 } else if meta.path.is_ident("complete") {
4320 complete = Some(meta.value()?.parse::<syn::Path>()?);
4321 } else if meta.path.is_ident("short") {
4322 let c = if meta.input.peek(syn::Token![=]) {
4323 meta.value()?.parse::<LitChar>()?.value()
4324 } else {
4325 bare.chars()
4326 .next()
4327 .ok_or_else(|| meta.error("cannot infer a short name"))?
4328 };
4329 match short {
4331 None => short = Some(c),
4332 Some(_) => short_aliases.push(c),
4333 }
4334 } else if meta.path.is_ident("long") {
4335 let name = if meta.input.peek(syn::Token![=]) {
4336 meta.value()?.parse::<LitStr>()?.value()
4337 } else {
4338 bare.replace('_', "-")
4339 };
4340 match long {
4342 None => long = Some(name),
4343 Some(_) => alias.push(name),
4344 }
4345 } else if meta.path.is_ident("positional") {
4346 positional = true;
4347 } else if meta.path.is_ident("count") {
4348 count = true;
4349 } else if meta.path.is_ident("subcommand") {
4350 subcommand = true;
4351 } else if meta.path.is_ident("global") {
4352 global = true;
4353 } else if meta.path.is_ident("alias") {
4354 alias.extend(aliases(&meta)?);
4355 } else if meta.path.is_ident("help") {
4356 help = Some(meta.value()?.parse::<LitStr>()?.value());
4357 } else if meta.path.is_ident("long_help") {
4358 long_help = Some(meta.value()?.parse::<LitStr>()?.value());
4359 } else if meta.path.is_ident("help_heading") {
4360 heading = Some(meta.value()?.parse::<LitStr>()?.value());
4361 } else if meta.path.is_ident("hide") {
4362 hide = true;
4363 } else if meta.path.is_ident("negate") {
4364 negate = Some(if meta.input.peek(syn::Token![=]) {
4365 let name = meta.value()?.parse::<LitStr>()?.value();
4366 Some(name.strip_prefix("--").unwrap_or(&name).to_owned())
4368 } else {
4369 None
4370 });
4371 } else if meta.path.is_ident("keywords") {
4372 keywords = true;
4373 } else if skip_spec(&meta)? {
4374 } else if meta.path.is_ident("plus") {
4375 plus = Some(meta.value()?.parse::<LitChar>()?.value());
4376 } else if meta.path.is_ident("stop_flags") {
4377 stop_flags = true;
4378 } else if meta.path.is_ident("values") {
4379 let count = meta.value()?.parse::<syn::Expr>()?;
4380 (values, values_max) = value_count(&count)?;
4381 } else if meta.path.is_ident("value_terminator") {
4382 value_terminator = Some(meta.value()?.parse::<LitStr>()?.value());
4383 } else if meta.path.is_ident("default_fn") {
4384 default_fn = Some(meta.value()?.parse::<syn::ExprPath>()?);
4385 } else if meta.path.is_ident("default_note") {
4386 default_note = Some(meta.value()?.parse::<LitStr>()?.value());
4387 } else if meta.path.is_ident("default_if") {
4388 let content;
4389 syn::parenthesized!(content in meta.input);
4390 let list =
4391 syn::punctuated::Punctuated::<LitStr, syn::Token![,]>::parse_terminated(&content)?;
4392 let mut words = list.iter().map(LitStr::value);
4393 default_ifs.push(match (words.next(), words.next(), words.next(), words.next()) {
4394 (Some(selector), Some(value), None, _) => (selector, None, value),
4395 (Some(selector), Some(equals), Some(value), None) => {
4396 (selector, Some(equals), value)
4397 }
4398 _ => {
4399 return Err(meta.error(
4400 "expected `default_if(\"--flag\", \"value\")` or \
4401 `default_if(\"--flag\", \"its value\", \"value\")`",
4402 ));
4403 }
4404 });
4405 } else if meta.path.is_ident("two_dashes") {
4406 two_dashes = true;
4407 } else if meta.path.is_ident("prefix") {
4408 prefix = true;
4409 } else if meta.path.is_ident("require_equals") {
4410 require_equals = true;
4411 } else if meta.path.is_ident("keep_equals") {
4412 keep_equals = true;
4413 } else if meta.path.is_ident("allow_hyphen_values") {
4414 hyphen_values = true;
4415 } else if meta.path.is_ident("allow_negative_numbers") {
4416 negative_numbers = true;
4417 } else if meta.path.is_ident("default_missing") {
4418 default_missing = Some(meta.value()?.parse::<LitStr>()?.value());
4419 } else if meta.path.is_ident("double_dash") {
4420 let mode = meta.value()?.parse::<LitStr>()?;
4421 double_dash = Some(match mode.value().as_str() {
4422 "required" => DoubleDash::Required,
4423 "automatic" => DoubleDash::Automatic,
4424 "optional" => DoubleDash::Optional,
4425 "preserve" => DoubleDash::Preserve,
4426 _ => {
4427 return Err(syn::Error::new(
4428 mode.span(),
4429 "expected \"required\", \"automatic\", \"optional\" or \"preserve\"",
4430 ));
4431 }
4432 });
4433 } else if meta.path.is_ident("conflicts") {
4434 conflicts.extend(aliases(&meta)?);
4435 } else if meta.path.is_ident("overrides") {
4436 overrides.extend(aliases(&meta)?);
4437 } else if meta.path.is_ident("requires") {
4438 requires.extend(aliases(&meta)?);
4439 } else if meta.path.is_ident("required_unless") {
4440 required_unless.extend(aliases(&meta)?);
4441 } else if meta.path.is_ident("required") {
4442 required = true;
4443 } else if meta.path.is_ident("group") {
4444 group = Some(meta.value()?.parse::<LitStr>()?.value());
4445 } else if meta.path.is_ident("env") {
4446 env = Some(meta.value()?.parse::<LitStr>()?.value());
4447 } else if meta.path.is_ident("default") {
4448 default = Some(meta.value()?.parse::<LitStr>()?.value());
4449 } else if meta.path.is_ident("choices") {
4450 let content;
4451 syn::parenthesized!(content in meta.input);
4452 let list =
4453 syn::punctuated::Punctuated::<LitStr, syn::Token![,]>::parse_terminated(&content)?;
4454 choices = Some(list.iter().map(LitStr::value).collect());
4455 } else if meta.path.is_ident("delimiter") {
4456 let c = meta.value()?.parse::<LitChar>()?;
4457 if !c.value().is_ascii() {
4458 return Err(syn::Error::new(c.span(), "a delimiter is one ASCII character"));
4459 }
4460 delimiter = Some(c.value() as u8);
4461 } else if meta.path.is_ident("value_name") {
4462 value_name = Some(meta.value()?.parse::<LitStr>()?.value());
4463 } else {
4464 return Err(meta.error(
4465 "unknown `arg` option; expected one of `short`, `long`, `alias`, `global`, `count`, \
4466 `positional`, `value_name`, `double_dash`, `subcommand`, `flatten`, `sequence`, `unknown`, `skip`, \
4467 `delimiter`, `choices`, `env`, `default`, `default_missing`, `allow_negative_numbers`, \
4468 `allow_hyphen_values`, `require_equals`, `keep_equals`, `negate`, `two_dashes`, `prefix`, `values`, \
4469 `value_terminator`, `default_fn`, `default_note`, `default_if`, \
4470 `stop_flags`, `plus`, `keywords`, `conflicts`, `overrides`, `requires`, `required`, `required_unless`, \
4471 `group`, `help`, `long_help`, `help_heading`, `hide`, `hint`, `complete`",
4472 ));
4473 }
4474 Ok(())
4475 })?;
4476 }
4477
4478 let error = |msg: String| Err(syn::Error::new(f.span(), msg));
4479 if delimiter.is_some() && (count || !matches!(kind(&f.ty), Kind::Many(_))) {
4480 return error("`delimiter` needs a `Vec<T>` field to put the pieces in".into());
4481 }
4482 let kind = if count {
4483 Kind::Count(f.ty.clone())
4484 } else {
4485 kind(&f.ty)
4486 };
4487 if default_missing.is_some()
4488 && (positional || subcommand || matches!(kind, Kind::Switch | Kind::Count(_)))
4489 {
4490 return error("`default_missing` is for flags that take a value".into());
4491 }
4492 if (hyphen_values || require_equals)
4493 && (positional || subcommand || matches!(kind, Kind::Switch | Kind::Count(_)))
4494 {
4495 return error(
4496 "`allow_hyphen_values` and `require_equals` are for flags that take a value".into(),
4497 );
4498 }
4499 if negative_numbers && (subcommand || matches!(kind, Kind::Switch | Kind::Count(_))) {
4500 return error("`allow_negative_numbers` is for fields that take a value".into());
4501 }
4502 let tristate =
4503 plus.is_some() && plus == short && matches!(&kind, Kind::Optional(ty) if is_bool(ty));
4504 if plus.is_some()
4505 && (positional || subcommand || !tristate && matches!(kind, Kind::Switch | Kind::Count(_)))
4506 {
4507 return error(
4508 "`plus` is for a flag taking a value, or an `Option<bool>` with the same `short` letter".into(),
4509 );
4510 }
4511 if keywords
4512 && (positional
4513 || subcommand
4514 || !matches!(kind, Kind::Required(_))
4515 || env.is_some()
4516 || default.is_some()
4517 || default_missing.is_some()
4518 || choices.is_some()
4519 || delimiter.is_some())
4520 {
4521 return error(
4522 "`keywords` is for a flag whose type derives `Args`, without `env`, `default`, \
4523 `default_missing`, `choices` or `delimiter`"
4524 .into(),
4525 );
4526 }
4527 if stop_flags && !positional {
4528 return error("`stop_flags` is for positional fields".into());
4529 }
4530 if double_dash.is_some() && !positional {
4531 return error("`double_dash` is for positional fields".into());
4532 }
4533 if (values > 1 || values_max.is_some())
4534 && (positional
4535 || subcommand
4536 || !matches!(kind, Kind::Many(_))
4537 || delimiter.is_some()
4538 || default_missing.is_some())
4539 {
4540 return error(
4541 "`values` is for a `Vec<T>` flag, without `delimiter` or `default_missing`".into(),
4542 );
4543 }
4544 if values_max.is_some() && require_equals {
4545 return error("a range of `values` takes detached words: not with `require_equals`".into());
4546 }
4547 if value_terminator.is_some() && values_max.is_none() {
4548 return error("`value_terminator` ends a range of `values` (`values = 1..`)".into());
4549 }
4550 let valueless = matches!(kind, Kind::Switch | Kind::Count(_)) && negate.is_none();
4551 if default_fn.is_some() && (default.is_some() || subcommand || keywords || valueless) {
4552 return error(
4553 "`default_fn` is for a field that takes a value, or `negate`, without `default`".into(),
4554 );
4555 }
4556 if default_note.is_some() && default_fn.is_none() {
4557 return error("`default_note` describes a `default_fn`".into());
4558 }
4559 if !default_ifs.is_empty() && (subcommand || keywords || matches!(kind, Kind::Count(_))) {
4560 return error("`default_if` is for a switch or a field that takes a value".into());
4561 }
4562 if let (Kind::Switch, Some((.., value))) =
4563 (&kind, default_ifs.iter().find(|d| !is_bool_word(&d.2)))
4564 {
4565 return error(format!(
4566 "a switch's `default_if` value is `true` or `false`, not `{value}`"
4567 ));
4568 }
4569 if two_dashes && (long.is_none() && alias.is_empty() || positional || subcommand) {
4570 return error("`two_dashes` is for flags with a long name".into());
4571 }
4572 if prefix
4573 && (short.is_none_or(|c| !c.is_ascii())
4574 || positional
4575 || subcommand
4576 || matches!(kind, Kind::Switch | Kind::Count(_)))
4577 {
4578 return error("`prefix` is for a value-taking flag with an ASCII `short` letter".into());
4579 }
4580 let flag_value_name =
4581 (!positional && !subcommand && !matches!(kind, Kind::Switch | Kind::Count(_)))
4582 .then(|| value_name.clone().unwrap_or_else(|| bare.to_uppercase()));
4583 let role = if subcommand {
4584 if positional || count || global || short.is_some() || long.is_some() || !alias.is_empty() {
4585 return error("a subcommand field takes no other `arg` options".into());
4586 }
4587 if !matches!(kind, Kind::Optional(_) | Kind::Required(_)) {
4588 return error("a subcommand field is `E` or `Option<E>`".into());
4589 }
4590 Role::Subcommand
4591 } else if positional {
4592 if short.is_some() || long.is_some() || global || !alias.is_empty() {
4593 return error("a positional field has no `short`, `long`, `alias` or `global`".into());
4594 }
4595 if matches!(kind, Kind::Switch | Kind::Count(_)) {
4596 return error("a positional field cannot be `bool` or `count`".into());
4597 }
4598 Role::Positional {
4599 double_dash: double_dash.unwrap_or(DoubleDash::Optional),
4600 name: value_name.unwrap_or_else(|| bare.to_uppercase()),
4601 }
4602 } else {
4603 if short.is_none() && long.is_none() && plus.is_none() {
4606 long = Some(bare.replace('_', "-"));
4607 }
4608 for l in long.iter().chain(&alias) {
4609 if l.is_empty() || l.starts_with('-') || l.contains('=') {
4610 return error(format!("`{l}` is not a usable long name"));
4611 }
4612 }
4613 if let Some(c) = short
4614 && (c == '-' || c == '=')
4615 {
4616 return error(format!("`{c}` is not a usable short name"));
4617 }
4618 Role::Flag {
4619 short,
4620 long,
4621 aliases: alias,
4622 global,
4623 }
4624 };
4625
4626 if choices.is_some()
4627 && (matches!(kind, Kind::Switch | Kind::Count(_)) || matches!(role, Role::Subcommand))
4628 {
4629 return error("`choices` needs a field that takes a value".into());
4630 }
4631 if (env.is_some() || default.is_some()) && matches!(role, Role::Subcommand) {
4632 return error("a subcommand field takes no `env` or `default`".into());
4633 }
4634 let negate = match negate {
4635 None => None,
4636 Some(_) if !matches!(kind, Kind::Switch) || !matches!(role, Role::Flag { .. }) => {
4637 return error("`negate` is for `bool` flags".into());
4638 }
4639 Some(Some(name)) => Some(name),
4640 Some(None) => match &role {
4641 Role::Flag { long: Some(l), .. } => Some(format!("no-{l}")),
4642 _ => return error("`negate` without a name needs a `long` to prefix".into()),
4643 },
4644 };
4645 if let Some(no) = &negate
4646 && (no.is_empty() || no.starts_with('-') || no.contains('='))
4647 {
4648 return error(format!("`{no}` is not a usable long name"));
4649 }
4650 match (&default, &negate) {
4651 (Some(d), Some(_)) if d != "true" && d != "false" => {
4652 return error("a negatable flag's `default` is \"true\" or \"false\"".into());
4653 }
4654 (Some(_), None) if matches!(kind, Kind::Switch | Kind::Count(_)) => {
4655 return error("`default` needs a field that takes a value, or `negate`".into());
4656 }
4657 _ => {}
4658 }
4659 if (hint.is_some() || complete.is_some())
4660 && (matches!(kind, Kind::Switch | Kind::Count(_))
4661 || !matches!(role, Role::Flag { .. } | Role::Positional { .. }))
4662 {
4663 return error("`hint` and `complete` are for a field that takes a value".into());
4664 }
4665 if hint.is_some() && complete.is_some() {
4666 return error("`complete` answers alone: `hint` beside it would never be asked".into());
4667 }
4668 let spec = spec_name(&f.attrs)?;
4669 if spec.is_some() && matches!(kind, Kind::Switch | Kind::Count(_)) {
4670 return error("`spec` names the type of a field's value, and this field has none".into());
4671 }
4672 Ok(Field {
4673 ident,
4674 spec,
4675 kind,
4676 role,
4677 delimiter,
4678 choices,
4679 env,
4680 default,
4681 conflicts,
4682 overrides,
4683 requires,
4684 required,
4685 required_unless,
4686 group,
4687 default_missing,
4688 long_help: long_help.or_else(|| help.clone()).unwrap_or(doc_long_help),
4689 help: help.unwrap_or(doc_help),
4690 heading,
4691 hide,
4692 value_name: if tristate { None } else { flag_value_name },
4693 hint,
4694 complete,
4695 negative_numbers,
4696 hyphen_values,
4697 require_equals,
4698 keep_equals,
4699 negate,
4700 two_dashes,
4701 prefix,
4702 values,
4703 values_max,
4704 value_terminator,
4705 default_fn,
4706 default_note,
4707 default_ifs,
4708 stop_flags,
4709 plus,
4710 tristate,
4711 keywords,
4712 short_aliases,
4713 })
4714}
4715
4716fn is_bool_word(word: &str) -> bool {
4717 matches!(word, "true" | "false")
4718}
4719
4720fn value_count(count: &syn::Expr) -> syn::Result<(usize, Option<usize>)> {
4723 let number = |e: &syn::Expr| match e {
4724 syn::Expr::Lit(syn::ExprLit {
4725 lit: syn::Lit::Int(n),
4726 ..
4727 }) => n.base10_parse::<usize>(),
4728 _ => Err(syn::Error::new(e.span(), "expected a number")),
4729 };
4730 let (min, max) = match count {
4731 syn::Expr::Range(range) => {
4732 let min = range.start.as_deref().map(number).transpose()?.unwrap_or(1);
4733 let max = match (&range.end, &range.limits) {
4734 (None, _) => usize::MAX,
4735 (Some(end), syn::RangeLimits::Closed(_)) => number(end)?,
4736 (Some(end), syn::RangeLimits::HalfOpen(_)) => number(end)?.saturating_sub(1),
4737 };
4738 (min, Some(max))
4739 }
4740 exact => (number(exact)?, None),
4741 };
4742 if min == 0 || max.is_some_and(|max| max < min) {
4743 return Err(syn::Error::new(
4744 count.span(),
4745 "`values` is at least 1, and a range does not end below its start",
4746 ));
4747 }
4748 Ok((min, max))
4749}
4750
4751fn spellings(f: &Field) -> Vec<&str> {
4753 let mut longs = f.longs();
4754 longs.extend(f.negate.as_deref());
4755 longs
4756}
4757
4758fn check_duplicates(fields: &[Field]) -> syn::Result<()> {
4759 for (i, a) in fields.iter().enumerate() {
4760 for b in &fields[..i] {
4761 let theirs = b.shorts();
4762 let clash = a
4763 .shorts()
4764 .into_iter()
4765 .find(|c| theirs.contains(c))
4766 .map(|c| format!("-{c}"))
4767 .or_else(|| {
4768 let longs = spellings(b);
4769 spellings(a)
4770 .into_iter()
4771 .find(|l| longs.contains(l))
4772 .map(|l| format!("--{l}"))
4773 });
4774 if let Some(name) = clash {
4775 return Err(syn::Error::new(
4776 a.ident.span(),
4777 format!("`{name}` is already used by `{}`", b.ident),
4778 ));
4779 }
4780 }
4781 }
4782 Ok(())
4783}
4784
4785fn check_positional_order(positionals: &[&Field]) -> syn::Result<()> {
4788 let rank = |f: &Field| match f.kind {
4789 Kind::Required(_) => 0,
4790 Kind::Optional(_) => 1,
4791 Kind::Many(_) | Kind::Switch | Kind::Count(_) => 2,
4792 };
4793 for pair in positionals.windows(2) {
4794 if rank(pair[1]) < rank(pair[0]) || rank(pair[0]) == 2 {
4795 return Err(syn::Error::new(
4796 pair[1].ident.span(),
4797 "positionals must be required, then optional, then one `Vec` last",
4798 ));
4799 }
4800 }
4801 Ok(())
4802}
4803
4804fn last_segment(ty: &Type) -> Option<&syn::PathSegment> {
4805 match ty {
4806 Type::Path(path) if path.qself.is_none() => path.path.segments.last(),
4807 _ => None,
4808 }
4809}
4810
4811fn inner(segment: &syn::PathSegment) -> Option<&Type> {
4812 match &segment.arguments {
4813 PathArguments::AngleBracketed(args) => match args.args.first()? {
4814 GenericArgument::Type(inner) => Some(inner),
4815 _ => None,
4816 },
4817 _ => None,
4818 }
4819}
4820
4821fn kind(ty: &Type) -> Kind {
4822 if is_bool(ty) {
4823 return Kind::Switch;
4824 }
4825 if let Some(last) = last_segment(ty)
4826 && let Some(inner) = inner(last)
4827 {
4828 if last.ident == "Option" {
4829 return Kind::Optional(inner.clone());
4830 }
4831 if last.ident == "Vec" {
4832 return Kind::Many(inner.clone());
4833 }
4834 }
4835 Kind::Required(ty.clone())
4836}