fix(variables): keep dynamic well-known vars live across declare -a / array assign#1228
Open
lu-zero wants to merge 1 commit into
Open
fix(variables): keep dynamic well-known vars live across declare -a / array assign#1228lu-zero wants to merge 1 commit into
declare -a / array assign#1228lu-zero wants to merge 1 commit into
Conversation
Contributor
Author
|
The Source code checks (1.88.0, ubuntu) failure is |
Performance Benchmark Report
Code Coverage Report: Only Changed Files listed
Minimum allowed coverage is Test Summary: bash-completion test suite
|
5239ee8 to
2af5bea
Compare
… array assign
`PIPESTATUS` (and other dynamic well-known variables: RANDOM, SECONDS,
FUNCNAME, BASH_LINENO, BASH_SOURCE, BASH_ALIASES, ...) are backed by a
`ShellValue::Dynamic { getter, setter }` whose `getter` is consulted on
every read and whose `setter` is a no-op (`|_| ()`) for all of them.
Two write paths were unconditionally overwriting `self.value` with a
static array, discarding the Dynamic binding and permanently freezing
the variable at whatever was last assigned:
1. `convert_to_indexed_array` -- the `_` arm hit for Dynamic values.
Triggered by `declare -a VAR=(...)`.
2. `assign` (non-append Array-literal arm) -- explicitly matched
`ShellValue::Dynamic { .. }` in the overwrite set. Triggered by a
plain `VAR=([0]="...")` assignment.
From that point on the getter closure was gone, so the variable stopped
refreshing for the rest of the shell's life. Minimal repro:
declare -a PIPESTATUS=([0]="1")
true | true | true
echo "${PIPESTATUS[@]}" # bash: 0 0 0 ; brush: 1 (frozen)
Fix:
- `convert_to_indexed_array`: add an explicit `ShellValue::Dynamic { .. }`
=> Ok(()) arm so the binding survives.
- `assign`: drop `ShellValue::Dynamic { .. }` from the overwrite set; the
existing `(ShellValue::Dynamic { .. }, _) => Ok(())` arm below now
handles it correctly.
`convert_to_associative_array` is intentionally left unchanged: real
bash itself permanently freezes PIPESTATUS into a plain associative
array after `declare -A`, and pre-fix brush already matched that
behavior. Changing it would diverge from bash compat.
Adds three compat test cases mirroring the repro (`declare -a`, plain
array assign, and `declare -A`) under well_known_vars.yaml.
2af5bea to
5add53b
Compare
Contributor
There was a problem hiding this comment.
Pull request overview
Fixes PIPESTATUS becoming static after indexed-array declarations or assignments.
Changes:
- Preserves dynamic values during indexed-array conversion and assignment.
- Adds compatibility tests for indexed, associative, and plain assignments.
- Review found the conversion is incorrectly applied to all dynamic variable types.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
brush-core/src/variables.rs |
Adjusts dynamic variable conversion and assignment. |
brush-shell/tests/cases/compat/well_known_vars.yaml |
Adds PIPESTATUS regression tests. |
| // freeze the variable at whatever snapshot we materialized, breaking it | ||
| // forever. Real bash keeps these variables live across `declare -a`, so | ||
| // accept the declaration syntactically but leave the value untouched. | ||
| ShellValue::Dynamic { .. } => Ok(()), |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
Once a script
declare -as or plain-array-assigns a dynamic well-known variable such asPIPESTATUS, brush never updates it again on later pipelines in that shell — it stays frozen at whatever was last assigned. Real bash keeps the variable live and refreshes it on every pipeline.Minimal repro:
0 0 0origin/main):1(frozen)Same freeze happens with a plain array assignment (no
declare):0 1 09Root cause
PIPESTATUS(and other dynamic well-known variables:RANDOM,SECONDS,FUNCNAME,BASH_LINENO,BASH_SOURCE,BASH_ALIASES, …) are backed by aShellValue::Dynamic { getter, setter }whosegetteris consulted on every read and whosesetteris a no-op (|_| ()) for all of them.Two write paths in
brush-core/src/variables.rsunconditionally overwroteself.valuewith a static array, discarding theDynamicbinding and permanently freezing the variable:convert_to_indexed_array— the_arm hit forDynamicvalues and replacedself.value. Triggered bydeclare -a VAR=(...)viabrush-builtins/src/declare.rs.assign(non-append Array-literal arm) — explicitly matchedShellValue::Dynamic { .. }in the overwrite set. Triggered by a plainVAR=([0]="...")assignment.From that point on the getter closure was gone, so the variable stopped refreshing for the rest of the shell's life.
Fix
convert_to_indexed_array: add an explicitShellValue::Dynamic { .. } => Ok(())arm so the binding survives.assign: dropShellValue::Dynamic { .. }from the overwrite set; the existing(ShellValue::Dynamic { .. }, _) => Ok(())arm below now handles it correctly.convert_to_associative_arrayintentionally unchangedconvert_to_associative_arrayhas the same overwrite pattern, but real bash is asymmetric here:declare -A PIPESTATUS=([x]="9")does permanently freeze it in bash itself (PIPESTATUS becomes a plain associative array that pipelines don't refresh). Pre-fix brush already matched bash's-Abehavior, so changing it would diverge from bash compat.Verification
All four behavioral cases now match bash:
declare -athen pipe0 0 00 0 0✓declare -Athen pipe99✓=([0]="9")then pipe0 1 00 1 0✓0 0 00 0 0✓Adds three compat test cases under
brush-shell/tests/cases/compat/well_known_vars.yaml(declare -a, plain array assign,declare -A).cargo fmt --checkclean.This surfaced in the wild: a worker-env dump/restore mechanism restored a
declare -a PIPESTATUS=([0]="1")line and a downstreampipestatus || diecheck fired incorrectly, misreporting a successful pipeline as failed.