js-on-sx: raise JS TypeError for non-callable callee, undefined()
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 59s

Calling a non-callable raised an OCaml-level Eval_error "Not callable"
that JS try/catch couldn't intercept. Added a (js-function? callable)
precheck in js-apply-fn that raises a TypeError instance via
(js-new-call TypeError (list msg)) so e instanceof TypeError is
true. Same swap for the undefined() branch in js-call-plain (was
raising a bare string). built-ins/String: 71/99 → 73/99 (canonical),
74/99 → 75/99 (isolated). conformance.sh: 148/148.
This commit is contained in:
2026-05-07 15:58:16 +00:00
parent cf0ba8a02a
commit 843c3a7e5e
4 changed files with 56 additions and 45 deletions

View File

@@ -425,30 +425,44 @@
(fn-val args) (fn-val args)
(let (let
((callable (if (and (dict? fn-val) (contains? (keys fn-val) "__callable__")) (get fn-val "__callable__") fn-val))) ((callable (if (and (dict? fn-val) (contains? (keys fn-val) "__callable__")) (get fn-val "__callable__") fn-val)))
(cond (if
((= (len args) 0) (callable)) (not (js-function? callable))
((= (len args) 1) (callable (nth args 0))) (raise
((= (len args) 2) (callable (nth args 0) (nth args 1))) (js-new-call
((= (len args) 3) TypeError
(callable (nth args 0) (nth args 1) (nth args 2))) (list (str (str fn-val) " is not a function"))))
((= (len args) 4) (cond
(callable (nth args 0) (nth args 1) (nth args 2) (nth args 3))) ((= (len args) 0) (callable))
((= (len args) 5) ((= (len args) 1) (callable (nth args 0)))
(callable ((= (len args) 2)
(nth args 0) (callable (nth args 0) (nth args 1)))
(nth args 1) ((= (len args) 3)
(nth args 2) (callable
(nth args 3) (nth args 0)
(nth args 4))) (nth args 1)
((= (len args) 6) (nth args 2)))
(callable ((= (len args) 4)
(nth args 0) (callable
(nth args 1) (nth args 0)
(nth args 2) (nth args 1)
(nth args 3) (nth args 2)
(nth args 4) (nth args 3)))
(nth args 5))) ((= (len args) 5)
(else (apply callable args)))))) (callable
(nth args 0)
(nth args 1)
(nth args 2)
(nth args 3)
(nth args 4)))
((= (len args) 6)
(callable
(nth args 0)
(nth args 1)
(nth args 2)
(nth args 3)
(nth args 4)
(nth args 5)))
(else (apply callable args)))))))
;; ── Relational comparisons ──────────────────────────────────────── ;; ── Relational comparisons ────────────────────────────────────────
@@ -608,7 +622,7 @@
(fn-val args) (fn-val args)
(cond (cond
((js-undefined? fn-val) ((js-undefined? fn-val)
(error "TypeError: undefined is not a function")) (raise (js-new-call TypeError (list "undefined is not a function"))))
((and (dict? fn-val) (contains? (keys fn-val) "__callable__")) ((and (dict? fn-val) (contains? (keys fn-val) "__callable__"))
(js-call-with-this :js-undefined (get fn-val "__callable__") args)) (js-call-with-this :js-undefined (get fn-val "__callable__") args))
(else (js-call-with-this :js-undefined fn-val args))))) (else (js-call-with-this :js-undefined fn-val args)))))

View File

@@ -1,22 +1,22 @@
{ {
"totals": { "totals": {
"pass": 71, "pass": 73,
"fail": 22, "fail": 20,
"skip": 1, "skip": 1,
"timeout": 6, "timeout": 6,
"total": 100, "total": 100,
"runnable": 99, "runnable": 99,
"pass_rate": 71.7 "pass_rate": 73.7
}, },
"categories": [ "categories": [
{ {
"category": "built-ins/String", "category": "built-ins/String",
"total": 100, "total": 100,
"pass": 71, "pass": 73,
"fail": 22, "fail": 20,
"skip": 1, "skip": 1,
"timeout": 6, "timeout": 6,
"pass_rate": 71.7, "pass_rate": 73.7,
"top_failures": [ "top_failures": [
[ [
"Test262Error (assertion failed)", "Test262Error (assertion failed)",
@@ -31,11 +31,11 @@
6 6
], ],
[ [
"Unhandled: Not callable: \\\\\\", "ReferenceError (undefined symbol)",
2 1
], ],
[ [
"ReferenceError (undefined symbol)", "SyntaxError (parse/unsupported syntax)",
1 1
] ]
] ]
@@ -54,10 +54,6 @@
"Timeout", "Timeout",
6 6
], ],
[
"Unhandled: Not callable: \\\\\\",
2
],
[ [
"ReferenceError (undefined symbol)", "ReferenceError (undefined symbol)",
1 1
@@ -68,6 +64,6 @@
] ]
], ],
"pinned_commit": "d5e73fc8d2c663554fb72e2380a8c2bc1a318a33", "pinned_commit": "d5e73fc8d2c663554fb72e2380a8c2bc1a318a33",
"elapsed_seconds": 375.3, "elapsed_seconds": 280.3,
"workers": 1 "workers": 1
} }

View File

@@ -1,16 +1,15 @@
# test262 scoreboard # test262 scoreboard
Pinned commit: `d5e73fc8d2c663554fb72e2380a8c2bc1a318a33` Pinned commit: `d5e73fc8d2c663554fb72e2380a8c2bc1a318a33`
Wall time: 375.3s Wall time: 280.3s
**Total:** 71/99 runnable passed (71.7%). Raw: pass=71 fail=22 skip=1 timeout=6 total=100. **Total:** 73/99 runnable passed (73.7%). Raw: pass=73 fail=20 skip=1 timeout=6 total=100.
## Top failure modes ## Top failure modes
- **10x** Test262Error (assertion failed) - **10x** Test262Error (assertion failed)
- **8x** TypeError: not a function - **8x** TypeError: not a function
- **6x** Timeout - **6x** Timeout
- **2x** Unhandled: Not callable: \\\
- **1x** ReferenceError (undefined symbol) - **1x** ReferenceError (undefined symbol)
- **1x** SyntaxError (parse/unsupported syntax) - **1x** SyntaxError (parse/unsupported syntax)
@@ -18,14 +17,14 @@ Wall time: 375.3s
| Category | Pass | Fail | Skip | Timeout | Total | Pass % | | Category | Pass | Fail | Skip | Timeout | Total | Pass % |
|---|---:|---:|---:|---:|---:|---:| |---|---:|---:|---:|---:|---:|---:|
| built-ins/String | 71 | 22 | 1 | 6 | 100 | 71.7% | | built-ins/String | 73 | 20 | 1 | 6 | 100 | 73.7% |
## Per-category top failures (min 10 runnable, worst first) ## Per-category top failures (min 10 runnable, worst first)
### built-ins/String (71/99 — 71.7%) ### built-ins/String (73/99 — 73.7%)
- **10x** Test262Error (assertion failed) - **10x** Test262Error (assertion failed)
- **8x** TypeError: not a function - **8x** TypeError: not a function
- **6x** Timeout - **6x** Timeout
- **2x** Unhandled: Not callable: \\\
- **1x** ReferenceError (undefined symbol) - **1x** ReferenceError (undefined symbol)
- **1x** SyntaxError (parse/unsupported syntax)

View File

@@ -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. Append-only record of completed iterations. Loop writes one line per iteration: date, what was done, test count delta.
- 2026-05-07 — **`js-apply-fn` raises a JS-level `TypeError` instance when the callee isn't callable.** Calling a non-callable (`'a'()`, `(1+2)()`, etc.) raised an OCaml-level `Eval_error "Not callable"` from the CEK call dispatcher, which the JS `try { } catch(e)` (which transpiles to `(guard ...)`) couldn't intercept. Added a `(js-function? callable)` precheck at the top of `js-apply-fn`: when false, `(raise (js-new-call TypeError ...))` produces an instance whose proto chain makes `e instanceof TypeError === true`. Also rewrote the `undefined()` case in `js-call-plain` to use the same constructor path (was raising a bare string). built-ins/String: 71/99 → 73/99 (canonical), 74/99 → 75/99 (isolated). conformance.sh: 148/148.
- 2026-05-07 — **`js-dict-get-walk` falls back to `Object.prototype` when an object has no `__proto__`.** Object literals (`{}`, `{a:1}`) didn't carry a `__proto__` link, so `({}).toString()` couldn't find `Object.prototype.toString` — and overriding `Object.prototype.toString` had no effect on plain objects. Added a cond clause in `js-dict-get-walk`: if the object has no `__proto__` AND is not `Object.prototype` itself, walk into `Object.prototype`. Termination guaranteed because Object.prototype is the recursion base case. Now `({}).toString() === "[object Object]"`, override of `Object.prototype.toString` propagates to plain objects, and `({a:1}).hasOwnProperty('a') === true`. built-ins/String: 69/99 → 71/99 (canonical), 71/99 → 74/99 (isolated). conformance.sh: 148/148. - 2026-05-07 — **`js-dict-get-walk` falls back to `Object.prototype` when an object has no `__proto__`.** Object literals (`{}`, `{a:1}`) didn't carry a `__proto__` link, so `({}).toString()` couldn't find `Object.prototype.toString` — and overriding `Object.prototype.toString` had no effect on plain objects. Added a cond clause in `js-dict-get-walk`: if the object has no `__proto__` AND is not `Object.prototype` itself, walk into `Object.prototype`. Termination guaranteed because Object.prototype is the recursion base case. Now `({}).toString() === "[object Object]"`, override of `Object.prototype.toString` propagates to plain objects, and `({a:1}).hasOwnProperty('a') === true`. built-ins/String: 69/99 → 71/99 (canonical), 71/99 → 74/99 (isolated). conformance.sh: 148/148.
- 2026-05-07 — **`js-new-call` accepts list-typed constructor returns (not just dict).** `new Array(1,2,3)` was returning an empty wrapper object because `js-new-call` only honoured a non-undefined return when `(type-of ret) === "dict"`; SX lists (which represent JS arrays here) were silently discarded in favour of the empty `obj`. Widened the check to accept `"list"` returns. Fixes `new Array(1,2,3).length`, `String(new Array(1,2,3))`, and any constructor whose body returns a list. built-ins/String 67/99 → 69/99 (canonical), 70/99 → 71/99 (isolated). conformance.sh: 148/148. - 2026-05-07 — **`js-new-call` accepts list-typed constructor returns (not just dict).** `new Array(1,2,3)` was returning an empty wrapper object because `js-new-call` only honoured a non-undefined return when `(type-of ret) === "dict"`; SX lists (which represent JS arrays here) were silently discarded in favour of the empty `obj`. Widened the check to accept `"list"` returns. Fixes `new Array(1,2,3).length`, `String(new Array(1,2,3))`, and any constructor whose body returns a list. built-ins/String 67/99 → 69/99 (canonical), 70/99 → 71/99 (isolated). conformance.sh: 148/148.