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
|
||||
// =====================================================================
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1,3 +1,3 @@
|
||||
(sxbc 1 "99bd2816b1cd7891"
|
||||
(code
|
||||
:constants ("hs-handler" {:upvalue-count 0 :arity 1 :constants ("hs-to-sx-from-source" "eval-expr-cek" "list" fn me let it event) :bytecode (20 0 0 16 0 48 1 17 1 20 1 0 1 3 0 1 4 0 52 2 0 1 1 5 0 1 6 0 2 52 2 0 2 1 7 0 2 52 2 0 2 52 2 0 2 16 1 52 2 0 3 52 2 0 3 49 1 50)} "hs-activate!" {:upvalue-count 0 :arity 1 :constants ("dom-get-attr" "_" "not" "dom-get-data" "hs-active" "dom-set-data" "hs-handler") :bytecode (20 0 0 16 0 1 1 0 48 2 17 1 16 1 6 33 15 0 5 20 3 0 16 0 1 4 0 48 2 52 2 0 1 33 30 0 20 5 0 16 0 1 4 0 3 48 3 5 20 6 0 16 1 48 1 17 2 16 2 16 0 49 1 32 1 0 2 50)} "hs-boot!" {:upvalue-count 0 :arity 0 :constants ("dom-query-all" "host-get" "host-global" "document" "body" "[_]" "for-each" {:upvalue-count 0 :arity 1 :constants ("hs-activate!") :bytecode (20 0 0 16 0 49 1 50)}) :bytecode (20 0 0 20 1 0 20 2 0 1 3 0 48 1 1 4 0 48 2 1 5 0 48 2 17 0 51 7 0 16 0 52 6 0 2 50)} "hs-boot-subtree!" {:upvalue-count 0 :arity 1 :constants ("dom-query-all" "[_]" "for-each" {:upvalue-count 0 :arity 1 :constants ("hs-activate!") :bytecode (20 0 0 16 0 49 1 50)} "dom-get-attr" "_" "hs-activate!") :bytecode (20 0 0 16 0 1 1 0 48 2 17 1 51 3 0 16 1 52 2 0 2 5 20 4 0 16 0 1 5 0 48 2 33 10 0 20 6 0 16 0 49 1 32 1 0 2 50)}) :bytecode (51 1 0 128 0 0 5 51 3 0 128 2 0 5 51 5 0 128 4 0 5 51 7 0 128 6 0 50)))
|
||||
:constants ("hs-handler" {:upvalue-count 0 :arity 1 :constants ("hs-to-sx-from-source" "eval-expr-cek" "list" fn me let it event) :bytecode (20 0 0 16 0 48 1 17 1 20 1 0 1 3 0 1 4 0 52 2 0 1 1 5 0 1 6 0 2 52 2 0 2 1 7 0 2 52 2 0 2 52 2 0 2 16 1 52 2 0 3 52 2 0 3 49 1 50)} "hs-activate!" {:upvalue-count 0 :arity 1 :constants ("dom-get-attr" "_" "not" "dom-get-data" "hs-active" "dom-set-data" "hs-handler") :bytecode (20 0 0 16 0 1 1 0 48 2 17 1 16 1 6 33 15 0 5 20 3 0 16 0 1 4 0 48 2 52 2 0 1 33 30 0 20 5 0 16 0 1 4 0 3 48 3 5 20 6 0 16 1 48 1 17 2 16 2 16 0 49 1 32 1 0 2 50)} "hs-boot!" {:upvalue-count 0 :arity 0 :constants ("dom-query-all" "host-get" "host-global" "document" "body" "[_]" "for-each" {:upvalue-count 0 :arity 1 :constants ("hs-activate!") :bytecode (20 0 0 16 0 49 1 50)}) :bytecode (20 0 0 1 3 0 52 2 0 1 1 4 0 52 1 0 2 1 5 0 48 2 17 0 51 7 0 16 0 52 6 0 2 50)} "hs-boot-subtree!" {:upvalue-count 0 :arity 1 :constants ("dom-query-all" "[_]" "for-each" {:upvalue-count 0 :arity 1 :constants ("hs-activate!") :bytecode (20 0 0 16 0 49 1 50)} "dom-get-attr" "_" "hs-activate!") :bytecode (20 0 0 16 0 1 1 0 48 2 17 1 51 3 0 16 1 52 2 0 2 5 20 4 0 16 0 1 5 0 48 2 33 10 0 20 6 0 16 0 49 1 32 1 0 2 50)}) :bytecode (51 1 0 128 0 0 5 51 3 0 128 2 0 5 51 5 0 128 4 0 5 51 7 0 128 6 0 50)))
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1006,6 +1006,7 @@
|
||||
"hs-last",
|
||||
"hs-repeat-times",
|
||||
"hs-repeat-forever",
|
||||
"hs-repeat-while",
|
||||
"hs-fetch",
|
||||
"hs-coerce",
|
||||
"hs-add",
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1792,7 +1792,7 @@
|
||||
blake2_js_for_wasm_create: blake2_js_for_wasm_create};
|
||||
}
|
||||
(globalThis))
|
||||
({"link":[["runtime-0db9b496",0],["prelude-d7e4b000",0],["stdlib-23ce0836",[]],["re-9a0de245",[2]],["sx-875d5bae",[2,3]],["jsoo_runtime-f96b44a8",[2]],["js_of_ocaml-651f6707",[2,5]],["dune__exe__Sx_browser-def18509",[2,4,6]],["std_exit-10fb8830",[2]],["start-f808dbe1",0]],"generated":(b=>{var
|
||||
({"link":[["runtime-0db9b496",0],["prelude-d7e4b000",0],["stdlib-23ce0836",[]],["re-9a0de245",[2]],["sx-624c0574",[2,3]],["jsoo_runtime-f96b44a8",[2]],["js_of_ocaml-651f6707",[2,5]],["dune__exe__Sx_browser-def18509",[2,4,6]],["std_exit-10fb8830",[2]],["start-f808dbe1",0]],"generated":(b=>{var
|
||||
c=b,a=b?.module?.export||b;return{"env":{"caml_ba_kind_of_typed_array":()=>{throw new
|
||||
Error("caml_ba_kind_of_typed_array not implemented")},"caml_exn_with_js_backtrace":()=>{throw new
|
||||
Error("caml_exn_with_js_backtrace not implemented")},"caml_int64_create_lo_mi_hi":()=>{throw new
|
||||
|
||||
@@ -1820,7 +1820,9 @@
|
||||
("define-syntax" (step-sf-define args env kont))
|
||||
(_
|
||||
(cond
|
||||
(and
|
||||
(has-key? *custom-special-forms* name)
|
||||
(not (env-has? env name)))
|
||||
(make-cek-value
|
||||
((get *custom-special-forms* name) args env)
|
||||
env
|
||||
@@ -1829,7 +1831,10 @@
|
||||
(let
|
||||
((mac (env-get env name)))
|
||||
(make-cek-state (expand-macro mac args env) env kont))
|
||||
(and *render-check* (*render-check* expr env))
|
||||
(and
|
||||
*render-check*
|
||||
(not (env-has? env name))
|
||||
(*render-check* expr env))
|
||||
(make-cek-value (*render-fn* expr env) env kont)
|
||||
:else (step-eval-call head args env kont)))))
|
||||
(step-eval-call head args env kont))))))
|
||||
|
||||
Reference in New Issue
Block a user