A prelude body moved by one live shadowing is compiled again when a later shadowing moves a call inside it

This commit is contained in:
Joseph Ferano 2026-09-25 22:51:02 +07:00
parent eb08f1300d
commit 6583392504
3 changed files with 101 additions and 3 deletions

View File

@ -1209,9 +1209,12 @@ let eval ?(origin = "<eval>") ?base ?forms ?pause ?(step = false) ?(running = tr
in
List.filter_map
(fun (f : Tast.fn) ->
if Check.internal_name f.Tast.name then
(if known t f.Tast.name || SM.mem f.Tast.name t.built then None
else Some f.Tast.name)
(* A moved body not yet in the process is installed; one that is
— moved by an earlier shadowing — is compiled again like any
other when a later shadowing moves a call inside it. *)
if Check.internal_name f.Tast.name
&& not (known t f.Tast.name || SM.mem f.Tast.name t.built)
then Some f.Tast.name
else
match SM.find_opt f.Tast.name t.built with
| Some b when calls_moved f b ->

View File

@ -5955,6 +5955,83 @@ let () =
[ [||]; [| "--two-process" |] ];
Own_tmp.remove deep;
(* ── Prelude functions shadowed live keep the prelude's own calls ──
[rand] and then [rand-int] redefined in a running program: the
prelude's [rand-float-range] still reaches the prelude's [rand], which
still reaches the prelude's [rand-int], so it answers something other
than what 4096 would make of it. The second shadowing moves a call
inside a body the first one had already moved. Both backends. *)
List.iter
(fun backend ->
let ssock = tmp ("shadow" ^ backend ^ ".sock")
and sout = tmp ("shadow" ^ backend ^ ".out") in
(try Sys.remove ssock with Sys_error _ -> ());
let fd =
Unix.openfile sout [ Unix.O_WRONLY; Unix.O_CREAT; Unix.O_TRUNC ] 0o600
in
let pid =
Unix.create_process flan
[| flan; "dev"; "programs/dev-pause.flan"; "-s"; ssock; backend |]
Unix.stdin fd Unix.stderr
in
Unix.close fd;
if not (listening ~pid ssock) then begin
fail "the %s shadowing daemon %s" backend !listen_why;
(try Unix.kill pid Sys.sigkill with Unix.Unix_error _ -> ())
end
else begin
let c = connect ssock in
let said r =
Option.value ~default:(status r) (Wire.string_field r "message")
in
(try
List.iter
(fun code ->
let r =
request c
(Printf.sprintf
"(:op \"eval\" :code %S :file \"programs/dev-pause.flan\")"
code)
in
if status r <> "ok" then
fail "%s: %s: %s" backend code (said r))
[ "(defn rand [] f64 0.5)"; "(defn rand-int [] u64 4096)" ];
let ask code =
let r =
request c
(Printf.sprintf
"(:op \"eval-expr\" :code %S :file \"programs/dev-pause.flan\")"
code)
in
Option.value ~default:(said r) (Wire.string_field r "value")
in
(match ask "(rand-int)", ask "(rand)" with
| "4096", "0.5" -> ()
| a, b ->
fail "%s: the shadowing bodies answer %s and %s" backend a b);
(* 4096 through the prelude's rand is 2^-52, which is what the
prelude's calls answered when they followed the new body. *)
let v = ask "(rand-float-range 0.0 1.0)" in
match float_of_string_opt v with
| Some x when x > 1e-9 && x < 1.0 -> ()
| _ ->
fail "%s: the prelude's rand-float-range followed a shadowing \
body: %s" backend v
with (Wire.Closed | Unix.Unix_error _) as e ->
fail "%s: the shadowing session ended: %s" backend
(Printexc.to_string e));
(try
ignore (Wire.send c "(:op \"close\")");
ignore (Wire.recv c)
with _ -> ());
(try Unix.close c with Unix.Unix_error _ -> ());
(try Unix.kill pid Sys.sigkill with Unix.Unix_error _ -> ());
(try ignore (Unix.waitpid [] pid) with Unix.Unix_error _ -> ())
end;
(try Sys.remove sout with Sys_error _ -> ()))
[ "--llvm"; "--x86" ];
(* ── A build that fails is a refusal, not the end of the session ── *)
(* Evaluating runs a compiler, and a compiler can fail in ways the

View File

@ -286,6 +286,24 @@ let () =
fail "redefining a shadowed rand-int again installed %s"
(String.concat " " c.Session.fns));
(* Two shadowings, in either order: the prelude body the first one moved is
compiled again when the second moves a call inside it. *)
List.iter
(fun (first, second, want) ->
let t, _ = Session.create ~file:"programs/reload.flan" () in
ignore (Session.eval ~origin:"programs/reload.flan" t first);
let c = Session.eval ~origin:"programs/reload.flan" t second in
List.iter
(fun n ->
if not (List.mem n c.Session.fns) then
fail "shadowing %S after %S did not install %s: %s" second first
n (String.concat " " c.Session.fns))
want)
[ ("(defn rand [] f64 0.5)", "(defn rand-int [] u64 4096)",
[ "rand-int"; "prelude~/rand-int"; "prelude~/rand" ]);
("(defn rand-int [] u64 4096)", "(defn rand [] f64 0.5)",
[ "rand"; "prelude~/rand" ]) ];
(* And a mark or a step once the program has a [pause] and a [step-point] of
its own: the call spliced in is still the prelude's, or a mark would run
the program's function and never stop. *)