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