fix(parse): don't split unknown --key=value into two positional args#628
fix(parse): don't split unknown --key=value into two positional args#628srnnkls wants to merge 1 commit into
Conversation
When a `--key=value` token is encountered and `--key` is not a declared flag, the value was pre-pushed to the input queue before the lookup, so the original `--key=value` token fell through to be captured as a positional argument and the pushed value was then consumed as a second positional. For a variadic-only spec, `--depth=3` produced both `--depth=3` and `3`. Gate the push on the flag being known and actually taking a value, the same way short flags already handle their remainder push (line 489). Unknown `--key=value` and value-less long flags now pass through to positional parsing as a single token.
There was a problem hiding this comment.
Code Review
This pull request modifies the parsing logic for long flags containing an equals sign (e.g., --flag=value) to ensure unknown flags are treated as single positional arguments, and includes a regression test for this behavior. Feedback was provided regarding a potential regression where values assigned to known flags that do not accept arguments (such as boolean flags) would be silently ignored; a suggestion was made to move the value-pushing logic to maintain consistency with existing short-flag handling.
| if f.arg.is_some() { | ||
| if !val.is_empty() { | ||
| input.push_front(val.to_string()); | ||
| } | ||
| out.flag_awaiting_value.push(Arc::clone(f)); |
There was a problem hiding this comment.
The current implementation moves the push_front logic inside the f.arg.is_some() block. While this correctly fixes the issue for unknown flags, it introduces a change in behavior for known flags that do not take arguments (e.g., boolean flags).
Previously, a value provided via = to a boolean flag (e.g., --verbose=true) would be pushed back and treated as a subsequent positional argument. With this change, that value is silently swallowed.
To maintain consistency with the short-flag parsing logic (lines 488-494), which pushes the remainder regardless of whether the flag takes an argument, consider moving the push_front call outside the f.arg.is_some() check but still within the if let Some(f) block. This ensures that unknown flags are still treated as single positional arguments while preserving existing behavior for known flags.
if !val.is_empty() {
input.push_front(val.to_string());
}
if f.arg.is_some() {
out.flag_awaiting_value.push(Arc::clone(f));
Greptile SummaryThis PR fixes a bug where an unknown long flag with an inline value (e.g.
Confidence Score: 4/5Safe to merge; the fix is narrowly scoped and the primary bug is well-tested. There is a subtle secondary behavior change for known boolean/count flags that receive an inline value, which is not covered by a test. The change correctly fixes the split-token bug and the new test validates the targeted scenario. The only concern is that moving lib/src/parse.rs — the Important Files Changed
|
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #628 +/- ##
==========================================
- Coverage 78.94% 78.90% -0.04%
==========================================
Files 49 49
Lines 7284 7309 +25
Branches 7284 7309 +25
==========================================
+ Hits 5750 5767 +17
- Misses 1147 1148 +1
- Partials 387 394 +7 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
Summary
When
--key=valueis parsed against a spec that doesn't declare--keyas a flag (e.g. a passthrough wrapper using onlyarg "[args]..." var=#true), the value is duplicated: both--key=valueandvalueend up in the variadic.Minimal repro (mise task; same shape against the parser directly):
Cause
lib/src/parse.rs:454-459(pre-fix):For
--depth=3against a spec that only declares[args]...:word=--depth,val=3.3is unconditionally pushed to the front of the input queue.--depthis not inavailable_flags— thecontinuedoesn't fire.--depth=3token falls through and is consumed as a positional.3— also consumed as a positional.The short-flag branch already gets this right: it only pushes the grouped-remainder back into the queue inside the
if let Some(f) = ...block (line 489).Fix
Move the
push_frontinside the lookup block, and only push when the flag actually takes a value (f.arg.is_some()). Unknown--key=valuetokens now pass through to positional parsing as a single token. Mirrors the short-flag pattern.Test plan
test_arg_var_true_unknown_long_eq_flag_not_splitcovering the variadic-only casecargo test— all tests pass (177 lib + integration suites)