clap-rs / clap

A full featured, fast Command Line Argument Parser for Rust
docs.rs/clap
Apache License 2.0
14.15k stars 1.03k forks source link

clap_complete panics if subcomand has bin_name defined #4996

Closed cymruu closed 1 month ago

cymruu commented 1 year ago

Please complete the following tasks

Rust Version

rustc 1.72.0-nightly (f20afcc45 2023-07-04)

Clap Version

master (1289534b)

Minimal reproducible code

Create a file named binary_wrapped.rs in clap/clap_complete/examples/

use clap::Command;
use clap_complete::{generate, shells::Bash, Generator};
use std::io;

fn build_cli() -> Command {
    clap::Command::new("wrapper")
        .bin_name("wrapper")
        .subcommand(
            clap::Command::new("cli")
                .bin_name("cli")
                .subcommand(clap::Command::new("test")),
        )
}

fn print_completions<G: Generator>(gen: G, cmd: &mut Command) {
    generate(gen, cmd, cmd.get_name().to_string(), &mut io::stdout());
}

fn main() {
    let mut cmd = build_cli();
    print_completions(Bash, &mut cmd);
}

Steps to reproduce the bug with the above code

In clap/clap_complete run cargo run --example binary_wrapped

Actual Behaviour

clap_complete panics

thread 'main' panicked at 'called `Option::unwrap()` on a `None` value', clap_complete/src/generator/utils.rs:26:39
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

RUST_BACKTRACE=1 cargo run --example binary_wrapped output below

click to expand ``` thread 'main' panicked at 'called `Option::unwrap()` on a `None` value', clap_complete/src/generator/utils.rs:26:39 stack backtrace: 0: rust_begin_unwind at /rustc/90c541806f23a127002de5b4038be731ba1458ca/library/std/src/panicking.rs:578:5 1: core::panicking::panic_fmt at /rustc/90c541806f23a127002de5b4038be731ba1458ca/library/core/src/panicking.rs:67:14 2: core::panicking::panic at /rustc/90c541806f23a127002de5b4038be731ba1458ca/library/core/src/panicking.rs:117:5 3: core::option::Option::unwrap at /rustc/90c541806f23a127002de5b4038be731ba1458ca/library/core/src/option.rs:950:21 4: clap_complete::generator::utils::find_subcommand_with_path at ./src/generator/utils.rs:26:15 5: clap_complete::shells::bash::all_options_for_path at ./src/shells/bash.rs:219:13 6: clap_complete::shells::bash::subcommand_details::{{closure}} at ./src/shells/bash.rs:151:23 7: core::iter::adapters::map::map_fold::{{closure}} at /rustc/90c541806f23a127002de5b4038be731ba1458ca/library/core/src/iter/adapters/map.rs:84:28 8: core::iter::traits::iterator::Iterator::fold at /rustc/90c541806f23a127002de5b4038be731ba1458ca/library/core/src/iter/traits/iterator.rs:2482:21 9: as core::iter::traits::iterator::Iterator>::fold at /rustc/90c541806f23a127002de5b4038be731ba1458ca/library/core/src/iter/adapters/map.rs:124:9 10: core::iter::traits::iterator::Iterator::for_each at /rustc/90c541806f23a127002de5b4038be731ba1458ca/library/core/src/iter/traits/iterator.rs:857:9 11: alloc::vec::Vec::extend_trusted at /rustc/90c541806f23a127002de5b4038be731ba1458ca/library/alloc/src/vec/mod.rs:2844:17 12: as alloc::vec::spec_extend::SpecExtend>::spec_extend at /rustc/90c541806f23a127002de5b4038be731ba1458ca/library/alloc/src/vec/spec_extend.rs:26:9 13: as core::iter::traits::collect::Extend>::extend at /rustc/90c541806f23a127002de5b4038be731ba1458ca/library/alloc/src/vec/mod.rs:2786:9 14: clap_complete::shells::bash::subcommand_details at ./src/shells/bash.rs:134:5 15: ::generate at ./src/shells/bash.rs:68:34 16: clap_complete::generator::_generate at ./src/generator/mod.rs:260:5 17: clap_complete::generator::generate at ./src/generator/mod.rs:255:5 18: binary_wrapped::print_completions at ./examples/binary_wrapped.rs:24:5 19: binary_wrapped::main at ./examples/binary_wrapped.rs:29:5 20: core::ops::function::FnOnce::call_once at /rustc/90c541806f23a127002de5b4038be731ba1458ca/library/core/src/ops/function.rs:250:5 ```

Expected Behaviour

Bash completions should be generated

Additional Context

I think the issue might be caused by the difference between the all_subcommands and subcommand_details functions. The first one contains custom implementation of method returning command paths, while the latter uses utility method called all_subcommands to generate them.

Debug Output

[clap_builder::builder::command]Command::_build: name="wrapper"
[clap_builder::builder::command]Command::_propagate:wrapper
[clap_builder::builder::command]Command::_check_help_and_version:wrapper expand_help_tree=true
[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 help subcommand
[clap_builder::builder::command]Command::_propagate_global_args:wrapper
[clap_builder::builder::debug_asserts]Command::_debug_asserts
[clap_builder::builder::debug_asserts]Arg::_debug_asserts:help
[clap_builder::builder::debug_asserts]Command::_verify_positionals
[clap_builder::builder::command]Command::_build: name="cli"
[clap_builder::builder::command]Command::_propagate:cli
[clap_builder::builder::command]Command::_check_help_and_version:cli expand_help_tree=true
[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 help subcommand
[clap_builder::builder::command]Command::_propagate_global_args:cli
[clap_builder::builder::debug_asserts]Command::_debug_asserts
[clap_builder::builder::debug_asserts]Arg::_debug_asserts:help
[clap_builder::builder::debug_asserts]Command::_verify_positionals
[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=true
[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:help
[clap_builder::builder::debug_asserts]Command::_verify_positionals
[clap_builder::builder::command]Command::_build: name="help"
[clap_builder::builder::command]Command::_propagate:help
[clap_builder::builder::command]Command::_check_help_and_version:help expand_help_tree=true
[clap_builder::builder::command]Command::long_help_exists
[clap_builder::builder::command]Command::_propagate_global_args:help
[clap_builder::builder::debug_asserts]Command::_debug_asserts
[clap_builder::builder::debug_asserts]Command::_verify_positionals
[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=true
[clap_builder::builder::command]Command::long_help_exists
[clap_builder::builder::command]Command::_propagate_global_args:test
[clap_builder::builder::debug_asserts]Command::_debug_asserts
[clap_builder::builder::debug_asserts]Command::_verify_positionals
[clap_builder::builder::command]Command::_build: name="help"
[clap_builder::builder::command]Command::_propagate:help
[clap_builder::builder::command]Command::_check_help_and_version:help expand_help_tree=true
[clap_builder::builder::command]Command::long_help_exists
[clap_builder::builder::command]Command::_propagate_global_args:help
[clap_builder::builder::debug_asserts]Command::_debug_asserts
[clap_builder::builder::debug_asserts]Command::_verify_positionals
[clap_builder::builder::command]Command::_build: name="help"
[clap_builder::builder::command]Command::_propagate:help
[clap_builder::builder::command]Command::_check_help_and_version:help expand_help_tree=true
[clap_builder::builder::command]Command::long_help_exists
[clap_builder::builder::command]Command::_propagate_global_args:help
[clap_builder::builder::debug_asserts]Command::_debug_asserts
[clap_builder::builder::debug_asserts]Command::_verify_positionals
[clap_builder::builder::command]Command::_build: name="cli"
[clap_builder::builder::command]Command::_propagate:cli
[clap_builder::builder::command]Command::_check_help_and_version:cli expand_help_tree=true
[clap_builder::builder::command]Command::long_help_exists
[clap_builder::builder::command]Command::_propagate_global_args:cli
[clap_builder::builder::debug_asserts]Command::_debug_asserts
[clap_builder::builder::debug_asserts]Command::_verify_positionals
[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=true
[clap_builder::builder::command]Command::long_help_exists
[clap_builder::builder::command]Command::_propagate_global_args:test
[clap_builder::builder::debug_asserts]Command::_debug_asserts
[clap_builder::builder::debug_asserts]Command::_verify_positionals
[clap_builder::builder::command]Command::_build: name="help"
[clap_builder::builder::command]Command::_propagate:help
[clap_builder::builder::command]Command::_check_help_and_version:help expand_help_tree=true
[clap_builder::builder::command]Command::long_help_exists
[clap_builder::builder::command]Command::_propagate_global_args:help
[clap_builder::builder::debug_asserts]Command::_debug_asserts
[clap_builder::builder::debug_asserts]Command::_verify_positionals
[clap_builder::builder::command]Command::_build_bin_names
[clap_builder::builder::command]Command::_build_bin_names:iter: bin_name set...
[clap_builder::builder::command]Command::_build_bin_names:iter: Setting usage_name of cli to "wrapper cli"
[clap_builder::builder::command]Command::_build_bin_names::iter: Using existing bin_name of cli (Some("cli"))
[clap_builder::builder::command]Command::_build_bin_names:iter: Setting display_name of cli to "wrapper-cli"
[clap_builder::builder::command]Command::_build_bin_names
[clap_builder::builder::command]Command::_build_bin_names:iter: bin_name set...
[clap_builder::builder::command]Command::_build_bin_names:iter: Setting usage_name of test to "cli test"
[clap_builder::builder::command]Command::_build_bin_names:iter: Setting bin_name of test to "cli test"
[clap_builder::builder::command]Command::_build_bin_names:iter: Setting display_name of test to "wrapper-cli-test"
[clap_builder::builder::command]Command::_build_bin_names
[clap_builder::builder::command]Command::_build_bin_names:iter: bin_name set...
[clap_builder::builder::command]Command::_build_bin_names:iter: Setting usage_name of help to "cli help"
[clap_builder::builder::command]Command::_build_bin_names:iter: Setting bin_name of help to "cli help"
[clap_builder::builder::command]Command::_build_bin_names:iter: Setting display_name of help to "wrapper-cli-help"
[clap_builder::builder::command]Command::_build_bin_names
[clap_builder::builder::command]Command::_build_bin_names:iter: bin_name set...
[clap_builder::builder::command]Command::_build_bin_names:iter: Setting usage_name of test to "cli help test"
[clap_builder::builder::command]Command::_build_bin_names:iter: Setting bin_name of test to "cli help test"
[clap_builder::builder::command]Command::_build_bin_names:iter: Setting display_name of test to "wrapper-cli-help-test"
[clap_builder::builder::command]Command::_build_bin_names
[clap_builder::builder::command]Command::_build_bin_names:iter: bin_name set...
[clap_builder::builder::command]Command::_build_bin_names:iter: Setting usage_name of help to "cli help help"
[clap_builder::builder::command]Command::_build_bin_names:iter: Setting bin_name of help to "cli help help"
[clap_builder::builder::command]Command::_build_bin_names:iter: Setting display_name of help to "wrapper-cli-help-help"
[clap_builder::builder::command]Command::_build_bin_names
[clap_builder::builder::command]Command::_build_bin_names:iter: bin_name set...
[clap_builder::builder::command]Command::_build_bin_names:iter: Setting usage_name of help to "wrapper help"
[clap_builder::builder::command]Command::_build_bin_names:iter: Setting bin_name of help to "wrapper help"
[clap_builder::builder::command]Command::_build_bin_names:iter: Setting display_name of help to "wrapper-help"
[clap_builder::builder::command]Command::_build_bin_names
[clap_builder::builder::command]Command::_build_bin_names:iter: bin_name set...
[clap_builder::builder::command]Command::_build_bin_names:iter: Setting usage_name of cli to "wrapper help cli"
[clap_builder::builder::command]Command::_build_bin_names:iter: Setting bin_name of cli to "wrapper help cli"
[clap_builder::builder::command]Command::_build_bin_names:iter: Setting display_name of cli to "wrapper-help-cli"
[clap_builder::builder::command]Command::_build_bin_names
[clap_builder::builder::command]Command::_build_bin_names:iter: bin_name set...
[clap_builder::builder::command]Command::_build_bin_names:iter: Setting usage_name of test to "wrapper help cli test"
[clap_builder::builder::command]Command::_build_bin_names:iter: Setting bin_name of test to "wrapper help cli test"
[clap_builder::builder::command]Command::_build_bin_names:iter: Setting display_name of test to "wrapper-help-cli-test"
[clap_builder::builder::command]Command::_build_bin_names
[clap_builder::builder::command]Command::_build_bin_names:iter: bin_name set...
[clap_builder::builder::command]Command::_build_bin_names:iter: Setting usage_name of help to "wrapper help help"
[clap_builder::builder::command]Command::_build_bin_names:iter: Setting bin_name of help to "wrapper help help"
[clap_builder::builder::command]Command::_build_bin_names:iter: Setting display_name of help to "wrapper-help-help"
[clap_builder::builder::command]Command::_build_bin_names
thread 'main' panicked at 'called `Option::unwrap()` on a `None` value', clap_complete/src/generator/utils.rs:26:39
epage commented 1 year ago

Ended up creating a test for this


#[test]
#[should_panic]
fn subcommand_has_binname() {
    use clap::Command;
    use clap_complete::{generate, shells::Bash};

    fn build_cli() -> Command {
        clap::Command::new("wrapper")
            .bin_name("wrapper")
            .subcommand(
                clap::Command::new("cli")
                    .bin_name("cli")
                    .subcommand(clap::Command::new("test")),
            )
    }

    let mut cmd = build_cli();
    let mut buffer = Vec::new();
    let name = cmd.get_name().to_string();
    generate(Bash, &mut cmd, name, &mut buffer);
}
epage commented 1 year ago

This is messy.

The problem is that we use names and bin names interchangeably throughout the code.

Personally, I would put effort more towards finishing #3166 than fixing this

epage commented 1 month ago

As I believe this is resolved with the native completions, I'm going to close this. If this was incorrect, let us know!

You can track stabilization at #3166