diff --git a/crates/zapcode-core/src/vm/mod.rs b/crates/zapcode-core/src/vm/mod.rs index f0f39d9..c1ae5b9 100644 --- a/crates/zapcode-core/src/vm/mod.rs +++ b/crates/zapcode-core/src/vm/mod.rs @@ -239,7 +239,7 @@ impl Vm { locals.push(args.get(i).cloned().unwrap_or(Value::Undefined)); } ParamPattern::Rest(_) => { - let rest: Vec = args[i..].to_vec(); + let rest: Vec = args.get(i..).map(|s| s.to_vec()).unwrap_or_default(); locals.push(Value::Array(rest)); } ParamPattern::DefaultValue { .. } => { @@ -247,8 +247,35 @@ impl Vm { // Keep Undefined so the compiler-emitted default init can fire locals.push(val); } - _ => { - locals.push(args.get(i).cloned().unwrap_or(Value::Undefined)); + // Destructuring params bind one local per name — matching the + // per-name locals declared in `compile_function_def`. Push a + // value for each *named* slot, in declaration order. + ParamPattern::ObjectDestructure(fields) => { + let arg = args.get(i).cloned().unwrap_or(Value::Undefined); + for field in fields { + let val = match &arg { + Value::Object(map) => map + .get(field.key.as_str()) + .cloned() + .unwrap_or(Value::Undefined), + _ => Value::Undefined, + }; + locals.push(val); + } + } + ParamPattern::ArrayDestructure(elems) => { + let arg = args.get(i).cloned().unwrap_or(Value::Undefined); + for (j, elem) in elems.iter().enumerate() { + // Only `Some(Ident)` slots declare a local (holes and + // unsupported nested patterns declare nothing) — stay aligned. + if let Some(ParamPattern::Ident(_)) = elem { + let val = match &arg { + Value::Array(a) => a.get(j).cloned().unwrap_or(Value::Undefined), + _ => Value::Undefined, + }; + locals.push(val); + } + } } } } diff --git a/crates/zapcode-core/tests/functions.rs b/crates/zapcode-core/tests/functions.rs index 89dc5a7..8255fe5 100644 --- a/crates/zapcode-core/tests/functions.rs +++ b/crates/zapcode-core/tests/functions.rs @@ -58,3 +58,24 @@ fn test_rest_params() { // Rest params create an array — need array indexing to work assert_eq!(result, Value::Int(15)); } + +// ── destructuring parameters (regression: bound whole arg, not elements) ───── + +#[test] +fn test_array_destructure_param() { + let r = eval_ts("const f = ([a, b]) => a + b; f([10, 20])").unwrap(); + assert_eq!(r, Value::Int(30)); +} + +#[test] +fn test_object_destructure_param() { + let r = eval_ts("const f = ({ x, y }) => x * y; f({ x: 6, y: 7 })").unwrap(); + assert_eq!(r, Value::Int(42)); +} + +#[test] +fn test_entries_map_destructure() { + let r = + eval_ts("Object.entries({ a: 1, b: 2 }).map(([k, v]) => `${k}${v}`).join(',')").unwrap(); + assert_eq!(r, Value::String("a1,b2".into())); +}