From 651d04496fc77a698e8ebb7080b7906aced25068 Mon Sep 17 00:00:00 2001 From: Joseph Ferano Date: Sat, 12 Sep 2026 10:39:44 +0700 Subject: [PATCH] Vec does not buy the accumulation pattern; capture does not exist yet MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- NEXT.md | 36 ++++++++++++++++++++---------------- 1 file changed, 20 insertions(+), 16 deletions(-) diff --git a/NEXT.md b/NEXT.md index 40a4c1e..1fc9a6e 100644 --- a/NEXT.md +++ b/NEXT.md @@ -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: