From 013ce153570d835d5ae4abbd0bf372bf9a65c9ab Mon Sep 17 00:00:00 2001 From: giles Date: Sat, 9 May 2026 05:56:01 +0000 Subject: [PATCH] js-on-sx: js-add ToPrimitive's Date and plain Objects via valueOf/toString --- lib/js/runtime.sx | 25 ++++++++++++++++++++++++- plans/js-on-sx.md | 2 ++ 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/lib/js/runtime.sx b/lib/js/runtime.sx index 0ab20538..21658401 100644 --- a/lib/js/runtime.sx +++ b/lib/js/runtime.sx @@ -1969,7 +1969,30 @@ (get v "__js_number_value__")) ((contains? (keys v) "__js_boolean_value__") (get v "__js_boolean_value__")) - (else v)))) + ((contains? (keys v) "__js_is_date__") + (js-add-call-method v "toString")) + (else (js-add-toprim-default v))))) + +(define + js-add-toprim-default + (fn + (v) + (let + ((via-valueof (js-add-call-method v "valueOf"))) + (cond + ((not (= (type-of via-valueof) "dict")) via-valueof) + (else (js-add-call-method v "toString")))))) + +(define + js-add-call-method + (fn + (v name) + (let + ((m (js-dict-get-walk v name))) + (cond + ((js-undefined? m) v) + ((not (js-function? m)) v) + (else (js-call-with-this v m (list))))))) (define js-add diff --git a/plans/js-on-sx.md b/plans/js-on-sx.md index dd0bf06e..2ac02f3e 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 — **`+` 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. - 2026-05-09 — **Rational handling in `js-typeof` / `js-to-string` / `js-strict-eq` / `js-loose-eq` / `Object.prototype.toString`.** Followup to the `js-to-number` fix. SX rationals were leaking into other paths: `typeof 1/2` returned `"object"` (should be `"number"`), `String(1/2)` fell into the dict branch and returned `"[object Object]"`, and `1/2 === 0.5` was false because strict-eq compared types and `"rational"` ≠ `"number"`. Added rational arms to `js-typeof` and `js-object-tostring-class`, normalised rationals via `(exact->inexact)` in `js-to-string`'s number branch, and introduced a `js-numeric-type?` / `js-numeric-norm` pair that lets strict-eq and loose-eq treat both numeric kinds uniformly. Result: language/expressions/strict-equals 16/22 → 19/22; Math 30/30 confirmed (no regression — but it never had one). Object/Array/Map unchanged. conformance.sh: 148/148.