Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

flatten with required breaks requirement checking #5435

Closed
2 tasks done
nihaals opened this issue Mar 28, 2024 · 1 comment
Closed
2 tasks done

flatten with required breaks requirement checking #5435

nihaals opened this issue Mar 28, 2024 · 1 comment
Labels
C-bug Category: Updating dependencies

Comments

@nihaals
Copy link
Contributor

nihaals commented Mar 28, 2024

Please complete the following tasks

Rust Version

rustc 1.77.1 (7cf61ebde 2024-03-27)

Clap Version

4.5.4

Minimal reproducible code

use clap::{Args, Parser, Subcommand};

#[derive(Parser)]
struct Cli {
    #[command(subcommand)]
    command: Commands,
}

#[derive(Subcommand)]
enum Commands {
    Test {
        #[command(flatten)]
        targets: Target,
    },
}

#[derive(Args)]
#[group(required = true)]
struct Target {
    #[arg(long)]
    all: bool,

    #[command(flatten)]
    items: Items,
}

#[derive(Args)]
#[group(multiple = true)]
struct Items {
    #[arg(long)]
    foo: bool,

    #[arg(long)]
    bar: bool,
}

fn main() {
    Cli::parse();
}

Steps to reproduce the bug with the above code

  • cargo run -- test --help
  • cargo run -- test
  • cargo run -- test --all
  • cargo run -- test --foo --bar
  • cargo run -- test --all --foo

Actual Behaviour

error: the following required arguments were not provided:
  <>

Expected Behaviour

Passing in --all should satisfy the requirement and it shouldn't show an empty list of required arguments.

Additional Context

The goal is to either require an individual item or --all. So test fails, test --all and test --foo --bar works and test --all --foo fails.

This is possible by avoiding flatten and using conflicts instead:

#[derive(Args)]
#[group(required = true)]
struct Target {
    #[arg(long)]
    all: bool,

    #[arg(long, conflicts_with = "all")]
    foo: bool,

    #[arg(long, conflicts_with = "all")]
    bar: bool,
}

I couldn't figure out a way to use conflicts as a workaround with flatten if the group is still required.

Also an extra question, I'm unsure if this is the best way to have a flag that essentially sets the values of a set of other flags (without checking if all is true and then mutating foo and bar to true/always checking if foo or all is true). It seems like setting a custom action wouldn't help here (to set foo and bar to true) but maybe there's a better way of setting up the flags here that avoids this issue.

Debug Output

[clap_builder::builder::command]Command::_do_parse
[clap_builder::builder::command]Command::_build: name="binary"
[clap_builder::builder::command]Command::_propagate:binary
[clap_builder::builder::command]Command::_check_help_and_version:binary expand_help_tree=false
[clap_builder::builder::command]Command::long_help_exists
[clap_builder::builder::command]Command::_check_help_and_version: Building default --help
[clap_builder::builder::command]Command::_check_help_and_version: Building default --version
[clap_builder::builder::command]Command::_check_help_and_version: Building help subcommand
[clap_builder::builder::command]Command::_propagate_global_args:binary
[clap_builder::builder::debug_asserts]Command::_debug_asserts
[clap_builder::builder::debug_asserts]Arg::_debug_asserts:help
[clap_builder::builder::debug_asserts]Arg::_debug_asserts:version
[clap_builder::builder::debug_asserts]Command::_verify_positionals
[clap_builder::parser::parser]Parser::get_matches_with
[clap_builder::parser::parser]Parser::get_matches_with: Begin parsing '"test"'
[clap_builder::parser::parser]Parser::possible_subcommand: arg=Ok("test")
[clap_builder::parser::parser]Parser::get_matches_with: sc=Some("test")
[clap_builder::parser::parser]Parser::parse_subcommand
[ clap_builder::output::usage]Usage::get_required_usage_from: incls=[], matcher=false, incl_last=true
[ clap_builder::output::usage]Usage::get_required_usage_from: unrolled_reqs=[]
[ clap_builder::output::usage]Usage::get_required_usage_from: ret_val=[]
[clap_builder::builder::command]Command::_build_subcommand Setting bin_name of test to "binary test"
[clap_builder::builder::command]Command::_build_subcommand Setting display_name of test to "binary-test"
[clap_builder::builder::command]Command::_build: name="test"
[clap_builder::builder::command]Command::_propagate:test
[clap_builder::builder::command]Command::_check_help_and_version:test expand_help_tree=false
[clap_builder::builder::command]Command::long_help_exists
[clap_builder::builder::command]Command::_check_help_and_version: Building default --help
[clap_builder::builder::command]Command::_propagate_global_args:test
[clap_builder::builder::debug_asserts]Command::_debug_asserts
[clap_builder::builder::debug_asserts]Arg::_debug_asserts:all
[clap_builder::builder::debug_asserts]Arg::_debug_asserts:foo
[clap_builder::builder::debug_asserts]Arg::_debug_asserts:bar
[clap_builder::builder::debug_asserts]Arg::_debug_asserts:help
[clap_builder::builder::debug_asserts]Command::_verify_positionals
[clap_builder::parser::parser]Parser::parse_subcommand: About to parse sc=test
[clap_builder::parser::parser]Parser::get_matches_with
[clap_builder::parser::parser]Parser::get_matches_with: Begin parsing '"--all"'
[clap_builder::parser::parser]Parser::possible_subcommand: arg=Ok("--all")
[clap_builder::parser::parser]Parser::get_matches_with: sc=None
[clap_builder::parser::parser]Parser::parse_long_arg
[clap_builder::parser::parser]Parser::parse_long_arg: Does it contain '='...
[clap_builder::parser::parser]Parser::parse_long_arg: Found valid arg or flag '--all'
[clap_builder::parser::parser]Parser::parse_long_arg("all"): Presence validated
[clap_builder::parser::parser]Parser::react action=SetTrue, identifier=Some(Long), source=CommandLine
[clap_builder::parser::parser]Parser::react: has default_missing_vals
[clap_builder::parser::parser]Parser::remove_overrides: id="all"
[clap_builder::parser::arg_matcher]ArgMatcher::start_custom_arg: id="all", source=CommandLine
[clap_builder::builder::command]Command::groups_for_arg: id="all"
[clap_builder::parser::parser]Parser::push_arg_values: ["true"]
[clap_builder::parser::parser]Parser::add_single_val_to_arg: cur_idx:=1
[clap_builder::parser::parser]Parser::get_matches_with: After parse_long_arg ValuesDone
[clap_builder::parser::parser]Parser::add_defaults
[clap_builder::parser::parser]Parser::add_defaults:iter:all:
[clap_builder::parser::parser]Parser::add_default_value: doesn't have conditional defaults
[clap_builder::parser::parser]Parser::add_default_value:iter:all: has default vals
[clap_builder::parser::parser]Parser::add_default_value:iter:all: was used
[clap_builder::parser::parser]Parser::add_defaults:iter:foo:
[clap_builder::parser::parser]Parser::add_default_value: doesn't have conditional defaults
[clap_builder::parser::parser]Parser::add_default_value:iter:foo: has default vals
[clap_builder::parser::parser]Parser::add_default_value:iter:foo: wasn't used
[clap_builder::parser::parser]Parser::react action=SetTrue, identifier=None, source=DefaultValue
[clap_builder::parser::arg_matcher]ArgMatcher::start_custom_arg: id="foo", source=DefaultValue
[clap_builder::parser::parser]Parser::push_arg_values: ["false"]
[clap_builder::parser::parser]Parser::add_single_val_to_arg: cur_idx:=2
[clap_builder::parser::parser]Parser::add_defaults:iter:bar:
[clap_builder::parser::parser]Parser::add_default_value: doesn't have conditional defaults
[clap_builder::parser::parser]Parser::add_default_value:iter:bar: has default vals
[clap_builder::parser::parser]Parser::add_default_value:iter:bar: wasn't used
[clap_builder::parser::parser]Parser::react action=SetTrue, identifier=None, source=DefaultValue
[clap_builder::parser::arg_matcher]ArgMatcher::start_custom_arg: id="bar", source=DefaultValue
[clap_builder::parser::parser]Parser::push_arg_values: ["false"]
[clap_builder::parser::parser]Parser::add_single_val_to_arg: cur_idx:=3
[clap_builder::parser::parser]Parser::add_defaults:iter:help:
[clap_builder::parser::parser]Parser::add_default_value: doesn't have conditional defaults
[clap_builder::parser::parser]Parser::add_default_value:iter:help: doesn't have default vals
[clap_builder::parser::validator]Validator::validate
[clap_builder::builder::command]Command::groups_for_arg: id="all"
[clap_builder::parser::validator]Conflicts::gather_direct_conflicts id="all", conflicts=[]
[clap_builder::parser::validator]Validator::validate_conflicts
[clap_builder::parser::validator]Validator::validate_exclusive
[clap_builder::parser::validator]Validator::validate_conflicts::iter: id="all"
[clap_builder::parser::validator]Conflicts::gather_conflicts: arg="all"
[clap_builder::parser::validator]Conflicts::gather_conflicts: conflicts=[]
[clap_builder::parser::validator]Validator::validate_required: required=ChildGraph([Child { id: "Target", children: [] }])
[clap_builder::parser::validator]Validator::gather_requires
[clap_builder::parser::validator]Validator::gather_requires:iter:"all"
[clap_builder::parser::validator]Validator::validate_required: is_exclusive_present=false
[clap_builder::parser::validator]Validator::validate_required:iter:aog="Target"
[clap_builder::parser::validator]Validator::validate_required:iter: This is a group
[clap_builder::builder::command]Command::unroll_args_in_group: group="Target"
[clap_builder::parser::validator]Validator::validate_required:iter: Missing "Target"
[clap_builder::parser::validator]Validator::missing_required_error; incl=["Target"]
[clap_builder::parser::validator]Validator::missing_required_error: reqs=ChildGraph([Child { id: "Target", children: [] }])
[ clap_builder::output::usage]Usage::get_required_usage_from: incls=["Target"], matcher=true, incl_last=true
[ clap_builder::output::usage]Usage::get_required_usage_from: unrolled_reqs=["Target"]
[clap_builder::builder::command]Command::unroll_args_in_group: group="Target"
[ clap_builder::output::usage]Usage::get_required_usage_from:iter:"Target" group is_present=false
[clap_builder::builder::command]Command::unroll_args_in_group: group="Target"
[clap_builder::builder::command]Command::unroll_args_in_group: group="Target"
[ clap_builder::output::usage]Usage::get_required_usage_from:iter:"Target" group is_present=false
[clap_builder::builder::command]Command::unroll_args_in_group: group="Target"
[ clap_builder::output::usage]Usage::get_required_usage_from: ret_val=[StyledStr("<>")]
[clap_builder::parser::validator]Validator::missing_required_error: req_args=[
    "<>",
]
[ clap_builder::output::usage]Usage::create_usage_with_title
[ clap_builder::output::usage]Usage::create_usage_no_title
[ clap_builder::output::usage]Usage::create_smart_usage
[ clap_builder::output::usage]Usage::write_arg_usage; incl_reqs=true
[ clap_builder::output::usage]Usage::write_args: incls=["all", "Target"]
[ clap_builder::output::usage]Usage::get_args: unrolled_reqs=["Target"]
[clap_builder::builder::command]Command::unroll_args_in_group: group="Target"
[clap_builder::builder::command]Command::unroll_args_in_group: group="Target"
[clap_builder::builder::command]Command::unroll_args_in_group: group="Target"
[clap_builder::builder::command]Command::unroll_args_in_group: group="Target"
[ clap_builder::output::usage]Usage::create_usage_with_title: usage=Usage: binary test --all <>
[clap_builder::builder::command]Command::color: Color setting...
[clap_builder::builder::command]Auto
[clap_builder::builder::command]Command::color: Color setting...
[clap_builder::builder::command]Auto
error: the following required arguments were not provided:
  <>
@nihaals nihaals added the C-bug Category: Updating dependencies label Mar 28, 2024
@epage
Copy link
Member

epage commented Mar 28, 2024

I believe this is another variant of #4697. There are ... issues when relying on the behavior of nested groups.

In both cases, when an argument is set, we also set the group. However, that isn't recursive up the chain and so when the top-level group checks the status of its direct members, it gets incomplete information.

Closing in favor of #4697 though we should not this variant of the symptom.

If there is a reason we should keep this open separately, let us know!

@epage epage closed this as not planned Won't fix, can't repro, duplicate, stale Mar 28, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
C-bug Category: Updating dependencies
Projects
None yet
Development

No branches or pull requests

2 participants