From d7cc6d1b39c3397fe0c70c35da6889dfe924621e Mon Sep 17 00:00:00 2001 From: giles Date: Sun, 10 May 2026 04:56:02 +0000 Subject: [PATCH] js-on-sx: split(undefined) returns whole string, funcexpr implicit return is undefined --- lib/js/runtime.sx | 19 ++++++++++++------- lib/js/transpile.sx | 2 +- plans/js-on-sx.md | 2 ++ 3 files changed, 15 insertions(+), 8 deletions(-) diff --git a/lib/js/runtime.sx b/lib/js/runtime.sx index 6db5b41b..0c8c698b 100644 --- a/lib/js/runtime.sx +++ b/lib/js/runtime.sx @@ -2969,14 +2969,19 @@ (&rest args) (let ((sep (if (= (len args) 0) :js-undefined (nth args 0))) - (limit - (if - (< (len args) 2) - -1 - (js-num-to-int (nth args 1))))) + (limit-raw (if (< (len args) 2) :js-undefined (nth args 1)))) (let - ((result (js-string-split s (js-to-string sep)))) - (if (< limit 0) result (js-list-take result limit)))))) + ((limit + (cond + ((js-undefined? limit-raw) -1) + (else (js-num-to-int (js-to-number limit-raw)))))) + (cond + ((js-undefined? sep) (js-make-list s)) + ((= limit 0) (js-make-list)) + (else + (let + ((result (js-string-split s (js-to-string sep)))) + (if (< limit 0) result (js-list-take result limit))))))))) ((= name "concat") (fn (&rest args) (js-string-concat-loop s args 0))) ((= name "includes") diff --git a/lib/js/transpile.sx b/lib/js/transpile.sx index 5eb29ec2..5e177205 100644 --- a/lib/js/transpile.sx +++ b/lib/js/transpile.sx @@ -1503,7 +1503,7 @@ (list (js-sym "fn") (list (js-sym "__return__")) - (cons (js-sym "begin") (append inits body-forms)))))) + (cons (js-sym "begin") (append (append inits body-forms) (list nil))))))) (list (js-sym "if") (list (js-sym "=") (js-sym "__r__") nil) diff --git a/plans/js-on-sx.md b/plans/js-on-sx.md index c4ce3bad..a6a0a666 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.split(undefined)` returns `[wholeString]`; function-expression bodies have spec-correct implicit `undefined` return.** (1) `js-string-method "split"` was calling `js-to-string` on the separator unconditionally, so `"undefinedd".split(undefined)` produced `["", "d"]` (split by `"undefined"`); also `limit=0` returned the whole-string list instead of `[]`. New arms: `undefined` separator → `[s]`, `limit=0` → `[]`, otherwise existing string-split. (2) Function expressions wrapped the body in `(call/cc (fn (__return__) (begin )))` and used the begin's last expression as the implicit return value. So `function F(){ this.x = function(){return 99} }` returned the inner lambda (because `js-set-prop` returns the rhs), and `new F()` saw a callable return and replaced the freshly-allocated `this` with it — so `i.x` was missing. Append `nil` to the begin so the implicit completion is always `:js-undefined`; explicit `return` still works via call/cc as before. Result: built-ins/String/prototype/split 8/30 → 10/30. Constructors with function-valued `this.X` now keep their assignments. conformance.sh: 148/148. + - 2026-05-10 — **Number/Boolean primitive method dispatch falls back to `Number.prototype` / `Boolean.prototype`.** When a user assigned a String method onto `Number.prototype` (e.g. `Number.prototype.toUpperCase = String.prototype.toUpperCase; NaN.toUpperCase()`), `js-invoke-number-method` rejected the unknown key with "is not a function (on number)" — it never walked the prototype. Added a fallback in both `js-invoke-number-method` and `js-invoke-boolean-method`: on unknown keys, `js-dict-get-walk` the constructor prototype; if found, `js-call-with-this` it. Result: built-ins/String/prototype/toUpperCase 16/25 → 19/25 (+3). Boolean 29/30. conformance.sh: 148/148. - 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.