Skip to main content

clap_builder/builder/
debug_asserts.rs

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