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:
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user