From 5f3a8e43c0f5079b1934d32253f7565b68933286 Mon Sep 17 00:00:00 2001 From: giles Date: Fri, 24 Apr 2026 06:40:46 +0000 Subject: [PATCH] js-on-sx: array-like receivers for Array.prototype.* methods MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Array.prototype.slice.call({length:3, 0:41, 1:42, 2:43}) used to crash with 'Not callable: {dict}' because js-array-proto-fn passed the dict straight into js-invoke-method, which then tried (append! dict x) etc. Now js-array-proto-fn converts dict-with-length receivers to a list via js-arraylike-to-list before dispatch. Mutation methods (push/pop/shift/ reverse/sort/fill) still require a real list — array-likes only work for read-only methods. Targets the 455x 'Not callable: {:length N :0 v1 :1 v2 ...}' scoreboard item. 6 new unit tests, 459/461 total. Co-Authored-By: Claude Opus 4.7 (1M context) --- lib/js/runtime.sx | 37 ++++++++++++++++++++++++++++++++++++- lib/js/test.sh | 22 ++++++++++++++++++++++ 2 files changed, 58 insertions(+), 1 deletion(-) diff --git a/lib/js/runtime.sx b/lib/js/runtime.sx index 023d4182..72bc49b3 100644 --- a/lib/js/runtime.sx +++ b/lib/js/runtime.sx @@ -1799,6 +1799,37 @@ result)) (else (list))))) +(define + js-arraylike-to-list + (fn + (v) + (cond + ((list? v) v) + ((= (type-of v) "string") (js-string-to-list v 0 (list))) + ((dict? v) + (let + ((n-val (get v "length"))) + (if + (or (= n-val nil) (js-undefined? n-val)) + (list) + (let + ((n (js-to-number n-val))) + (js-arraylike-to-list-loop v 0 n (list)))))) + (else (list))))) + +(define + js-arraylike-to-list-loop + (fn + (v i n acc) + (if + (>= i n) + acc + (let + ((val (get v (str i)))) + (do + (append! acc (if (= val nil) :js-undefined val)) + (js-arraylike-to-list-loop v (+ i 1) n acc)))))) + (define js-string-to-list (fn @@ -1933,7 +1964,11 @@ (name) (fn (&rest args) - (let ((this-val (js-this))) (js-invoke-method this-val name args))))) + (let + ((this-val (js-this))) + (let + ((recv (cond ((list? this-val) this-val) ((and (dict? this-val) (contains? (keys this-val) "length")) (js-arraylike-to-list this-val)) (else this-val)))) + (js-invoke-method recv name args)))))) (define js-array-from diff --git a/lib/js/test.sh b/lib/js/test.sh index fb266335..b6512405 100755 --- a/lib/js/test.sh +++ b/lib/js/test.sh @@ -1169,6 +1169,20 @@ cat > "$TMPFILE" << 'EPOCHS' (epoch 3405) (eval "(js-eval \"var arr2 = [1,2]; Array.prototype.push.call(arr2, 10, 20); arr2.length\")") +;; ── Phase 11.arrlike: Array.prototype.* on {length, 0:..., 1:...} ── +(epoch 3500) +(eval "(js-eval \"var a = {length: 3, 0: 41, 1: 42, 2: 43}; Array.prototype.slice.call(a).length\")") +(epoch 3501) +(eval "(js-eval \"var a = {length: 3, 0: 41, 1: 42, 2: 43}; Array.prototype.slice.call(a).join(',')\")") +(epoch 3502) +(eval "(js-eval \"var a = {length: 3, 0: 1, 1: 2, 2: 3}; Array.prototype.map.call(a, function(x){return x*2;}).join(',')\")") +(epoch 3503) +(eval "(js-eval \"var a = {length: 3, 0: 1, 1: 2, 2: 3}; Array.prototype.indexOf.call(a, 2)\")") +(epoch 3504) +(eval "(js-eval \"var a = {length: 3, 0: 1, 1: 2, 2: 3}; Array.prototype.filter.call(a, function(x){return x>1;}).join(',')\")") +(epoch 3505) +(eval "(js-eval \"var a = {length: 3, 0: 10, 1: 20, 2: 30}; var sum = 0; Array.prototype.forEach.call(a, function(x){sum += x;}); sum\")") + EPOCHS @@ -1805,6 +1819,14 @@ check 3403 "fn.call this-binding" '42' check 3404 "fn.apply arg-unpack" '7' check 3405 "Array.prototype.push.call arr" '4' +# ── Phase 11.arrlike: array-like receivers on Array.prototype ─ +check 3500 "slice.call arrLike length" '3' +check 3501 "slice.call arrLike join" '"41,42,43"' +check 3502 "map.call arrLike" '"2,4,6"' +check 3503 "indexOf.call arrLike" '1' +check 3504 "filter.call arrLike" '"2,3"' +check 3505 "forEach.call arrLike sum" '60' + TOTAL=$((PASS + FAIL)) if [ $FAIL -eq 0 ]; then echo "✓ $PASS/$TOTAL JS-on-SX tests passed"