From 0df2b1c7b2638e0e93f4b10387b05f7f7f7cf060 Mon Sep 17 00:00:00 2001 From: giles Date: Fri, 8 May 2026 02:21:54 +0000 Subject: [PATCH] js-on-sx: hoist var across nested blocks; var-decls become set! MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit JS var is function-scoped, but the transpiler only collected top-level vars and re-emitted (define) everywhere; for-body var shadowed the outer (un-hoisted) scope. Three-part fix: 1. js-collect-var-names recurses into js-block/js-for/js-while /js-do-while/js-if/js-try/js-switch/js-for-of-in; 2. var-kind decls emit (set! ...) instead of (define ...) since the binding is already created at function scope; 3. js-block uses js-transpile-stmt-list (no re-hoist) instead of js-transpile-stmts. built-ins/Array: 17/45 → 18/45, String: 77/99 → 78/99. conformance.sh: 148/148. --- lib/js/test262-scoreboard.json | 54 ++++++++++++--------------- lib/js/test262-scoreboard.md | 26 ++++++------- lib/js/transpile.sx | 67 ++++++++++++++++++++++++++++------ plans/js-on-sx.md | 2 + 4 files changed, 93 insertions(+), 56 deletions(-) diff --git a/lib/js/test262-scoreboard.json b/lib/js/test262-scoreboard.json index e8d3818c..09db5212 100644 --- a/lib/js/test262-scoreboard.json +++ b/lib/js/test262-scoreboard.json @@ -1,41 +1,37 @@ { "totals": { - "pass": 17, - "fail": 21, - "skip": 5, - "timeout": 7, - "total": 50, - "runnable": 45, - "pass_rate": 37.8 + "pass": 78, + "fail": 15, + "skip": 1, + "timeout": 6, + "total": 100, + "runnable": 99, + "pass_rate": 78.8 }, "categories": [ { - "category": "built-ins/Array", - "total": 50, - "pass": 17, - "fail": 21, - "skip": 5, - "timeout": 7, - "pass_rate": 37.8, + "category": "built-ins/String", + "total": 100, + "pass": 78, + "fail": 15, + "skip": 1, + "timeout": 6, + "pass_rate": 78.8, "top_failures": [ [ "Test262Error (assertion failed)", - 16 + 13 ], [ "Timeout", - 7 - ], - [ - "TypeError: not a function", - 2 + 6 ], [ "ReferenceError (undefined symbol)", - 2 + 1 ], [ - "Unhandled: Not callable: {:2 43 :1 42 :0 41 :length 3}\\", + "SyntaxError (parse/unsupported syntax)", 1 ] ] @@ -44,26 +40,22 @@ "top_failure_modes": [ [ "Test262Error (assertion failed)", - 16 + 13 ], [ "Timeout", - 7 - ], - [ - "TypeError: not a function", - 2 + 6 ], [ "ReferenceError (undefined symbol)", - 2 + 1 ], [ - "Unhandled: Not callable: {:2 43 :1 42 :0 41 :length 3}\\", + "SyntaxError (parse/unsupported syntax)", 1 ] ], "pinned_commit": "d5e73fc8d2c663554fb72e2380a8c2bc1a318a33", - "elapsed_seconds": 151.1, + "elapsed_seconds": 224.9, "workers": 1 } \ No newline at end of file diff --git a/lib/js/test262-scoreboard.md b/lib/js/test262-scoreboard.md index 328abca4..0611ce69 100644 --- a/lib/js/test262-scoreboard.md +++ b/lib/js/test262-scoreboard.md @@ -1,30 +1,28 @@ # test262 scoreboard Pinned commit: `d5e73fc8d2c663554fb72e2380a8c2bc1a318a33` -Wall time: 151.1s +Wall time: 224.9s -**Total:** 17/45 runnable passed (37.8%). Raw: pass=17 fail=21 skip=5 timeout=7 total=50. +**Total:** 78/99 runnable passed (78.8%). Raw: pass=78 fail=15 skip=1 timeout=6 total=100. ## Top failure modes -- **16x** Test262Error (assertion failed) -- **7x** Timeout -- **2x** TypeError: not a function -- **2x** ReferenceError (undefined symbol) -- **1x** Unhandled: Not callable: {:2 43 :1 42 :0 41 :length 3}\ +- **13x** Test262Error (assertion failed) +- **6x** Timeout +- **1x** ReferenceError (undefined symbol) +- **1x** SyntaxError (parse/unsupported syntax) ## Categories (worst pass-rate first, min 10 runnable) | Category | Pass | Fail | Skip | Timeout | Total | Pass % | |---|---:|---:|---:|---:|---:|---:| -| built-ins/Array | 17 | 21 | 5 | 7 | 50 | 37.8% | +| built-ins/String | 78 | 15 | 1 | 6 | 100 | 78.8% | ## Per-category top failures (min 10 runnable, worst first) -### built-ins/Array (17/45 — 37.8%) +### built-ins/String (78/99 — 78.8%) -- **16x** Test262Error (assertion failed) -- **7x** Timeout -- **2x** TypeError: not a function -- **2x** ReferenceError (undefined symbol) -- **1x** Unhandled: Not callable: {:2 43 :1 42 :0 41 :length 3}\ +- **13x** Test262Error (assertion failed) +- **6x** Timeout +- **1x** ReferenceError (undefined symbol) +- **1x** SyntaxError (parse/unsupported syntax) diff --git a/lib/js/transpile.sx b/lib/js/transpile.sx index 7f9e4971..5b219e59 100644 --- a/lib/js/transpile.sx +++ b/lib/js/transpile.sx @@ -116,7 +116,8 @@ ((js-tag? ast "js-arrow") (js-transpile-arrow (nth ast 1) (nth ast 2))) ((js-tag? ast "js-program") (js-transpile-stmts (nth ast 1))) - ((js-tag? ast "js-block") (js-transpile-stmts (nth ast 1))) + ((js-tag? ast "js-block") + (cons (js-sym "begin") (js-transpile-stmt-list (nth ast 1)))) ((js-tag? ast "js-exprstmt") (js-transpile (nth ast 1))) ((js-tag? ast "js-empty") nil) ((js-tag? ast "js-var") @@ -509,11 +510,55 @@ (stmts) (cond ((empty? stmts) (list)) - ((and (list? (first stmts)) (js-tag? (first stmts) "js-var") (= (nth (first stmts) 1) "var")) + (else (append - (js-collect-var-decl-names (nth (first stmts) 2)) - (js-collect-var-names (rest stmts)))) - (else (js-collect-var-names (rest stmts)))))) + (js-collect-var-names-stmt (first stmts)) + (js-collect-var-names (rest stmts))))))) + +(define + js-collect-var-names-stmt + (fn + (stmt) + (cond + ((not (list? stmt)) (list)) + ((and (js-tag? stmt "js-var") (= (nth stmt 1) "var")) + (js-collect-var-decl-names (nth stmt 2))) + ((js-tag? stmt "js-block") (js-collect-var-names (nth stmt 1))) + ((js-tag? stmt "js-for") + (append + (js-collect-var-names-stmt (nth stmt 1)) + (js-collect-var-names-stmt (nth stmt 4)))) + ((js-tag? stmt "js-for-of-in") + (js-collect-var-names-stmt (nth stmt 4))) + ((js-tag? stmt "js-while") + (js-collect-var-names-stmt (nth stmt 2))) + ((js-tag? stmt "js-do-while") + (js-collect-var-names-stmt (nth stmt 1))) + ((js-tag? stmt "js-if") + (append + (js-collect-var-names-stmt (nth stmt 2)) + (if (>= (len stmt) 4) (js-collect-var-names-stmt (nth stmt 3)) (list)))) + ((js-tag? stmt "js-try") + (append + (js-collect-var-names-stmt (nth stmt 1)) + (if (and (>= (len stmt) 3) (list? (nth stmt 2))) + (js-collect-var-names-stmt (nth (nth stmt 2) 2)) + (list)) + (if (>= (len stmt) 4) (js-collect-var-names-stmt (nth stmt 3)) (list)))) + ((js-tag? stmt "js-switch") + (js-collect-var-names-cases (nth stmt 2))) + (else (list))))) + +(define + js-collect-var-names-cases + (fn + (cases) + (cond + ((empty? cases) (list)) + (else + (append + (js-collect-var-names (nth (first cases) 2)) + (js-collect-var-names-cases (rest cases))))))) (define js-dedup-names @@ -985,12 +1030,12 @@ (define js-transpile-var - (fn (kind decls) (cons (js-sym "begin") (js-vardecl-forms decls)))) + (fn (kind decls) (cons (js-sym "begin") (js-vardecl-forms decls (= kind "var"))))) (define js-vardecl-forms (fn - (decls) + (decls is-var) (cond ((empty? decls) (list)) (else @@ -1000,10 +1045,10 @@ ((js-tag? d "js-vardecl") (cons (list - (js-sym "define") + (js-sym (if is-var "set!" "define")) (js-sym (nth d 1)) (js-transpile (nth d 2))) - (js-vardecl-forms (rest decls)))) + (js-vardecl-forms (rest decls) is-var))) ((js-tag? d "js-vardecl-obj") (let ((names (nth d 1)) @@ -1014,7 +1059,7 @@ (js-vardecl-obj-forms names tmp-sym - (js-vardecl-forms (rest decls)))))) + (js-vardecl-forms (rest decls) is-var))))) ((js-tag? d "js-vardecl-arr") (let ((names (nth d 1)) @@ -1026,7 +1071,7 @@ names tmp-sym 0 - (js-vardecl-forms (rest decls)))))) + (js-vardecl-forms (rest decls) is-var))))) (else (error "js-vardecl-forms: unexpected decl")))))))) (define diff --git a/plans/js-on-sx.md b/plans/js-on-sx.md index c51d3b5b..9f66b7a7 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-08 — **`var` declarations hoist out of nested blocks; nested `var` becomes `set!`.** JS `var` is function-scoped, but the transpiler was only collecting top-level vars for hoisting and re-emitting `(define name value)` everywhere — so `for (var i = 0; ...) { var r = i; } r` saw `r` as undefined because the inner `(define r ...)` shadowed the (un-hoisted) outer scope. Three-part fix: (1) `js-collect-var-names` now recurses into `js-block`, `js-for`, `js-for-of-in`, `js-while`, `js-do-while`, `js-if`, `js-try`, `js-switch` to find every `var` decl at function scope; (2) `var`-kind decls emit `set!` (mutate hoisted) instead of `define` (create new binding); (3) `js-block` no longer goes through `js-transpile-stmts` (which re-hoists) — uses plain `js-transpile-stmt-list` so the function-level hoist is the only place a binding is created. built-ins/Array: 17/45 → 18/45, String: 77/99 → 78/99. conformance.sh: 148/148. + - 2026-05-08 — **`arr.length = N` extends the array (no-op for shrink).** `js-list-set!` was a no-op for the `length` key. Added a clause that pads with `js-undefined` via `js-pad-list!` when N > current length. Skipped truncation for now: the `pop-last!` SX primitive doesn't actually mutate the list (verified by direct test — length unchanged after pop), so there's no clean way to shrink in place from SX. Extension covers the common test262 cases (`var x = []; x.length = 5`). built-ins/Array: 16/45 → 17/45. conformance.sh: 148/148. - 2026-05-08 — **Arrays inherit unknown properties from `Array.prototype` (and onwards via `__proto__`).** `Array.prototype.myprop = 42; var x = []; x.myprop` was returning undefined and `x.hasOwnProperty(...)` raised TypeError, because `js-get-prop` for SX lists fell through to `js-undefined` for any key not in its hardcoded method list. Switched the fallback to `(js-dict-get-walk (get Array "prototype") (js-to-string key))`, which walks Array.prototype → (via the recent `__proto__` fallback) Object.prototype. Now custom Array.prototype properties propagate, and `arr.hasOwnProperty` resolves to `Object.prototype.hasOwnProperty`. built-ins/Array: 14/45 → 16/45. conformance.sh: 148/148.