diff --git a/hosts/ocaml/bin/integration_tests.ml b/hosts/ocaml/bin/integration_tests.ml index 11788d48..1d42ce71 100644 --- a/hosts/ocaml/bin/integration_tests.ml +++ b/hosts/ocaml/bin/integration_tests.ml @@ -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" + "
" (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";