• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 use std::cmp::Ordering;
2 
3 use clap_lex::RawOsStr;
4 
5 use crate::builder::OsStr;
6 use crate::builder::ValueRange;
7 use crate::mkeymap::KeyType;
8 use crate::util::FlatSet;
9 use crate::util::Id;
10 use crate::ArgAction;
11 use crate::INTERNAL_ERROR_MSG;
12 use crate::{Arg, Command, ValueHint};
13 
assert_app(cmd: &Command)14 pub(crate) fn assert_app(cmd: &Command) {
15     debug!("Command::_debug_asserts");
16 
17     let mut short_flags = vec![];
18     let mut long_flags = vec![];
19 
20     // Invalid version flag settings
21     if cmd.get_version().is_none() && cmd.get_long_version().is_none() {
22         // PropagateVersion is meaningless if there is no version
23         assert!(
24             !cmd.is_propagate_version_set(),
25             "Command {}: No version information via Command::version or Command::long_version to propagate",
26             cmd.get_name(),
27         );
28 
29         // Used `Command::mut_arg("version", ..) but did not provide any version information to display
30         let version_needed = cmd
31             .get_arguments()
32             .filter(|x| matches!(x.get_action(), ArgAction::Version))
33             .map(|x| x.get_id())
34             .collect::<Vec<_>>();
35 
36         assert_eq!(version_needed, Vec::<&str>::new(), "Command {}: `ArgAction::Version` used without providing Command::version or Command::long_version"
37             ,cmd.get_name()
38         );
39     }
40 
41     for sc in cmd.get_subcommands() {
42         if let Some(s) = sc.get_short_flag().as_ref() {
43             short_flags.push(Flag::Command(format!("-{}", s), sc.get_name()));
44         }
45 
46         for short_alias in sc.get_all_short_flag_aliases() {
47             short_flags.push(Flag::Command(format!("-{}", short_alias), sc.get_name()));
48         }
49 
50         if let Some(l) = sc.get_long_flag().as_ref() {
51             assert!(!l.starts_with('-'), "Command {}: long_flag {:?} must not start with a `-`, that will be handled by the parser", sc.get_name(), l);
52             long_flags.push(Flag::Command(format!("--{}", l), sc.get_name()));
53         }
54 
55         for long_alias in sc.get_all_long_flag_aliases() {
56             long_flags.push(Flag::Command(format!("--{}", long_alias), sc.get_name()));
57         }
58     }
59 
60     for arg in cmd.get_arguments() {
61         assert_arg(arg);
62 
63         assert!(
64             !cmd.is_multicall_set(),
65             "Command {}: Arguments like {} cannot be set on a multicall command",
66             cmd.get_name(),
67             arg.get_id()
68         );
69 
70         if let Some(s) = arg.get_short() {
71             short_flags.push(Flag::Arg(format!("-{}", s), arg.get_id().as_str()));
72         }
73 
74         for (short_alias, _) in &arg.short_aliases {
75             short_flags.push(Flag::Arg(
76                 format!("-{}", short_alias),
77                 arg.get_id().as_str(),
78             ));
79         }
80 
81         if let Some(l) = arg.get_long() {
82             assert!(!l.starts_with('-'), "Argument {}: long {:?} must not start with a `-`, that will be handled by the parser", arg.get_id(), l);
83             long_flags.push(Flag::Arg(format!("--{}", l), arg.get_id().as_str()));
84         }
85 
86         for (long_alias, _) in &arg.aliases {
87             long_flags.push(Flag::Arg(
88                 format!("--{}", long_alias),
89                 arg.get_id().as_str(),
90             ));
91         }
92 
93         // Name conflicts
94         if let Some((first, second)) = cmd.two_args_of(|x| x.get_id() == arg.get_id()) {
95             panic!(
96             "Command {}: Argument names must be unique, but '{}' is in use by more than one argument or group{}",
97             cmd.get_name(),
98             arg.get_id(),
99             duplicate_tip(cmd, first, second),
100         );
101         }
102 
103         // Long conflicts
104         if let Some(l) = arg.get_long() {
105             if let Some((first, second)) = cmd.two_args_of(|x| x.get_long() == Some(l)) {
106                 panic!(
107                     "Command {}: Long option names must be unique for each argument, \
108                             but '--{}' is in use by both '{}' and '{}'{}",
109                     cmd.get_name(),
110                     l,
111                     first.get_id(),
112                     second.get_id(),
113                     duplicate_tip(cmd, first, second)
114                 )
115             }
116         }
117 
118         // Short conflicts
119         if let Some(s) = arg.get_short() {
120             if let Some((first, second)) = cmd.two_args_of(|x| x.get_short() == Some(s)) {
121                 panic!(
122                     "Command {}: Short option names must be unique for each argument, \
123                             but '-{}' is in use by both '{}' and '{}'{}",
124                     cmd.get_name(),
125                     s,
126                     first.get_id(),
127                     second.get_id(),
128                     duplicate_tip(cmd, first, second),
129                 )
130             }
131         }
132 
133         // Index conflicts
134         if let Some(idx) = arg.index {
135             if let Some((first, second)) =
136                 cmd.two_args_of(|x| x.is_positional() && x.get_index() == Some(idx))
137             {
138                 panic!(
139                     "Command {}: Argument '{}' has the same index as '{}' \
140                     and they are both positional arguments\n\n\t \
141                     Use `Arg::num_args(1..)` to allow one \
142                     positional argument to take multiple values",
143                     cmd.get_name(),
144                     first.get_id(),
145                     second.get_id()
146                 )
147             }
148         }
149 
150         // requires, r_if, r_unless
151         for req in &arg.requires {
152             assert!(
153                 cmd.id_exists(&req.1),
154                 "Command {}: Argument or group '{}' specified in 'requires*' for '{}' does not exist",
155                 cmd.get_name(),
156                 req.1,
157                 arg.get_id(),
158             );
159         }
160 
161         for req in &arg.r_ifs {
162             assert!(
163                 !arg.is_required_set(),
164                 "Argument {}: `required` conflicts with `required_if_eq*`",
165                 arg.get_id()
166             );
167             assert!(
168                 cmd.id_exists(&req.0),
169                 "Command {}: Argument or group '{}' specified in 'required_if_eq*' for '{}' does not exist",
170                     cmd.get_name(),
171                 req.0,
172                 arg.get_id()
173             );
174         }
175 
176         for req in &arg.r_ifs_all {
177             assert!(
178                 !arg.is_required_set(),
179                 "Argument {}: `required` conflicts with `required_if_eq_all`",
180                 arg.get_id()
181             );
182             assert!(
183                 cmd.id_exists(&req.0),
184                 "Command {}: Argument or group '{}' specified in 'required_if_eq_all' for '{}' does not exist",
185                     cmd.get_name(),
186                 req.0,
187                 arg.get_id()
188             );
189         }
190 
191         for req in &arg.r_unless {
192             assert!(
193                 !arg.is_required_set(),
194                 "Argument {}: `required` conflicts with `required_unless*`",
195                 arg.get_id()
196             );
197             assert!(
198                 cmd.id_exists(req),
199                 "Command {}: Argument or group '{}' specified in 'required_unless*' for '{}' does not exist",
200                     cmd.get_name(),
201                 req,
202                 arg.get_id(),
203             );
204         }
205 
206         for req in &arg.r_unless_all {
207             assert!(
208                 !arg.is_required_set(),
209                 "Argument {}: `required` conflicts with `required_unless*`",
210                 arg.get_id()
211             );
212             assert!(
213                 cmd.id_exists(req),
214                 "Command {}: Argument or group '{}' specified in 'required_unless*' for '{}' does not exist",
215                     cmd.get_name(),
216                 req,
217                 arg.get_id(),
218             );
219         }
220 
221         // blacklist
222         for req in &arg.blacklist {
223             assert!(
224                 cmd.id_exists(req),
225                 "Command {}: Argument or group '{}' specified in 'conflicts_with*' for '{}' does not exist",
226                     cmd.get_name(),
227                 req,
228                 arg.get_id(),
229             );
230         }
231 
232         // overrides
233         for req in &arg.overrides {
234             assert!(
235                 cmd.id_exists(req),
236                 "Command {}: Argument or group '{}' specified in 'overrides_with*' for '{}' does not exist",
237                     cmd.get_name(),
238                 req,
239                 arg.get_id(),
240             );
241         }
242 
243         if arg.is_last_set() {
244             assert!(
245                 arg.get_long().is_none(),
246                 "Command {}: Flags or Options cannot have last(true) set. '{}' has both a long and last(true) set.",
247                     cmd.get_name(),
248                 arg.get_id()
249             );
250             assert!(
251                 arg.get_short().is_none(),
252                 "Command {}: Flags or Options cannot have last(true) set. '{}' has both a short and last(true) set.",
253                     cmd.get_name(),
254                 arg.get_id()
255             );
256         }
257 
258         assert!(
259             !(arg.is_required_set() && arg.is_global_set()),
260             "Command {}: Global arguments cannot be required.\n\n\t'{}' is marked as both global and required",
261                     cmd.get_name(),
262             arg.get_id()
263         );
264 
265         if arg.get_value_hint() == ValueHint::CommandWithArguments {
266             assert!(
267                 arg.is_positional(),
268                 "Command {}: Argument '{}' has hint CommandWithArguments and must be positional.",
269                 cmd.get_name(),
270                 arg.get_id()
271             );
272 
273             assert!(
274                 arg.is_trailing_var_arg_set() || arg.is_last_set(),
275                 "Command {}: Positional argument '{}' has hint CommandWithArguments, so Command must have `trailing_var_arg(true)` or `last(true)` set.",
276                     cmd.get_name(),
277                 arg.get_id()
278             );
279         }
280     }
281 
282     for group in cmd.get_groups() {
283         let derive_hint = if cfg!(feature = "derive") {
284             " (note: `Args` implicitly creates `ArgGroup`s; disable with `#[group(skip)]`"
285         } else {
286             ""
287         };
288 
289         // Name conflicts
290         assert!(
291             cmd.get_groups().filter(|x| x.id == group.id).count() < 2,
292             "Command {}: Argument group name must be unique\n\n\t'{}' is already in use{}",
293             cmd.get_name(),
294             group.get_id(),
295             derive_hint
296         );
297 
298         // Groups should not have naming conflicts with Args
299         assert!(
300             !cmd.get_arguments().any(|x| x.get_id() == group.get_id()),
301             "Command {}: Argument group name '{}' must not conflict with argument name{}",
302             cmd.get_name(),
303             group.get_id(),
304             derive_hint
305         );
306 
307         for arg in &group.args {
308             // Args listed inside groups should exist
309             assert!(
310                 cmd.get_arguments().any(|x| x.get_id() == arg),
311                 "Command {}: Argument group '{}' contains non-existent argument '{}'",
312                 cmd.get_name(),
313                 group.get_id(),
314                 arg
315             );
316         }
317     }
318 
319     // Conflicts between flags and subcommands
320 
321     long_flags.sort_unstable();
322     short_flags.sort_unstable();
323 
324     detect_duplicate_flags(&long_flags, "long");
325     detect_duplicate_flags(&short_flags, "short");
326 
327     let mut subs = FlatSet::new();
328     for sc in cmd.get_subcommands() {
329         assert!(
330             subs.insert(sc.get_name()),
331             "Command {}: command name `{}` is duplicated",
332             cmd.get_name(),
333             sc.get_name()
334         );
335         for alias in sc.get_all_aliases() {
336             assert!(
337                 subs.insert(alias),
338                 "Command {}: command `{}` alias `{}` is duplicated",
339                 cmd.get_name(),
340                 sc.get_name(),
341                 alias
342             );
343         }
344     }
345 
346     _verify_positionals(cmd);
347 
348     #[cfg(feature = "help")]
349     if let Some(help_template) = cmd.get_help_template() {
350         assert!(
351             !help_template.to_string().contains("{flags}"),
352             "Command {}: {}",
353                     cmd.get_name(),
354             "`{flags}` template variable was removed in clap3, they are now included in `{options}`",
355         );
356         assert!(
357             !help_template.to_string().contains("{unified}"),
358             "Command {}: {}",
359             cmd.get_name(),
360             "`{unified}` template variable was removed in clap3, use `{options}` instead"
361         );
362         #[cfg(feature = "unstable-v5")]
363         assert!(
364             !help_template.to_string().contains("{bin}"),
365             "Command {}: {}",
366             cmd.get_name(),
367             "`{bin}` template variable was removed in clap5, use `{name}` instead"
368         )
369     }
370 
371     cmd._panic_on_missing_help(cmd.is_help_expected_set());
372     assert_app_flags(cmd);
373 }
374 
duplicate_tip(cmd: &Command, first: &Arg, second: &Arg) -> &'static str375 fn duplicate_tip(cmd: &Command, first: &Arg, second: &Arg) -> &'static str {
376     if !cmd.is_disable_help_flag_set()
377         && (first.get_id() == Id::HELP || second.get_id() == Id::HELP)
378     {
379         " (call `cmd.disable_help_flag(true)` to remove the auto-generated `--help`)"
380     } else if !cmd.is_disable_version_flag_set()
381         && (first.get_id() == Id::VERSION || second.get_id() == Id::VERSION)
382     {
383         " (call `cmd.disable_version_flag(true)` to remove the auto-generated `--version`)"
384     } else {
385         ""
386     }
387 }
388 
389 #[derive(Eq)]
390 enum Flag<'a> {
391     Command(String, &'a str),
392     Arg(String, &'a str),
393 }
394 
395 impl PartialEq for Flag<'_> {
eq(&self, other: &Flag) -> bool396     fn eq(&self, other: &Flag) -> bool {
397         self.cmp(other) == Ordering::Equal
398     }
399 }
400 
401 impl PartialOrd for Flag<'_> {
partial_cmp(&self, other: &Flag) -> Option<Ordering>402     fn partial_cmp(&self, other: &Flag) -> Option<Ordering> {
403         use Flag::*;
404 
405         match (self, other) {
406             (Command(s1, _), Command(s2, _))
407             | (Arg(s1, _), Arg(s2, _))
408             | (Command(s1, _), Arg(s2, _))
409             | (Arg(s1, _), Command(s2, _)) => {
410                 if s1 == s2 {
411                     Some(Ordering::Equal)
412                 } else {
413                     s1.partial_cmp(s2)
414                 }
415             }
416         }
417     }
418 }
419 
420 impl Ord for Flag<'_> {
cmp(&self, other: &Self) -> Ordering421     fn cmp(&self, other: &Self) -> Ordering {
422         self.partial_cmp(other).unwrap()
423     }
424 }
425 
detect_duplicate_flags(flags: &[Flag], short_or_long: &str)426 fn detect_duplicate_flags(flags: &[Flag], short_or_long: &str) {
427     use Flag::*;
428 
429     for (one, two) in find_duplicates(flags) {
430         match (one, two) {
431             (Command(flag, one), Command(_, another)) if one != another => panic!(
432                 "the '{}' {} flag is specified for both '{}' and '{}' subcommands",
433                 flag, short_or_long, one, another
434             ),
435 
436             (Arg(flag, one), Arg(_, another)) if one != another => panic!(
437                 "{} option names must be unique, but '{}' is in use by both '{}' and '{}'",
438                 short_or_long, flag, one, another
439             ),
440 
441             (Arg(flag, arg), Command(_, sub)) | (Command(flag, sub), Arg(_, arg)) => panic!(
442                 "the '{}' {} flag for the '{}' argument conflicts with the short flag \
443                      for '{}' subcommand",
444                 flag, short_or_long, arg, sub
445             ),
446 
447             _ => {}
448         }
449     }
450 }
451 
452 /// Find duplicates in a sorted array.
453 ///
454 /// The algorithm is simple: the array is sorted, duplicates
455 /// must be placed next to each other, we can check only adjacent elements.
find_duplicates<T: PartialEq>(slice: &[T]) -> impl Iterator<Item = (&T, &T)>456 fn find_duplicates<T: PartialEq>(slice: &[T]) -> impl Iterator<Item = (&T, &T)> {
457     slice.windows(2).filter_map(|w| {
458         if w[0] == w[1] {
459             Some((&w[0], &w[1]))
460         } else {
461             None
462         }
463     })
464 }
465 
assert_app_flags(cmd: &Command)466 fn assert_app_flags(cmd: &Command) {
467     macro_rules! checker {
468         ($a:ident requires $($b:ident)|+) => {
469             if cmd.$a() {
470                 let mut s = String::new();
471 
472                 $(
473                     if !cmd.$b() {
474                         use std::fmt::Write;
475                         write!(&mut s, "  AppSettings::{} is required when AppSettings::{} is set.\n", std::stringify!($b), std::stringify!($a)).unwrap();
476                     }
477                 )+
478 
479                 if !s.is_empty() {
480                     panic!("{}", s)
481                 }
482             }
483         };
484         ($a:ident conflicts $($b:ident)|+) => {
485             if cmd.$a() {
486                 let mut s = String::new();
487 
488                 $(
489                     if cmd.$b() {
490                         use std::fmt::Write;
491                         write!(&mut s, "  AppSettings::{} conflicts with AppSettings::{}.\n", std::stringify!($b), std::stringify!($a)).unwrap();
492                     }
493                 )+
494 
495                 if !s.is_empty() {
496                     panic!("{}\n{}", cmd.get_name(), s)
497                 }
498             }
499         };
500     }
501 
502     checker!(is_multicall_set conflicts is_no_binary_name_set);
503 }
504 
505 #[cfg(debug_assertions)]
_verify_positionals(cmd: &Command) -> bool506 fn _verify_positionals(cmd: &Command) -> bool {
507     debug!("Command::_verify_positionals");
508     // Because you must wait until all arguments have been supplied, this is the first chance
509     // to make assertions on positional argument indexes
510     //
511     // First we verify that the index highest supplied index, is equal to the number of
512     // positional arguments to verify there are no gaps (i.e. supplying an index of 1 and 3
513     // but no 2)
514 
515     let highest_idx = cmd
516         .get_keymap()
517         .keys()
518         .filter_map(|x| {
519             if let KeyType::Position(n) = x {
520                 Some(*n)
521             } else {
522                 None
523             }
524         })
525         .max()
526         .unwrap_or(0);
527 
528     let num_p = cmd.get_keymap().keys().filter(|x| x.is_position()).count();
529 
530     assert!(
531         highest_idx == num_p,
532         "Found positional argument whose index is {} but there \
533              are only {} positional arguments defined",
534         highest_idx,
535         num_p
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             "When using a positional argument with `.num_args(1..)` that is *not the \
587                  last* positional argument, the last positional argument (i.e. the one \
588                  with the highest index) *must* have .required(true) or .last(true) set."
589         );
590 
591         // We make sure if the second to last is Multiple the last is ArgSettings::Last
592         let ok = second_to_last.is_multiple() || last.is_last_set();
593         assert!(
594             ok,
595             "Only the last positional argument, or second to last positional \
596                  argument may be set to `.num_args(1..)`"
597         );
598 
599         // Next we check how many have both Multiple and not a specific number of values set
600         let count = cmd
601             .get_positionals()
602             .filter(|p| {
603                 p.is_multiple_values_set()
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     assert_eq!(
708         arg.get_action().takes_values(),
709         arg.is_takes_value_set(),
710         "Argument `{}`'s selected action {:?} contradicts `takes_value`",
711         arg.get_id(),
712         arg.get_action()
713     );
714     if let Some(action_type_id) = arg.get_action().value_type_id() {
715         assert_eq!(
716             action_type_id,
717             arg.get_value_parser().type_id(),
718             "Argument `{}`'s selected action {:?} contradicts `value_parser` ({:?})",
719             arg.get_id(),
720             arg.get_action(),
721             arg.get_value_parser()
722         );
723     }
724 
725     if arg.get_value_hint() != ValueHint::Unknown {
726         assert!(
727             arg.is_takes_value_set(),
728             "Argument '{}' has value hint but takes no value",
729             arg.get_id()
730         );
731 
732         if arg.get_value_hint() == ValueHint::CommandWithArguments {
733             assert!(
734                 arg.is_multiple_values_set(),
735                 "Argument '{}' uses hint CommandWithArguments and must accept multiple values",
736                 arg.get_id()
737             )
738         }
739     }
740 
741     if arg.index.is_some() {
742         assert!(
743             arg.is_positional(),
744             "Argument '{}' is a positional argument and can't have short or long name versions",
745             arg.get_id()
746         );
747         assert!(
748             arg.is_takes_value_set(),
749             "Argument '{}` is positional, it must take a value{}",
750             arg.get_id(),
751             if arg.get_id() == Id::HELP {
752                 " (`mut_arg` no longer works with implicit `--help`)"
753             } else if arg.get_id() == Id::VERSION {
754                 " (`mut_arg` no longer works with implicit `--version`)"
755             } else {
756                 ""
757             }
758         );
759     }
760 
761     let num_vals = arg.get_num_args().expect(INTERNAL_ERROR_MSG);
762     // This can be the cause of later asserts, so put this first
763     if num_vals != ValueRange::EMPTY {
764         // HACK: Don't check for flags to make the derive easier
765         let num_val_names = arg.get_value_names().unwrap_or(&[]).len();
766         if num_vals.max_values() < num_val_names {
767             panic!(
768                 "Argument {}: Too many value names ({}) compared to `num_args` ({})",
769                 arg.get_id(),
770                 num_val_names,
771                 num_vals
772             );
773         }
774     }
775 
776     assert_eq!(
777         num_vals.takes_values(),
778         arg.is_takes_value_set(),
779         "Argument {}: mismatch between `num_args` ({}) and `takes_value`",
780         arg.get_id(),
781         num_vals,
782     );
783     assert_eq!(
784         num_vals.is_multiple(),
785         arg.is_multiple_values_set(),
786         "Argument {}: mismatch between `num_args` ({}) and `multiple_values`",
787         arg.get_id(),
788         num_vals,
789     );
790 
791     if 1 < num_vals.min_values() {
792         assert!(
793             !arg.is_require_equals_set(),
794             "Argument {}: cannot accept more than 1 arg (num_args={}) with require_equals",
795             arg.get_id(),
796             num_vals
797         );
798     }
799 
800     if num_vals == ValueRange::SINGLE {
801         assert!(
802             !arg.is_multiple_values_set(),
803             "Argument {}: mismatch between `num_args` and `multiple_values`",
804             arg.get_id()
805         );
806     }
807 
808     assert_arg_flags(arg);
809 
810     assert_defaults(arg, "default_value", arg.default_vals.iter());
811     assert_defaults(
812         arg,
813         "default_missing_value",
814         arg.default_missing_vals.iter(),
815     );
816     assert_defaults(
817         arg,
818         "default_value_if",
819         arg.default_vals_ifs
820             .iter()
821             .filter_map(|(_, _, default)| default.as_ref()),
822     );
823 }
824 
assert_arg_flags(arg: &Arg)825 fn assert_arg_flags(arg: &Arg) {
826     macro_rules! checker {
827         ($a:ident requires $($b:ident)|+) => {
828             if arg.$a() {
829                 let mut s = String::new();
830 
831                 $(
832                     if !arg.$b() {
833                         use std::fmt::Write;
834                         write!(&mut s, "  Arg::{} is required when Arg::{} is set.\n", std::stringify!($b), std::stringify!($a)).unwrap();
835                     }
836                 )+
837 
838                 if !s.is_empty() {
839                     panic!("Argument {:?}\n{}", arg.get_id(), s)
840                 }
841             }
842         }
843     }
844 
845     checker!(is_hide_possible_values_set requires is_takes_value_set);
846     checker!(is_allow_hyphen_values_set requires is_takes_value_set);
847     checker!(is_allow_negative_numbers_set requires is_takes_value_set);
848     checker!(is_require_equals_set requires is_takes_value_set);
849     checker!(is_last_set requires is_takes_value_set);
850     checker!(is_hide_default_value_set requires is_takes_value_set);
851     checker!(is_multiple_values_set requires is_takes_value_set);
852     checker!(is_ignore_case_set requires is_takes_value_set);
853 }
854 
assert_defaults<'d>( arg: &Arg, field: &'static str, defaults: impl IntoIterator<Item = &'d OsStr>, )855 fn assert_defaults<'d>(
856     arg: &Arg,
857     field: &'static str,
858     defaults: impl IntoIterator<Item = &'d OsStr>,
859 ) {
860     for default_os in defaults {
861         let value_parser = arg.get_value_parser();
862         let assert_cmd = Command::new("assert");
863         if let Some(delim) = arg.get_value_delimiter() {
864             let default_os = RawOsStr::new(default_os);
865             for part in default_os.split(delim) {
866                 if let Err(err) = value_parser.parse_ref(&assert_cmd, Some(arg), &part.to_os_str())
867                 {
868                     panic!(
869                         "Argument `{}`'s {}={:?} failed validation: {}",
870                         arg.get_id(),
871                         field,
872                         part.to_str_lossy(),
873                         err
874                     );
875                 }
876             }
877         } else if let Err(err) = value_parser.parse_ref(&assert_cmd, Some(arg), default_os) {
878             panic!(
879                 "Argument `{}`'s {}={:?} failed validation: {}",
880                 arg.get_id(),
881                 field,
882                 default_os,
883                 err
884             );
885         }
886     }
887 }
888