Skip to content

Parsing two var args separated by -- #6200

@mkovaxx

Description

@mkovaxx

Please complete the following tasks

Rust Version

rustc 1.90.0 (1159e78c4 2025-09-14)

Clap Version

4.5.53

Minimal reproducible code

repo: https://github.com/mkovaxx/repro-clap-two-varargs

use clap::Parser;

fn main() {
    let args = CliWithTwoVarArgs::parse_from(std::env::args());
    println!("BEFORE: {:?}", args.before);
    println!("AFTER: {:?}", args.after);
}

#[derive(Clone, Debug, Parser)]
pub struct CliWithTwoVarArgs {
    #[arg(
        value_name = "ARGS_BEFORE",
        num_args = 0..,
        allow_hyphen_values = true,
        help = "Options before `--`",
    )]
    pub before: Vec<String>,

    #[arg(
        value_name = "ARGS_AFTER",
        last = true,
        num_args = 0..,
        allow_hyphen_values = true,
        help = "Options after `--`",
    )]
    pub after: Vec<String>,
}

#[cfg(test)]
mod test {
    use super::*;

    const BIN_NAME: &str = "main";

    #[test]
    fn parse_some_stuff() {
        let args = CliWithTwoVarArgs::parse_from([
            BIN_NAME,
            "--release",
            "--",
            "--expand-errors",
            "--rlimit=100",
        ]);

        assert_eq!(args.before, vec!["--release".to_owned()]);
        assert_eq!(
            args.after,
            vec!["--expand-errors".to_owned(), "--rlimit=100".to_owned()]
        );
    }
}

Steps to reproduce the bug with the above code

cargo test

Actual Behaviour

All arguments, including the --, are in the before field, and after is empty.

Expected Behaviour

Arguments before the -- marker should be in the before field, and arguments after in the after field.

Additional Context

The expectation above is informed by the output of --help:

Usage: repro-clap-two-varargs [ARGS_BEFORE]... [-- [ARGS_AFTER]...]

Arguments:
  [ARGS_BEFORE]...  Options before `--`
  [ARGS_AFTER]...   Options after `--`

Options:
  -h, --help  Print help

Debug Output

running 1 test
[clap_builder::builder::command]Command::_do_parse
[clap_builder::builder::command]Command::_build: name="repro-clap-two-varargs"
[clap_builder::builder::command]Command::_propagate:repro-clap-two-varargs
[clap_builder::builder::command]Command::_check_help_and_version:repro-clap-two-varargs 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:repro-clap-two-varargs
[clap_builder::builder::debug_asserts]Command::_debug_asserts
[clap_builder::builder::debug_asserts]Arg::_debug_asserts:before
[clap_builder::builder::debug_asserts]Arg::_debug_asserts:after
[clap_builder::builder::debug_asserts]Arg::_debug_asserts:help
[clap_builder::builder::debug_asserts]Command::_verify_positionals
[clap_builder::parser::parser]Parser::get_matches_with
[clap_builder::parser::parser]Parser::parse
[clap_builder::parser::parser]Parser::get_matches_with: Begin parsing '"--release"'
[clap_builder::parser::parser]Parser::possible_subcommand: arg=Ok("--release")
[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::possible_long_flag_subcommand: arg="release"
[clap_builder::parser::parser]Parser::parse_long_args: positional at 1 allows hyphens
[clap_builder::parser::parser]Parser::get_matches_with: After parse_long_arg MaybeHyphenValue
[clap_builder::parser::parser]Parser::get_matches_with: Positional counter...1
[clap_builder::parser::parser]Parser::get_matches_with: Low index multiples...false
[clap_builder::parser::parser]Parser::get_matches_with: Begin parsing '"--"'
[clap_builder::parser::parser]Parser::get_matches_with: Positional counter...1
[clap_builder::parser::parser]Parser::get_matches_with: Low index multiples...false
[clap_builder::parser::parser]Parser::get_matches_with: Begin parsing '"--expand-errors"'
[clap_builder::parser::parser]Parser::parse_long_arg
[clap_builder::parser::parser]Parser::parse_long_arg: prior arg accepts hyphenated values
[clap_builder::parser::parser]Parser::get_matches_with: After parse_long_arg MaybeHyphenValue
[clap_builder::parser::parser]Parser::get_matches_with: Positional counter...1
[clap_builder::parser::parser]Parser::get_matches_with: Low index multiples...false
[clap_builder::parser::parser]Parser::get_matches_with: Begin parsing '"--rlimit=100"'
[clap_builder::parser::parser]Parser::parse_long_arg
[clap_builder::parser::parser]Parser::parse_long_arg: prior arg accepts hyphenated values
[clap_builder::parser::parser]Parser::get_matches_with: After parse_long_arg MaybeHyphenValue
[clap_builder::parser::parser]Parser::get_matches_with: Positional counter...1
[clap_builder::parser::parser]Parser::get_matches_with: Low index multiples...false
[clap_builder::parser::parser]Parser::resolve_pending: id="before"
[clap_builder::parser::parser]Parser::react action=Append, identifier=Some(Index), source=CommandLine
[clap_builder::parser::parser]Parser::remove_overrides: id="before"
[clap_builder::parser::arg_matcher]ArgMatcher::start_custom_arg: id="before", source=CommandLine
[clap_builder::builder::command]Command::groups_for_arg: id="before"
[clap_builder::parser::arg_matcher]ArgMatcher::start_custom_arg: id="CliWithTwoVarArgs", source=CommandLine
[clap_builder::parser::parser]Parser::push_arg_values: ["--release", "--", "--expand-errors", "--rlimit=100"]
[clap_builder::parser::parser]Parser::add_single_val_to_arg: cur_idx:=1
[clap_builder::parser::parser]Parser::add_single_val_to_arg: cur_idx:=2
[clap_builder::parser::parser]Parser::add_single_val_to_arg: cur_idx:=3
[clap_builder::parser::parser]Parser::add_single_val_to_arg: cur_idx:=4
[clap_builder::parser::arg_matcher]ArgMatcher::needs_more_vals: o=before, pending=0
[clap_builder::parser::arg_matcher]ArgMatcher::needs_more_vals: expected=0.., actual=0
[clap_builder::parser::parser]Parser::react not enough values passed in, leaving it to the validator to complain
[clap_builder::parser::parser]Parser::add_defaults
[clap_builder::parser::parser]Parser::add_defaults:iter:before:
[clap_builder::parser::parser]Parser::add_default_value: doesn't have conditional defaults
[clap_builder::parser::parser]Parser::add_default_value:iter:before: doesn't have default vals
[clap_builder::parser::parser]Parser::add_defaults:iter:after:
[clap_builder::parser::parser]Parser::add_default_value: doesn't have conditional defaults
[clap_builder::parser::parser]Parser::add_default_value:iter:after: doesn't have default vals
[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="before"
[clap_builder::parser::validator]Conflicts::gather_direct_conflicts id="before", conflicts=[]
[clap_builder::parser::validator]Conflicts::gather_direct_conflicts id="CliWithTwoVarArgs", conflicts=[]
[clap_builder::parser::validator]Validator::validate_conflicts
[clap_builder::parser::validator]Validator::validate_exclusive
[clap_builder::parser::validator]Validator::validate_conflicts::iter: id="before"
[clap_builder::parser::validator]Conflicts::gather_conflicts: arg="before"
[clap_builder::parser::validator]Conflicts::gather_conflicts: conflicts=[]
[clap_builder::parser::validator]Validator::validate_required: required=ChildGraph([])
[clap_builder::parser::validator]Validator::gather_requires
[clap_builder::parser::validator]Validator::gather_requires:iter:"before"
[clap_builder::parser::validator]Validator::gather_requires:iter:"CliWithTwoVarArgs"
[clap_builder::parser::validator]Validator::gather_requires:iter:"CliWithTwoVarArgs":group
[clap_builder::parser::validator]Validator::validate_required: is_exclusive_present=false
[clap_builder::parser::arg_matcher]ArgMatcher::get_global_values: global_arg_vec=[]
test test::parse_some_stuff ... FAILED

failures:

---- test::parse_some_stuff stdout ----

thread 'test::parse_some_stuff' panicked at src/main.rs:45:9:
assertion left == right failed
left: ["--release", "--", "--expand-errors", "--rlimit=100"]
right: ["--release"]
note: run with RUST_BACKTRACE=1 environment variable to display a backtrace

failures:
test::parse_some_stuff

test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

error: test failed, to rerun pass --bin repro-clap-two-varargs

Metadata

Metadata

Assignees

No one assigned

    Labels

    A-parsingArea: Parser's logic and needs it changed somehow.C-bugCategory: bugS-triageStatus: New; needs maintainer attention.

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions