From 43cf590541976d417e54330dc90e2c9ea7330967 Mon Sep 17 00:00:00 2001 From: giles Date: Wed, 25 Mar 2026 16:16:00 +0000 Subject: [PATCH] Expand JIT closure tests: nested, mutual recursion, set!, island, deep 22 JIT closure scoping tests covering: - Basic closure var in map callback + context switch - Signal + letrec + map (stepper pattern) - Nested closures (inner lambda sees outer let var) - Mutual recursion in letrec (is-even/is-odd) - set! mutation of closure var after JIT compilation - defisland with signal + letrec + map - Deep nesting (for-each inside map inside letrec inside let) All test the critical invariant: JIT-compiled lambdas must use their closure's vm_env_ref, not the caller's globals. Co-Authored-By: Claude Opus 4.6 (1M context) --- hosts/ocaml/bin/integration_tests.ml | 88 ++++++++++++++++++++++++++++ 1 file changed, 88 insertions(+) diff --git a/hosts/ocaml/bin/integration_tests.ml b/hosts/ocaml/bin/integration_tests.ml index 1d42ce71..f741177a 100644 --- a/hosts/ocaml/bin/integration_tests.ml +++ b/hosts/ocaml/bin/integration_tests.ml @@ -555,6 +555,94 @@ let () = assert_contains "jit signal closure: after other render" "4" (let _ = sx_render_html "(div \"break\")" in sx_render_html "(~jit-signal-test)"); + (* 6. Nested closures — lambda inside lambda, both with closure vars *) + assert_no_error "defcomp with nested closures" (fun () -> + ignore (Sx_ref.eval_expr + (List.hd (Sx_parser.parse_all + "(defcomp ~jit-nested (&key) + (let ((prefix \">\")) + (letrec ((wrap (fn (x) + (let ((suffix \"<\")) + (str prefix x suffix))))) + (div (map (fn (item) (span (wrap item))) + (list \"a\" \"b\"))))))")) + (Env env))); + assert_contains "nested closure: inner sees outer var" + ">a<" (sx_render_html "(~jit-nested)"); + assert_contains "nested closure: second item" + ">b<" (sx_render_html "(~jit-nested)"); + (* After unrelated render, nested closures still work *) + assert_contains "nested closure: survives context switch" + ">a<" (let _ = sx_render_html "(p \"x\")" in sx_render_html "(~jit-nested)"); + + (* 7. Mutual recursion in letrec *) + assert_no_error "defcomp with mutual recursion" (fun () -> + ignore (Sx_ref.eval_expr + (List.hd (Sx_parser.parse_all + "(defcomp ~jit-mutual (&key) + (letrec ((is-even (fn (n) + (if (= n 0) true (is-odd (- n 1))))) + (is-odd (fn (n) + (if (= n 0) false (is-even (- n 1)))))) + (div + (span (str (is-even 4))) + (span (str (is-odd 3))))))")) + (Env env))); + assert_contains "mutual recursion: is-even 4" "true" (sx_render_html "(~jit-mutual)"); + assert_contains "mutual recursion: is-odd 3" "true" (sx_render_html "(~jit-mutual)"); + assert_contains "mutual recursion: survives context switch" + "true" (let _ = sx_render_html "(div \"y\")" in sx_render_html "(~jit-mutual)"); + + (* 8. set! modifying closure var after JIT compilation *) + assert_no_error "defcomp with set! mutation" (fun () -> + ignore (Sx_ref.eval_expr + (List.hd (Sx_parser.parse_all + "(defcomp ~jit-setbang (&key) + (let ((counter 0)) + (letrec ((bump (fn () (set! counter (+ counter 1)) counter)) + (get-count (fn () counter))) + (div (span (str (bump))) + (span (str (bump))) + (span (str (get-count)))))))")) + (Env env))); + (* Each render should restart counter at 0 since it's a fresh let *) + assert_contains "set! mutation: first bump" "1" (sx_render_html "(~jit-setbang)"); + assert_contains "set! mutation: second bump" "2" (sx_render_html "(~jit-setbang)"); + + (* 9. Island with signal + effect + letrec — the stepper pattern *) + assert_no_error "defisland with signal+letrec+map" (fun () -> + ignore (Sx_ref.eval_expr + (List.hd (Sx_parser.parse_all + "(defisland ~jit-island-test () + (let ((items (signal (list \"x\" \"y\" \"z\"))) + (label (signal \"test\"))) + (letrec ((format-item (fn (item) + (str (deref label) \":\" item)))) + (div (map (fn (i) (span (format-item i))) + (deref items))))))")) + (Env env))); + assert_contains "island signal+letrec: renders" + "test:x" (sx_render_html "(~jit-island-test)"); + assert_contains "island signal+letrec: after other render" + "test:y" (let _ = sx_render_html "(p \"z\")" in sx_render_html "(~jit-island-test)"); + + (* 10. Deep nesting — for-each inside map inside letrec inside let *) + assert_no_error "defcomp with deep nesting" (fun () -> + ignore (Sx_ref.eval_expr + (List.hd (Sx_parser.parse_all + "(defcomp ~jit-deep (&key) + (let ((rows (list (list 1 2) (list 3 4)))) + (letrec ((sum-row (fn (row) + (reduce + 0 row)))) + (div (map (fn (row) + (span (str (sum-row row)))) + rows)))))")) + (Env env))); + assert_contains "deep nesting: first row sum" "3" (sx_render_html "(~jit-deep)"); + assert_contains "deep nesting: second row sum" "7" (sx_render_html "(~jit-deep)"); + assert_contains "deep nesting: survives context switch" + "3" (let _ = sx_render_html "(div \"w\")" in sx_render_html "(~jit-deep)"); + (* ================================================================== *) Printf.printf "\n"; Printf.printf "============================================================\n";