From 1fef6ec94dc72f942873ca337744600e1feaaf17 Mon Sep 17 00:00:00 2001 From: giles Date: Sat, 9 May 2026 18:08:33 +0000 Subject: [PATCH] js-on-sx: Array.prototype forEach/map/filter honour thisArg + pass (v,i,arr) --- lib/js/runtime.sx | 60 +++++++++++++++++++++++++++++++++++++---------- plans/js-on-sx.md | 2 ++ 2 files changed, 49 insertions(+), 13 deletions(-) diff --git a/lib/js/runtime.sx b/lib/js/runtime.sx index 4c156ad9..4ccb6b3b 100644 --- a/lib/js/runtime.sx +++ b/lib/js/runtime.sx @@ -2222,13 +2222,40 @@ ((sep (if (= (len args) 0) "," (js-to-string (nth args 0))))) (js-list-join arr sep)))) ((= name "concat") (fn (&rest args) (js-list-concat arr args))) - ((= name "map") (fn (f) (js-list-map-loop f arr 0 (list)))) + ((= name "map") + (fn (&rest args) + (let + ((f (if (empty? args) :js-undefined (nth args 0))) + (this-arg + (cond + ((< (len args) 2) js-global-this) + ((js-undefined? (nth args 1)) js-global-this) + ((= (nth args 1) nil) js-global-this) + (else (nth args 1))))) + (js-list-map-loop f arr this-arg 0 (list))))) ((= name "filter") - (fn (f) (js-list-filter-loop f arr 0 (list)))) + (fn (&rest args) + (let + ((f (if (empty? args) :js-undefined (nth args 0))) + (this-arg + (cond + ((< (len args) 2) js-global-this) + ((js-undefined? (nth args 1)) js-global-this) + ((= (nth args 1) nil) js-global-this) + (else (nth args 1))))) + (js-list-filter-loop f arr this-arg 0 (list))))) ((= name "forEach") (fn - (f) - (begin (js-list-foreach-loop f arr 0) js-undefined))) + (&rest args) + (let + ((f (if (empty? args) :js-undefined (nth args 0))) + (this-arg + (cond + ((< (len args) 2) js-global-this) + ((js-undefined? (nth args 1)) js-global-this) + ((= (nth args 1) nil) js-global-this) + (else (nth args 1))))) + (begin (js-list-foreach-loop f arr this-arg 0) js-undefined)))) ((= name "reduce") (fn (&rest args) @@ -2337,7 +2364,7 @@ (fn (f) (let - ((mapped (js-list-map-loop f arr 0 (list)))) + ((mapped (js-list-map-loop f arr js-global-this 0 (list)))) (js-list-flat-loop mapped 1 (list))))) ((= name "findLast") (fn (f) (js-list-find-last-loop f arr (- (len arr) 1)))) @@ -2514,35 +2541,42 @@ (define js-list-map-loop (fn - (f arr i acc) + (f arr this-arg i acc) (cond ((>= i (len arr)) acc) (else (do - (append! acc (f (nth arr i))) - (js-list-map-loop f arr (+ i 1) acc)))))) + (append! + acc + (js-call-with-this this-arg f (list (nth arr i) i arr))) + (js-list-map-loop f arr this-arg (+ i 1) acc)))))) (define js-list-filter-loop (fn - (f arr i acc) + (f arr this-arg i acc) (cond ((>= i (len arr)) acc) (else (do (let ((v (nth arr i))) - (if (js-to-boolean (f v)) (append! acc v) nil)) - (js-list-filter-loop f arr (+ i 1) acc)))))) + (if + (js-to-boolean (js-call-with-this this-arg f (list v i arr))) + (append! acc v) + nil)) + (js-list-filter-loop f arr this-arg (+ i 1) acc)))))) (define js-list-foreach-loop (fn - (f arr i) + (f arr this-arg i) (cond ((>= i (len arr)) nil) (else - (do (f (nth arr i)) (js-list-foreach-loop f arr (+ i 1))))))) + (do + (js-call-with-this this-arg f (list (nth arr i) i arr)) + (js-list-foreach-loop f arr this-arg (+ i 1))))))) (define js-list-reduce-loop diff --git a/plans/js-on-sx.md b/plans/js-on-sx.md index 542110aa..370dd6d6 100644 --- a/plans/js-on-sx.md +++ b/plans/js-on-sx.md @@ -158,6 +158,8 @@ Each item: implement → tests → update progress. Mark `[x]` when tests green. Append-only record of completed iterations. Loop writes one line per iteration: date, what was done, test count delta. +- 2026-05-09 — **`Array.prototype.forEach`/`map`/`filter` honour `thisArg` and pass `(value, index, array)` to callback.** Was calling the callback with just `(value)` from a bare `(f x)` and ignoring the optional second `thisArg` parameter. Per spec, the callback receives `(value, index, array)` and `this` is `thisArg ?? globalThis` in non-strict. Updated the prototype methods to take `&rest args`, extract `thisArg` (defaulting to globalThis when null/undefined), and route through `js-call-with-this` with the full triple. Updated `js-list-foreach-loop` / `js-list-map-loop` / `js-list-filter-loop` accordingly. Result: built-ins/Array/prototype/forEach 2/30 → 9/30, filter 5/30 → 10/30. Array 18/30, Object 30/30, Map 18/30 unchanged. conformance.sh: 148/148. + - 2026-05-09 — **`Map.prototype.forEach` / `Set.prototype.forEach` honour `thisArg` and pass `(value, key, collection)` to callback.** Was hardcoding `js-undefined` as the callback receiver and only passing `(value, key)`. Per spec, the callback receives `(value, key, collection)` and `this` is `thisArg ?? globalThis` in non-strict. Updated `js-map-do-foreach` / `js-set-do-foreach` to accept an optional `thisArg`, defaulting to `globalThis` when null/undefined; the prototype methods now route the second positional arg through. Result: built-ins/Map/prototype 11/30 → 13/30, built-ins/Set/prototype +similar. Map 18/30 holds. conformance.sh: 148/148. - 2026-05-09 — **`for…in` walks the prototype chain (with shadowing) but stops at native prototypes.** Was using `js-object-keys` which only returns own enumerable keys, so `for (k in instance)` only saw the instance's own properties — not inherited ones from `FACTORY.prototype`. Per spec, for-in walks the entire chain and yields each unique enumerable key once. Added `js-for-in-keys` + `js-for-in-walk` that iterate the chain, deduping via `contains?`. Stops at `Object.prototype` / `Array.prototype` / etc. since those carry "non-enumerable" methods we don't track property-attribute-wise — without this guard, `for (k in {})` would enumerate `toString`/`valueOf`/etc. Result: language/statements/for-in 10/30 → 12/30. Object 30/30, Array 18/30 unchanged. conformance.sh: 148/148.