Skip to content

Commit 2928e36

Browse files
authored
fix(interpreter): handle compound array assignment in local builtin (#888)
## Summary - `local arr=(one two three)` produced an empty array because compound assignment syntax `=(...)` was only recognized when `-a` or `-A` flags were present - Detect `=(...)` syntax regardless of flags in both function-scope and global-scope paths - Also handle global-scope `local` with compound assoc array assignments - Unskip `nameref_local_dynamic_scope` test that depended on this fix (22 skipped, down from 23) ## Test plan - [x] New spec tests `local_array_compound_assignment` and `local_array_compound_in_global` pass - [x] Previously skipped `nameref_local_dynamic_scope` now passes - [x] All 1812 bash spec tests pass (100%, 22 skipped) - [x] Full `cargo test --all-features` passes - [x] `cargo fmt --check` and `cargo clippy` clean Closes #877
1 parent b2e6d88 commit 2928e36

File tree

4 files changed

+76
-10
lines changed

4 files changed

+76
-10
lines changed

crates/bashkit/src/interpreter/mod.rs

Lines changed: 51 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4425,11 +4425,9 @@ impl Interpreter {
44254425
if is_internal_variable(var_name) {
44264426
continue;
44274427
}
4428-
// Handle compound array assignment: local -a arr=(1 2 3)
4429-
if (flags.array || flags.assoc)
4430-
&& value.starts_with('(')
4431-
&& value.ends_with(')')
4432-
{
4428+
// Handle compound array assignment: local arr=(1 2 3) or local -a/-A arr=(...)
4429+
let is_compound = value.starts_with('(') && value.ends_with(')');
4430+
if is_compound {
44334431
let inner = &value[1..value.len() - 1];
44344432
if flags.assoc {
44354433
let arr = self.assoc_arrays.entry(var_name.to_string()).or_default();
@@ -4538,7 +4536,54 @@ impl Interpreter {
45384536
if is_internal_variable(var_name) {
45394537
continue;
45404538
}
4541-
if flags.nameref {
4539+
let is_compound = value.starts_with('(') && value.ends_with(')');
4540+
if is_compound {
4541+
let inner = &value[1..value.len() - 1];
4542+
if flags.assoc {
4543+
let arr = self.assoc_arrays.entry(var_name.to_string()).or_default();
4544+
arr.clear();
4545+
let mut rest = inner.trim();
4546+
while let Some(bracket_start) = rest.find('[') {
4547+
if let Some(bracket_end) = rest[bracket_start..].find(']') {
4548+
let key = &rest[bracket_start + 1..bracket_start + bracket_end];
4549+
let after = &rest[bracket_start + bracket_end + 1..];
4550+
if let Some(eq_rest) = after.strip_prefix('=') {
4551+
let eq_rest = eq_rest.trim_start();
4552+
let (val, remainder) =
4553+
if let Some(stripped) = eq_rest.strip_prefix('"') {
4554+
if let Some(end_q) = stripped.find('"') {
4555+
(
4556+
&stripped[..end_q],
4557+
stripped[end_q + 1..].trim_start(),
4558+
)
4559+
} else {
4560+
(stripped.trim_end_matches('"'), "")
4561+
}
4562+
} else {
4563+
match eq_rest.find(char::is_whitespace) {
4564+
Some(sp) => {
4565+
(&eq_rest[..sp], eq_rest[sp..].trim_start())
4566+
}
4567+
None => (eq_rest, ""),
4568+
}
4569+
};
4570+
arr.insert(key.to_string(), val.to_string());
4571+
rest = remainder;
4572+
} else {
4573+
break;
4574+
}
4575+
} else {
4576+
break;
4577+
}
4578+
}
4579+
} else {
4580+
let arr = self.arrays.entry(var_name.to_string()).or_default();
4581+
arr.clear();
4582+
for (idx, val) in inner.split_whitespace().enumerate() {
4583+
arr.insert(idx, val.trim_matches('"').to_string());
4584+
}
4585+
}
4586+
} else if flags.nameref {
45424587
self.variables
45434588
.insert(format!("_NAMEREF_{}", var_name), value.to_string());
45444589
} else {

crates/bashkit/tests/spec_cases/bash/arrays.test.sh

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -212,3 +212,26 @@ echo "${arr[@]}"
212212
### expect
213213
10 20 30 40 99
214214
### end
215+
216+
### local_array_compound_assignment
217+
# local arr=(a b c) should initialize the array
218+
myfunc() {
219+
local arr=(one two three)
220+
echo "count: ${#arr[@]}"
221+
echo "values: ${arr[*]}"
222+
}
223+
myfunc
224+
### expect
225+
count: 3
226+
values: one two three
227+
### end
228+
229+
### local_array_compound_in_global
230+
# local arr=(...) at global scope should also work
231+
local arr=(x y z)
232+
echo "${#arr[@]}"
233+
echo "${arr[1]}"
234+
### expect
235+
3
236+
y
237+
### end

crates/bashkit/tests/spec_cases/bash/nameref.test.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ jam
4444

4545
### nameref_local_dynamic_scope
4646
# pass local array by reference via dynamic scoping
47-
### skip: TODO parser does not handle local arr=(...) syntax (indexed array after command name)
47+
### bash_diff: nameref + local array by reference
4848
show_value() {
4949
local -n array_name=$1
5050
local idx=$2

crates/bashkit/tests/spec_tests.rs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
//! - `### skip: reason` - Skip test entirely (not run in any test)
99
//! - `### bash_diff: reason` - Known difference from real bash (runs in spec tests, excluded from comparison)
1010
//!
11-
//! ## Skipped Tests (33 total)
11+
//! ## Skipped Tests (32 total)
1212
//!
1313
//! Actual `### skip:` markers across spec test files:
1414
//!
@@ -24,8 +24,6 @@
2424
//! - [ ] od output format varies
2525
//! - [ ] hexdump -C output format varies
2626
//!
27-
//! ### nameref.test.sh (1 skipped)
28-
//! - [ ] parser does not handle local arr=(...) syntax
2927
//!
3028
//! ### parse-errors.test.sh (6 skipped)
3129
//! - [ ] parser does not reject unexpected 'do' keyword

0 commit comments

Comments
 (0)