Skip to content

docs(zsh-standard): require capturing a parameter before localizing it - #603

Merged
ss-o merged 1 commit into
mainfrom
feature-602
Sep 2, 2026
Merged

docs(zsh-standard): require capturing a parameter before localizing it#603
ss-o merged 1 commit into
mainfrom
feature-602

Conversation

@ss-o

@ss-o ss-o commented Sep 2, 2026

Copy link
Copy Markdown
Member

Adds zsh/parameters/capture-before-localizing to the Zsh Scripting Standard. Documentation and policy only; no behaviour change in this repository.

Why

z-shell/zi lost a working search path twice in one function for the same reason, and nothing in the standard covered it.

zi#477 removed this as dead:

local +h FPATH=$PLUGINS_DIR${fpath_elements:+:${(j.:.)fpath_elements[@]}}:$FPATH
local +h -a fpath
fpath=( $PLUGIN_DIR $fpath_elements $fpath )

It was not dead. fpath and FPATH are tied, so the scalar assignment already created the local pair populated with the caller's value, and the local +h -a fpath that follows does not reset it because the parameter is by then already local to that scope. Remove the scalar and the array declaration creates a fresh empty array, $fpath on the right-hand side expands to nothing, and the assignment silently truncates itself to $PLUGIN_DIR. Reproduced on zsh 5.9.2:

with the scalar:  count=4 : /tmp/plug /tmp/plug /aaa /bbb
without:          count=1 : /tmp/plug

Immediate autoload +X of any function the loading plug-in did not own then stopped resolving, which broke Aloxaf/fzf-tab completely. Tracked as zi#488, fixed in zi#491.

Two properties combine, and each is individually easy to misread. A second local on a parameter already local to the same scope is not a reset, so the declaration order looks redundant when it is load-bearing. And localizing a tied parameter starts it empty, so the common idiom x=( new $x ) self-truncates rather than extending. Together they produce a construct where deleting an apparently inert line changes behaviour with no error and no diagnostic.

zsh/parameters/avoid-special-name-collisions already names the path/PATH tie, but only to warn against repurposing the name. Nothing covered what the tie does to localization. The same shape applies to path, cdpath, manpath, and module_path.

What changed

The rule in .github/instructions/zsh-scripting.instructions.md, its object in lib/zsh-standard-policy.json, and both ordered inventories in scripts/validate-zsh-standard-policy.py (NORMATIVE_RULE_IDS and STARTUP_PROFILE_RULE_IDS).

The frozen consumer-parser golden moves with it, exactly as scripts/test_validate_zsh_standard_policy.py instructs when a rule is added: block count 64 to 65, startup-file membership 49 to 50, and the digest replaced with the reported actual.

Verification

python3 scripts/validate-zsh-standard-policy.py      -> passed
python3 scripts/test_validate_zsh_standard_policy.py -> Ran 99 tests, OK

Closes #602

z-shell/zi lost a working search path twice in one function for the same
reason, and nothing in the standard covered it.

zi#477 removed this as dead:

    local +h FPATH=$PLUGINS_DIR${fpath_elements:+:...}:$FPATH
    local +h -a fpath
    fpath=( $PLUGIN_DIR $fpath_elements $fpath )

It was not. `fpath' and `FPATH' are tied, so the scalar assignment already
created the local pair populated with the caller's value, and the `local +h -a
fpath' that follows does not reset it because the parameter is by then already
local to that scope. Remove the scalar and the array declaration creates a fresh
empty array, `$fpath' on the right-hand side expands to nothing, and the
assignment silently truncates itself to $PLUGIN_DIR. Immediate `autoload +X' of
any function the loading plug-in did not own then stopped resolving.

Two properties combine, each individually easy to misread: a second `local' on a
parameter already local to the same scope is not a reset, and localizing a tied
parameter starts it empty so `x=( new $x )' self-truncates. Together they make a
load-bearing line look inert, and deleting it changes behaviour with no error.

zsh/parameters/avoid-special-name-collisions already names the path/PATH tie,
but only to warn against repurposing the name; nothing covered what the tie does
to localization. The same shape applies to path, cdpath, manpath, and
module_path.

Add zsh/parameters/capture-before-localizing to the parameters section, with the
matching object in lib/zsh-standard-policy.json and both ordered inventories in
the validator. The frozen consumer-parser golden moves accordingly: the rule
count goes from 64 to 65, startup-file membership from 49 to 50, and the digest
is replaced as scripts/test_validate_zsh_standard_policy.py instructs.

Evidence: z-shell/zi#488, fixed in z-shell/zi#491.

Closes #602
@ss-o
ss-o requested a review from a team as a code owner September 2, 2026 22:04
@ss-o
ss-o merged commit 46b9e6d into main Sep 2, 2026
11 checks passed
@ss-o
ss-o deleted the feature-602 branch September 2, 2026 22:53
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.

Zsh standard: rule for capturing a parameter's value before localizing it

1 participant