From d07c7b4e2c6c7d197bbe96d414047d054dce8fff Mon Sep 17 00:00:00 2001 From: Joseph Ferano Date: Fri, 25 Sep 2026 22:28:58 +0700 Subject: [PATCH] A prelude function shadowed live keeps the prelude's own calls on the prelude's body, as a rebuild does --- TODO.org | 5 ----- lib/session.ml | 45 +++++++++++++++++++++++++++++++++++++++++++- test/test_session.ml | 21 +++++++++++++++++++++ 3 files changed, 65 insertions(+), 6 deletions(-) diff --git a/TODO.org b/TODO.org index fa59faf1..232707a1 100644 --- a/TODO.org +++ b/TODO.org @@ -1480,11 +1480,6 @@ Its signature changes in the session but its body is not recompiled, so every ca stops on StaleCall naming a type nobody wrote. Proposal: recompile such callers. Postponed 2026-09-25 while .fln takes priority. -** TODO A prelude function shadowed live is reached by the prelude's own calls -A defn of a prelude function's name sent to a running =flan dev= installs into the -host's cell for that name, so the prelude's calls compiled into the host follow it; -a rebuild gives them the prelude's again, as =Check.shadow_prelude= intends. - ** DONE The dev loop, step 1: the reload primitive A list of top-level forms is recompiled and installed into a running process, and call sites compiled before those forms existed follow them through an indirection diff --git a/lib/session.ml b/lib/session.ml index 8369e89a..a759824a 100644 --- a/lib/session.ml +++ b/lib/session.ml @@ -1158,9 +1158,52 @@ let eval ?(origin = "") ?base ?forms ?pause ?(step = false) ?(running = tr | None -> None) program.Tast.fns in + (* A name that takes over a prelude function's moves the prelude's body to + [Check.prelude_alias] and the prelude's own calls with it (see + [Check.shadow_prelude]). The process was built with those calls going + through the name's cell, which the new body is about to be installed + into, so the prelude's body is installed under its new name and every + body whose calls moved is compiled again: the prelude keeps its own + function, as a rebuild would give it. *) + let prelude_moved = + if not (List.exists (fun (f : Tast.fn) -> Check.internal_name f.Tast.name) + program.Tast.fns) + then [] + else + let calls_moved (f : Tast.fn) (b : built) = + let hit = ref false in + let see (e : Tast.expr) = + match e.Tast.e with + | Tast.Call (m, _) + | Tast.FnAddr (Tast.Fnval m) | Tast.Closure (Tast.Fnval m, _) + when Check.internal_name m + && not (List.exists + (fun (s : site) -> String.equal s.callee m) b.sites) -> + hit := true + | _ -> () + in + List.iter (Tast.walk see) f.Tast.body; + List.iter (Tast.walk see) f.Tast.fdefers; + !hit + 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) + else + match SM.find_opt f.Tast.name t.built with + | Some b when calls_moved f b -> + (* A lifted clause is compiled with the body it came from. *) + (match f.Tast.fparent with + | Some p when p <> "" -> Some p + | _ -> Some f.Tast.name) + | _ -> None) + program.Tast.fns + in let fns = List.sort_uniq String.compare - (declared_fns @ def_inits @ from_generics @ new_instances) + (declared_fns @ def_inits @ from_generics @ new_instances @ prelude_moved) in (* A constant that changed and can be published: known to the host, not consumed by the checker. The module stores its new value at the frame diff --git a/test/test_session.ml b/test/test_session.ml index 241a5ef7..2f64cf33 100644 --- a/test/test_session.ml +++ b/test/test_session.ml @@ -265,6 +265,27 @@ let () = if has c.Session.ir "flan_dev_cell" then fail "a name the host has went through the registry"; + (* A defn of a prelude function's name, sent live. The host's prelude calls + [rand-int] through the cell the new body goes into, so the prelude's body + moves to its own name and its callers are compiled again to call it, as + a rebuild would have them. Once: a second redefinition moves nothing. *) + (let t, _ = Session.create ~file:"programs/reload.flan" () in + let c = + Session.eval ~origin:"programs/reload.flan" t "(defn rand-int [] u64 4096)" + in + List.iter + (fun n -> + if not (List.mem n c.Session.fns) then + fail "shadowing rand-int live did not install %s: %s" n + (String.concat " " c.Session.fns)) + [ "rand-int"; "prelude~/rand-int"; "rand"; "rand-int-range" ]; + let c = + Session.eval ~origin:"programs/reload.flan" t "(defn rand-int [] u64 8)" + in + if c.Session.fns <> [ "rand-int" ] then + fail "redefining a shadowed rand-int again installed %s" + (String.concat " " c.Session.fns)); + (* DWARF in a redefinition module, which is a property of the session and not of the call. [Emit.redefinition] has taken a ~debug argument all along and was tested with it; what was missing was anyone passing it, so