Fix hydration: effect was a no-op primitive, bytecode compiler emitted CALL_PRIM
Root cause: sx_primitives.ml registered "effect" as a native no-op (for SSR). The bytecode compiler's (primitive? "effect") returned true, so it emitted OP_CALL_PRIM instead of OP_GLOBAL_GET + OP_CALL. The VM's CALL_PRIM handler found the native Nil-returning stub and never called the real effect function from core-signals.sx. Fix: Remove effect and register-in-scope from the primitives table. The server overrides them via env_bind in sx_server.ml (after compilation), which doesn't affect primitive? checks. Also: VM CALL_PRIM now falls back to cek_call for non-NativeFn values (safety net for any other functions that get misclassified). 15/15 source mode, 15/15 bytecode mode. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -686,11 +686,15 @@ let () =
|
||||
| None -> raise (Eval_error ("Store not found: " ^ name)))
|
||||
| _ -> raise (Eval_error "use-store: expected (name)"));
|
||||
register "clear-stores" (fun _args -> Hashtbl.clear store_registry; Nil);
|
||||
(* SSR stubs — effect is no-op on server (signals.sx guards with client?),
|
||||
resource returns loading state. Other browser primitives only appear
|
||||
inside effect bodies which never execute during SSR. *)
|
||||
register "effect" (fun _args -> Nil);
|
||||
register "register-in-scope" (fun _args -> Nil);
|
||||
(* SSR stubs — resource returns loading state on server.
|
||||
NOTE: effect and register-in-scope must NOT be registered as primitives
|
||||
here — the bytecode compiler uses primitive? to decide CALL_PRIM vs
|
||||
GLOBAL_GET+CALL. If effect is a primitive, bytecoded modules emit
|
||||
CALL_PRIM which returns Nil instead of calling the real effect function
|
||||
from core-signals.sx. The server overrides effect in sx_server.ml via
|
||||
env_bind AFTER compilation. *)
|
||||
(* register "effect" — REMOVED: see note above *)
|
||||
(* register "register-in-scope" — REMOVED: see note above *)
|
||||
(* resource — SSR stub: return signal with {loading: true}, client hydrates real fetch *)
|
||||
register "resource" (fun _args ->
|
||||
let state = Hashtbl.create 8 in
|
||||
|
||||
@@ -396,6 +396,8 @@ and run vm =
|
||||
in
|
||||
(match fn_val with
|
||||
| NativeFn (_, fn) -> fn args
|
||||
| VmClosure _ | Lambda _ | Component _ | Island _ ->
|
||||
Sx_ref.cek_call fn_val (List args)
|
||||
| _ -> Nil)
|
||||
with Eval_error msg ->
|
||||
raise (Eval_error (Printf.sprintf "%s (in CALL_PRIM \"%s\" with %d args)"
|
||||
|
||||
Reference in New Issue
Block a user