Repository: clap-rs/clap (language: rust)

Issue:
clap_complete: subcommands are broken in bash if the binary name is not the same as the package name
### Please complete the following tasks

- [X] I have searched the [discussions](https://github.com/clap-rs/clap/discussions)
- [X] I have searched the [open](https://github.com/clap-rs/clap/issues) and [rejected](https://github.com/clap-rs/clap/issues?q=is%3Aissue+label%3AS-wont-fix+is%3Aclosed) issues

### Rust Version

rustc -V

### Clap Version

4.4.11

### Minimal reproducible code

Cargo.toml
```toml
[package]
name = "package-name"
version = "0.1.0"
edition = "2021"

[[bin]]
name = "bin-name"
path = "src/main.rs"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
clap = { version = "4.4.11", features = ["derive"] }
clap_complete = { version = "4.4.4"}
```
main.rs
```rust
use clap::{Parser, Subcommand};
use clap_complete::Shell;

pub const BIN_NAME: &str = env!("CARGO_BIN_NAME");

#[derive(Debug, Parser)]
enum Cli {
    #[clap(subcommand)]
    Command(MySubcommand),
    ShellCompletions,
}

#[derive(Subcommand, Debug)]
enum MySubcommand {
    Get,
    Set,
}

fn main() {
    match Cli::parse() {
        Cli::Command(sub) => match sub {
            MySubcommand::Get => println!("get"),
            MySubcommand::Set => println!("set"),
        },
        Cli::ShellCompletions => {
            println!("Generating completions");
            use clap::CommandFactory;

            clap_complete::generate_to(Shell::Bash, &mut Cli::command(), BIN_NAME, "./")
                .unwrap();
        }
    }
}

```


### Steps to reproduce the bug with the above code

```bash
cargo run -- shell-completions

# Install completions
sudo cp bin-name.bash /usr/share/bash-completion/completions/bin-name
sudo cp target/debug/bin-name /usr/bin/
sudo chmod +x /usr/bin/bin-name
```

There will be no completions for the `get` or `set` subcommands when typing `bin-name command` and pressing tab.

### Actual Behaviour

The completion file will refer to the package name for subcommands.  Completions for top level commands will still use the binary name, hence the user will only get top level completions.

```bash
...
    for i in ${COMP_WORDS[@]}
    do
        case "${cmd},${i}" in
            ",$1")
                cmd="bin__name"
                ;;
            package__name,command)
                cmd="package__name__command"
                ;;
            package__name,help)
                cmd="package__name__help"
                ;;
            package__name,shell-completions)
                cmd="package__name__shell__completions"
                ;;
            package__name__command,get)
                cmd="package__name__command__get"
...
```

### Expected Behaviour

The correct completion file looks like this:

```bash
...
    for i in ${COMP_WORDS[@]}
    do
        case "${cmd},${i}" in
            ",$1")
                cmd="bin__name"
                ;;
            bin__name,command)
                cmd="bin__name__command"
                ;;
            bin__name,help)
                cmd="bin__name__help"
                ;;
            bin__name,shell-completions)
                cmd="bin__name__shell__completions"
                ;;
            bin__name__command,get)
                cmd="bin__name__command__get"
...
```

Installing that file will generate completions for e.g. `bin-name command get` .

### Additional Context

_No response_

### Debug Output

_No response_


Discussion / hints:

Fix patch:
diff --git a/clap_complete/src/shells/bash.rs b/clap_complete/src/shells/bash.rs
index 726078ab366..708a2144912 100644
--- a/clap_complete/src/shells/bash.rs
+++ b/clap_complete/src/shells/bash.rs
@@ -18,6 +18,8 @@ impl Generator for Bash {
             .get_bin_name()
             .expect("crate::generate should have set the bin_name");
 
+        let fn_name = bin_name.replace('-', "__");
+
         w!(
             buf,
             format!(
@@ -65,10 +67,10 @@ else
 fi
 ",
                 name = bin_name,
-                cmd = bin_name.replace('-', "__"),
+                cmd = fn_name,
                 name_opts = all_options_for_path(cmd, bin_name),
                 name_opts_details = option_details_for_path(cmd, bin_name),
-                subcmds = all_subcommands(cmd),
+                subcmds = all_subcommands(cmd, &fn_name),
                 subcmd_details = subcommand_details(cmd)
             )
             .as_bytes()
@@ -76,7 +78,7 @@ fi
     }
 }
 
-fn all_subcommands(cmd: &Command) -> String {
+fn all_subcommands(cmd: &Command, parent_fn_name: &str) -> String {
     debug!("all_subcommands");
 
     fn add_command(
@@ -106,9 +108,8 @@ fn all_subcommands(cmd: &Command) -> String {
         }
     }
     let mut subcmds = vec![];
-    let fn_name = cmd.get_name().replace('-', "__");
     for subcmd in cmd.get_subcommands() {
-        add_command(&fn_name, subcmd, &mut subcmds);
+        add_command(parent_fn_name, subcmd, &mut subcmds);
     }
     subcmds.sort();
 


Test patch:
diff --git a/clap_complete/tests/snapshots/custom_bin_name.bash b/clap_complete/tests/snapshots/custom_bin_name.bash
new file mode 100644
index 00000000000..9ba9e9a2b81
--- /dev/null
+++ b/clap_complete/tests/snapshots/custom_bin_name.bash
@@ -0,0 +1,110 @@
+_bin-name() {
+    local i cur prev opts cmd
+    COMPREPLY=()
+    cur="${COMP_WORDS[COMP_CWORD]}"
+    prev="${COMP_WORDS[COMP_CWORD-1]}"
+    cmd=""
+    opts=""
+
+    for i in ${COMP_WORDS[@]}
+    do
+        case "${cmd},${i}" in
+            ",$1")
+                cmd="bin__name"
+                ;;
+            bin__name,help)
+                cmd="bin__name__help"
+                ;;
+            bin__name,test)
+                cmd="bin__name__test"
+                ;;
+            bin__name__help,help)
+                cmd="bin__name__help__help"
+                ;;
+            bin__name__help,test)
+                cmd="bin__name__help__test"
+                ;;
+            *)
+                ;;
+        esac
+    done
+
+    case "${cmd}" in
+        bin__name)
+            opts="-c -v -h --help test help"
+            if [[ ${cur} == -* || ${COMP_CWORD} -eq 1 ]] ; then
+                COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
+                return 0
+            fi
+            case "${prev}" in
+                *)
+                    COMPREPLY=()
+                    ;;
+            esac
+            COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
+            return 0
+            ;;
+        bin__name__help)
+            opts="test help"
+            if [[ ${cur} == -* || ${COMP_CWORD} -eq 2 ]] ; then
+                COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
+                return 0
+            fi
+            case "${prev}" in
+                *)
+                    COMPREPLY=()
+                    ;;
+            esac
+            COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
+            return 0
+            ;;
+        bin__name__help__help)
+            opts=""
+            if [[ ${cur} == -* || ${COMP_CWORD} -eq 3 ]] ; then
+                COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
+                return 0
+            fi
+            case "${prev}" in
+                *)
+                    COMPREPLY=()
+                    ;;
+            esac
+            COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
+            return 0
+            ;;
+        bin__name__help__test)
+            opts=""
+            if [[ ${cur} == -* || ${COMP_CWORD} -eq 3 ]] ; then
+                COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
+                return 0
+            fi
+            case "${prev}" in
+                *)
+                    COMPREPLY=()
+                    ;;
+            esac
+            COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
+            return 0
+            ;;
+        bin__name__test)
+            opts="-d -c -h --help"
+            if [[ ${cur} == -* || ${COMP_CWORD} -eq 2 ]] ; then
+                COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
+                return 0
+            fi
+            case "${prev}" in
+                *)
+                    COMPREPLY=()
+                    ;;
+            esac
+            COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
+            return 0
+            ;;
+    esac
+}
+
+if [[ "${BASH_VERSINFO[0]}" -eq 4 && "${BASH_VERSINFO[1]}" -ge 4 || "${BASH_VERSINFO[0]}" -gt 4 ]]; then
+    complete -F _bin-name -o nosort -o bashdefault -o default bin-name
+else
+    complete -F _bin-name -o bashdefault -o default bin-name
+fi
diff --git a/clap_complete/tests/snapshots/custom_bin_name.elvish b/clap_complete/tests/snapshots/custom_bin_name.elvish
new file mode 100644
index 00000000000..734c6cb3e3a
--- /dev/null
+++ b/clap_complete/tests/snapshots/custom_bin_name.elvish
@@ -0,0 +1,44 @@
+
+use builtin;
+use str;
+
+set edit:completion:arg-completer[bin-name] = {|@words|
+    fn spaces {|n|
+        builtin:repeat $n ' ' | str:join ''
+    }
+    fn cand {|text desc|
+        edit:complex-candidate $text &display=$text' '(spaces (- 14 (wcswidth $text)))$desc
+    }
+    var command = 'bin-name'
+    for word $words[1..-1] {
+        if (str:has-prefix $word '-') {
+            break
+        }
+        set command = $command';'$word
+    }
+    var completions = [
+        &'bin-name'= {
+            cand -c 'c'
+            cand -v 'v'
+            cand -h 'Print help'
+            cand --help 'Print help'
+            cand test 'Subcommand'
+            cand help 'Print this message or the help of the given subcommand(s)'
+        }
+        &'bin-name;test'= {
+            cand -d 'd'
+            cand -c 'c'
+            cand -h 'Print help'
+            cand --help 'Print help'
+        }
+        &'bin-name;help'= {
+            cand test 'Subcommand'
+            cand help 'Print this message or the help of the given subcommand(s)'
+        }
+        &'bin-name;help;test'= {
+        }
+        &'bin-name;help;help'= {
+        }
+    ]
+    $completions[$command]
+}
diff --git a/clap_complete/tests/snapshots/custom_bin_name.fish b/clap_complete/tests/snapshots/custom_bin_name.fish
new file mode 100644
index 00000000000..5652ac0aab0
--- /dev/null
+++ b/clap_complete/tests/snapshots/custom_bin_name.fish
@@ -0,0 +1,10 @@
+complete -c bin-name -n "__fish_use_subcommand" -s c
+complete -c bin-name -n "__fish_use_subcommand" -s v
+complete -c bin-name -n "__fish_use_subcommand" -s h -l help -d 'Print help'
+complete -c bin-name -n "__fish_use_subcommand" -f -a "test" -d 'Subcommand'
+complete -c bin-name -n "__fish_use_subcommand" -f -a "help" -d 'Print this message or the help of the given subcommand(s)'
+complete -c bin-name -n "__fish_seen_subcommand_from test" -s d
+complete -c bin-name -n "__fish_seen_subcommand_from test" -s c
+complete -c bin-name -n "__fish_seen_subcommand_from test" -s h -l help -d 'Print help'
+complete -c bin-name -n "__fish_seen_subcommand_from help; and not __fish_seen_subcommand_from test; and not __fish_seen_subcommand_from help" -f -a "test" -d 'Subcommand'
+complete -c bin-name -n "__fish_seen_subcommand_from help; and not __fish_seen_subcommand_from test; and not __fish_seen_subcommand_from help" -f -a "help" -d 'Print this message or the help of the given subcommand(s)'
diff --git a/clap_complete/tests/snapshots/custom_bin_name.ps1 b/clap_complete/tests/snapshots/custom_bin_name.ps1
new file mode 100644
index 00000000000..d65e8c32698
--- /dev/null
+++ b/clap_complete/tests/snapshots/custom_bin_name.ps1
@@ -0,0 +1,54 @@
+
+using namespace System.Management.Automation
+using namespace System.Management.Automation.Language
+
+Register-ArgumentCompleter -Native -CommandName 'bin-name' -ScriptBlock {
+    param($wordToComplete, $commandAst, $cursorPosition)
+
+    $commandElements = $commandAst.CommandElements
+    $command = @(
+        'bin-name'
+        for ($i = 1; $i -lt $commandElements.Count; $i++) {
+            $element = $commandElements[$i]
+            if ($element -isnot [StringConstantExpressionAst] -or
+                $element.StringConstantType -ne [StringConstantType]::BareWord -or
+                $element.Value.StartsWith('-') -or
+                $element.Value -eq $wordToComplete) {
+                break
+        }
+        $element.Value
+    }) -join ';'
+
+    $completions = @(switch ($command) {
+        'bin-name' {
+            [CompletionResult]::new('-c', 'c', [CompletionResultType]::ParameterName, 'c')
+            [CompletionResult]::new('-v', 'v', [CompletionResultType]::ParameterName, 'v')
+            [CompletionResult]::new('-h', 'h', [CompletionResultType]::ParameterName, 'Print help')
+            [CompletionResult]::new('--help', 'help', [CompletionResultType]::ParameterName, 'Print help')
+            [CompletionResult]::new('test', 'test', [CompletionResultType]::ParameterValue, 'Subcommand')
+            [CompletionResult]::new('help', 'help', [CompletionResultType]::ParameterValue, 'Print this message or the help of the given subcommand(s)')
+            break
+        }
+        'bin-name;test' {
+            [CompletionResult]::new('-d', 'd', [CompletionResultType]::ParameterName, 'd')
+            [CompletionResult]::new('-c', 'c', [CompletionResultType]::ParameterName, 'c')
+            [CompletionResult]::new('-h', 'h', [CompletionResultType]::ParameterName, 'Print help')
+            [CompletionResult]::new('--help', 'help', [CompletionResultType]::ParameterName, 'Print help')
+            break
+        }
+        'bin-name;help' {
+            [CompletionResult]::new('test', 'test', [CompletionResultType]::ParameterValue, 'Subcommand')
+            [CompletionResult]::new('help', 'help', [CompletionResultType]::ParameterValue, 'Print this message or the help of the given subcommand(s)')
+            break
+        }
+        'bin-name;help;test' {
+            break
+        }
+        'bin-name;help;help' {
+            break
+        }
+    })
+
+    $completions.Where{ $_.CompletionText -like "$wordToComplete*" } |
+        Sort-Object -Property ListItemText
+}
diff --git a/clap_complete/tests/snapshots/custom_bin_name.zsh b/clap_complete/tests/snapshots/custom_bin_name.zsh
new file mode 100644
index 00000000000..205bd23caa1
--- /dev/null
+++ b/clap_complete/tests/snapshots/custom_bin_name.zsh
@@ -0,0 +1,104 @@
+#compdef bin-name
+
+autoload -U is-at-least
+
+_bin-name() {
+    typeset -A opt_args
+    typeset -a _arguments_options
+    local ret=1
+
+    if is-at-least 5.2; then
+        _arguments_options=(-s -S -C)
+    else
+        _arguments_options=(-s -C)
+    fi
+
+    local context curcontext="$curcontext" state line
+    _arguments "${_arguments_options[@]}" \
+'-c[]' \
+'(-c)-v[]' \
+'-h[Print help]' \
+'--help[Print help]' \
+":: :_bin-name_commands" \
+"*::: :->my-app" \
+&& ret=0
+    case $state in
+    (my-app)
+        words=($line[1] "${words[@]}")
+        (( CURRENT += 1 ))
+        curcontext="${curcontext%:*:*}:bin-name-command-$line[1]:"
+        case $line[1] in
+            (test)
+_arguments "${_arguments_options[@]}" \
+'*-d[]' \
+'-c[]' \
+'-h[Print help]' \
+'--help[Print help]' \
+&& ret=0
+;;
+(help)
+_arguments "${_arguments_options[@]}" \
+":: :_bin-name__help_commands" \
+"*::: :->help" \
+&& ret=0
+
+    case $state in
+    (help)
+        words=($line[1] "${words[@]}")
+        (( CURRENT += 1 ))
+        curcontext="${curcontext%:*:*}:bin-name-help-command-$line[1]:"
+        case $line[1] in
+            (test)
+_arguments "${_arguments_options[@]}" \
+&& ret=0
+;;
+(help)
+_arguments "${_arguments_options[@]}" \
+&& ret=0
+;;
+        esac
+    ;;
+esac
+;;
+        esac
+    ;;
+esac
+}
+
+(( $+functions[_bin-name_commands] )) ||
+_bin-name_commands() {
+    local commands; commands=(
+'test:Subcommand' \
+'help:Print this message or the help of the given subcommand(s)' \
+    )
+    _describe -t commands 'bin-name commands' commands "$@"
+}
+(( $+functions[_bin-name__help_commands] )) ||
+_bin-name__help_commands() {
+    local commands; commands=(
+'test:Subcommand' \
+'help:Print this message or the help of the given subcommand(s)' \
+    )
+    _describe -t commands 'bin-name help commands' commands "$@"
+}
+(( $+functions[_bin-name__help__help_commands] )) ||
+_bin-name__help__help_commands() {
+    local commands; commands=()
+    _describe -t commands 'bin-name help help commands' commands "$@"
+}
+(( $+functions[_bin-name__help__test_commands] )) ||
+_bin-name__help__test_commands() {
+    local commands; commands=()
+    _describe -t commands 'bin-name help test commands' commands "$@"
+}
+(( $+functions[_bin-name__test_commands] )) ||
+_bin-name__test_commands() {
+    local commands; commands=()
+    _describe -t commands 'bin-name test commands' commands "$@"
+}
+
+if [ "$funcstack[1]" = "_bin-name" ]; then
+    _bin-name "$@"
+else
+    compdef _bin-name bin-name
+fi
diff --git a/clap_complete/tests/testsuite/bash.rs b/clap_complete/tests/testsuite/bash.rs
index 2e960136704..3452ac63d99 100644
--- a/clap_complete/tests/testsuite/bash.rs
+++ b/clap_complete/tests/testsuite/bash.rs
@@ -72,6 +72,19 @@ fn sub_subcommands() {
     );
 }
 
+#[test]
+fn custom_bin_name() {
+    let name = "my-app";
+    let bin_name = "bin-name";
+    let cmd = common::basic_command(name);
+    common::assert_matches_path(
+        "tests/snapshots/custom_bin_name.bash",
+        clap_complete::shells::Bash,
+        cmd,
+        bin_name,
+    );
+}
+
 #[test]
 fn value_hint() {
     let name = "my-app";
diff --git a/clap_complete/tests/testsuite/elvish.rs b/clap_complete/tests/testsuite/elvish.rs
index 09179509891..7f16183da37 100644
--- a/clap_complete/tests/testsuite/elvish.rs
+++ b/clap_complete/tests/testsuite/elvish.rs
@@ -72,6 +72,19 @@ fn sub_subcommands() {
     );
 }
 
+#[test]
+fn custom_bin_name() {
+    let name = "my-app";
+    let bin_name = "bin-name";
+    let cmd = common::basic_command(name);
+    common::assert_matches_path(
+        "tests/snapshots/custom_bin_name.elvish",
+        clap_complete::shells::Elvish,
+        cmd,
+        bin_name,
+    );
+}
+
 #[test]
 fn value_hint() {
     let name = "my-app";
diff --git a/clap_complete/tests/testsuite/fish.rs b/clap_complete/tests/testsuite/fish.rs
index 217470f7b71..ca6f40e4d71 100644
--- a/clap_complete/tests/testsuite/fish.rs
+++ b/clap_complete/tests/testsuite/fish.rs
@@ -72,6 +72,19 @@ fn sub_subcommands() {
     );
 }
 
+#[test]
+fn custom_bin_name() {
+    let name = "my-app";
+    let bin_name = "bin-name";
+    let cmd = common::basic_command(name);
+    common::assert_matches_path(
+        "tests/snapshots/custom_bin_name.fish",
+        clap_complete::shells::Fish,
+        cmd,
+        bin_name,
+    );
+}
+
 #[test]
 fn value_hint() {
     let name = "my-app";
diff --git a/clap_complete/tests/testsuite/powershell.rs b/clap_complete/tests/testsuite/powershell.rs
index afc9b42dc57..6b8e405074d 100644
--- a/clap_complete/tests/testsuite/powershell.rs
+++ b/clap_complete/tests/testsuite/powershell.rs
@@ -72,6 +72,19 @@ fn sub_subcommands() {
     );
 }
 
+#[test]
+fn custom_bin_name() {
+    let name = "my-app";
+    let bin_name = "bin-name";
+    let cmd = common::basic_command(name);
+    common::assert_matches_path(
+        "tests/snapshots/custom_bin_name.ps1",
+        clap_complete::shells::PowerShell,
+        cmd,
+        bin_name,
+    );
+}
+
 #[test]
 fn value_hint() {
     let name = "my-app";
diff --git a/clap_complete/tests/testsuite/zsh.rs b/clap_complete/tests/testsuite/zsh.rs
index fdf685c5cf4..861b7a077c1 100644
--- a/clap_complete/tests/testsuite/zsh.rs
+++ b/clap_complete/tests/testsuite/zsh.rs
@@ -72,6 +72,19 @@ fn sub_subcommands() {
     );
 }
 
+#[test]
+fn custom_bin_name() {
+    let name = "my-app";
+    let bin_name = "bin-name";
+    let cmd = common::basic_command(name);
+    common::assert_matches_path(
+        "tests/snapshots/custom_bin_name.zsh",
+        clap_complete::shells::Zsh,
+        cmd,
+        bin_name,
+    );
+}
+
 #[test]
 fn value_hint() {
     let name = "my-app";