clap_builder/builder/
debug_asserts.rs

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