From cb272317bcdf658384edca7fa2139c6b5e5a7571 Mon Sep 17 00:00:00 2001 From: giles Date: Sat, 9 May 2026 06:29:06 +0000 Subject: [PATCH] js-on-sx: js-to-number returns NaN for functions, coerces lists --- lib/js/runtime.sx | 3 +++ plans/js-on-sx.md | 2 ++ 2 files changed, 5 insertions(+) diff --git a/lib/js/runtime.sx b/lib/js/runtime.sx index 21658401..411b26fd 100644 --- a/lib/js/runtime.sx +++ b/lib/js/runtime.sx @@ -1369,6 +1369,9 @@ ((= (type-of v) "number") v) ((= (type-of v) "rational") (exact->inexact v)) ((= (type-of v) "string") (js-string-to-number v)) + ((or (= (type-of v) "lambda") (= (type-of v) "function") (= (type-of v) "component")) + (js-nan-value)) + ((= (type-of v) "list") (if (= (len v) 0) 0 (if (= (len v) 1) (js-to-number (nth v 0)) (js-nan-value)))) ((= (type-of v) "dict") (cond ((contains? (keys v) "__js_number_value__") diff --git a/plans/js-on-sx.md b/plans/js-on-sx.md index 2ac02f3e..0e4f199b 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 — **`js-to-number` of functions/lists returns NaN / sensible coercion (was 0).** `js-to-number` had no clauses for `lambda`/`function`/`component`/`list` types, so they fell into the `(else 0)` arm. Per spec: ToNumber of any function is NaN, and ToNumber of an Array goes through ToPrimitive which calls `Array.prototype.toString` (the comma-join), so `[]` → "" → 0, `[5]` → "5" → 5, and `[1,2]` → "1,2" → NaN. Added explicit lambda/function/component clauses (return NaN) and a list clause (length 0 → 0, length 1 → recurse, else NaN). Now `function(){return 1} - function(){return 1}` is NaN instead of 0. Result: language/expressions/subtraction 25/30 → 26/30; multiplication 90%, division 83% confirmed unchanged-or-better. Object/Array/Number unchanged. conformance.sh: 148/148. + - 2026-05-09 — **`+` operator now ToPrimitive's plain Objects + Dates via `valueOf`/`toString`.** Followup to the wrapper-unwrap fix. `js-add-unwrap` only handled `__js_string_value__` / `__js_number_value__` / `__js_boolean_value__` markers — for plain `{}` or `new Date()`, it returned the dict as-is, which then fell into `js-to-number` and produced `NaN`. Added two helpers: `js-add-toprim-default` calls `valueOf()` first (the "default" hint, used by `+`), and falls back to `toString()` if valueOf returns an object; for Date instances (`__js_is_date__` marker) we go straight to `toString` per spec. `js-add-call-method` walks the proto chain via `js-dict-get-walk`, calls the method with the receiver bound, and gives up if the slot is missing or not callable. Now `date + date === date.toString() + date.toString()`. Result: language/expressions/addition 23/30 → 24/30. Object/Array unchanged. conformance.sh: 148/148. - 2026-05-09 — **`+` operator unwraps Number/String/Boolean wrapper objects before deciding string-vs-numeric.** `js-add` was only checking `(type-of a)` / `(type-of b)` for `"string"` to decide string concat — but a `new String("1")` instance is type `"dict"`, so `new String("1") + "1"` was falling into the numeric branch and producing `2` instead of `"11"`. Added `js-add-unwrap` (mirrors ToPrimitive for the wrapper cases): if a dict has `__js_string_value__` / `__js_number_value__` / `__js_boolean_value__`, return the inner primitive. Then `js-add` applies the string-concat-vs-numeric decision to the unwrapped values. Result: language/expressions/addition 19/30 → 23/30. String stays 30/30. Number/Object unchanged. conformance.sh: 148/148.