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); \