From b34d0d7d59d5713e5d5fd0db45eb488a6014e732 Mon Sep 17 00:00:00 2001 From: Joseph Ferano Date: Fri, 11 Sep 2026 12:26:28 +0700 Subject: [PATCH] Say that refusing a signature change is a stopgap plan.org's hot reload section now says a signature-changing redefinition should make a new internal function version with its own trampoline: new code resolves the name to it, existing callers and stored Fn values keep the old one safely, and the session warns at every tracked caller site still on the old signature. session.ml refuses the change outright, with a reason that reads like the final answer. None of the three parts exists - no function versions, no trampolines, and no record of which source location called what - so the refusal stays, because the alternative to refusing is not the new design, it is a silent argument mismatch. What changes here is only that the code and NEXT.md now say which one it is, so the next person reads it as the stopgap it is. --- NEXT.md | 15 ++++++++++++++- lib/session.ml | 11 ++++++++++- 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/NEXT.md b/NEXT.md index 30cc99e..17c62bb 100644 --- a/NEXT.md +++ b/NEXT.md @@ -607,12 +607,25 @@ Two things the session knows that no single evaluation could: | Change | What it would have broken | |---|---| - | a function's signature | a cell is a bare `ptr`; every call site compiled before the change still passes the old arguments through it | + | a function's signature | a cell is a bare `ptr`; every call site compiled before the change still passes the old arguments through it — **and this is now a stopgap**, see below | | a global's type | the storage exists and has a shape — reuse reads at the wrong offsets, replacement discards the state the reload exists to preserve | | a struct's fields | the values the process is holding have the old layout | | a `defconst`'s value, **when the checker consumed it** | it is in the *shape* of the program — `(defconst rows (/ h c))` decides `grid`'s type before anything else resolves — so no store can reach it | | a `defenum` member | `:space` is erased to an `i32` literal in the caller, so it is folded there too | + **The signature row is the one the plan has moved past.** plan.org now says a + signature-changing redefinition should make a new internal function version + with its own trampoline: newly compiled code resolves the name to it, while + existing callers and stored `Fn` values keep the old version and stay safe, + and the session warns at every tracked caller site still targeting the old + signature — recompiling one either retargets it or gives an ordinary type + error. Open decision #6 records it the same way. None of the three parts + exists: there are no function versions, no trampolines (a cell holds a body + address today), and no record of which source locations called what. So the + refusal stays, because the alternative to refusing is not the new design, it + is a silent argument mismatch. It is a stopgap and the message should not be + read as the final answer. + A `defvar`'s *initial value* is deliberately **not** in that table. Its storage holds live state the program moved past long ago, and refusing to change the initialiser would be refusing "edit the code, keep the sand". Same diff --git a/lib/session.ml b/lib/session.ml index 36440a9..a4d2121 100644 --- a/lib/session.ml +++ b/lib/session.ml @@ -130,7 +130,16 @@ let compatible ~loc (old_ : Tast.program) (new_ : Tast.program) = && Types.equal f.Tast.ret g.Tast.ret in (* A cell holds a bare pointer. Every call site compiled before this - change still passes the old arguments through it. *) + change still passes the old arguments through it. + + This refusal is correct for what is built and is *not* the design + plan.org now describes: a signature change should make a new + internal function version with its own trampoline, leave existing + callers and stored [Fn] values safely on the old one, and warn at + each tracked stale caller site. That needs versions, trampolines + and caller tracking, none of which exist — so this stays a refusal + until they do, rather than becoming a silent mismatch. See + plan.org, Hot reload, and open decision #6. *) if not same then fail loc "%s changes signature, from (Fn [%s] %s) to (Fn [%s] %s); \