Vec does not buy the accumulation pattern; capture does not exist yet

Step 2 claimed (Vec T) unblocks (fn [c] (push errors c) ...) and it does not.
check.ml's captured reads ctx.outer only to raise a better refusal and lookup
never reads it at all, so closure capture — spec-memory.md's case 2 — is
unbuilt. And the same spec captures a Vec by pointer rather than by value,
a split no capture path has needed because every capturable type today is a
value type. Two separate items, named as such under step 2.

Item 1 stops restating the four decisions and points at the section holding
them, keeping only the consequence the build order turns on. The Carp line
numbers were checked and are right, so they come out of the bug entry.
This commit is contained in:
Joseph Ferano 2026-09-12 10:39:44 +07:00
parent c2116a5e71
commit 651d04496f

36
NEXT.md
View File

@ -328,15 +328,12 @@ plan.org's single line on it (831) names a `for` the language does not have and
procedures passed as a runtime argument (`Map_Info`, `base/runtime/core.odin:369`). That runtime is what
`spec-memory.md` specifies.
**The four questions that used to sit here are answered**, in `spec-memory.md`'s "Allocators" section, frozen along
with the rest of that file. Storage is released at exactly two points, `(free v)` and `(free-all a)`, and nothing is
released at scope exit. `drop` takes a pointer, fires only inside `free`, and a value carrying one may not be
constructed against an allocator lacking `can-free` — which is what stops an arena reset from having to run
destructors. Alignment is a property of the type, computed at the call site, passed as a parameter, and not stored in
the header. And an allocator that cannot satisfy a request signals `StorageExhausted` inside a `restart-case`
offering `retry`, so `push`, `put` and `clone` keep the result types they would otherwise have and no allocating
operation returns a `Result`. One question is left open there on purpose; it does not block the build. The order to
build the thing in is below.
**The four questions that used to sit here are answered**, in `spec-memory.md`'s "Allocators" section, which is
frozen along with the rest of that file: when storage is released, the `drop` hook, alignment, and allocation
failure. Read them there rather than in a second copy here. The one consequence the build order below turns on is
that no allocating operation returns an error — a failure signals `StorageExhausted` under a `retry` restart — so
`push` and `put` are `Unit`, `clone` returns the container, and no signature grows a `Result`. One question is left
open in that section on purpose; it does not block the build.
2. **Typed restarts — `(use-value [v T] v)`.** The author's third TODO, and the most-wanted thing across every
comparative study. SBCL's report: restarts without parameters lose "the entire supply-a-value half of the standard
@ -396,9 +393,17 @@ expander last, on 6's unions.
information without the round trip.
2. **`(Vec T)`**, over the type-erased runtime: `ptr + len + cap + allocator` in release, plus the generation word and
the epoch in dev. `push`, `reserve`, `at`, `len`, `as-slice`, `free`, `clone`. `size_of`/`align_of` are produced at
the call site, which here is simply the concrete call site, there being no generics yet. This is what unblocks the
accumulation pattern `handler-bind` was built for — `(fn [c] (push errors c) ...)` — which conditions want today and
cannot have.
the call site, which here is simply the concrete call site, there being no generics yet.
**`Vec` alone does not buy the accumulation pattern, and it is worth knowing that before step 2 is scoped.**
`(fn [c] (push errors c) ...)` over an enclosing `(Vec ParseError)` is the pattern `handler-bind` exists for, and it
needs two further things. Capture does not exist at all: `check.ml`'s `captured` (`:176`) consults `ctx.outer` only
to raise a better refusal — "a handler cannot see %s ... Use a global, or pass it on the condition" — and `lookup`
(`:167`) never reads `outer`. That is `spec-memory.md`'s case 2, a non-escaping `fn` capturing by value into a stack
environment, and it is unbuilt. On top of it, the same spec says a captured `Vec` or `Map` is captured **by pointer,
not moved**, and every capturable type today is a value type, so the by-value/by-pointer split has never had to
exist in a capture path. Both are their own work and neither falls out of `Vec`. Step 2 delivers a container;
accumulating into one from a handler is a separate item and should be planned as one.
3. **`StorageExhausted` and `retry`, with step 2 and not after.** `restart-case` and the transfer channel exist, so
this is a compiler-emitted restart at each allocating site and little else. It goes in at the same time because the
signatures depend on it: retrofitting it later adds a transfer check to every call site of every allocating
@ -445,10 +450,9 @@ expander last, on 6's unions.
`base/runtime/dynamic_array_internal.odin:107` and `:128`. The two that miss: `Map_Cell_Info` is at `core.odin:351`,
not `:350`; and `check.ml:1670` is the FFI `Declare` arm, not the `defer` registration — the claim it is offered for,
that a top-level `defer` is checked in a scope holding only parameters and globals, is true and lives in `check_fn`
at `check.ml:1800``1812`. `check.ml:505` is the `defer` refusal exactly as cited. Also worth a word when that
section is next edited: Carp's `getDropFunc` is at `Memory.hs:804` and the drop-before-delete emit at `Emit.hs:1044`,
both within a couple of lines of what is written, and `docs/Drop.md` confirms the sentence they support — "`A.drop`
will be run ... when the `let` scope ends".
at `check.ml:1800``1812`. `check.ml:505` is the `defer` refusal exactly as cited, and the Carp citations are right:
`getDropFunc` is `Memory.hs:804`, the drop-before-delete emit is `Emit.hs:1044`, and `docs/Drop.md` says outright
that `A.drop` "will be run ... when the `let` scope ends".
- ~~`web/examples/breakdemo.out` is stale and `check.sh` fails on it.~~ **Fixed.** Commit `4a6a8fa` made the break
banner number its restarts and the `.out` was never repinned. Nothing had to drive the socket in the end: