Root cause: _env_bind_hook mirrored ALL env_bind calls (including
lambda parameter bindings) to the shared VM globals table. Factory
functions like make-page-fn that return closures capturing different
values for the same param names (default-name, prefix, suffix) would
have the last call's values overwrite all previous closures' captured
state in globals. OP_GLOBAL_GET reads globals first, so all closures
returned the last factory call's values.
Fix: only sync root-env bindings (parent=None) to VM globals. Lambda
parameter bindings stay in their local env, found via vm_closure_env
fallback in OP_GLOBAL_GET.
Also in this commit:
- OP_CLOSURE propagates parent vm_closure_env to child closures
- Remove JIT globals injection (closure vars found via env chain)
- sx_server.ml: SX-Request header → returns text/sx (aser only)
- sx_server.ml: diagnostic endpoint GET /sx/_debug/{env,eval,route}
- sx_server.ml: page helper stubs for deep page rendering
- sx_server.ml: skip client-libs/ dir (browser-only definitions)
- adapter-html.sx: unknown components → HTML comment (not error)
- sx-platform.js: .sxbc fallback loader for bytecode modules
- Delete sx_http.ml (standalone HTTP server, unused)
- Delete stale .sxbc.json files (arity=0 bug, replaced by .sxbc)
- 7 new closure isolation tests in test-closure-isolation.sx
- mcp_tree.ml: emit arity + upvalue-count in .sxbc.json output
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
70 lines
2.3 KiB
Plaintext
70 lines
2.3 KiB
Plaintext
(defsuite
|
|
"closure-isolation"
|
|
(deftest
|
|
"basic factory: two closures are independent"
|
|
(let
|
|
((mk (fn (x) (fn () x))) (a (mk 1)) (b (mk 2)))
|
|
(assert-equal (a) 1)
|
|
(assert-equal (b) 2)))
|
|
(deftest
|
|
"factory with multiple params"
|
|
(let
|
|
((mk (fn (x y) (fn (s) (str x s y))))
|
|
(a (mk "A" "1"))
|
|
(b (mk "B" "2"))
|
|
(c (mk "C" "3")))
|
|
(assert-equal (a "-") "A-1")
|
|
(assert-equal (b "-") "B-2")
|
|
(assert-equal (c "-") "C-3")))
|
|
(deftest
|
|
"earlier closure unaffected by later factory call"
|
|
(let
|
|
((mk (fn (x y) (fn (s) (str x s y)))) (a (mk "A" "1")))
|
|
(let
|
|
((b (mk "B" "2")) (c (mk "C" "3")))
|
|
(assert-equal (a "x") "Ax1")
|
|
(assert-equal (b "x") "Bx2")
|
|
(assert-equal (c "x") "Cx3"))))
|
|
(deftest
|
|
"factory with nil branch (make-page-fn pattern)"
|
|
(let
|
|
((mk (fn (default prefix suffix) (fn (slug) (if (nil? slug) default (str prefix slug suffix)))))
|
|
(examples (mk "EX-DEFAULT" "EX-" ""))
|
|
(sxtp (mk "SX-DEFAULT" "SX-" "-content")))
|
|
(assert-equal (examples nil) "EX-DEFAULT")
|
|
(assert-equal (examples "counter") "EX-counter")
|
|
(assert-equal (sxtp nil) "SX-DEFAULT")
|
|
(assert-equal (sxtp "overview") "SX-overview-content")
|
|
(assert-equal (examples nil) "EX-DEFAULT")))
|
|
(deftest
|
|
"ten closures from same factory all independent"
|
|
(let
|
|
((mk (fn (tag) (fn () tag)))
|
|
(fns (map mk (list "a" "b" "c" "d" "e" "f" "g" "h" "i" "j"))))
|
|
(assert-equal
|
|
(map (fn (f) (f)) fns)
|
|
(list "a" "b" "c" "d" "e" "f" "g" "h" "i" "j"))))
|
|
(deftest
|
|
"nested factory: inner closures independent"
|
|
(let
|
|
((outer (fn (x) (fn (y) (fn () (str x y))))))
|
|
(let
|
|
((a (outer "A")) (b (outer "B")))
|
|
(let
|
|
((a1 (a "1")) (a2 (a "2")) (b1 (b "1")))
|
|
(assert-equal (a1) "A1")
|
|
(assert-equal (a2) "A2")
|
|
(assert-equal (b1) "B1")
|
|
(assert-equal (a1) "A1")))))
|
|
(deftest
|
|
"closure captures survive across define"
|
|
(do
|
|
(define _ci-mk (fn (x y) (fn (s) (str x s y))))
|
|
(define _ci-f1 (_ci-mk "A" "1"))
|
|
(define _ci-f2 (_ci-mk "B" "2"))
|
|
(define _ci-f3 (_ci-mk "C" "3"))
|
|
(assert-equal (_ci-f1 "-") "A-1")
|
|
(assert-equal (_ci-f2 "-") "B-2")
|
|
(assert-equal (_ci-f3 "-") "C-3")
|
|
(assert-equal (_ci-f1 "x") "Ax1"))))
|