1 use std::cmp::Ordering;
2 
3 use crate::builder::ValueRange;
4 use crate::mkeymap::KeyType;
5 use crate::util::FlatSet;
6 use crate::util::Id;
7 use crate::ArgAction;
8 use crate::INTERNAL_ERROR_MSG;
9 use crate::{Arg, Command, ValueHint};
10 
assert_app(cmd: &Command)11 pub(crate) fn assert_app(cmd: &Command) {
12     debug!("Command::_debug_asserts");
13 
14     let mut short_flags = vec![];
15     let mut long_flags = vec![];
16 
17     // Invalid version flag settings
18     if cmd.get_version().is_none() && cmd.get_long_version().is_none() {
19         // PropagateVersion is meaningless if there is no version
20         assert!(
21             !cmd.is_propagate_version_set(),
22             "Command {}: No version information via Command::version or Command::long_version to propagate",
23             cmd.get_name(),
24         );
25 
26         // Used `Command::mut_arg("version", ..) but did not provide any version information to display
27         let version_needed = cmd
28             .get_arguments()
29             .filter(|x| matches!(x.get_action(), ArgAction::Version))
30             .map(|x| x.get_id())
31             .collect::<Vec<_>>();
32 
33         assert_eq!(version_needed, Vec::<&str>::new(), "Command {}: `ArgAction::Version` used without providing Command::version or Command::long_version"
34             ,cmd.get_name()
35         );
36     }
37 
38     for sc in cmd.get_subcommands() {
39         if let Some(s) = sc.get_short_flag().as_ref() {
40             short_flags.push(Flag::Command(format!("-{s}"), sc.get_name()));
41         }
42 
43         for short_alias in sc.get_all_short_flag_aliases() {
44             short_flags.push(Flag::Command(format!("-{short_alias}"), sc.get_name()));
45         }
46 
47         if let Some(l) = sc.get_long_flag().as_ref() {
48             assert!(!l.starts_with('-'), "Command {}: long_flag {:?} must not start with a `-`, that will be handled by the parser", sc.get_name(), l);
49             long_flags.push(Flag::Command(format!("--{l}"), sc.get_name()));
50         }
51 
52         for long_alias in sc.get_all_long_flag_aliases() {
53             long_flags.push(Flag::Command(format!("--{long_alias}"), sc.get_name()));
54         }
55     }
56 
57     for arg in cmd.get_arguments() {
58         assert_arg(arg);
59 
60         assert!(
61             !cmd.is_multicall_set(),
62             "Command {}: Arguments like {} cannot be set on a multicall command",
63             cmd.get_name(),
64             arg.get_id()
65         );
66 
67         if let Some(s) = arg.get_short() {
68             short_flags.push(Flag::Arg(format!("-{s}"), arg.get_id().as_str()));
69         }
70 
71         for (short_alias, _) in &arg.short_aliases {
72             short_flags.push(Flag::Arg(format!("-{short_alias}"), arg.get_id().as_str()));
73         }
74 
75         if let Some(l) = arg.get_long() {
76             assert!(!l.starts_with('-'), "Argument {}: long {:?} must not start with a `-`, that will be handled by the parser", arg.get_id(), l);
77             long_flags.push(Flag::Arg(format!("--{l}"), arg.get_id().as_str()));
78         }
79 
80         for (long_alias, _) in &arg.aliases {
81             long_flags.push(Flag::Arg(format!("--{long_alias}"), arg.get_id().as_str()));
82         }
83 
84         // Name conflicts
85         if let Some((first, second)) = cmd.two_args_of(|x| x.get_id() == arg.get_id()) {
86             panic!(
87             "Command {}: Argument names must be unique, but '{}' is in use by more than one argument or group{}",
88             cmd.get_name(),
89             arg.get_id(),
90             duplicate_tip(cmd, first, second),
91         );
92         }
93 
94         // Long conflicts
95         if let Some(l) = arg.get_long() {
96             if let Some((first, second)) = cmd.two_args_of(|x| x.get_long() == Some(l)) {
97                 panic!(
98                     "Command {}: Long option names must be unique for each argument, \
99                             but '--{}' is in use by both '{}' and '{}'{}",
100                     cmd.get_name(),
101                     l,
102                     first.get_id(),
103                     second.get_id(),
104                     duplicate_tip(cmd, first, second)
105                 )
106             }
107         }
108 
109         // Short conflicts
110         if let Some(s) = arg.get_short() {
111             if let Some((first, second)) = cmd.two_args_of(|x| x.get_short() == Some(s)) {
112                 panic!(
113                     "Command {}: Short option names must be unique for each argument, \
114                             but '-{}' is in use by both '{}' and '{}'{}",
115                     cmd.get_name(),
116                     s,
117                     first.get_id(),
118                     second.get_id(),
119                     duplicate_tip(cmd, first, second),
120                 )
121             }
122         }
123 
124         // Index conflicts
125         if let Some(idx) = arg.index {
126             if let Some((first, second)) =
127                 cmd.two_args_of(|x| x.is_positional() && x.get_index() == Some(idx))
128             {
129                 panic!(
130                     "Command {}: Argument '{}' has the same index as '{}' \
131                     and they are both positional arguments\n\n\t \
132                     Use `Arg::num_args(1..)` to allow one \
133                     positional argument to take multiple values",
134                     cmd.get_name(),
135                     first.get_id(),
136                     second.get_id()
137                 )
138             }
139         }
140 
141         // requires, r_if, r_unless
142         for req in &arg.requires {
143             assert!(
144                 cmd.id_exists(&req.1),
145                 "Command {}: Argument or group '{}' specified in 'requires*' for '{}' does not exist",
146                 cmd.get_name(),
147                 req.1,
148                 arg.get_id(),
149             );
150         }
151 
152         for req in &arg.r_ifs {
153             assert!(
154                 !arg.is_required_set(),
155                 "Argument {}: `required` conflicts with `required_if_eq*`",
156                 arg.get_id()
157             );
158             assert!(
159                 cmd.id_exists(&req.0),
160                 "Command {}: Argument or group '{}' specified in 'required_if_eq*' for '{}' does not exist",
161                     cmd.get_name(),
162                 req.0,
163                 arg.get_id()
164             );
165         }
166 
167         for req in &arg.r_ifs_all {
168             assert!(
169                 !arg.is_required_set(),
170                 "Argument {}: `required` conflicts with `required_if_eq_all`",
171                 arg.get_id()
172             );
173             assert!(
174                 cmd.id_exists(&req.0),
175                 "Command {}: Argument or group '{}' specified in 'required_if_eq_all' for '{}' does not exist",
176                     cmd.get_name(),
177                 req.0,
178                 arg.get_id()
179             );
180         }
181 
182         for req in &arg.r_unless {
183             assert!(
184                 !arg.is_required_set(),
185                 "Argument {}: `required` conflicts with `required_unless*`",
186                 arg.get_id()
187             );
188             assert!(
189                 cmd.id_exists(req),
190                 "Command {}: Argument or group '{}' specified in 'required_unless*' for '{}' does not exist",
191                     cmd.get_name(),
192                 req,
193                 arg.get_id(),
194             );
195         }
196 
197         for req in &arg.r_unless_all {
198             assert!(
199                 !arg.is_required_set(),
200                 "Argument {}: `required` conflicts with `required_unless*`",
201                 arg.get_id()
202             );
203             assert!(
204                 cmd.id_exists(req),
205                 "Command {}: Argument or group '{}' specified in 'required_unless*' for '{}' does not exist",
206                     cmd.get_name(),
207                 req,
208                 arg.get_id(),
209             );
210         }
211 
212         // blacklist
213         for req in &arg.blacklist {
214             assert!(
215                 cmd.id_exists(req),
216                 "Command {}: Argument or group '{}' specified in 'conflicts_with*' for '{}' does not exist",
217                     cmd.get_name(),
218                 req,
219                 arg.get_id(),
220             );
221         }
222 
223         // overrides
224         for req in &arg.overrides {
225             assert!(
226                 cmd.id_exists(req),
227                 "Command {}: Argument or group '{}' specified in 'overrides_with*' for '{}' does not exist",
228                     cmd.get_name(),
229                 req,
230                 arg.get_id(),
231             );
232         }
233 
234         if arg.is_last_set() {
235             assert!(
236                 arg.get_long().is_none(),
237                 "Command {}: Flags or Options cannot have last(true) set. '{}' has both a long and last(true) set.",
238                     cmd.get_name(),
239                 arg.get_id()
240             );
241             assert!(
242                 arg.get_short().is_none(),
243                 "Command {}: Flags or Options cannot have last(true) set. '{}' has both a short and last(true) set.",
244                     cmd.get_name(),
245                 arg.get_id()
246             );
247         }
248 
249         assert!(
250             !(arg.is_required_set() && arg.is_global_set()),
251             "Command {}: Global arguments cannot be required.\n\n\t'{}' is marked as both global and required",
252                     cmd.get_name(),
253             arg.get_id()
254         );
255 
256         if arg.get_value_hint() == ValueHint::CommandWithArguments {
257             assert!(
258                 arg.is_positional(),
259                 "Command {}: Argument '{}' has hint CommandWithArguments and must be positional.",
260                 cmd.get_name(),
261                 arg.get_id()
262             );
263 
264             assert!(
265                 arg.is_trailing_var_arg_set() || arg.is_last_set(),
266                 "Command {}: Positional argument '{}' has hint CommandWithArguments, so Command must have `trailing_var_arg(true)` or `last(true)` set.",
267                     cmd.get_name(),
268                 arg.get_id()
269             );
270         }
271     }
272 
273     for group in cmd.get_groups() {
274         // Name conflicts
275         assert!(
276             cmd.get_groups().filter(|x| x.id == group.id).count() < 2,
277             "Command {}: Argument group name must be unique\n\n\t'{}' is already in use",
278             cmd.get_name(),
279             group.get_id(),
280         );
281 
282         // Groups should not have naming conflicts with Args
283         assert!(
284             !cmd.get_arguments().any(|x| x.get_id() == group.get_id()),
285             "Command {}: Argument group name '{}' must not conflict with argument name",
286             cmd.get_name(),
287             group.get_id(),
288         );
289 
290         for arg in &group.args {
291             // Args listed inside groups should exist
292             assert!(
293                 cmd.get_arguments().any(|x| x.get_id() == arg),
294                 "Command {}: Argument group '{}' contains non-existent argument '{}'",
295                 cmd.get_name(),
296                 group.get_id(),
297                 arg
298             );
299         }
300 
301         for arg in &group.requires {
302             // Args listed inside groups should exist
303             assert!(
304                 cmd.id_exists(arg),
305                 "Command {}: Argument group '{}' requires non-existent '{}' id",
306                 cmd.get_name(),
307                 group.get_id(),
308                 arg
309             );
310         }
311 
312         for arg in &group.conflicts {
313             // Args listed inside groups should exist
314             assert!(
315                 cmd.id_exists(arg),
316                 "Command {}: Argument group '{}' conflicts with non-existent '{}' id",
317                 cmd.get_name(),
318                 group.get_id(),
319                 arg
320             );
321         }
322     }
323 
324     // Conflicts between flags and subcommands
325 
326     long_flags.sort_unstable();
327     short_flags.sort_unstable();
328 
329     detect_duplicate_flags(&long_flags, "long");
330     detect_duplicate_flags(&short_flags, "short");
331 
332     let mut subs = FlatSet::new();
333     for sc in cmd.get_subcommands() {
334         assert!(
335             subs.insert(sc.get_name()),
336             "Command {}: command name `{}` is duplicated",
337             cmd.get_name(),
338             sc.get_name()
339         );
340         for alias in sc.get_all_aliases() {
341             assert!(
342                 subs.insert(alias),
343                 "Command {}: command `{}` alias `{}` is duplicated",
344                 cmd.get_name(),
345                 sc.get_name(),
346                 alias
347             );
348         }
349     }
350 
351     _verify_positionals(cmd);
352 
353     #[cfg(feature = "help")]
354     if let Some(help_template) = cmd.get_help_template() {
355         assert!(
356             !help_template.to_string().contains("{flags}"),
357             "Command {}: {}",
358                     cmd.get_name(),
359             "`{flags}` template variable was removed in clap3, they are now included in `{options}`",
360         );
361         assert!(
362             !help_template.to_string().contains("{unified}"),
363             "Command {}: {}",
364             cmd.get_name(),
365             "`{unified}` template variable was removed in clap3, use `{options}` instead"
366         );
367         #[cfg(feature = "unstable-v5")]
368         assert!(
369             !help_template.to_string().contains("{bin}"),
370             "Command {}: {}",
371             cmd.get_name(),
372             "`{bin}` template variable was removed in clap5, use `{name}` instead"
373         )
374     }
375 
376     cmd._panic_on_missing_help(cmd.is_help_expected_set());
377     assert_app_flags(cmd);
378 }
379 
duplicate_tip(cmd: &Command, first: &Arg, second: &Arg) -> &'static str380 fn duplicate_tip(cmd: &Command, first: &Arg, second: &Arg) -> &'static str {
381     if !cmd.is_disable_help_flag_set()
382         && (first.get_id() == Id::HELP || second.get_id() == Id::HELP)
383     {
384         " (call `cmd.disable_help_flag(true)` to remove the auto-generated `--help`)"
385     } else if !cmd.is_disable_version_flag_set()
386         && (first.get_id() == Id::VERSION || second.get_id() == Id::VERSION)
387     {
388         " (call `cmd.disable_version_flag(true)` to remove the auto-generated `--version`)"
389     } else {
390         ""
391     }
392 }
393 
394 #[derive(Eq)]
395 enum Flag<'a> {
396     Command(String, &'a str),
397     Arg(String, &'a str),
398 }
399 
400 impl PartialEq for Flag<'_> {
eq(&self, other: &Flag) -> bool401     fn eq(&self, other: &Flag) -> bool {
402         self.cmp(other) == Ordering::Equal
403     }
404 }
405 
406 impl PartialOrd for Flag<'_> {
partial_cmp(&self, other: &Flag) -> Option<Ordering>407     fn partial_cmp(&self, other: &Flag) -> Option<Ordering> {
408         Some(self.cmp(other))
409     }
410 }
411 
412 impl Ord for Flag<'_> {
cmp(&self, other: &Self) -> Ordering413     fn cmp(&self, other: &Self) -> Ordering {
414         use Flag::*;
415 
416         match (self, other) {
417             (Command(s1, _), Command(s2, _))
418             | (Arg(s1, _), Arg(s2, _))
419             | (Command(s1, _), Arg(s2, _))
420             | (Arg(s1, _), Command(s2, _)) => {
421                 if s1 == s2 {
422                     Ordering::Equal
423                 } else {
424                     s1.cmp(s2)
425                 }
426             }
427         }
428     }
429 }
430 
detect_duplicate_flags(flags: &[Flag], short_or_long: &str)431 fn detect_duplicate_flags(flags: &[Flag], short_or_long: &str) {
432     use Flag::*;
433 
434     for (one, two) in find_duplicates(flags) {
435         match (one, two) {
436             (Command(flag, one), Command(_, another)) if one != another => panic!(
437                 "the '{flag}' {short_or_long} flag is specified for both '{one}' and '{another}' subcommands"
438             ),
439 
440             (Arg(flag, one), Arg(_, another)) if one != another => panic!(
441                 "{short_or_long} option names must be unique, but '{flag}' is in use by both '{one}' and '{another}'"
442             ),
443 
444             (Arg(flag, arg), Command(_, sub)) | (Command(flag, sub), Arg(_, arg)) => panic!(
445                 "the '{flag}' {short_or_long} flag for the '{arg}' argument conflicts with the short flag \
446                      for '{sub}' subcommand"
447             ),
448 
449             _ => {}
450         }
451     }
452 }
453 
454 /// Find duplicates in a sorted array.
455 ///
456 /// The algorithm is simple: the array is sorted, duplicates
457 /// must be placed next to each other, we can check only adjacent elements.
find_duplicates<T: PartialEq>(slice: &[T]) -> impl Iterator<Item = (&T, &T)>458 fn find_duplicates<T: PartialEq>(slice: &[T]) -> impl Iterator<Item = (&T, &T)> {
459     slice.windows(2).filter_map(|w| {
460         if w[0] == w[1] {
461             Some((&w[0], &w[1]))
462         } else {
463             None
464         }
465     })
466 }
467 
assert_app_flags(cmd: &Command)468 fn assert_app_flags(cmd: &Command) {
469     macro_rules! checker {
470         ($a:ident requires $($b:ident)|+) => {
471             if cmd.$a() {
472                 let mut s = String::new();
473 
474                 $(
475                     if !cmd.$b() {
476                         use std::fmt::Write;
477                         write!(&mut s, "  AppSettings::{} is required when AppSettings::{} is set.\n", std::stringify!($b), std::stringify!($a)).unwrap();
478                     }
479                 )+
480 
481                 if !s.is_empty() {
482                     panic!("{s}")
483                 }
484             }
485         };
486         ($a:ident conflicts $($b:ident)|+) => {
487             if cmd.$a() {
488                 let mut s = String::new();
489 
490                 $(
491                     if cmd.$b() {
492                         use std::fmt::Write;
493                         write!(&mut s, "  AppSettings::{} conflicts with AppSettings::{}.\n", std::stringify!($b), std::stringify!($a)).unwrap();
494                     }
495                 )+
496 
497                 if !s.is_empty() {
498                     panic!("{}\n{}", cmd.get_name(), s)
499                 }
500             }
501         };
502     }
503 
504     checker!(is_multicall_set conflicts is_no_binary_name_set);
505 }
506 
507 #[cfg(debug_assertions)]
_verify_positionals(cmd: &Command) -> bool508 fn _verify_positionals(cmd: &Command) -> bool {
509     debug!("Command::_verify_positionals");
510     // Because you must wait until all arguments have been supplied, this is the first chance
511     // to make assertions on positional argument indexes
512     //
513     // First we verify that the index highest supplied index, is equal to the number of
514     // positional arguments to verify there are no gaps (i.e. supplying an index of 1 and 3
515     // but no 2)
516 
517     let highest_idx = cmd
518         .get_keymap()
519         .keys()
520         .filter_map(|x| {
521             if let KeyType::Position(n) = x {
522                 Some(*n)
523             } else {
524                 None
525             }
526         })
527         .max()
528         .unwrap_or(0);
529 
530     let num_p = cmd.get_keymap().keys().filter(|x| x.is_position()).count();
531 
532     assert!(
533         highest_idx == num_p,
534         "Found positional argument whose index is {highest_idx} but there \
535              are only {num_p} positional arguments defined",
536     );
537 
538     for arg in cmd.get_arguments() {
539         if arg.index.unwrap_or(0) == highest_idx {
540             assert!(
541                 !arg.is_trailing_var_arg_set() || !arg.is_last_set(),
542                 "{}:{}: `Arg::trailing_var_arg` and `Arg::last` cannot be used together",
543                 cmd.get_name(),
544                 arg.get_id()
545             );
546 
547             if arg.is_trailing_var_arg_set() {
548                 assert!(
549                     arg.is_multiple(),
550                     "{}:{}: `Arg::trailing_var_arg` must accept multiple values",
551                     cmd.get_name(),
552                     arg.get_id()
553                 );
554             }
555         } else {
556             assert!(
557                 !arg.is_trailing_var_arg_set(),
558                 "{}:{}: `Arg::trailing_var_arg` can only apply to last positional",
559                 cmd.get_name(),
560                 arg.get_id()
561             );
562         }
563     }
564 
565     // Next we verify that only the highest index has takes multiple arguments (if any)
566     let only_highest = |a: &Arg| a.is_multiple() && (a.get_index().unwrap_or(0) != highest_idx);
567     if cmd.get_positionals().any(only_highest) {
568         // First we make sure if there is a positional that allows multiple values
569         // the one before it (second to last) has one of these:
570         //  * a value terminator
571         //  * ArgSettings::Last
572         //  * The last arg is Required
573 
574         // We can't pass the closure (it.next()) to the macro directly because each call to
575         // find() (iterator, not macro) gets called repeatedly.
576         let last = &cmd.get_keymap()[&KeyType::Position(highest_idx)];
577         let second_to_last = &cmd.get_keymap()[&KeyType::Position(highest_idx - 1)];
578 
579         // Either the final positional is required
580         // Or the second to last has a terminator or .last(true) set
581         let ok = last.is_required_set()
582             || (second_to_last.terminator.is_some() || second_to_last.is_last_set())
583             || last.is_last_set();
584         assert!(
585             ok,
586             "Positional argument `{last}` *must* have `required(true)` or `last(true)` set \
587             because a prior positional argument (`{second_to_last}`) has `num_args(1..)`"
588         );
589 
590         // We make sure if the second to last is Multiple the last is ArgSettings::Last
591         let ok = second_to_last.is_multiple() || last.is_last_set();
592         assert!(
593             ok,
594             "Only the last positional argument, or second to last positional \
595                  argument may be set to `.num_args(1..)`"
596         );
597 
598         // Next we check how many have both Multiple and not a specific number of values set
599         let count = cmd
600             .get_positionals()
601             .filter(|p| {
602                 p.is_multiple_values_set()
603                     && p.get_value_terminator().is_none()
604                     && !p.get_num_args().expect(INTERNAL_ERROR_MSG).is_fixed()
605             })
606             .count();
607         let ok = count <= 1
608             || (last.is_last_set()
609                 && last.is_multiple()
610                 && second_to_last.is_multiple()
611                 && count == 2);
612         assert!(
613             ok,
614             "Only one positional argument with `.num_args(1..)` set is allowed per \
615                  command, unless the second one also has .last(true) set"
616         );
617     }
618 
619     let mut found = false;
620 
621     if cmd.is_allow_missing_positional_set() {
622         // Check that if a required positional argument is found, all positions with a lower
623         // index are also required.
624         let mut foundx2 = false;
625 
626         for p in cmd.get_positionals() {
627             if foundx2 && !p.is_required_set() {
628                 assert!(
629                     p.is_required_set(),
630                     "Found non-required positional argument with a lower \
631                          index than a required positional argument by two or more: {:?} \
632                          index {:?}",
633                     p.get_id(),
634                     p.get_index()
635                 );
636             } else if p.is_required_set() && !p.is_last_set() {
637                 // Args that .last(true) don't count since they can be required and have
638                 // positionals with a lower index that aren't required
639                 // Imagine: prog <req1> [opt1] -- <req2>
640                 // Both of these are valid invocations:
641                 //      $ prog r1 -- r2
642                 //      $ prog r1 o1 -- r2
643                 if found {
644                     foundx2 = true;
645                     continue;
646                 }
647                 found = true;
648                 continue;
649             } else {
650                 found = false;
651             }
652         }
653     } else {
654         // Check that if a required positional argument is found, all positions with a lower
655         // index are also required
656         for p in (1..=num_p).rev().filter_map(|n| cmd.get_keymap().get(&n)) {
657             if found {
658                 assert!(
659                     p.is_required_set(),
660                     "Found non-required positional argument with a lower \
661                          index than a required positional argument: {:?} index {:?}",
662                     p.get_id(),
663                     p.get_index()
664                 );
665             } else if p.is_required_set() && !p.is_last_set() {
666                 // Args that .last(true) don't count since they can be required and have
667                 // positionals with a lower index that aren't required
668                 // Imagine: prog <req1> [opt1] -- <req2>
669                 // Both of these are valid invocations:
670                 //      $ prog r1 -- r2
671                 //      $ prog r1 o1 -- r2
672                 found = true;
673                 continue;
674             }
675         }
676     }
677     assert!(
678         cmd.get_positionals().filter(|p| p.is_last_set()).count() < 2,
679         "Only one positional argument may have last(true) set. Found two."
680     );
681     if cmd
682         .get_positionals()
683         .any(|p| p.is_last_set() && p.is_required_set())
684         && cmd.has_subcommands()
685         && !cmd.is_subcommand_negates_reqs_set()
686     {
687         panic!(
688             "Having a required positional argument with .last(true) set *and* child \
689                  subcommands without setting SubcommandsNegateReqs isn't compatible."
690         );
691     }
692 
693     true
694 }
695 
assert_arg(arg: &Arg)696 fn assert_arg(arg: &Arg) {
697     debug!("Arg::_debug_asserts:{}", arg.get_id());
698 
699     // Self conflict
700     // TODO: this check should be recursive
701     assert!(
702         !arg.blacklist.iter().any(|x| x == arg.get_id()),
703         "Argument '{}' cannot conflict with itself",
704         arg.get_id(),
705     );
706 
707     if arg.is_takes_value_set() {
708         assert!(
709             arg.get_action().takes_values(),
710             "Argument `{}`'s selected action {:?} contradicts `takes_value`",
711             arg.get_id(),
712             arg.get_action()
713         );
714     }
715     if let Some(action_type_id) = arg.get_action().value_type_id() {
716         assert_eq!(
717             action_type_id,
718             arg.get_value_parser().type_id(),
719             "Argument `{}`'s selected action {:?} contradicts `value_parser` ({:?})",
720             arg.get_id(),
721             arg.get_action(),
722             arg.get_value_parser()
723         );
724     }
725 
726     if arg.get_value_hint() != ValueHint::Unknown {
727         assert!(
728             arg.is_takes_value_set(),
729             "Argument '{}' has value hint but takes no value",
730             arg.get_id()
731         );
732 
733         if arg.get_value_hint() == ValueHint::CommandWithArguments {
734             assert!(
735                 arg.is_multiple_values_set(),
736                 "Argument '{}' uses hint CommandWithArguments and must accept multiple values",
737                 arg.get_id()
738             )
739         }
740     }
741 
742     if arg.index.is_some() {
743         assert!(
744             arg.is_positional(),
745             "Argument '{}' is a positional argument and can't have short or long name versions",
746             arg.get_id()
747         );
748         assert!(
749             arg.is_takes_value_set(),
750             "Argument '{}` is positional and it must take a value but action is {:?}{}",
751             arg.get_id(),
752             arg.get_action(),
753             if arg.get_id() == Id::HELP {
754                 " (`mut_arg` no longer works with implicit `--help`)"
755             } else if arg.get_id() == Id::VERSION {
756                 " (`mut_arg` no longer works with implicit `--version`)"
757             } else {
758                 ""
759             }
760         );
761     }
762 
763     let num_vals = arg.get_num_args().expect(INTERNAL_ERROR_MSG);
764     // This can be the cause of later asserts, so put this first
765     if num_vals != ValueRange::EMPTY {
766         // HACK: Don't check for flags to make the derive easier
767         let num_val_names = arg.get_value_names().unwrap_or(&[]).len();
768         if num_vals.max_values() < num_val_names {
769             panic!(
770                 "Argument {}: Too many value names ({}) compared to `num_args` ({})",
771                 arg.get_id(),
772                 num_val_names,
773                 num_vals
774             );
775         }
776     }
777 
778     assert_eq!(
779         num_vals.is_multiple(),
780         arg.is_multiple_values_set(),
781         "Argument {}: mismatch between `num_args` ({}) and `multiple_values`",
782         arg.get_id(),
783         num_vals,
784     );
785 
786     if 1 < num_vals.min_values() {
787         assert!(
788             !arg.is_require_equals_set(),
789             "Argument {}: cannot accept more than 1 arg (num_args={}) with require_equals",
790             arg.get_id(),
791             num_vals
792         );
793     }
794 
795     if num_vals == ValueRange::SINGLE {
796         assert!(
797             !arg.is_multiple_values_set(),
798             "Argument {}: mismatch between `num_args` and `multiple_values`",
799             arg.get_id()
800         );
801     }
802 
803     assert_arg_flags(arg);
804 }
805 
assert_arg_flags(arg: &Arg)806 fn assert_arg_flags(arg: &Arg) {
807     macro_rules! checker {
808         ($a:ident requires $($b:ident)|+) => {
809             if arg.$a() {
810                 let mut s = String::new();
811 
812                 $(
813                     if !arg.$b() {
814                         use std::fmt::Write;
815                         write!(&mut s, "  Arg::{} is required when Arg::{} is set.\n", std::stringify!($b), std::stringify!($a)).unwrap();
816                     }
817                 )+
818 
819                 if !s.is_empty() {
820                     panic!("Argument {:?}\n{}", arg.get_id(), s)
821                 }
822             }
823         }
824     }
825 
826     checker!(is_hide_possible_values_set requires is_takes_value_set);
827     checker!(is_allow_hyphen_values_set requires is_takes_value_set);
828     checker!(is_allow_negative_numbers_set requires is_takes_value_set);
829     checker!(is_require_equals_set requires is_takes_value_set);
830     checker!(is_last_set requires is_takes_value_set);
831     checker!(is_hide_default_value_set requires is_takes_value_set);
832     checker!(is_multiple_values_set requires is_takes_value_set);
833     checker!(is_ignore_case_set requires is_takes_value_set);
834 }
835