From 87bf3711c4dac22b146d51efb90dd7ff8d0707fb Mon Sep 17 00:00:00 2001 From: giles Date: Sat, 9 May 2026 17:33:29 +0000 Subject: [PATCH] js-on-sx: Map/Set.prototype.forEach honour thisArg + pass (v,k,coll) --- lib/js/runtime.sx | 43 ++++++++++++++++++++++++++++--------------- plans/js-on-sx.md | 2 ++ 2 files changed, 30 insertions(+), 15 deletions(-) diff --git a/lib/js/runtime.sx b/lib/js/runtime.sx index 5717ccbf..4c156ad9 100644 --- a/lib/js/runtime.sx +++ b/lib/js/runtime.sx @@ -5687,23 +5687,30 @@ (define js-map-do-foreach (fn - (m cb) + (m cb &rest opts) (let - ((ks (get m "__map_keys__")) (vs (get m "__map_vals__"))) + ((ks (get m "__map_keys__")) + (vs (get m "__map_vals__")) + (this-arg + (cond + ((empty? opts) js-global-this) + ((js-undefined? (nth opts 0)) js-global-this) + ((= (nth opts 0) nil) js-global-this) + (else (nth opts 0))))) (begin - (js-map-foreach-loop ks vs cb 0 (len ks)) + (js-map-foreach-loop ks vs cb this-arg m 0 (len ks)) js-undefined)))) (define js-map-foreach-loop (fn - (ks vs cb i n) + (ks vs cb this-arg m i n) (cond ((>= i n) nil) (else (begin - (js-call-with-this js-undefined cb (list (nth vs i) (nth ks i))) - (js-map-foreach-loop ks vs cb (+ i 1) n)))))) + (js-call-with-this this-arg cb (list (nth vs i) (nth ks i) m)) + (js-map-foreach-loop ks vs cb this-arg m (+ i 1) n)))))) (define Map @@ -5716,7 +5723,7 @@ :has (fn (k) (js-map-do-has (js-this) k)) :delete (fn (k) (js-map-do-delete (js-this) k)) :clear (fn () (js-map-do-clear (js-this))) - :forEach (fn (cb) (js-map-do-foreach (js-this) cb)) + :forEach (fn (&rest args) (let ((cb (if (empty? args) :js-undefined (nth args 0))) (ta (if (>= (len args) 2) (nth args 1) :js-undefined))) (js-map-do-foreach (js-this) cb ta))) :keys (fn () (let ((ks (get (js-this) "__map_keys__"))) (js-list-copy ks))) :values (fn () (let ((vs (get (js-this) "__map_vals__"))) (js-list-copy vs))) :entries @@ -5834,26 +5841,32 @@ (define js-set-do-foreach (fn - (s cb) + (s cb &rest opts) (let - ((items (get s "__set_items__"))) + ((items (get s "__set_items__")) + (this-arg + (cond + ((empty? opts) js-global-this) + ((js-undefined? (nth opts 0)) js-global-this) + ((= (nth opts 0) nil) js-global-this) + (else (nth opts 0))))) (begin - (js-set-foreach-loop items cb 0 (len items)) + (js-set-foreach-loop items cb this-arg s 0 (len items)) js-undefined)))) (define js-set-foreach-loop (fn - (items cb i n) + (items cb this-arg s i n) (cond ((>= i n) nil) (else (begin (js-call-with-this - js-undefined + this-arg cb - (list (nth items i) (nth items i))) - (js-set-foreach-loop items cb (+ i 1) n)))))) + (list (nth items i) (nth items i) s)) + (js-set-foreach-loop items cb this-arg s (+ i 1) n)))))) (define Set @@ -5865,7 +5878,7 @@ :has (fn (v) (js-set-do-has (js-this) v)) :delete (fn (v) (js-set-do-delete (js-this) v)) :clear (fn () (js-set-do-clear (js-this))) - :forEach (fn (cb) (js-set-do-foreach (js-this) cb)) + :forEach (fn (&rest args) (let ((cb (if (empty? args) :js-undefined (nth args 0))) (ta (if (>= (len args) 2) (nth args 1) :js-undefined))) (js-set-do-foreach (js-this) cb ta))) :keys (fn () (js-list-copy (get (js-this) "__set_items__"))) :values (fn () (js-list-copy (get (js-this) "__set_items__"))) :entries diff --git a/plans/js-on-sx.md b/plans/js-on-sx.md index d8739734..542110aa 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 — **`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. - 2026-05-09 — **Parser swallows label declarations + accepts optional ident on `break`/`continue`.** Was rejecting `outer: while (...) { break outer; }` at parse time. Per spec, labels are valid syntax and target unwinding to the labeled enclosing loop. Added a parser branch for ` ':' ` that just parses through to the inner statement (label is dropped; the runtime treats unlabeled `break`/`continue` the same way for the common case where the inner loop is the target). Also extended `break`/`continue` to optionally consume a trailing ident. Result: language/statements/while 14/30 → 16/30, for 27/30 → 28/30. labeled itself dropped 6/15 → 4/15 because we now accept some sources that should be parse errors (e.g. `label: let x;` is a SyntaxError per spec) — net positive across the suite. Object 30/30, Array 18/30 unchanged. conformance.sh: 148/148.