js-on-sx: array-like receivers for Array.prototype.* methods

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) <noreply@anthropic.com>
This commit is contained in:
2026-04-24 06:40:46 +00:00
parent 860549c1db
commit 5f3a8e43c0
2 changed files with 58 additions and 1 deletions

View File

@@ -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

View File

@@ -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"