Add failing tests: set! in named let loop (WASM + native OCaml)

Named let creates a loop continuation but set! inside the loop body
does not mutate bindings in the enclosing let scope. Affects both
the WASM kernel and native OCaml CEK evaluator.

6 failing Node tests cover:
- set! counter (simplest case)
- set! counter with named let params
- set! list accumulator via append
- append! + set! counter combo
- set! string concatenation
- nested named let set!

3 baselines pass: plain let set!, functional named let, plain append!

Also adds spec/tests/test-named-let-set.sx (7 assertions, first
fails and aborts — confirms bug exists in spec test suite too).

This is the root cause of empty source code blocks on all example
pages: tokenize-sx uses set! in named let → empty tokens →
highlight returns "(<> )" → empty <code> blocks.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-31 17:57:45 +00:00
parent a2348e5281
commit 3598a34e3d
2 changed files with 134 additions and 0 deletions

View File

@@ -0,0 +1,52 @@
(assert=
(let ((x 0)) (let loop () (when (< x 5) (set! x (+ x 1)) (loop))) x)
5
"set! counter in named let loop")
(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")
(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")
(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)")
(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")
(assert= (let ((x 0)) (set! x 42) x) 42 "set! in plain let (baseline)")
(assert=
(let loop ((i 0) (acc 0)) (if (< i 5) (loop (+ i 1) (+ acc i)) acc))
10
"named let without set! (baseline)")

View File

@@ -0,0 +1,82 @@
#!/usr/bin/env node
/**
* test-named-let-set.js — WASM kernel tests for set! inside named let loops.
*
* FAILING: set! inside named let body does not persist mutations to
* the enclosing scope. This breaks tokenize-sx → highlight → source display.
*/
const { createSxEnv } = require('./sx-harness');
let passed = 0, failed = 0;
const origLog = console.log;
const origErr = console.error;
function test(name, expr, expected) {
let env;
try {
// Fresh env per test to avoid cross-contamination
// But creating env is slow, so we'll reuse one below
} catch(e) {}
}
async function main() {
origLog('=== Named Let + set! Tests (WASM kernel) ===\n');
const t0 = Date.now();
const env = await createSxEnv({});
function check(name, expr, expected) {
const result = env.eval(expr);
const expectedStr = JSON.stringify(expected);
const resultVal = result?.items ? result.items : result;
const resultStr = JSON.stringify(resultVal);
if (resultStr === expectedStr) {
passed++;
origLog(` \u2713 ${name}`);
} else {
failed++;
origErr(` \u2717 ${name}: expected ${expectedStr}, got ${resultStr}`);
}
}
// ---- Baselines (should pass) ----
origLog('Baselines:');
check('set! in plain let',
'(let ((x 0)) (set! x 42) x)', 42);
check('named let without set! (functional accumulator)',
'(trampoline (let loop ((i 0) (acc 0)) (if (< i 5) (loop (+ i 1) (+ acc i)) acc)))', 10);
check('append! on outer binding (no named let)',
'(let ((xs (list))) (append! xs 1) (append! xs 2) xs)', [1, 2]);
// ---- Named let + set! (the broken cases) ----
origLog('\nNamed let + set!:');
check('set! counter in named let loop',
'(let ((x 0)) (let loop () (when (< x 5) (set! x (+ x 1)) (loop))) x)', 5);
check('set! counter in named let loop (with param)',
'(let ((total 0)) (let loop ((i 1)) (when (<= i 10) (set! total (+ total i)) (loop (+ i 1)))) total)', 55);
check('set! list via append in named let',
'(let ((acc (list))) (let loop ((i 0)) (when (< i 3) (set! acc (append acc (list i))) (loop (+ i 1)))) acc)', [0, 1, 2]);
check('append! in named let',
'(let ((acc (list)) (i 0)) (let loop () (when (< i 3) (append! acc i) (set! i (+ i 1)) (loop))) acc)', [0, 1, 2]);
check('set! string concat in named let',
'(let ((result "")) (let loop ((i 0)) (when (< i 3) (set! result (str result (str i))) (loop (+ i 1)))) result)', "012");
// ---- Nested named let ----
origLog('\nNested:');
check('set! in nested named let',
'(let ((count 0)) (let outer ((i 0)) (when (< i 3) (let inner ((j 0)) (when (< j 2) (set! count (+ count 1)) (inner (+ j 1)))) (outer (+ i 1)))) count)', 6);
env.close();
const dt = Date.now() - t0;
origLog(`\n=== ${passed} passed, ${failed} failed (${dt}ms) ===`);
process.exit(failed > 0 ? 1 : 0);
}
main().catch(e => { origErr(e); process.exit(1); });