From ef34122a259e47f53ded25b7b49d538b6a182768 Mon Sep 17 00:00:00 2001 From: giles Date: Sun, 29 Mar 2026 18:08:47 +0000 Subject: [PATCH] Fix 30 test failures: OCaml renderer primitives + condition signal rename OCaml HTML renderer (sx_render.ml) silently returned "" when env_get failed for primitive function calls (str, +, len, etc.) inside HTML elements. The Eval_error catch now falls through to eval_expr which resolves primitives correctly. Fixes 21 rendering tests. Rename condition system special form from "signal" to "signal-condition" in evaluator.sx, matching the OCaml bootstrapped evaluator (sx_ref.ml). This avoids clashing with the reactive signal function. Fixes 9 condition system tests. 1166 passed, 0 failed. Co-Authored-By: Claude Opus 4.6 (1M context) --- hosts/ocaml/lib/sx_render.ml | 16 +++-- spec/evaluator.sx | 2 +- spec/tests/test-conditions.sx | 109 ++++++++++++++++++++++++++++++++++ 3 files changed, 121 insertions(+), 6 deletions(-) create mode 100644 spec/tests/test-conditions.sx diff --git a/hosts/ocaml/lib/sx_render.ml b/hosts/ocaml/lib/sx_render.ml index 59ec5d14..141ead78 100644 --- a/hosts/ocaml/lib/sx_render.ml +++ b/hosts/ocaml/lib/sx_render.ml @@ -280,7 +280,10 @@ and render_list_to_html head args env = | _ -> let result = Sx_ref.eval_expr (List (head :: args)) (Env env) in do_render_to_html result env) - with Eval_error _ -> "") + with Eval_error _ -> + (* Symbol not in env — might be a primitive; eval the full expression *) + let result = Sx_ref.eval_expr (List (head :: args)) (Env env) in + do_render_to_html result env) | _ -> let result = Sx_ref.eval_expr (List (head :: args)) (Env env) in do_render_to_html result env @@ -530,10 +533,13 @@ and render_list_buf buf head args env = | _ -> let result = Sx_ref.eval_expr (List (head :: args)) (Env env) in render_to_buf buf result env) - with Eval_error msg -> - (* Unknown symbol/component — skip silently during SSR. - The client will render from page-sx. *) - Printf.eprintf "[ssr-skip] %s\n%!" msg) + with Eval_error _ -> + (* Symbol not in env — might be a primitive; eval the full expression *) + (try + let result = Sx_ref.eval_expr (List (head :: args)) (Env env) in + render_to_buf buf result env + with Eval_error msg -> + Printf.eprintf "[ssr-skip] %s\n%!" msg)) | _ -> (try let result = Sx_ref.eval_expr (List (head :: args)) (Env env) in diff --git a/spec/evaluator.sx b/spec/evaluator.sx index 0c4572b3..3db249c2 100644 --- a/spec/evaluator.sx +++ b/spec/evaluator.sx @@ -1050,7 +1050,7 @@ ("emitted" (step-sf-emitted args env kont)) ("handler-bind" (step-sf-handler-bind args env kont)) ("restart-case" (step-sf-restart-case args env kont)) - ("signal" (step-sf-signal args env kont)) + ("signal-condition" (step-sf-signal args env kont)) ("invoke-restart" (step-sf-invoke-restart args env kont)) ("match" (step-sf-match args env kont)) ("dynamic-wind" diff --git a/spec/tests/test-conditions.sx b/spec/tests/test-conditions.sx new file mode 100644 index 00000000..7b18d5f6 --- /dev/null +++ b/spec/tests/test-conditions.sx @@ -0,0 +1,109 @@ +(defsuite + "handler-bind-basic" + (deftest + "handler-bind returns body value when no signal" + (assert-equal + 42 + (handler-bind (((fn (c) true) (fn (c) "handled"))) 42))) + (deftest + "signal with matching handler returns handler value" + (assert-equal + "handled: boom" + (handler-bind + (((fn (c) true) (fn (c) (str "handled: " c)))) + (signal-condition "boom")))) + (deftest + "signal without handler errors" + (assert-throws (fn () (signal-condition "unhandled")))) + (deftest + "signal returns handler value to call site" + (assert-equal + 51 + (handler-bind + (((fn (c) true) (fn (c) (* c 10)))) + (+ 1 (signal-condition 5))))) + (deftest + "handler-bind multiple handlers picks first match" + (assert-equal + "got-b" + (handler-bind + (((fn (c) (= c "type-a")) (fn (c) "got-a")) + ((fn (c) (= c "type-b")) (fn (c) "got-b"))) + (signal-condition "type-b")))) + (deftest + "nested handler-bind inner takes precedence" + (assert-equal + "inner-handler" + (handler-bind + (((fn (c) true) (fn (c) "outer"))) + (handler-bind + (((fn (c) (= c "inner")) (fn (c) "inner-handler"))) + (signal-condition "inner"))))) + (deftest + "handler-bind with multi-expression body" + (assert-equal + 10 + (handler-bind (((fn (c) true) (fn (c) c))) (+ 1 2) (* 2 5))))) + +(defsuite + "restart-case-basic" + (deftest + "restart-case returns body value when no signal" + (assert-equal 3 (restart-case (+ 1 2) (use-default () 0)))) + (deftest + "invoke-restart finds and runs restart" + (assert-equal + 42 + (handler-bind + (((fn (c) true) (fn (c) (invoke-restart (quote use-default))))) + (restart-case + (do (signal-condition "bad") "unreachable") + (use-default () 42))))) + (deftest + "invoke-restart with argument" + (assert-equal + 99 + (handler-bind + (((fn (c) true) (fn (c) (invoke-restart (quote use-value) 99)))) + (restart-case + (do (signal-condition "bad") "unreachable") + (use-value (v) v))))) + (deftest + "invoke-restart unknown name errors" + (assert-throws (fn () (invoke-restart (quote nonexistent))))) + (deftest + "nested restart-case uses inner restart" + (assert-equal + "inner-fallback" + (handler-bind + (((fn (c) true) (fn (c) (invoke-restart (quote fallback))))) + (restart-case + (restart-case + (do (signal-condition "err") "unreachable") + (fallback () "inner-fallback")) + (fallback () "outer-fallback"))))) + (deftest + "signal + restart computes replacement value" + (assert-equal + 25 + (handler-bind + (((fn (c) true) (fn (c) (invoke-restart (quote use-value) (* c 3))))) + (+ 10 (restart-case (signal-condition 5) (use-value (v) v))))))) + +(defsuite + "comp-trace" + (deftest + "component call creates trace frame" + (defcomp ~trace-inner () "inner") + (assert-equal "inner" (~trace-inner))) + (deftest + "nested component calls work" + (defcomp ~trace-child () "child") + (defcomp ~trace-parent () (~trace-child)) + (assert-equal "child" (~trace-parent))) + (deftest + "component error via cek-try" + (defcomp ~broken-comp () (error "deliberate")) + (let + ((result (cek-try (fn () (~broken-comp))))) + (assert-true (= (type-of result) "list")))))