From 83b4afcd7afcecc1bfa8e7a1585c5172bd4eb6d2 Mon Sep 17 00:00:00 2001 From: giles Date: Tue, 31 Mar 2026 21:34:19 +0000 Subject: [PATCH] Fix named-let set! scoping + convert test to deftest Named let's sf-named-let used call-lambda which returns a thunk that was never trampolined. The body executed in a disconnected env, so set! couldn't reach outer let bindings. Fixed by using cek-call which evaluates through the full CEK machine with proper env chain. Also converted test-named-let-set.sx from assert= (uses broken = for lists) to deftest/assert-equal (uses deep equal?). JS standard: 1120/1120, JS full: 1600/1600. Zero failures. Co-Authored-By: Claude Opus 4.6 (1M context) --- shared/static/scripts/sx-browser.js | 8 +-- spec/evaluator.sx | 2 +- spec/tests/test-named-let-set.sx | 78 ++++++++++++----------------- 3 files changed, 37 insertions(+), 51 deletions(-) diff --git a/shared/static/scripts/sx-browser.js b/shared/static/scripts/sx-browser.js index cf7c957e..b2f051f3 100644 --- a/shared/static/scripts/sx-browser.js +++ b/shared/static/scripts/sx-browser.js @@ -24,7 +24,7 @@ // ========================================================================= var NIL = Object.freeze({ _nil: true, toString: function() { return "nil"; } }); - var SX_VERSION = "2026-03-31T10:16:31Z"; + var SX_VERSION = "2026-03-31T21:28:15Z"; function isNil(x) { return x === NIL || x === null || x === undefined; } function isSxTruthy(x) { return x !== false && !isNil(x); } @@ -422,7 +422,7 @@ PRIMITIVES["len"] = function(c) { return Array.isArray(c) ? c.length : typeof c === "string" ? c.length : Object.keys(c).length; }; PRIMITIVES["first"] = function(c) { return c && c.length > 0 ? c[0] : NIL; }; PRIMITIVES["last"] = function(c) { return c && c.length > 0 ? c[c.length - 1] : NIL; }; - PRIMITIVES["rest"] = function(c) { if (c && typeof c.slice !== "function") { console.error("[sx-debug] rest called on non-sliceable:", typeof c, c, new Error().stack); return []; } return c ? c.slice(1) : []; }; + PRIMITIVES["rest"] = function(c) { if (!c || c._nil) return []; if (typeof c.slice !== "function") return []; return c.slice(1); }; PRIMITIVES["nth"] = function(c, n) { return c && n >= 0 && n < c.length ? c[n] : NIL; }; PRIMITIVES["cons"] = function(x, c) { return [x].concat(c || []); }; PRIMITIVES["append"] = function(c, x) { return (c || []).concat(Array.isArray(x) ? x : [x]); }; @@ -1366,7 +1366,7 @@ return append_b(inits, nth(binding, 1)); }, bindings) : reduce(function(acc, pai envBind(lambdaClosure(loopFn), loopName, loopFn); return (function() { var initVals = map(function(e) { return trampoline(evalExpr(e, env)); }, inits); - return callLambda(loopFn, initVals, env); + return cekCall(loopFn, initVals); })(); })(); })(); }; @@ -6683,7 +6683,7 @@ PRIMITIVES["batch"] = batch; PRIMITIVES["notify-subscribers"] = notifySubscribers; // flush-subscribers - var flushSubscribers = function(s) { return forEach(function(sub) { return sub(); }, signalSubscribers(s)); }; + var flushSubscribers = function(s) { return forEach(function(sub) { return cekCall(sub, NIL); }, signalSubscribers(s)); }; PRIMITIVES["flush-subscribers"] = flushSubscribers; // dispose-computed diff --git a/spec/evaluator.sx b/spec/evaluator.sx index a96cc3eb..05532022 100644 --- a/spec/evaluator.sx +++ b/spec/evaluator.sx @@ -544,7 +544,7 @@ (env-bind! (lambda-closure loop-fn) loop-name loop-fn) (let ((init-vals (map (fn (e) (trampoline (eval-expr e env))) inits))) - (call-lambda loop-fn init-vals env)))))) + (cek-call loop-fn init-vals)))))) (define sf-lambda diff --git a/spec/tests/test-named-let-set.sx b/spec/tests/test-named-let-set.sx index 395350c2..6657c37d 100644 --- a/spec/tests/test-named-let-set.sx +++ b/spec/tests/test-named-let-set.sx @@ -1,52 +1,38 @@ -(assert= - (let ((x 0)) (let loop () (when (< x 5) (set! x (+ x 1)) (loop))) x) - 5 - "set! counter in named let loop") +(defsuite "named-let-set" + (deftest "set! counter in named let loop" + (assert-equal 5 + (let ((x 0)) (let loop () (when (< x 5) (set! x (+ x 1)) (loop))) x))) -(assert= - (let - ((acc (list))) - (let - loop - ((i 0)) - (when (< i 3) (set! acc (append acc (list i))) (loop (+ i 1)))) - acc) - (list 0 1 2) - "set! list accumulator in named let with param") + (deftest "set! list accumulator in named let with param" + (assert-equal (list 0 1 2) + (let ((acc (list))) + (let loop ((i 0)) + (when (< i 3) (set! acc (append acc (list i))) (loop (+ i 1)))) + acc))) -(assert= - (let - ((acc (list)) (i 0)) - (let loop () (when (< i 3) (append! acc i) (set! i (+ i 1)) (loop))) - acc) - (list 0 1 2) - "append! + set! counter in named let") + (deftest "append! + set! counter in named let" + (assert-equal (list 0 1 2) + (let ((acc (list)) (i 0)) + (let loop () (when (< i 3) (append! acc i) (set! i (+ i 1)) (loop))) + acc))) -(assert= - (let - ((sum 0)) - (let - loop - ((i 1)) - (when (<= i 10) (set! sum (+ sum i)) (loop (+ i 1)))) - sum) - 55 - "set! sum in named let loop (1..10)") + (deftest "set! sum in named let loop (1..10)" + (assert-equal 55 + (let ((sum 0)) + (let loop ((i 1)) + (when (<= i 10) (set! sum (+ sum i)) (loop (+ i 1)))) + sum))) -(assert= - (let - ((result "")) - (let - loop - ((i 0)) - (when (< i 3) (set! result (str result (str i))) (loop (+ i 1)))) - result) - "012" - "set! string accumulator in named let") + (deftest "set! string accumulator in named let" + (assert-equal "012" + (let ((result "")) + (let loop ((i 0)) + (when (< i 3) (set! result (str result (str i))) (loop (+ i 1)))) + result))) -(assert= (let ((x 0)) (set! x 42) x) 42 "set! in plain let (baseline)") + (deftest "set! in plain let (baseline)" + (assert-equal 42 (let ((x 0)) (set! x 42) x))) -(assert= - (let loop ((i 0) (acc 0)) (if (< i 5) (loop (+ i 1) (+ acc i)) acc)) - 10 - "named let without set! (baseline)") + (deftest "named let without set! (baseline)" + (assert-equal 10 + (let loop ((i 0) (acc 0)) (if (< i 5) (loop (+ i 1) (+ acc i)) acc)))))