From 00f88fde13bfd7445701a1102ad61d626a4e48af Mon Sep 17 00:00:00 2001 From: Mykhailo Chalyi Date: Fri, 27 Mar 2026 04:53:02 +0000 Subject: [PATCH] fix(interpreter): resolve namerefs in parameter expansion for assoc array subscripts `resolve_param_expansion_name` didn't resolve namerefs before looking up array names for subscript access and bulk operations (`[@]`, `[*]`). This caused `${ref[key]:-default}` and `${ref[key]:+alt}` to always treat the value as unset when `ref` was a nameref to an associative array. The fix resolves namerefs for `arr_name` in both the `[@]`/`[*]` bulk access path and the individual `[key]` subscript path within `resolve_param_expansion_name`, matching the behavior of `expand_variable`. Closes #801 --- crates/bashkit/src/interpreter/mod.rs | 12 +++-- .../tests/spec_cases/bash/nameref.test.sh | 54 +++++++++++++++++++ 2 files changed, 62 insertions(+), 4 deletions(-) diff --git a/crates/bashkit/src/interpreter/mod.rs b/crates/bashkit/src/interpreter/mod.rs index 2c683556..b17c8daf 100644 --- a/crates/bashkit/src/interpreter/mod.rs +++ b/crates/bashkit/src/interpreter/mod.rs @@ -6312,12 +6312,14 @@ impl Interpreter { .strip_suffix("[@]") .or_else(|| name.strip_suffix("[*]")) { + // Resolve nameref: if arr_name is a nameref, follow it to the target + let resolved_arr_name = self.resolve_nameref(arr_name); let sep = if is_star { self.get_ifs_separator() } else { " ".to_string() }; - if let Some(arr) = self.assoc_arrays.get(arr_name) { + if let Some(arr) = self.assoc_arrays.get(resolved_arr_name) { let is_set = !arr.is_empty(); let mut keys: Vec<_> = arr.keys().collect(); keys.sort(); @@ -6325,7 +6327,7 @@ impl Interpreter { keys.iter().filter_map(|k| arr.get(*k).cloned()).collect(); return (is_set, values.join(&sep)); } - if let Some(arr) = self.arrays.get(arr_name) { + if let Some(arr) = self.arrays.get(resolved_arr_name) { let is_set = !arr.is_empty(); let mut indices: Vec<_> = arr.keys().collect(); indices.sort(); @@ -6343,15 +6345,17 @@ impl Interpreter { && name.ends_with(']') { let arr_name = &name[..bracket]; + // Resolve nameref: if arr_name is a nameref, follow it to the target + let resolved_arr_name = self.resolve_nameref(arr_name); let key = &name[bracket + 1..name.len() - 1]; - if let Some(arr) = self.assoc_arrays.get(arr_name) { + if let Some(arr) = self.assoc_arrays.get(resolved_arr_name) { let expanded_key = self.expand_variable_or_literal(key); return match arr.get(&expanded_key) { Some(v) => (true, v.clone()), None => (false, String::new()), }; } - if let Some(arr) = self.arrays.get(arr_name) + if let Some(arr) = self.arrays.get(resolved_arr_name) && let Ok(idx) = key.parse::() { return match arr.get(&idx) { diff --git a/crates/bashkit/tests/spec_cases/bash/nameref.test.sh b/crates/bashkit/tests/spec_cases/bash/nameref.test.sh index 86eb968f..e6d4c586 100644 --- a/crates/bashkit/tests/spec_cases/bash/nameref.test.sh +++ b/crates/bashkit/tests/spec_cases/bash/nameref.test.sh @@ -264,3 +264,57 @@ echo "$result" ### expect computed ### end + +### nameref_assoc_default_value +# ${ref[key]:-default} through nameref to assoc array (issue #801) +declare -A m=([x]=1 [y]=2) +f() { + local -n ref="$1" + echo "${ref[x]:-EMPTY}" + echo "${ref[y]:-EMPTY}" + echo "${ref[z]:-EMPTY}" +} +f m +### expect +1 +2 +EMPTY +### end + +### nameref_assoc_replacement +# ${ref[key]:+alt} through nameref to assoc array +declare -A m=([a]=val) +f() { + local -n ref="$1" + echo "${ref[a]:+found}" + echo "${ref[missing]:+found}" +} +f m +### expect +found + +### end + +### nameref_assoc_subscript_at_default +# ${ref[@]:-default} through nameref to assoc array +declare -A m=([k]=v) +f() { + local -n ref="$1" + echo "${ref[@]:-EMPTY}" +} +f m +### expect +v +### end + +### nameref_assoc_subscript_at_empty +# ${ref[@]:-default} through nameref to empty assoc array +declare -A m +f() { + local -n ref="$1" + echo "${ref[@]:-EMPTY}" +} +f m +### expect +EMPTY +### end