fix(parse): keep inherited global flags when a subcommand re-declares them as non-global#649
Conversation
There was a problem hiding this comment.
Code Review
This pull request fixes a parser bug where a value-taking global flag placed before a mounted subcommand is dropped from recognized flags if the subcommand re-declares the same flag as non-global, leading to parsing errors. To resolve this, a new helper function merge_subcommand_flags is introduced to prevent non-global re-declarations from shadowing inherited global flags. The PR also adds comprehensive integration and unit tests to prevent regressions. The reviewer suggested a minor optimization in merge_subcommand_flags to simplify the redundancy check to available.contains_key(&key), as all remaining flags in the map are already guaranteed to be global.
Greptile SummaryThis PR fixes a
Confidence Score: 5/5Safe to merge — the change is narrowly scoped to flag-merge logic during subcommand descent and does not alter any existing behaviour when subcommands do not re-declare inherited global flags. The new No files require special attention. Important Files Changed
Reviews (3): Last reviewed commit: "fix(parse): keep inherited global flags ..." | Re-trigger Greptile |
… them as non-global When descending into a subcommand (including mounted ones), a global flag that the child re-declared as non-global would shadow and then be dropped from `available_flags` on the next descent. A global flag placed before the subcommand (e.g. `-C dir run task`) then stopped being recognized in phase 2 and was mis-validated against the subcommand's `choices` positional arg, producing "Invalid choice for arg ...". Merge subcommand flags with global precedence so inherited global flags survive the descent and are still consumed as flags (and recorded in `as_env()`). This fixes the parser-side root cause referenced by jdx/mise#10069. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1c5311e to
8f11e99
Compare
|
Thanks for the review! Applied the suggestion — since �vailable.retain(|_, f| f.global) runs first and the merged |
8f11e99 to
30f983e
Compare
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #649 +/- ##
==========================================
+ Coverage 79.33% 79.58% +0.24%
==========================================
Files 49 49
Lines 7342 7430 +88
Branches 7342 7430 +88
==========================================
+ Hits 5825 5913 +88
- Misses 1141 1142 +1
+ Partials 376 375 -1 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
Summary
Fixes a
parse_partialbug where a value-taking global flag placed before a (mounted) subcommand breaks completion/parsing when the subcommand re-declares that same flag as non-global.This fixes the parser-side root cause referenced by jdx/mise#10069.
Reproduction
mise emits a usage spec where
runmounts tasks dynamically, a task can have achoicespositional arg, andrunre-declares the global-C/--cdflag as non-global. Completing such a task with the global flag in front:Expected:
alpha beta gamma.Root cause
During phase 1 (subcommand scan / global-flag gathering), each descent did:
When a child subcommand re-declares a parent's global flag (same
-C/--cdkey) as non-global,extendoverwrites the inherited global entry with the non-global one, and the next descent'sretain(|_, f| f.global)then drops it entirely.With the global flag no longer in
available_flags, phase 2 no longer recognizes the leftover-Ctoken and feeds it to the task'schoicespositional arg →validate_choicesbails.Fix
Replace
retain+extendwith a smallmerge_subcommand_flagshelper that merges with global precedence: a subcommand's non-global re-declaration no longer shadows an inherited global flag. The global flag stays recognized, so phase 2 consumes it as a flag (and records it inas_env(), preservingusage_*env vars for normal execution and for mount scripts) instead of mistaking it for a positional.Tests
lib/src/parse.rs):test_prefix_global_flag_does_not_pollute_choicesbuilds the post-mount structure directly (root global-C/--cd,runre-declaring it as non-global, mountedsample:runwith achoicesarg) and asserts: no false "Invalid choice" bail, the inherited global flag survives descent, the flag value still reachesas_env()(usage_cd), valid choices still parse, and genuinely invalid choices are still rejected.cli/tests/complete_word.rs+examples/mounted-global-flags-choices.sh): a new fixture whose mountedchoicesvary byusage_cd, so the test also confirms the global flag value propagates through the mount despite the non-global re-declaration.Downstream note
mise currently has a temporary workaround (promoting the conflicting
run/tasks runflags toglobal=truein its completion spec generation). Once this lands, that workaround becomes a no-op and can be removed; I'll follow up on jdx/mise#10069.🤖 Generated with Claude Code