From 30f0151065251b2e69bb6c76435a37a6fbbde5c9 Mon Sep 17 00:00:00 2001 From: Ramb Memburg <46289413+memburg@users.noreply.github.com> Date: Tue, 26 May 2026 16:03:33 -0400 Subject: [PATCH] fix JS callback arity in array methods --- reference/REFERENCE.md | 6 ++++++ reference/Types.md | 6 ++++++ spec/giavascript_spec.cr | 8 ++++++++ src/giavascript/runtime_types.cr | 21 ++++++++++++++++++++- 4 files changed, 40 insertions(+), 1 deletion(-) diff --git a/reference/REFERENCE.md b/reference/REFERENCE.md index 68d9066..c944074 100644 --- a/reference/REFERENCE.md +++ b/reference/REFERENCE.md @@ -203,6 +203,12 @@ Status of built-in methods and properties on GiavaScript runtime types. | `values()` | Instance method | Not available | | `with()` | Instance method | Not available | +#### Array callback argument behavior + +- In array methods that accept callbacks, user-defined callback functions use JavaScript-compatible argument normalization. +- Extra callback arguments are ignored. +- Missing callback arguments are passed as `undefined`. + ### Object | Member | Kind | Status | diff --git a/reference/Types.md b/reference/Types.md index cd40b35..d751511 100644 --- a/reference/Types.md +++ b/reference/Types.md @@ -97,6 +97,12 @@ Status of built-in methods and properties on GiavaScript runtime types. | `values()` | Instance method | Not available | | `with()` | Instance method | Not available | +### Array callback argument behavior + +- In array methods that accept callbacks, user-defined callback functions use JavaScript-compatible argument normalization. +- Extra callback arguments are ignored. +- Missing callback arguments are passed as `undefined`. + ## Object | Member | Kind | Status | diff --git a/spec/giavascript_spec.cr b/spec/giavascript_spec.cr index 5422fb1..833f15d 100644 --- a/spec/giavascript_spec.cr +++ b/spec/giavascript_spec.cr @@ -874,6 +874,14 @@ describe GiavaScript do interpreter.eval("numbers.findIndex(function(value, index, array) { return value * 2 == array.length + index + 1; });").should eq(["3"]) end + it "uses JS-compatible callback arity for array methods" do + interpreter = GiavaScript::Interpreter.new + interpreter.eval("var array = [1, 4, 9, 16];").should eq([] of String) + interpreter.eval("array.map(function(x) { return x * 2; });").should eq(["[2, 8, 18, 32]"]) + interpreter.eval("array.map(function(value, index, source, extra) { return extra; });").should eq(["[undefined, undefined, undefined, undefined]"]) + interpreter.eval("[1, 2, 3].reduce(function(acc, value) { return acc + value; }, 0);").should eq(["6"]) + end + it "supports Array.reduce with and without an initial value" do interpreter = GiavaScript::Interpreter.new interpreter.eval("[1, 2, 3].reduce(function(acc, value, index, array) { return acc + value + index + array.length; }, 0);").should eq(["18"]) diff --git a/src/giavascript/runtime_types.cr b/src/giavascript/runtime_types.cr index b5a5706..92b48fe 100644 --- a/src/giavascript/runtime_types.cr +++ b/src/giavascript/runtime_types.cr @@ -959,7 +959,26 @@ module GiavaScript raise ExpressionError.new("Error: #{method_name} callback invoker is not configured") end - invoker.call(callback, args) + callback_args = normalize_callback_args(callback, args) + invoker.call(callback, callback_args) + end + + private def normalize_callback_args(callback : Value, args : Array(Value)) : Array(Value) + return args unless callback.is_a?(UserFunction) + + expected_count = callback.parameters.size + provided_count = args.size + return args if expected_count == provided_count + + normalized = Array(Value).new(expected_count) + index = 0 + + while index < expected_count + normalized << (index < provided_count ? args[index] : UNDEFINED) + index += 1 + end + + normalized end private def string_argument(value : Value, method_name : String) : String