Add JIT closure scoping tests
Tests the exact pattern that broke the home stepper: a component with letrec bindings referenced inside a map callback. The JIT compiles the callback with closure vars merged into vm_env_ref. Subsequent renders must use that env, not the caller's globals. 7 tests covering: - letrec closure var in map callback (fmt function) - Render, unrelated render, re-render (env not polluted) - Signal + letrec + map (the stepper pattern) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -7,12 +7,7 @@
|
||||
Usage:
|
||||
dune exec bin/integration_tests.exe *)
|
||||
|
||||
module Sx_types = Sx.Sx_types
|
||||
module Sx_parser = Sx.Sx_parser
|
||||
module Sx_primitives = Sx.Sx_primitives
|
||||
module Sx_runtime = Sx.Sx_runtime
|
||||
module Sx_ref = Sx.Sx_ref
|
||||
module Sx_render = Sx.Sx_render
|
||||
(* Modules accessed directly — library is unwrapped *)
|
||||
|
||||
open Sx_types
|
||||
|
||||
@@ -513,6 +508,53 @@ let () =
|
||||
(reset! s (list 1 2 3))
|
||||
(len (deref s)))");
|
||||
|
||||
(* ================================================================== *)
|
||||
Printf.printf "\nSuite: JIT closure scoping\n%!";
|
||||
|
||||
(* The JIT bug: when a lambda captures closure vars (e.g. from let/letrec),
|
||||
the VM must use the closure's vm_env_ref (which has the merged bindings),
|
||||
not the caller's globals (which lacks them). This test reproduces the
|
||||
exact pattern that broke the home stepper: a component with a letrec
|
||||
binding referenced inside a map callback. *)
|
||||
|
||||
(* 1. Define a component whose body uses letrec + map with closure var *)
|
||||
assert_no_error "defcomp with letrec+map closure var" (fun () ->
|
||||
ignore (Sx_ref.eval_expr
|
||||
(List.hd (Sx_parser.parse_all
|
||||
"(defcomp ~jit-test (&key)
|
||||
(let ((items (list \"a\" \"b\" \"c\")))
|
||||
(letrec ((fmt (fn (x) (str \"[\" x \"]\"))))
|
||||
(div (map (fn (item) (span (fmt item))) items)))))"))
|
||||
(Env env)));
|
||||
|
||||
(* 2. Render it — this triggers JIT compilation of the map callback *)
|
||||
assert_contains "jit closure: first render"
|
||||
"[a]" (sx_render_html "(~jit-test)");
|
||||
|
||||
(* 3. Render something ELSE — tests that the JIT-compiled closure
|
||||
still works when called in a different context *)
|
||||
assert_contains "jit closure: unrelated render between"
|
||||
"<p>" (sx_render_html "(p \"hello\")");
|
||||
|
||||
(* 4. Render the component AGAIN — the JIT-compiled map callback
|
||||
must still find 'fmt' via its closure env, not the caller's globals *)
|
||||
assert_contains "jit closure: second render still works"
|
||||
"[b]" (sx_render_html "(~jit-test)");
|
||||
|
||||
(* 5. Test with signal (the actual stepper pattern) *)
|
||||
assert_no_error "defcomp with signal+map closure" (fun () ->
|
||||
ignore (Sx_ref.eval_expr
|
||||
(List.hd (Sx_parser.parse_all
|
||||
"(defcomp ~jit-signal-test (&key)
|
||||
(let ((data (signal (list 1 2 3))))
|
||||
(letrec ((double (fn (x) (* x 2))))
|
||||
(div (map (fn (item) (span (str (double item)))) (deref data))))))"))
|
||||
(Env env)));
|
||||
|
||||
assert_contains "jit signal closure: renders" "4" (sx_render_html "(~jit-signal-test)");
|
||||
assert_contains "jit signal closure: after other render"
|
||||
"4" (let _ = sx_render_html "(div \"break\")" in sx_render_html "(~jit-signal-test)");
|
||||
|
||||
(* ================================================================== *)
|
||||
Printf.printf "\n";
|
||||
Printf.printf "============================================================\n";
|
||||
|
||||
Reference in New Issue
Block a user