diff --git a/lib/js/runtime.sx b/lib/js/runtime.sx index c8930c5d..c2177bf2 100644 --- a/lib/js/runtime.sx +++ b/lib/js/runtime.sx @@ -339,6 +339,15 @@ ((= (type-of v) "boolean") (js-new-call Boolean (js-args v))) (else v)))) +(define + js-call-this-coerce + (fn + (recv v) + (cond + ((or (= (type-of recv) "lambda") (= (type-of recv) "component")) + (js-coerce-this-arg v)) + (else v)))) + (define js-invoke-function-method (fn @@ -352,7 +361,7 @@ (< (len args) 1) (list) (js-list-slice args 1 (len args))))) - (js-call-with-this (js-coerce-this-arg raw-this) recv rest))) + (js-call-with-this (js-call-this-coerce recv raw-this) recv rest))) ((= key "apply") (let ((raw-this (if (< (len args) 1) :js-undefined (nth args 0))) @@ -360,7 +369,7 @@ (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))))) - (js-call-with-this (js-coerce-this-arg raw-this) recv rest)))) + (js-call-with-this (js-call-this-coerce recv raw-this) recv rest)))) ((= key "bind") (cond ((not (js-function? recv)) @@ -4754,7 +4763,7 @@ (let ((this-val (js-this))) (let - ((s (cond ((= (type-of this-val) "string") this-val) ((and (= (type-of this-val) "dict") (contains? (keys this-val) "__js_string_value__")) (get this-val "__js_string_value__")) (else "[object Object]")))) + ((s (cond ((or (= this-val nil) (js-undefined? this-val)) (raise (js-new-call TypeError (js-args (str "String.prototype." name " called on null or undefined"))))) ((= (type-of this-val) "string") this-val) ((and (= (type-of this-val) "dict") (contains? (keys this-val) "__js_string_value__")) (get this-val "__js_string_value__")) (else (js-to-string this-val))))) (js-invoke-method s name args)))) :length (js-string-proto-fn-length name) :name name})) diff --git a/plans/js-on-sx.md b/plans/js-on-sx.md index c8a4bb70..6d7dffec 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-10 — **`String.prototype.*` ToString-coerces non-string/non-undef this; `.call` / `.apply` skip global-coercion for built-in callables.** `String.prototype.trim.call(false)` was returning `"[object Object]"` because (a) `.call`/`.apply` blanket-coerced null/undefined `thisArg` to `js-global-this`, swallowing the original null, and (b) `js-string-proto-fn` fell back to `"[object Object]"` for any non-string this. (1) `js-string-proto-fn` now ToString-coerces primitive thisVal and raises TypeError for null/undefined (matches `RequireObjectCoercible` semantics for built-in String methods). (2) New `js-call-this-coerce` helper applies the legacy `js-coerce-this-arg` only when `recv` is a user lambda/component; built-in dict-with-`__callable__` methods get the raw `thisArg` (so they can see and reject null/undefined themselves, or accept primitive thisArgs without ToObject). Result: built-ins/String/prototype/trim 7/30 → 30/30 (+23). Function/prototype/apply 10/30 → 21/30. expressions/array 21/30 → 22/30. conformance.sh: 148/148. + - 2026-05-10 — **`**` / `Math.pow` honour JS spec edge cases for NaN, ±0, abs(base)=1+Infinity, plus `Number.prototype.valueOf` accepts ignored args.** (1) New `js-pow-spec` shared by `js-pow` (operator) and `js-math-pow`: NaN exponent → NaN, exponent 0 → 1 (even with NaN base), NaN base + non-zero exp → NaN, abs(base)=1 with exp=±Infinity → NaN. Underlying `pow` handles the rest. (2) Number.prototype.valueOf was `(fn () ...)` and rejected the spec-allowed extra arg with "lambda expects 0 args, got 1"; now `(fn (&rest args) ...)`. Result: language/expressions/exponentiation 23/30 → 25/30 (+2). built-ins/Math/pow 27/27 holds. conformance.sh: 148/148. - 2026-05-10 — **`Number.prototype.toString(radix)` no longer crashes on rational division-by-zero.** `js-num-to-str-radix` was probing for ±Infinity by comparing against `(/ 1 0)` / `(/ -1 0)` — but on the rational arithmetic path that throws "rational: division by zero" before the comparison ever happens, so every `Number(x).toString(radix)` call exploded. Replaced the probes with `(js-infinity-value)` / `(- 0 (js-infinity-value))` and the NaN check with `js-number-is-nan`. Result: built-ins/Number/prototype/toString 0/30 → 29/30 (+29). Number 26/30. conformance.sh: 148/148.