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

Issue:
Support `allow_hyphen_values` in native completions
### 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

### Clap Version

master

### Describe your use case

```
use clap::{CommandFactory, Parser};
use clap_complete::dynamic::shells::CompleteCommand;
#[derive(Parser, Debug)]
#[clap(name = "dynamic", about = "A dynamic command line tool")]
struct Cli {
    /// The subcommand to run complete
    #[command(subcommand)]
    complete: Option<CompleteCommand>,
    /// Output format
    #[clap(short = 'F', long, value_parser = ["--json", "--yaml", "--toml"], allow_hyphen_values = true)]
    format: Option<String>,
    #[clap(long)]
    json: Option<String>,
}
fn main() {
    let cli = Cli::parse();
    if let Some(completions) = cli.complete {
        completions.complete(&mut Cli::command());
    }
    // normal logic continues...
}
```
For the command line:
```
dynamic --format --json --js[TAB]
```
There is no completion generated.
it should be:
```
dynamic --format --json --json 
```

This seems to be a very specific use case, but it shows that there is an error in parsing during dynamic completion when the allow_hyphen_values setting is enabled, or that allow_hyphen_values is not being considered.

### Describe the solution you'd like

https://github.com/clap-rs/clap/blob/5efa52ad4501393d50e236d10a979313a61d4929/clap_complete/src/dynamic/completer.rs#L76
https://github.com/clap-rs/clap/blob/5efa52ad4501393d50e236d10a979313a61d4929/clap_complete/src/dynamic/completer.rs#L79
https://github.com/clap-rs/clap/blob/5efa52ad4501393d50e236d10a979313a61d4929/clap_complete/src/dynamic/completer.rs#L112
do something more in these branches

### Alternatives, if applicable

_No response_

### Additional Context

There are also user cases related to the mistaken parsing.
## Case 1
```
use clap::{CommandFactory, Parser};
use clap_complete::dynamic::shells::CompleteCommand;
#[derive(Parser, Debug)]
#[clap(name = "dynamic", about = "A dynamic command line tool")]
struct Cli {
    /// The subcommand to run complete
    #[command(subcommand)]
    complete: Option<CompleteCommand>,
    /// Output format
    #[clap(short = 'F', long, value_parser = ["json", "yaml", "toml"], allow_hyphen_values = true)]
    format: Option<String>,
    #[clap(value_parser = ["--pos_a"], index = 1)]
    positional_a: Option<String>,
    #[clap(value_parser = ["pos_b"], index = 2)]
    positional_b: Option<String>,
}
fn main() {
    let cli = Cli::parse();
    if let Some(completions) = cli.complete {
        completions.complete(&mut Cli::command());
    }
    // normal logic continues...
}
```
For the command line
```
dynamic --format json --pos_a [TAB]
```
completions are:
```
--format  --help    --pos_a   -F        -h        help
```
it should be:
```
pos_b --format  --help    -F        -h        help
```

## Case 2
```
use clap::{CommandFactory, Parser};
use clap_complete::dynamic::shells::CompleteCommand;
#[derive(Parser, Debug)]
#[clap(name = "dynamic", about = "A dynamic command line tool")]
struct Cli {
    /// The subcommand to run complete
    #[command(subcommand)]
    complete: Option<CompleteCommand>,
    /// Output format
    #[clap(short = 'F', long, value_parser = ["json", "yaml", "toml"], allow_hyphen_values = true)]
    format: Option<String>,
    #[clap(value_parser = ["-a"], index = 1)]
    positional_a: Option<String>,
    #[clap(value_parser = ["pos_b"], index = 2)]
    positional_b: Option<String>,
}
fn main() {
    let cli = Cli::parse();
    if let Some(completions) = cli.complete {
        completions.complete(&mut Cli::command());
    }
    // normal logic continues...
}
```
For the command line
```
dynamic --format json -a [TAB]
```
completions are:
```
--format  --help    -F        -a        -h        help
```
it should be:
```
pos_b --format  --help    -F        -h        help
```


Discussion / hints:
Track issue: https://github.com/clap-rs/clap/issues/3166
- [code](https://github.com/clap-rs/clap/blob/master/clap_complete/src/dynamic)
- [tests](https://github.com/clap-rs/clap/blob/master/clap_complete/tests/testsuite/dynamic.rs)

Fix patch:
diff --git a/clap_complete/src/engine/complete.rs b/clap_complete/src/engine/complete.rs
index b9bb252293e..75dad1b2b14 100644
--- a/clap_complete/src/engine/complete.rs
+++ b/clap_complete/src/engine/complete.rs
@@ -58,6 +58,11 @@ pub fn complete(
                 parse_positional(current_cmd, pos_index, is_escaped, current_state);
         } else if arg.is_escape() {
             is_escaped = true;
+        } else if opt_allows_hyphen(&current_state, &arg) {
+            match current_state {
+                ParseState::Opt((opt, count)) => next_state = parse_opt_value(opt, count),
+                _ => unreachable!("else branch is only reachable in Opt state"),
+            }
         } else if let Some((flag, value)) = arg.to_long() {
             if let Ok(flag) = flag {
                 let opt = current_cmd.get_arguments().find(|a| {
@@ -69,10 +74,14 @@ pub fn complete(
                     });
                     is_find.unwrap_or(false)
                 });
-                if opt.map(|o| o.get_action().takes_values()).unwrap_or(false) {
-                    if value.is_none() {
-                        next_state = ParseState::Opt((opt.unwrap(), 1));
+
+                if let Some(opt) = opt {
+                    if opt.get_action().takes_values() && value.is_none() {
+                        next_state = ParseState::Opt((opt, 1));
                     };
+                } else if pos_allows_hyphen(current_cmd, pos_index) {
+                    (next_state, pos_index) =
+                        parse_positional(current_cmd, pos_index, is_escaped, current_state);
                 }
             }
         } else if let Some(short) = arg.to_short() {
@@ -81,6 +90,9 @@ pub fn complete(
                 if short.next_value_os().is_none() {
                     next_state = ParseState::Opt((opt, 1));
                 }
+            } else if pos_allows_hyphen(current_cmd, pos_index) {
+                (next_state, pos_index) =
+                    parse_positional(current_cmd, pos_index, is_escaped, current_state);
             }
         } else {
             match current_state {
@@ -88,14 +100,7 @@ pub fn complete(
                     (next_state, pos_index) =
                         parse_positional(current_cmd, pos_index, is_escaped, current_state);
                 }
-
-                ParseState::Opt((opt, count)) => {
-                    let range = opt.get_num_args().expect("built");
-                    let max = range.max_values();
-                    if count < max {
-                        next_state = ParseState::Opt((opt, count + 1));
-                    }
-                }
+                ParseState::Opt((opt, count)) => next_state = parse_opt_value(opt, count),
             }
         }
     }
@@ -546,3 +551,32 @@ fn parse_positional<'a>(
         ),
     }
 }
+
+/// Parse optional flag argument. Return new state
+fn parse_opt_value(opt: &clap::Arg, count: usize) -> ParseState<'_> {
+    let range = opt.get_num_args().expect("built");
+    let max = range.max_values();
+    if count < max {
+        ParseState::Opt((opt, count + 1))
+    } else {
+        ParseState::ValueDone
+    }
+}
+
+fn pos_allows_hyphen(cmd: &clap::Command, pos_index: usize) -> bool {
+    cmd.get_positionals()
+        .find(|a| a.get_index() == Some(pos_index))
+        .map(|p| p.is_allow_hyphen_values_set())
+        .unwrap_or(false)
+}
+
+fn opt_allows_hyphen(state: &ParseState<'_>, arg: &clap_lex::ParsedArg<'_>) -> bool {
+    let val = arg.to_value_os();
+    if val.starts_with("-") {
+        if let ParseState::Opt((opt, _)) = state {
+            return opt.is_allow_hyphen_values_set();
+        }
+    }
+
+    false
+}


Test patch:
diff --git a/clap_complete/tests/testsuite/engine.rs b/clap_complete/tests/testsuite/engine.rs
index 5acb77bc9e2..d802bb148cf 100644
--- a/clap_complete/tests/testsuite/engine.rs
+++ b/clap_complete/tests/testsuite/engine.rs
@@ -981,6 +981,167 @@ a_pos,c_pos"
     );
 }
 
+#[test]
+fn suggest_allow_hyphen() {
+    let mut cmd = Command::new("exhaustive")
+        .arg(
+            clap::Arg::new("format")
+                .long("format")
+                .short('F')
+                .allow_hyphen_values(true)
+                .value_parser(["--json", "--toml", "--yaml"]),
+        )
+        .arg(clap::Arg::new("json").long("json"));
+
+    assert_data_eq!(complete!(cmd, "--format --j[TAB]"), snapbox::str!["--json"]);
+    assert_data_eq!(complete!(cmd, "-F --j[TAB]"), snapbox::str!["--json"]);
+    assert_data_eq!(complete!(cmd, "--format --t[TAB]"), snapbox::str!["--toml"]);
+    assert_data_eq!(complete!(cmd, "-F --t[TAB]"), snapbox::str!["--toml"]);
+
+    assert_data_eq!(
+        complete!(cmd, "--format --[TAB]"),
+        snapbox::str![
+            "--json
+--toml
+--yaml"
+        ]
+    );
+
+    assert_data_eq!(
+        complete!(cmd, "-F --[TAB]"),
+        snapbox::str![
+            "--json
+--toml
+--yaml"
+        ]
+    );
+
+    assert_data_eq!(
+        complete!(cmd, "--format --json --j[TAB]"),
+        snapbox::str!["--json"]
+    );
+
+    assert_data_eq!(
+        complete!(cmd, "-F --json --j[TAB]"),
+        snapbox::str!["--json"]
+    );
+}
+
+#[test]
+fn suggest_positional_long_allow_hyphen() {
+    let mut cmd = Command::new("exhaustive")
+        .arg(
+            clap::Arg::new("format")
+                .long("format")
+                .short('F')
+                .allow_hyphen_values(true)
+                .value_parser(["--json", "--toml", "--yaml"]),
+        )
+        .arg(
+            clap::Arg::new("positional_a")
+                .value_parser(["--pos_a"])
+                .index(1)
+                .allow_hyphen_values(true),
+        )
+        .arg(
+            clap::Arg::new("positional_b")
+                .index(2)
+                .value_parser(["pos_b"]),
+        );
+
+    assert_data_eq!(
+        complete!(cmd, "--format --json --pos[TAB]"),
+        snapbox::str!["--pos_a"]
+    );
+    assert_data_eq!(
+        complete!(cmd, "-F --json --pos[TAB]"),
+        snapbox::str!["--pos_a"]
+    );
+
+    assert_data_eq!(
+        complete!(cmd, "--format --json --pos_a [TAB]"),
+        snapbox::str![
+            "--format
+--help	Print help
+-F
+-h	Print help
+pos_b"
+        ]
+    );
+    assert_data_eq!(
+        complete!(cmd, "-F --json --pos_a [TAB]"),
+        snapbox::str![
+            "--format
+--help	Print help
+-F
+-h	Print help
+pos_b"
+        ]
+    );
+
+    assert_data_eq!(
+        complete!(cmd, "--format --json --pos_a p[TAB]"),
+        snapbox::str!["pos_b"]
+    );
+    assert_data_eq!(
+        complete!(cmd, "-F --json --pos_a p[TAB]"),
+        snapbox::str!["pos_b"]
+    );
+}
+
+#[test]
+fn suggest_positional_short_allow_hyphen() {
+    let mut cmd = Command::new("exhaustive")
+        .arg(
+            clap::Arg::new("format")
+                .long("format")
+                .short('F')
+                .allow_hyphen_values(true)
+                .value_parser(["--json", "--toml", "--yaml"]),
+        )
+        .arg(
+            clap::Arg::new("positional_a")
+                .value_parser(["-a"])
+                .index(1)
+                .allow_hyphen_values(true),
+        )
+        .arg(
+            clap::Arg::new("positional_b")
+                .index(2)
+                .value_parser(["pos_b"]),
+        );
+
+    assert_data_eq!(
+        complete!(cmd, "--format --json -a [TAB]"),
+        snapbox::str![
+            "--format
+--help	Print help
+-F
+-h	Print help
+pos_b"
+        ]
+    );
+    assert_data_eq!(
+        complete!(cmd, "-F --json -a [TAB]"),
+        snapbox::str![
+            "--format
+--help	Print help
+-F
+-h	Print help
+pos_b"
+        ]
+    );
+
+    assert_data_eq!(
+        complete!(cmd, "--format --json -a p[TAB]"),
+        snapbox::str!["pos_b"]
+    );
+    assert_data_eq!(
+        complete!(cmd, "-F --json -a p[TAB]"),
+        snapbox::str!["pos_b"]
+    );
+}
+
 fn complete(cmd: &mut Command, args: impl AsRef<str>, current_dir: Option<&Path>) -> String {
     let input = args.as_ref();
     let mut args = vec![std::ffi::OsString::from(cmd.get_name())];