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