diff --git a/lib/js/runtime.sx b/lib/js/runtime.sx index 751d0370..a0578f2a 100644 --- a/lib/js/runtime.sx +++ b/lib/js/runtime.sx @@ -4030,33 +4030,78 @@ (fn (o) (cond + ((or (= o nil) (js-undefined? o)) + (raise (js-new-call TypeError (js-args "Object.values called on null or undefined")))) + ((= (type-of o) "string") + (let ((result (list)) (n (len o))) + (begin + (js-string-values-loop result o 0 n) + result))) ((dict? o) (let ((result (list))) - (for-each (fn (k) (append! result (get o k))) (keys o)) + (for-each + (fn (k) (if (js-key-internal? k) nil (append! result (get o k)))) + (js-object-keys o)) result)) (else (list))))) +(define + js-string-values-loop + (fn + (acc s i n) + (cond + ((>= i n) nil) + (else + (begin + (append! acc (char-at s i)) + (js-string-values-loop acc s (+ i 1) n)))))) + (define js-object-entries (fn (o) (cond + ((or (= o nil) (js-undefined? o)) + (raise (js-new-call TypeError (js-args "Object.entries called on null or undefined")))) + ((= (type-of o) "string") + (let ((result (list)) (n (len o))) + (begin + (js-string-entries-loop result o 0 n) + result))) ((dict? o) (let ((result (list))) (for-each (fn (k) - (let - ((pair (list))) - (append! pair k) - (append! pair (get o k)) - (append! result pair))) - (keys o)) + (if + (js-key-internal? k) + nil + (let + ((pair (list))) + (begin + (append! pair k) + (append! pair (get o k)) + (append! result pair))))) + (js-object-keys o)) result)) (else (list))))) +(define + js-string-entries-loop + (fn + (acc s i n) + (cond + ((>= i n) nil) + (else + (let ((pair (list))) + (begin + (append! pair (js-to-string i)) + (append! pair (char-at s i)) + (append! acc pair) + (js-string-entries-loop acc s (+ i 1) n))))))) + (define js-object-assign (fn diff --git a/plans/js-on-sx.md b/plans/js-on-sx.md index 2959a7e9..d59628ba 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 — **`Object.values`/`entries` throw on null/undefined and walk strings.** Same shape as the previous `Object.keys` fix. Both methods returned `(list)` for non-dict input; per spec they ToObject the argument and yield the property values / `[k, v]` pairs. Added explicit branches: null/undefined → TypeError, string → walk character indices, dict → iterate own enumerable keys (skipping internal `__js_order__` / `__proto__`). Result: built-ins/Object/values 5/16 → 8/16, entries 5/17 → 9/17. Object 30/30 holds. conformance.sh: 148/148. + - 2026-05-09 — **`Object.keys` throws TypeError on null/undefined and walks indices on strings/arrays.** Was returning `(list)` for non-dict input — `Object.keys(null)` silently returned `[]` instead of throwing per spec, and `Object.keys("abc")` returned `[]` instead of `["0","1","2"]`. Added explicit branches: null/undefined → TypeError, string/list → `["0","1",..."n-1"]` via `js-string-keys-loop`. Result: built-ins/Object/keys 19/30 → 22/30. Object 30/30, Map 18/30 unchanged. conformance.sh: 148/148. - 2026-05-09 — **`Object.assign` ToObject's target, throws TypeError on null/undefined, copies own enumerable props from string sources.** Was returning the raw target unchanged when given a primitive (`Object.assign("a")` returned the string `"a"`), and silently no-op'd on null/undefined target instead of throwing per spec. Now coerces target via `js-coerce-this-arg` (boxes primitives), guards null/undefined with TypeError, and walks each source: dict → copy own keys (skipping internal `__js_order__` / `__proto__`), string → copy each character at numeric index, null/undefined → skip. Now `Object.assign("a")` returns a String wrapper whose `valueOf()` is `"a"`, and `Object.assign(null)` throws TypeError. Result: built-ins/Object/assign 5/25 → 13/25 (+8). Object 30/30 holds. conformance.sh: 148/148.