From 85414df86804ecc5b75c6c7ec8d0283aee1096fd Mon Sep 17 00:00:00 2001 From: giles Date: Sun, 10 May 2026 09:31:52 +0000 Subject: [PATCH] js-on-sx: Map/Set prototype methods throw TypeError on non-Map/Set this --- lib/js/runtime.sx | 152 +++++++++++++++++++++++++++++----------------- plans/js-on-sx.md | 2 + 2 files changed, 97 insertions(+), 57 deletions(-) diff --git a/lib/js/runtime.sx b/lib/js/runtime.sx index 9a2c6a67..8ecbe495 100644 --- a/lib/js/runtime.sx +++ b/lib/js/runtime.sx @@ -6394,66 +6394,86 @@ (else nil))) entries)))) +(define + js-map-check! + (fn + (m method) + (cond + ((or (not (dict? m)) (not (contains? (keys m) "__map_keys__"))) + (raise + (js-new-call TypeError + (js-args (str "Map.prototype." method " called on non-Map object"))))) + (else nil)))) + (define js-map-do-set (fn (m k v) - (let - ((ks (get m "__map_keys__")) (vs (get m "__map_vals__"))) + (begin + (js-map-check! m "set") (let - ((idx (js-list-find-index ks k 0 (len ks)))) - (cond - ((>= idx 0) (begin (set-nth! vs idx v) m)) - (else - (begin - (append! ks k) - (append! vs v) - (dict-set! m "size" (len ks)) - m))))))) + ((ks (get m "__map_keys__")) (vs (get m "__map_vals__"))) + (let + ((idx (js-list-find-index ks k 0 (len ks)))) + (cond + ((>= idx 0) (begin (set-nth! vs idx v) m)) + (else + (begin + (append! ks k) + (append! vs v) + (dict-set! m "size" (len ks)) + m)))))))) (define js-map-do-get (fn (m k) - (let - ((ks (get m "__map_keys__")) (vs (get m "__map_vals__"))) + (begin + (js-map-check! m "get") (let - ((idx (js-list-find-index ks k 0 (len ks)))) - (cond ((>= idx 0) (nth vs idx)) (else js-undefined)))))) + ((ks (get m "__map_keys__")) (vs (get m "__map_vals__"))) + (let + ((idx (js-list-find-index ks k 0 (len ks)))) + (cond ((>= idx 0) (nth vs idx)) (else js-undefined))))))) (define js-map-do-has (fn (m k) - (let - ((ks (get m "__map_keys__"))) - (>= (js-list-find-index ks k 0 (len ks)) 0)))) + (begin + (js-map-check! m "has") + (let + ((ks (get m "__map_keys__"))) + (>= (js-list-find-index ks k 0 (len ks)) 0))))) (define js-map-do-delete (fn (m k) - (let - ((ks (get m "__map_keys__")) (vs (get m "__map_vals__"))) + (begin + (js-map-check! m "delete") (let - ((idx (js-list-find-index ks k 0 (len ks)))) - (cond - ((< idx 0) false) - (else - (let - ((new-ks (js-list-remove-at! ks idx)) - (new-vs (js-list-remove-at! vs idx))) - (begin - (dict-set! m "__map_keys__" new-ks) - (dict-set! m "__map_vals__" new-vs) - (dict-set! m "size" (len new-ks)) - true)))))))) + ((ks (get m "__map_keys__")) (vs (get m "__map_vals__"))) + (let + ((idx (js-list-find-index ks k 0 (len ks)))) + (cond + ((< idx 0) false) + (else + (let + ((new-ks (js-list-remove-at! ks idx)) + (new-vs (js-list-remove-at! vs idx))) + (begin + (dict-set! m "__map_keys__" new-ks) + (dict-set! m "__map_vals__" new-vs) + (dict-set! m "size" (len new-ks)) + true))))))))) (define js-map-do-clear (fn (m) (begin + (js-map-check! m "clear") (dict-set! m "__map_keys__" (list)) (dict-set! m "__map_vals__" (list)) (dict-set! m "size" 0) @@ -6562,53 +6582,71 @@ ((items (js-iterable-to-list iter))) (for-each (fn (x) (js-set-do-add s x)) items)))) +(define + js-set-check! + (fn + (s method) + (cond + ((or (not (dict? s)) (not (contains? (keys s) "__set_items__"))) + (raise + (js-new-call TypeError + (js-args (str "Set.prototype." method " called on non-Set object"))))) + (else nil)))) + (define js-set-do-add (fn (s v) - (let - ((items (get s "__set_items__"))) + (begin + (js-set-check! s "add") (let - ((idx (js-list-find-index items v 0 (len items)))) - (cond - ((>= idx 0) s) - (else - (begin - (append! items v) - (dict-set! s "size" (len items)) - s))))))) + ((items (get s "__set_items__"))) + (let + ((idx (js-list-find-index items v 0 (len items)))) + (cond + ((>= idx 0) s) + (else + (begin + (append! items v) + (dict-set! s "size" (len items)) + s)))))))) (define js-set-do-has (fn (s v) - (let - ((items (get s "__set_items__"))) - (>= (js-list-find-index items v 0 (len items)) 0)))) + (begin + (js-set-check! s "has") + (let + ((items (get s "__set_items__"))) + (>= (js-list-find-index items v 0 (len items)) 0))))) (define js-set-do-delete (fn (s v) - (let - ((items (get s "__set_items__"))) + (begin + (js-set-check! s "delete") (let - ((idx (js-list-find-index items v 0 (len items)))) - (cond - ((< idx 0) false) - (else - (let - ((new-items (js-list-remove-at! items idx))) - (begin - (dict-set! s "__set_items__" new-items) - (dict-set! s "size" (len new-items)) - true)))))))) + ((items (get s "__set_items__"))) + (let + ((idx (js-list-find-index items v 0 (len items)))) + (cond + ((< idx 0) false) + (else + (let + ((new-items (js-list-remove-at! items idx))) + (begin + (dict-set! s "__set_items__" new-items) + (dict-set! s "size" (len new-items)) + true))))))))) (define js-set-do-clear (fn (s) (begin + (js-set-check! s "clear") (dict-set! s "__set_items__" (list)) (dict-set! s "size" 0) js-undefined))) diff --git a/plans/js-on-sx.md b/plans/js-on-sx.md index a9776e4e..71332712 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 — **`Map.prototype.*` and `Set.prototype.*` raise TypeError when called on non-Map / non-Set `this`.** All five `js-map-do-*` and four `js-set-do-*` helpers were assuming `this` had `__map_keys__` / `__set_items__`, so `Map.prototype.clear.call({})` silently returned undefined (after creating dangling state) instead of throwing. Added `js-map-check!` / `js-set-check!` guards run as the first step of each method; raise spec-correct `TypeError` instances. Result: built-ins/Map 18/30 → 22/30 (+4). built-ins/Set 15/30 → 28/30 (+13). conformance.sh: 148/148. + - 2026-05-10 — **`Date.UTC` / `new Date(...)` propagate NaN/±Infinity arguments and return NaN.** `Date.UTC()` (no args) returned 0 instead of NaN; `Date.UTC(NaN, ...)` did the math and produced bogus ms; `new Date(year, NaN)` constructed a normal Date instead of an invalid one. Added `js-date-args-have-nan?` (also detects ±Infinity and propagates from rationals) used by both `Date.UTC` and the multi-arg constructor branch; UTC now returns NaN on no-arg / any-NaN-arg / out-of-range result, and `new Date(args)` stores NaN in `__date_value__` when any arg is NaN. Also fixed `js-date-from-one(undefined)` to return NaN. Result: built-ins/Date/UTC 6/16 → 10/16 (+4). Date 17/30 → 26/30 (timeouts dropped from 12 → 4 because invalid Dates now short-circuit). conformance.sh: 148/148. - 2026-05-10 — **Real `Date` construction + getters via Howard-Hinnant civil-day arithmetic.** `js-date-from-parts` now computes a true ms-since-epoch from `(year, month, day, hour, min, sec, ms)` via `js-date-civil-to-days` (the inverse of last iteration's `days-to-ymd`), with the legacy 2-digit-year coercion (0..99 → 1900+y). `getFullYear/Month/Date/Day/Hours/Minutes/Seconds/Milliseconds` (UTC + non-UTC) all share a new `js-date-getter`: TypeErrors on non-Date this, returns NaN on invalid time, otherwise decomposes ms into y/m/d/h/m/s/ms/dow. Plus added `Date.prototype.constructor = Date` (was missing). Result: each of the 8 Date getter categories went 2/6 → 5/6 (+3 each, +24 total). Date toISOString 11/16 → 13/16. Some Date construction-loop tests now exceed the 15s per-test timeout — the new civil math is heavier than the old (year-1970)*ms-per-year approximation, but correctness wins. conformance.sh: 148/148.