Skip to content

fix(help): Render partially-optional values with [] - #6454

Merged
epage merged 2 commits into
clap-rs:masterfrom
aashish-thapa:fix/partially-optional-value-names
Jul 31, 2026
Merged

fix(help): Render partially-optional values with []#6454
epage merged 2 commits into
clap-rs:masterfrom
aashish-thapa:fix/partially-optional-value-names

Conversation

@aashish-thapa

Copy link
Copy Markdown
Contributor

Fixes #4847.

While looking through the open help-rendering issues, I noticed this bug had already been tackled twice. #4910
went stale after accumulating conflicts, and #6376 was withdrawn because it lacked a regression test. The
underlying fix was straightforward, so I picked it up and completed it with the missing test coverage.

The issue was that options using a value-count range, such as num_args(1..=2) with named values, rendered
every value name as required:

    --example <FOO> <BAR>

even though the parser accepts fewer values (for example, --example FOO). This caused the help output to
contradict the parser's behavior. With this change, only the required values are rendered as required, while
additional values are shown as optional:

    --example <FOO> [BAR]

Value names beyond the minimum required count are now wrapped in []. Options whose entire value is optional
(min == 0) are already bracketed by the caller, so they continue to render as <name> to avoid producing
[[...]].

I also added unit tests covering ranged, require_equals, and unbounded (1..=3) cases, along with a help
snapshot test to prevent regressions. The full workspace test suite, cargo fmt, and cargo clippy all pass.

@epage

epage commented Jul 27, 2026

Copy link
Copy Markdown
Member

FYI we would prefer tests to be added in an earlier commit, reproducing the bad behavior, see our contrib docs as well as https://epage.github.io/dev/pr-style/#c-split

Comment thread clap_builder/src/builder/arg.rs Outdated
Comment thread clap_builder/src/builder/arg.rs Outdated
@aashish-thapa
aashish-thapa force-pushed the fix/partially-optional-value-names branch 2 times, most recently from 934db25 to 46b650f Compare July 31, 2026 16:01
@aashish-thapa

Copy link
Copy Markdown
Contributor Author

Thanks for the review, all three addressed.

Rebased onto master and split into a test commit then the fix, so the fix commit's diff now shows only the rendering change.

Pulled out is_optional_val as suggested. One small difference: I derived it from the local num_vals rather than self.get_min_vals(), since get_min_vals() has an expect() while render_arg_val falls back to 1.into() when num_vals is None. The call in stylize_arg_suffix is guarded by !is_positional() so it never hits that, but render_arg_val does run for positionals. Same value, just no new panic path.

On required(true).num_args(0..) for a positional: nothing rejects it today. It renders [pos]... but the parser still errors with MissingRequiredArgument, so it's the same help/parser mismatch as #4847, just a pre-existing one. This PR doesn't change it either way, so I added positional_display_zero_or_more_values_required pinning the current output plus a comment on why required loses to min == 0.

Happy to add a debug assert rejecting that combination if you want it, but it'd break anyone building that arg today so I kept it out of this PR. Let me know if you'd rather have it here or as a follow-up.

Comment thread clap_builder/src/builder/arg.rs
Comment thread clap_builder/src/builder/arg.rs Outdated
Comment thread clap_builder/src/builder/arg.rs Outdated
Comment thread clap_builder/src/builder/arg.rs Outdated
Options with a value-count range render every value name as required,
even for the values the parser will happily leave out.
An option declared with a value-count range, like `num_args(1..=2)` with
two value names, rendered as

    --example <FOO> <BAR>

while the parser accepts `--example FOO`, so the help contradicted what
the parser would take. Render the value names past the minimum as
optional instead

    --example <FOO> [BAR]

An option whose value is optional as a whole is bracketed by
`stylize_arg_suffix`, so its names stay unbracketed to avoid `[[name]]`.

Fixes clap-rs#4847
@aashish-thapa
aashish-thapa force-pushed the fix/partially-optional-value-names branch from 46b650f to 1ca5541 Compare July 31, 2026 16:36
@aashish-thapa

Copy link
Copy Markdown
Contributor Author

Thanks, all four addressed.

  • Moved is_optional_val into the loop next to its use.
  • Dropped the misplaced comment; the one that's left no longer names the function.
  • Dropped the positional comment too — !required || is_past_min reads fine on its own, and positional_display_zero_or_more_values_required documents the case.
  • You're right about require_equals: --example=FOO BAR errors with UnknownArgument and --example FOO BAR with NoEquals, so the second value is unreachable. Added value_delimiter(',') so the test covers a config that actually parses (--example=FOO,BAR); rendering is unchanged.

Still happy to add debug asserts for either unparseable combo — positional required(true).num_args(0..), or require_equals with max > 1 and no delimiter — here or as a follow-up.

@aashish-thapa
aashish-thapa requested a review from epage July 31, 2026 17:22
@epage

epage commented Jul 31, 2026

Copy link
Copy Markdown
Member

Thanks! Looks good!

If you want to do a follow up to add to debug_assert.rs the requires_equals condition, that would work. I'm unsure about the required one.

@epage
epage merged commit d0a3980 into clap-rs:master Jul 31, 2026
25 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Multiple optional arguments are displayed incorrectly in help output

2 participants