1use std::cmp::Ordering;
2
3use clap_lex::RawOsStr;
4
5use crate::builder::OsStr;
6use crate::builder::ValueRange;
7use crate::mkeymap::KeyType;
8use crate::util::FlatSet;
9use crate::util::Id;
10use crate::ArgAction;
11use crate::INTERNAL_ERROR_MSG;
12use crate::{Arg, Command, ValueHint};
13
14pub(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
369fn 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)]
384enum Flag<'a> {
385    Command(String, &'a str),
386    Arg(String, &'a str),
387}
388
389impl PartialEq for Flag<'_> {
390    fn eq(&self, other: &Flag) -> bool {
391        self.cmp(other) == Ordering::Equal
392    }
393}
394
395impl PartialOrd for Flag<'_> {
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
414impl Ord for Flag<'_> {
415    fn cmp(&self, other: &Self) -> Ordering {
416        self.partial_cmp(other).unwrap()
417    }
418}
419
420fn 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.
447fn 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
457fn 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)]
497fn _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
685fn 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
814fn 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
844fn 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