Fix env-shadowing: rebind host extension points after .sx file load
evaluator.sx defines *custom-special-forms* and register-special-form! which shadow the host's native bindings when loaded at runtime. The native bindings route to Sx_ref.custom_special_forms (the dict the CEK evaluator checks), but the SX-level defines create a separate dict. Fix: rebind_host_extensions runs after every load command, re-asserting the native register-special-form! and *custom-special-forms* bindings. Add regression test: custom form registered before evaluator.sx load survives and remains callable via CEK dispatch afterward. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -711,6 +711,18 @@ let register_jit_hook env =
|
|||||||
|
|
||||||
|
|
||||||
(* ====================================================================== *)
|
(* ====================================================================== *)
|
||||||
|
(* Re-assert host-provided extension points after loading .sx files.
|
||||||
|
evaluator.sx defines *custom-special-forms* and register-special-form!
|
||||||
|
which shadow the native bindings from setup_evaluator_bridge. *)
|
||||||
|
let rebind_host_extensions env =
|
||||||
|
Hashtbl.replace env.bindings "register-special-form!"
|
||||||
|
(NativeFn ("register-special-form!", fun args ->
|
||||||
|
match args with
|
||||||
|
| [String name; handler] ->
|
||||||
|
ignore (Sx_ref.register_special_form (String name) handler); Nil
|
||||||
|
| _ -> raise (Eval_error "register-special-form!: expected (name handler)")));
|
||||||
|
ignore (env_bind env "*custom-special-forms*" Sx_ref.custom_special_forms)
|
||||||
|
|
||||||
(* Command dispatch *)
|
(* Command dispatch *)
|
||||||
(* ====================================================================== *)
|
(* ====================================================================== *)
|
||||||
|
|
||||||
@@ -727,6 +739,9 @@ let rec dispatch env cmd =
|
|||||||
ignore (Sx_ref.eval_expr expr (Env env));
|
ignore (Sx_ref.eval_expr expr (Env env));
|
||||||
incr count
|
incr count
|
||||||
) exprs;
|
) exprs;
|
||||||
|
(* Rebind host extension points after .sx load — evaluator.sx
|
||||||
|
defines *custom-special-forms* which shadows the native dict *)
|
||||||
|
rebind_host_extensions env;
|
||||||
send_ok_value (Number (float_of_int !count))
|
send_ok_value (Number (float_of_int !count))
|
||||||
with
|
with
|
||||||
| Eval_error msg -> send_error msg
|
| Eval_error msg -> send_error msg
|
||||||
@@ -1142,7 +1157,9 @@ let cli_load_files env files =
|
|||||||
ignore (Sx_ref.eval_expr expr (Env env))
|
ignore (Sx_ref.eval_expr expr (Env env))
|
||||||
) exprs
|
) exprs
|
||||||
end
|
end
|
||||||
) files
|
) files;
|
||||||
|
(* Rebind after load in case .sx files shadowed host extension points *)
|
||||||
|
rebind_host_extensions env
|
||||||
|
|
||||||
let cli_mode mode =
|
let cli_mode mode =
|
||||||
let env = make_server_env () in
|
let env = make_server_env () in
|
||||||
|
|||||||
10
run-tests.sh
10
run-tests.sh
@@ -101,6 +101,16 @@ check('defhandler via eval', '(has-key? (defhandler test-h (&key x) x) \"__type\
|
|||||||
check('definition-form-extensions populated', '(> (len *definition-form-extensions*) 0)', 'true')
|
check('definition-form-extensions populated', '(> (len *definition-form-extensions*) 0)', 'true')
|
||||||
check('RENDER_HTML_FORMS has defstyle', '(contains? RENDER_HTML_FORMS \"defstyle\")', 'true')
|
check('RENDER_HTML_FORMS has defstyle', '(contains? RENDER_HTML_FORMS \"defstyle\")', 'true')
|
||||||
|
|
||||||
|
# Env-shadowing regression: custom forms survive evaluator.sx load
|
||||||
|
bridge2 = OcamlSync()
|
||||||
|
bridge2.eval('(register-special-form! \"shadow-test\" (fn (args env) 42))')
|
||||||
|
bridge2.load('spec/evaluator.sx')
|
||||||
|
check('custom form survives evaluator.sx load',
|
||||||
|
bridge2.eval('(has-key? *custom-special-forms* \"shadow-test\")'), 'true')
|
||||||
|
bridge2.eval('(register-special-form! \"post-load\" (fn (args env) 99))')
|
||||||
|
check('custom form callable after evaluator.sx load',
|
||||||
|
bridge2.eval('(post-load 1)'), '99')
|
||||||
|
|
||||||
print(f'\\nResults: {ok} passed, {fail} failed')
|
print(f'\\nResults: {ok} passed, {fail} failed')
|
||||||
import sys; sys.exit(1 if fail > 0 else 0)
|
import sys; sys.exit(1 if fail > 0 else 0)
|
||||||
"
|
"
|
||||||
|
|||||||
Reference in New Issue
Block a user