From f0dffd275d275ea3c11d2c57139e23b8836f3485 Mon Sep 17 00:00:00 2001 From: giles Date: Fri, 8 May 2026 15:31:33 +0000 Subject: [PATCH] js-on-sx: arguments object + Array.from mapFn calling convention Three related fixes: 1. Every JS function body binds arguments to (cons p1 ... __extra_args__), so arguments[k] and arguments.length work as expected. 2. Array.from(iter, mapFn) invokes mapFn through js-call-with-this with the index as second arg (was (map-fn x), missing index and inheriting outer this). 3. thisArg defaults to js-global-this when omitted (per non-strict ES). conformance.sh: 148/148. --- lib/js/runtime.sx | 11 ++++++++--- lib/js/transpile.sx | 19 ++++++++++++++++++- plans/js-on-sx.md | 2 ++ 3 files changed, 28 insertions(+), 4 deletions(-) diff --git a/lib/js/runtime.sx b/lib/js/runtime.sx index 2324362d..b952bb7d 100644 --- a/lib/js/runtime.sx +++ b/lib/js/runtime.sx @@ -3611,7 +3611,11 @@ (let ((src (js-iterable-to-list (nth args 0))) (map-fn - (if (< (len args) 2) nil (nth args 1)))) + (if (< (len args) 2) nil (nth args 1))) + (this-arg + (if (or (< (len args) 3) (js-undefined? (nth args 2)) (= (nth args 2) nil)) + js-global-this + (nth args 2)))) (if (= map-fn nil) (let @@ -3623,8 +3627,9 @@ (for-each (fn (x) - (append! result (map-fn x)) - (set! i (+ i 1))) + (begin + (append! result (js-call-with-this this-arg map-fn (list x i))) + (set! i (+ i 1)))) src) result))))))) diff --git a/lib/js/transpile.sx b/lib/js/transpile.sx index d5013408..ce09fb0f 100644 --- a/lib/js/transpile.sx +++ b/lib/js/transpile.sx @@ -940,6 +940,21 @@ (js-param-sym (first params)) (js-build-param-list (rest params))))))) +(define + js-arguments-build-form + (fn + (params) + (cond + ((empty? params) + (js-sym "__extra_args__")) + ((and (list? (first params)) (js-tag? (first params) "js-rest")) + (js-sym (nth (first params) 1))) + (else + (list + (js-sym "cons") + (js-param-sym (first params)) + (js-arguments-build-form (rest params))))))) + (define js-param-init-forms (fn @@ -1402,7 +1417,9 @@ param-syms (list (js-sym "let") - (list (list (js-sym "this") (list (js-sym "js-this")))) + (list + (list (js-sym "this") (list (js-sym "js-this"))) + (list (js-sym "arguments") (js-arguments-build-form params))) (list (js-sym "let") (list diff --git a/plans/js-on-sx.md b/plans/js-on-sx.md index 6f3c858c..8d712ae9 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-08 — **`arguments` object inside JS functions; `Array.from` calls mapFn correctly.** Three related fixes: (1) Every JS function body now binds `arguments` to `(cons p1 (cons p2 ... __extra_args__))` — a list of all received args, declared and rest. (2) `Array.from(iter, mapFn)` now invokes mapFn through `js-call-with-this` with the index as second arg (was `(map-fn x)` direct, missing index and inheriting outer `this`). (3) Defaults the `thisArg` to `js-global-this` when caller didn't pass one (per non-strict ES). Now `function f() { return arguments[1]; } f(1, 2)` returns 2; `Array.from([1,2,3], (v, i) => v + i*100)` returns `[1, 102, 203]`. conformance.sh: 148/148. + - 2026-05-08 — **`String(arr)` consults `Array.prototype.toString` (not the hardcoded join).** Was always emitting the comma-joined elements via `js-list-join`, so user-visible mutations of `Array.prototype.toString` had no effect on `String(arr)` / `"" + arr`. Now look up the override via `js-dict-get-walk` and call it on the list as `this`; fall back to `(js-list-join v ",")` when the override doesn't return a string. Default behaviour preserved (Array.prototype.toString already calls `js-list-join`). built-ins/String fail count: 11 → 9. conformance.sh: 148/148. - 2026-05-08 — **Top-level `this` resolves to the global object.** Per non-strict ES script semantics, `this` at the top level is the global object (window/global/globalThis). Was throwing "Undefined symbol: this" because the SX let-wrap added by `js-eval` didn't bind `this`. Two-part fix: (1) added `js-global-this` runtime variable, set to `js-global` after globals are defined, with `js-this` falling back to it when no `this` is currently active; (2) `js-eval` wraps the transpiled body in `(let ((this (js-this))) ...)` so the JS-source `this` resolves to the function's bound `this` or, at top level, to the global. Fixes `String(this)`, `this.Object === Object`, etc. built-ins/Object: 46/50 → 47/50. conformance.sh: 148/148.