js-on-sx: wrapper constructor-detection, Array.prototype.toString, >>> operator
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 11s

Number.__callable__ and String.__callable__ now check this.__proto__ ===
Number/String.prototype before writing wrapper slots, preventing false-positive
mutation when called as plain function. js-to-number extended to unwrap
wrapper dicts and call valueOf/toString for plain objects. Array.prototype.toString
replaced with a direct js-list-join implementation (eliminates infinite recursion
via js-invoke-method on dict-based arrays). >>> added to transpiler + runtime.

String test262 subset: 62→66/100. 529/530 unit, 147/148 slice.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-25 19:22:53 +00:00
parent ea63b6d9bb
commit 97180b4aa3
5 changed files with 232 additions and 54 deletions

View File

@@ -904,6 +904,36 @@
((= v false) 0)
((= (type-of v) "number") v)
((= (type-of v) "string") (js-string-to-number v))
((= (type-of v) "dict")
(cond
((contains? (keys v) "__js_number_value__")
(get v "__js_number_value__"))
((contains? (keys v) "__js_boolean_value__")
(if (get v "__js_boolean_value__") 1 0))
((contains? (keys v) "__js_string_value__")
(js-string-to-number (get v "__js_string_value__")))
(else
(let
((valueof-fn (js-get-prop v "valueOf")))
(if
(= (type-of valueof-fn) "lambda")
(let
((result (js-call-with-this v valueof-fn ())))
(if
(not (= (type-of result) "dict"))
(js-to-number result)
(let
((tostr-fn (js-get-prop v "toString")))
(if
(= (type-of tostr-fn) "lambda")
(let
((result2 (js-call-with-this v tostr-fn ())))
(if
(not (= (type-of result2) "dict"))
(js-to-number result2)
(js-nan-value)))
(js-nan-value)))))
(js-nan-value))))))
(else 0))))
(define
@@ -1172,10 +1202,35 @@
(else
(if
(= (type-of v) "dict")
(if
(contains? (keys v) "__js_string_value__")
(get v "__js_string_value__")
"[object Object]")
(cond
((contains? (keys v) "__js_string_value__")
(get v "__js_string_value__"))
((contains? (keys v) "__js_number_value__")
(js-number-to-string (get v "__js_number_value__")))
((contains? (keys v) "__js_boolean_value__")
(if (get v "__js_boolean_value__") "true" "false"))
(else
(let
((tostr-fn (js-get-prop v "toString")))
(if
(= (type-of tostr-fn) "lambda")
(let
((result (js-call-with-this v tostr-fn ())))
(if
(= (type-of result) "dict")
(let
((valueof-fn (js-get-prop v "valueOf")))
(if
(= (type-of valueof-fn) "lambda")
(let
((result2 (js-call-with-this v valueof-fn ())))
(if
(= (type-of result2) "dict")
"[object Object]"
(js-to-string result2)))
"[object Object]"))
(js-to-string result)))
"[object Object]"))))
(str v))))))
(define
@@ -1348,6 +1403,15 @@
(define js-mod (fn (a b) (mod (js-to-number a) (js-to-number b))))
(define
js-unsigned-rshift
(fn
(l r)
(let
((lu32 (modulo (js-math-trunc (js-to-number l)) 4294967296))
(shift (modulo (js-math-trunc (js-to-number r)) 32)))
(floor (/ lu32 (js-math-pow 2 shift))))))
(define js-pow (fn (a b) (pow (js-to-number a) (js-to-number b))))
(define js-neg (fn (a) (- 0 (js-to-number a))))
@@ -1990,11 +2054,11 @@
(fn
(i)
(let
((idx (js-num-to-int i)))
((idx (js-num-to-int (js-to-number i))))
(if
(and (>= idx 0) (< idx (len s)))
(char-code (char-at s idx))
0))))
(and (>= idx 0) (< idx (unicode-len s)))
(unicode-char-code-at s idx)
(js-nan-value)))))
((= name "indexOf")
(fn
(&rest args)
@@ -2402,7 +2466,7 @@
(else js-undefined)))
((= (type-of obj) "string")
(cond
((= key "length") (len obj))
((= key "length") (unicode-len obj))
((= (type-of key) "number")
(if
(and (>= key 0) (< key (len obj)))
@@ -2709,6 +2773,52 @@
(dict-set! (get Number "prototype") "constructor" Number)
(dict-set!
Number
"__callable__"
(fn
(&rest args)
(let
((raw (if (= (len args) 0) 0 (js-to-number (nth args 0)))))
(let
((this-val (js-this)))
(if
(and
(dict? this-val)
(contains? (keys this-val) "__proto__")
(= (get this-val "__proto__") (get Number "prototype")))
(begin (dict-set! this-val "__js_number_value__" raw) this-val)
raw)))))
(dict-set!
(get Number "prototype")
"valueOf"
(fn
()
(let
((this-val (js-this)))
(if
(and
(dict? this-val)
(contains? (keys this-val) "__js_number_value__"))
(get this-val "__js_number_value__")
this-val))))
(dict-set!
(get Number "prototype")
"toString"
(fn
(&rest args)
(let
((this-raw (js-this)))
(let
((this-val (if (and (dict? this-raw) (contains? (keys this-raw) "__js_number_value__")) (get this-raw "__js_number_value__") this-raw)))
(let
((radix (if (empty? args) 10 (js-to-number (nth args 0)))))
(js-num-to-str-radix
this-val
(if (or (= radix nil) (js-undefined? radix)) 10 radix)))))))
(define isFinite js-global-is-finite)
(define isNaN js-global-is-nan)
@@ -3167,6 +3277,17 @@
(dict-set! Array "name" "Array")
(dict-set!
(get Array "prototype")
"toString"
(fn
(&rest args)
(let
((this-val (js-this)))
(let
((items (cond ((list? this-val) this-val) ((and (dict? this-val) (contains? (keys this-val) "length")) (js-arraylike-to-list this-val)) (else (list)))))
(js-list-join items ",")))))
(define
js-string-from-char-code
(fn (&rest args) (js-string-from-char-code-loop args 0 "")))
@@ -3207,10 +3328,14 @@
(if
(>= i (len args))
acc
(js-string-from-char-code-loop
args
(+ i 1)
(str acc (js-code-to-char (js-num-to-int (nth args i))))))))
(let
((n (js-to-number (nth args i))))
(let
((code (if (js-global-is-nan n) 0 (modulo (js-math-trunc n) 65536))))
(js-string-from-char-code-loop
args
(+ i 1)
(str acc (char-from-code code))))))))
(define
js-string-proto-fn
@@ -3220,7 +3345,9 @@
(&rest args)
(let
((this-val (js-this)))
(js-invoke-method (js-to-string this-val) name args)))))
(let
((s (cond ((= (type-of this-val) "string") this-val) ((and (= (type-of this-val) "dict") (contains? (keys this-val) "__js_string_value__")) (get this-val "__js_string_value__")) (else "[object Object]"))))
(js-invoke-method s name args))))))
(define String {:fromCharCode js-string-from-char-code :__callable__ (fn (&rest args) (if (= (len args) 0) "" (js-to-string (nth args 0)))) :prototype {:toLowerCase (js-string-proto-fn "toLowerCase") :concat (js-string-proto-fn "concat") :startsWith (js-string-proto-fn "startsWith") :padEnd (js-string-proto-fn "padEnd") :codePointAt (js-string-proto-fn "codePointAt") :lastIndexOf (js-string-proto-fn "lastIndexOf") :indexOf (js-string-proto-fn "indexOf") :localeCompare (js-string-proto-fn "localeCompare") :split (js-string-proto-fn "split") :endsWith (js-string-proto-fn "endsWith") :trim (js-string-proto-fn "trim") :valueOf (js-string-proto-fn "valueOf") :at (js-string-proto-fn "at") :normalize (js-string-proto-fn "normalize") :substring (js-string-proto-fn "substring") :replaceAll (js-string-proto-fn "replaceAll") :repeat (js-string-proto-fn "repeat") :padStart (js-string-proto-fn "padStart") :search (js-string-proto-fn "search") :toUpperCase (js-string-proto-fn "toUpperCase") :trimEnd (js-string-proto-fn "trimEnd") :toString (js-string-proto-fn "toString") :toLocaleLowerCase (js-string-proto-fn "toLocaleLowerCase") :charCodeAt (js-string-proto-fn "charCodeAt") :slice (js-string-proto-fn "slice") :charAt (js-string-proto-fn "charAt") :match (js-string-proto-fn "match") :includes (js-string-proto-fn "includes") :trimStart (js-string-proto-fn "trimStart") :toLocaleUpperCase (js-string-proto-fn "toLocaleUpperCase") :replace (js-string-proto-fn "replace")} :raw (fn (&rest args) (if (empty? args) "" (js-to-string (nth args 0))))})
@@ -3232,6 +3359,8 @@
(dict-set! String "fromCodePoint" js-string-from-code-point)
(dict-set! String "fromCharCode" js-string-from-char-code)
(dict-set!
String
"__callable__"
@@ -3242,7 +3371,10 @@
(let
((this-val (js-this)))
(if
(dict? this-val)
(and
(dict? this-val)
(contains? (keys this-val) "__proto__")
(= (get this-val "__proto__") (get String "prototype")))
(begin
(dict-set! this-val "__js_string_value__" raw)
(dict-set! this-val "length" (len raw))
@@ -3260,6 +3392,50 @@
(dict-set! Boolean "name" "Boolean")
(dict-set! Boolean "prototype" {:constructor Boolean})
(dict-set!
Boolean
"__callable__"
(fn
(&rest args)
(let
((val (if (> (len args) 0) (js-to-boolean (nth args 0)) false)))
(let
((this-val (js-this)))
(if
(dict? this-val)
(begin
(dict-set! this-val "__js_boolean_value__" val)
(dict-set! this-val "__proto__" (get Boolean "prototype"))
this-val)
(if val true false))))))
(dict-set!
(get Boolean "prototype")
"valueOf"
(fn
(&rest args)
(let
((this-val (js-this)))
(if
(and
(= (type-of this-val) "dict")
(contains? (keys this-val) "__js_boolean_value__"))
(get this-val "__js_boolean_value__")
this-val))))
(dict-set!
(get Boolean "prototype")
"toString"
(fn
(&rest args)
(let
((this-val (js-this)))
(let
((b (if (and (= (type-of this-val) "dict") (contains? (keys this-val) "__js_boolean_value__")) (get this-val "__js_boolean_value__") this-val)))
(if b "true" "false")))))
(define
parseInt
(fn

View File

@@ -1,42 +1,42 @@
{
"totals": {
"pass": 62,
"fail": 29,
"pass": 66,
"fail": 25,
"skip": 1130,
"timeout": 9,
"total": 1230,
"runnable": 100,
"pass_rate": 62.0
"pass_rate": 66.0
},
"categories": [
{
"category": "built-ins/String",
"total": 1223,
"pass": 62,
"fail": 29,
"pass": 66,
"fail": 25,
"skip": 1123,
"timeout": 9,
"pass_rate": 62.0,
"pass_rate": 66.0,
"top_failures": [
[
"Test262Error (assertion failed)",
24
14
],
[
"Timeout",
9
],
[
"Unhandled: Not callable: \\\\\\",
1
"TypeError: not a function",
6
],
[
"ReferenceError (undefined symbol)",
1
2
],
[
"SyntaxError (parse/unsupported syntax)",
1
"Unhandled: Not callable: \\\\\\",
2
]
]
},
@@ -54,34 +54,30 @@
"top_failure_modes": [
[
"Test262Error (assertion failed)",
24
14
],
[
"Timeout",
9
],
[
"Unhandled: Not callable: \\\\\\",
1
"TypeError: not a function",
6
],
[
"ReferenceError (undefined symbol)",
1
2
],
[
"Unhandled: Not callable: \\\\\\",
2
],
[
"SyntaxError (parse/unsupported syntax)",
1
],
[
"TypeError: not a function",
1
],
[
"Unhandled: js-transpile-binop: unsupported op: >>>\\",
1
]
],
"pinned_commit": "d5e73fc8d2c663554fb72e2380a8c2bc1a318a33",
"elapsed_seconds": 40.5,
"workers": 7
"elapsed_seconds": 157.9,
"workers": 1
}

View File

@@ -1,32 +1,31 @@
# test262 scoreboard
Pinned commit: `d5e73fc8d2c663554fb72e2380a8c2bc1a318a33`
Wall time: 40.5s
Wall time: 157.9s
**Total:** 62/100 runnable passed (62.0%). Raw: pass=62 fail=29 skip=1130 timeout=9 total=1230.
**Total:** 66/100 runnable passed (66.0%). Raw: pass=66 fail=25 skip=1130 timeout=9 total=1230.
## Top failure modes
- **24x** Test262Error (assertion failed)
- **14x** Test262Error (assertion failed)
- **9x** Timeout
- **1x** Unhandled: Not callable: \\\
- **1x** ReferenceError (undefined symbol)
- **6x** TypeError: not a function
- **2x** ReferenceError (undefined symbol)
- **2x** Unhandled: Not callable: \\\
- **1x** SyntaxError (parse/unsupported syntax)
- **1x** TypeError: not a function
- **1x** Unhandled: js-transpile-binop: unsupported op: >>>\
## Categories (worst pass-rate first, min 10 runnable)
| Category | Pass | Fail | Skip | Timeout | Total | Pass % |
|---|---:|---:|---:|---:|---:|---:|
| built-ins/String | 62 | 29 | 1123 | 9 | 1223 | 62.0% |
| built-ins/String | 66 | 25 | 1123 | 9 | 1223 | 66.0% |
## Per-category top failures (min 10 runnable, worst first)
### built-ins/String (62/100 — 62.0%)
### built-ins/String (66/100 — 66.0%)
- **24x** Test262Error (assertion failed)
- **14x** Test262Error (assertion failed)
- **9x** Timeout
- **1x** Unhandled: Not callable: \\\
- **1x** ReferenceError (undefined symbol)
- **1x** SyntaxError (parse/unsupported syntax)
- **6x** TypeError: not a function
- **2x** ReferenceError (undefined symbol)
- **2x** Unhandled: Not callable: \\\

View File

@@ -295,6 +295,11 @@
(list (js-sym "js-undefined?") (js-sym "_a")))
(js-transpile r)
(js-sym "_a"))))
((= op ">>>")
(list
(js-sym "js-unsigned-rshift")
(js-transpile l)
(js-transpile r)))
(else (error (str "js-transpile-binop: unsupported op: " op))))))
;; ── Object literal ────────────────────────────────────────────────

View File

@@ -164,6 +164,8 @@ Append-only record of completed iterations. Loop writes one line per iteration:
- 2026-04-25 — **String fixes (constructor, indexOf/split/lastIndexOf multi-arg, fromCodePoint, matchAll, js-to-string dict fix).** Added `String.fromCodePoint` (fixes 1 ReferenceError); fixed `indexOf`/`lastIndexOf`/`split` to accept optional second argument; added `matchAll` stub; wired string property dispatch `else` fallback to `String.prototype` (fixes `'a'.constructor === String`); fixed `js-to-string` for dicts to return `"[object Object]"` instead of recursing into circular `String.prototype.constructor` structure. Scoreboard: String 42→43, timeouts 32→13. Total 162→202/300 (54%→67.3%). 529/530 unit, 148/148 slice.
- 2026-04-25 — **Number/String wrapper constructor-detection fix + Array.prototype.toString + js-to-number for wrappers + `>>>` operator.** `Number.__callable__` and `String.__callable__` now check `this.__proto__ === Number/String.prototype` before treating the call as a constructor — prevents false-positive slot-writing when called as plain function. `js-to-number` extended to unwrap `__js_number/boolean/string_value__` wrapper dicts and call `valueOf`/`toString` for plain objects. `Array.prototype.toString` replaced with a direct implementation using `js-list-join` (avoids infinite recursion when called on dict-based arrays). `>>>` (unsigned right-shift) added to transpiler + runtime (`js-unsigned-rshift` via modulo-4294967296). String test262 subset: 62→66/100. 529/530 unit, 147/148 slice.
- 2026-04-25 — **Math methods (trig/log/hyperbolic/bit ops).** Added 22 missing Math methods to `runtime.sx`: `sin`, `cos`, `tan`, `asin`, `acos`, `atan`, `atan2`, `sinh`, `cosh`, `tanh`, `asinh`, `acosh`, `atanh`, `exp`, `log`, `log2`, `log10`, `expm1`, `log1p`, `clz32`, `imul`, `fround`. All use existing SX primitives. `clz32` uses log2-based formula; `imul` uses modulo arithmetic; `fround` stubs to identity. Addresses 36x "TypeError: not a function" in built-ins/Math (43% → ~79% expected). 529/530 unit (unchanged), 148/148 slice. Commit `5f38e49b`.
- 2026-04-25 — **`var` hoisting.** Added `js-collect-var-decl-names`, `js-collect-var-names`, `js-dedup-names`, `js-var-hoist-forms` helpers to `transpile.sx`. Modified `js-transpile-stmts`, `js-transpile-funcexpr`, and `js-transpile-funcexpr-async` to prepend `(define name :js-undefined)` forms for all `var`-declared names before function-declaration hoists. Shallow collection (direct statements only). 4 new tests: program-level var, hoisted before use → undefined, var in function, var + assign. 529/530 unit (+4), 148/148 slice unchanged. Commit `11315d91`.