From 802544fdc652a04934fb7e3e5773c05488102f6c Mon Sep 17 00:00:00 2001 From: giles Date: Sat, 9 May 2026 14:13:57 +0000 Subject: [PATCH] js-on-sx: call/apply box primitive thisArg per non-strict ToObject --- lib/js/runtime.sx | 24 ++++++++++++++++-------- plans/js-on-sx.md | 2 ++ 2 files changed, 18 insertions(+), 8 deletions(-) diff --git a/lib/js/runtime.sx b/lib/js/runtime.sx index c2c74a6b..6301e476 100644 --- a/lib/js/runtime.sx +++ b/lib/js/runtime.sx @@ -326,6 +326,19 @@ 0 (+ 1 (js-count-real-params (rest params))))))))) +(define + js-coerce-this-arg + (fn + (v) + (cond + ((js-undefined? v) js-global-this) + ((= v nil) js-global-this) + ((or (= (type-of v) "number") (= (type-of v) "rational")) + (js-new-call Number (js-args v))) + ((= (type-of v) "string") (js-new-call String (js-args v))) + ((= (type-of v) "boolean") (js-new-call Boolean (js-args v))) + (else v)))) + (define js-invoke-function-method (fn @@ -339,20 +352,15 @@ (< (len args) 1) (list) (js-list-slice args 1 (len args))))) - (let - ((this-arg - (if (or (js-undefined? raw-this) (= raw-this nil)) js-global-this raw-this))) - (js-call-with-this this-arg recv rest)))) + (js-call-with-this (js-coerce-this-arg raw-this) recv rest))) ((= key "apply") (let ((raw-this (if (< (len args) 1) :js-undefined (nth args 0))) (arr (if (< (len args) 2) (list) (nth args 1)))) (let - ((rest (cond ((= arr nil) (list)) ((js-undefined? arr) (list)) ((list? arr) arr) (else (js-iterable-to-list arr)))) - (this-arg - (if (or (js-undefined? raw-this) (= raw-this nil)) js-global-this raw-this))) - (js-call-with-this this-arg recv rest)))) + ((rest (cond ((= arr nil) (list)) ((js-undefined? arr) (list)) ((list? arr) arr) (else (js-iterable-to-list arr))))) + (js-call-with-this (js-coerce-this-arg raw-this) recv rest)))) ((= key "bind") (cond ((not (js-function? recv)) diff --git a/plans/js-on-sx.md b/plans/js-on-sx.md index 3253d9c1..c1edaf5b 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 — **`Function.prototype.call` / `apply` box primitive `thisArg` per non-strict ToObject.** Per spec, in non-strict mode the called function receives `ToObject(thisArg)` as `this` — so `f.call(1)` should see a `Number(1)` wrapper, not the raw primitive. We were passing primitives through unchanged, so `this.touched = true` inside the function silently no-op'd (`js-set-prop` on a number returns val unchanged). Extracted a `js-coerce-this-arg` helper that does the spec coercion: undefined/null → globalThis, number/rational → `new Number(v)`, string → `new String(v)`, boolean → `new Boolean(v)`, else as-is. Result: built-ins/Function/prototype/call 19/30 → 23/30, apply 22/30 → 25/30. bind 22/30, Object 30/30 unchanged. conformance.sh: 148/148. + - 2026-05-09 — **`Function.prototype.bind` throws TypeError when target isn't callable.** Per spec step 2 of `bind`, if the target (the receiver) isn't callable, throw TypeError. We were happily building a `(fn (&rest more) ...)` closure that would later fail to call — long after the bind() invocation. Added a `(not (js-function? recv))` guard at the top of the bind branch in `js-invoke-function-method` that raises a `TypeError` instance via `js-new-call`. Now `Function.prototype.bind.call(undefined)` etc. throw at the bind call site. Result: built-ins/Function/prototype/bind 14/30 → 22/30 (+8), call 18/30 → 19/30. Object 30/30. conformance.sh: 148/148. - 2026-05-09 — **`Function.prototype.{call, apply, bind}` carry their spec lengths and names.** Per spec, `Function.prototype.call.length === 1`, `apply.length === 2`, `bind.length === 1`. We were storing them as bare lambdas with `&rest args`, so `js-fn-length` fell back to the param-counting path which yielded 0. Wrapped each in the dict-with-`__callable__` pattern with explicit `length` and `name` slots; `toString` got `length: 0`. Result: built-ins/Function/prototype/apply 18/30 → 22/30, call 17/30 → 18/30. bind 14/30 holds (its remaining failures are deeper bind semantics — bound length, target check). Object 30/30. conformance.sh: 148/148.