From e4c92a19d43b936ecad73405451a665ebf1a7d22 Mon Sep 17 00:00:00 2001 From: giles Date: Sat, 9 May 2026 08:35:11 +0000 Subject: [PATCH] js-on-sx: Error/TypeError/etc return instance when called without new --- lib/js/runtime.sx | 161 +++++++++++----------------------------------- plans/js-on-sx.md | 2 + 2 files changed, 38 insertions(+), 125 deletions(-) diff --git a/lib/js/runtime.sx b/lib/js/runtime.sx index 22b47557..b96a1356 100644 --- a/lib/js/runtime.sx +++ b/lib/js/runtime.sx @@ -817,27 +817,34 @@ ((dict-has? obj "__proto__") (js-in-walk (get obj "__proto__") skey)) (else false)))) +(define + js-error-init! + (fn + (this name args) + (begin + (dict-set! + this + "message" + (if (= (len args) 0) "" (js-to-string (nth args 0)))) + (dict-set! this "name" name) + (dict-set! this "__js_error_data__" true) + this))) + +(define + js-error-receiver + (fn + (ctor) + (let + ((this (js-this))) + (cond + ((= (type-of this) "dict") this) + (else (js-new-call ctor (list))))))) + (define Error (fn (&rest args) - (let - ((this (js-this))) - (begin - (if - (= (type-of this) "dict") - (do - (dict-set! - this - "message" - (if - (= (len args) 0) - "" - (js-to-string (nth args 0)))) - (dict-set! this "name" "Error") - (dict-set! this "__js_error_data__" true)) - nil) - this)))) + (js-error-init! (js-error-receiver Error) "Error" args))) (define js-error-is-error @@ -868,124 +875,28 @@ (define TypeError - (fn - (&rest args) - (let - ((this (js-this))) - (begin - (if - (= (type-of this) "dict") - (do - (dict-set! - this - "message" - (if - (= (len args) 0) - "" - (js-to-string (nth args 0)))) - (dict-set! this "name" "TypeError") - (dict-set! this "__js_error_data__" true)) - nil) - this)))) + (fn (&rest args) + (js-error-init! (js-error-receiver TypeError) "TypeError" args))) (define RangeError - (fn - (&rest args) - (let - ((this (js-this))) - (begin - (if - (= (type-of this) "dict") - (do - (dict-set! - this - "message" - (if - (= (len args) 0) - "" - (js-to-string (nth args 0)))) - (dict-set! this "name" "RangeError") - (dict-set! this "__js_error_data__" true)) - nil) - this)))) + (fn (&rest args) + (js-error-init! (js-error-receiver RangeError) "RangeError" args))) (define SyntaxError - (fn - (&rest args) - (let - ((this (js-this))) - (begin - (if - (= (type-of this) "dict") - (do - (dict-set! - this - "message" - (if - (= (len args) 0) - "" - (js-to-string (nth args 0)))) - (dict-set! this "name" "SyntaxError") - (dict-set! this "__js_error_data__" true)) - nil) - this)))) + (fn (&rest args) + (js-error-init! (js-error-receiver SyntaxError) "SyntaxError" args))) (define ReferenceError - (fn - (&rest args) - (let - ((this (js-this))) - (begin - (if - (= (type-of this) "dict") - (do - (dict-set! - this - "message" - (if - (= (len args) 0) - "" - (js-to-string (nth args 0)))) - (dict-set! this "name" "ReferenceError") - (dict-set! this "__js_error_data__" true)) - nil) - this)))) + (fn (&rest args) + (js-error-init! (js-error-receiver ReferenceError) "ReferenceError" args))) (define URIError - (fn - (&rest args) - (let - ((this (js-this))) - (begin - (if - (= this :js-undefined) - nil - (do - (dict-set! - this - "message" - (if (empty? args) "" (js-to-string (nth args 0)))) - (dict-set! this "name" "URIError") - (dict-set! this "__js_error_data__" true))) - this)))) + (fn (&rest args) + (js-error-init! (js-error-receiver URIError) "URIError" args))) (define EvalError - (fn - (&rest args) - (let - ((this (js-this))) - (begin - (if - (= this :js-undefined) - nil - (do - (dict-set! - this - "message" - (if (empty? args) "" (js-to-string (nth args 0)))) - (dict-set! this "name" "EvalError") - (dict-set! this "__js_error_data__" true))) - this)))) + (fn (&rest args) + (js-error-init! (js-error-receiver EvalError) "EvalError" args))) (define AggregateError :js-undefined) diff --git a/plans/js-on-sx.md b/plans/js-on-sx.md index 3109c1fd..b1d6d293 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 — **`Error(msg)` / `TypeError(msg)` / etc. (called without `new`) now return a proper instance.** Was checking `(if (= (type-of this) "dict") nil)` and falling through to return undefined when called as a plain function — but per spec, every Error subclass must return a new instance regardless of `new`. Refactored each constructor to `(js-error-init! (js-error-receiver Ctor) "Name" args)`: `js-error-receiver` returns `this` if it's a dict (the `new`-call case) and otherwise re-enters via `js-new-call ctor (list)` to create a properly-prototyped instance; `js-error-init!` sets `message`, `name`, `__js_error_data__`. Cleaner than the seven near-identical duplicated bodies. Result: built-ins/Error 17/30 → 22/30 (+5), language/expressions/instanceof 18/30 → 20/30. NativeErrors holds at 27/30. conformance.sh: 148/148. + - 2026-05-09 — **`typeof ` returns `"undefined"` instead of throwing ReferenceError.** Per JS spec, `typeof` on an unresolvable Reference is special-cased — it must return `"undefined"` without throwing. We were transpiling `typeof X` to `(js-typeof )`, and the symbol lookup itself errored for undeclared globals. New transpiler branch in `js-transpile-unop`: when the operand is a `js-ident`, emit `(if (or (env-has? (current-env) "name") (dict-has? js-global "name")) (js-typeof ) "undefined")` — checks both the lexical env (for local var/let/const/parameters) and the global object, and only references the symbol when the if branch is taken (SX `if` is lazy, so the unbound symbol in the false branch never errors). Result: language/expressions/typeof 9/13 → 10/13, built-ins/Object 29/30 → 30/30 (full pass — the `S15.2.1.1_A2_T11.js` test was using `typeof obj` on an undeclared name). conformance.sh: 148/148. - 2026-05-09 — **`==` returns false when either side is NaN, even across the numeric/string paths.** `js-loose-eq` was converting both sides to numbers (`Number.NaN == "string"` → `NaN == NaN`) and using SX `(=)`, which apparently returns true when both NaN values are the same reference. Per JS, NaN compares unequal to everything including itself. Wrapped both cross-type numeric/string branches in `(or (js-number-is-nan an) (js-number-is-nan bn))` short-circuits to false. Result: language/expressions/equals 20/30 → 23/30. strict-equals/Number/Object unchanged. conformance.sh: 148/148.