Fix: local bindings now shadow HTML tag special forms in browser evaluator
Root cause: sx_browser.ml registered all HTML tags (a, b, i, p, s, u, g, etc.) as custom special forms. The evaluator's step_eval_list checked custom special forms BEFORE checking local env bindings. So (let ((a (fn () 42))) (a)) matched the HTML tag <a> instead of calling the local function a. Fix: skip custom special forms AND render-check when the symbol is bound in the local env. Added (not (env-has? env name)) guard to both checks in step-eval-list (spec/evaluator.sx and transpiled sx_ref.ml). This was the root cause of "[sx] resume: Not callable: nil" — after hs-wait resumed, calling letrec-bound functions like wait-boot (which is not an HTML tag) worked, but any function whose name collided with an HTML tag failed. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -299,6 +299,48 @@ node -e '
|
||||
K.eval("(let ((d (list))) (let ((el (with-island-scope (fn (x) (append! d x)) (fn () (render-to-dom (quote (div :class (deref test-reactive-sig) \"content\")) (global-env) nil))))) (reset! test-reactive-sig \"after\") (dom-get-attr el \"class\")))"),
|
||||
"after");
|
||||
|
||||
// =====================================================================
|
||||
// Section 4: Letrec + perform resume (async _driveAsync)
|
||||
// =====================================================================
|
||||
|
||||
// Define the letrec+perform pattern — this matches the test-runner island
|
||||
K.eval("(define __letrec-test-fn (letrec ((other (fn () \"from-other\")) (go (fn () (do (perform {:op \"io-sleep\" :args (list 50)}) (other))))) go))");
|
||||
|
||||
// Get the function as a JS-callable value
|
||||
var letrecFn = K.eval("__letrec-test-fn");
|
||||
if (typeof letrecFn !== "function") {
|
||||
fail++; console.error("FAIL: letrec-fn not callable, got: " + typeof letrecFn);
|
||||
} else {
|
||||
// Call via callFn — same path as island click handlers
|
||||
var letrecResult = K.callFn(letrecFn, []);
|
||||
// Resume through all suspensions — tests that resume() preserves letrec env
|
||||
try {
|
||||
while (letrecResult && letrecResult.suspended) { letrecResult = letrecResult.resume(null); }
|
||||
assert("letrec sibling after perform resume", letrecResult, "from-other");
|
||||
} catch(e) {
|
||||
fail++; console.error("FAIL: letrec perform resume: " + (e.message || e));
|
||||
}
|
||||
}
|
||||
|
||||
// Recursive letrec after perform — the wait-boot pattern
|
||||
K.eval("(define __wb-counter 0)");
|
||||
K.eval("(define __recur-test-fn (letrec ((recur (fn () (set! __wb-counter (+ __wb-counter 1)) (if (>= __wb-counter 3) \"done\" (do (perform {:op \"io-sleep\" :args (list 10)}) (recur)))))) (fn () (set! __wb-counter 0) (recur))))");
|
||||
|
||||
var recurFn = K.eval("__recur-test-fn");
|
||||
if (typeof recurFn !== "function") {
|
||||
fail++; console.error("FAIL: recur-fn not callable, got: " + typeof recurFn);
|
||||
} else {
|
||||
var recurResult = K.callFn(recurFn, []);
|
||||
try {
|
||||
// Resume through all suspensions synchronously
|
||||
while (recurResult && recurResult.suspended) { recurResult = recurResult.resume(null); }
|
||||
assert("recursive letrec after perform", recurResult, "done");
|
||||
assert("recursive letrec counter", K.eval("__wb-counter"), 3);
|
||||
} catch(e) {
|
||||
fail++; console.error("FAIL: recursive letrec perform: " + (e.message || e));
|
||||
}
|
||||
}
|
||||
|
||||
// =====================================================================
|
||||
// Summary
|
||||
// =====================================================================
|
||||
|
||||
Reference in New Issue
Block a user