From 802bfc7aa574a72a4481978ba4905ee26c2b185f Mon Sep 17 00:00:00 2001 From: Mykhailo Chalyi Date: Thu, 26 Mar 2026 06:49:57 +0000 Subject: [PATCH] feat(interpreter): nameref resolution for associative array operations Resolve namerefs in: - ${!ref[@]} (array key listing via ArrayIndices) - ${#ref[@]} (array length via ArrayLength) - ref+=("value") (array append via AssignmentValue::Array) Closes #801 --- crates/bashkit/src/interpreter/mod.rs | 14 ++- .../spec_cases/bash/nameref-assoc.test.sh | 98 +++++++++++++++++++ 2 files changed, 107 insertions(+), 5 deletions(-) create mode 100644 crates/bashkit/tests/spec_cases/bash/nameref-assoc.test.sh diff --git a/crates/bashkit/src/interpreter/mod.rs b/crates/bashkit/src/interpreter/mod.rs index bdb431a8..b246d5cc 100644 --- a/crates/bashkit/src/interpreter/mod.rs +++ b/crates/bashkit/src/interpreter/mod.rs @@ -3168,7 +3168,9 @@ impl Interpreter { expanded_values.push((value, has_command_subst)); } - let arr = self.arrays.entry(assignment.name.clone()).or_default(); + // Resolve nameref for array assignments + let arr_name = self.resolve_nameref(&assignment.name).to_string(); + let arr = self.arrays.entry(arr_name).or_default(); let mut idx = if assignment.append { arr.keys().max().map(|k| k + 1).unwrap_or(0) } else { @@ -5921,9 +5923,10 @@ impl Interpreter { result.push_str(&names.join(" ")); } WordPart::ArrayLength(name) => { - if let Some(arr) = self.assoc_arrays.get(name) { + let resolved = self.resolve_nameref(name); + if let Some(arr) = self.assoc_arrays.get(resolved) { result.push_str(&arr.len().to_string()); - } else if let Some(arr) = self.arrays.get(name) { + } else if let Some(arr) = self.arrays.get(resolved) { result.push_str(&arr.len().to_string()); } else { result.push('0'); @@ -6028,12 +6031,13 @@ impl Interpreter { } // "${!arr[@]}" - array keys/indices as separate fields if let WordPart::ArrayIndices(name) = &word.parts[0] { - if let Some(arr) = self.assoc_arrays.get(name) { + let resolved = self.resolve_nameref(name); + if let Some(arr) = self.assoc_arrays.get(resolved) { let mut keys: Vec<_> = arr.keys().cloned().collect(); keys.sort(); return Ok(keys); } - if let Some(arr) = self.arrays.get(name) { + if let Some(arr) = self.arrays.get(resolved) { let mut indices: Vec<_> = arr.keys().collect(); indices.sort(); return Ok(indices.iter().map(|i| i.to_string()).collect()); diff --git a/crates/bashkit/tests/spec_cases/bash/nameref-assoc.test.sh b/crates/bashkit/tests/spec_cases/bash/nameref-assoc.test.sh new file mode 100644 index 00000000..7d625b5a --- /dev/null +++ b/crates/bashkit/tests/spec_cases/bash/nameref-assoc.test.sh @@ -0,0 +1,98 @@ +### nameref_assign_key_to_assoc +# Nameref assign key to caller's associative array +add_entry() { + local -n ref="$1" + ref["hello"]="world" + ref["foo"]="bar" +} +declare -A mymap +add_entry mymap +echo "${mymap[hello]}" +echo "${mymap[foo]}" +### expect +world +bar +### end + +### nameref_iterate_assoc_keys +# Nameref iterate keys of associative array +show_keys() { + local -n ref="$1" + for key in "${!ref[@]}"; do + echo "${key}=${ref[$key]}" + done | sort +} +declare -A colors=([red]=ff0000 [green]=00ff00 [blue]=0000ff) +show_keys colors +### expect +blue=0000ff +green=00ff00 +red=ff0000 +### end + +### nameref_append_indexed_array +# Nameref append to caller's indexed array +collect() { + local -n arr_ref="$1" + arr_ref+=("alpha") + arr_ref+=("beta") + arr_ref+=("gamma") +} +items=() +collect items +echo "${#items[@]}" +echo "${items[0]} ${items[1]} ${items[2]}" +### expect +3 +alpha beta gamma +### end + +### nameref_two_refs_same_function +# Two namerefs in same function (harness pattern) +collect_from() { + local dir="$1" + local -n map_ref="$2" + local -n order_ref="$3" + map_ref["key-a"]="${dir}/file-a" + map_ref["key-b"]="${dir}/file-b" + order_ref+=("key-a") + order_ref+=("key-b") +} +declare -A my_map +my_order=() +collect_from "/src" my_map my_order +echo "${my_map[key-a]}" +echo "${my_map[key-b]}" +echo "${my_order[0]} ${my_order[1]}" +### expect +/src/file-a +/src/file-b +key-a key-b +### end + +### nameref_assoc_length +# Nameref with associative array length +count_entries() { + local -n ref="$1" + echo "${#ref[@]}" +} +declare -A data=([x]=1 [y]=2 [z]=3) +count_entries data +### expect +3 +### end + +### nameref_overwrite_assoc_key +# Nameref overwrite existing key in associative array +update() { + local -n ref="$1" + ref["name"]="updated" +} +declare -A record=([name]=original [age]=30) +update record +echo "${record[name]}" +echo "${record[age]}" +### expect +updated +30 +### end