Two frames offering `retry` put both on the break loop's list and only the inner one within reach: §4's walk takes the first frame offering a name, by definition, so the outer clause was drawn, offered, and unreachable. The old prompt showed `retry` twice and sent the string either way. An index is the only thing that can say which one, which is why SBCL identifies them positionally too. An index is worthless against a stack that moves, though, and this one moves: the break loop is the poll loop, so every restart-case an evaluation enters pushes and pops the same global list between the listing and the choice. So the list is read once on entry and copied — names into the agent's own buffer, frames as the addresses a transfer carries — and every answer comes from that. The name still travels with the index as a receipt, checked against the snapshot and refused if the two have drifted, so a bare integer can be wrong out loud. And the third state. A restart below the thunk a break is inside was accepted, announced, and silently not taken: `flan_reload_call` holds its own transfer channel and drops it on return, so the unwind stops at the thunk. The boundary is now recorded where it is made, at the call — frames a restart-case inside the thunk pushes are above it and still work — and such a restart is listed, marked, and refused with the reason. `break.flan` grew the shadowed pair, and 900 is a value no by-name lookup in that file can produce.
35 lines
1.2 KiB
Plaintext
35 lines
1.2 KiB
Plaintext
;;;; The dev-build break loop — spec-conditions.md §2.
|
|
;;;;
|
|
;;;; An unhandled error used to kill the program. Here it stops instead, on the
|
|
;;;; frame that erred with nothing unwound, and waits for someone to pick a
|
|
;;;; restart. Two of them, and the run takes a different one each time, so a
|
|
;;;; break loop that always resumed the same way could not pass.
|
|
(import agent "vendor:agent")
|
|
|
|
(defstruct Missing [id i32])
|
|
|
|
(defn fetch [n i32] i32
|
|
(restart-case
|
|
(do (error (Missing {:id n})) 0)
|
|
(use-placeholder [] -1)
|
|
(retry [] 7)))
|
|
|
|
;;; Two frames offering the same name, which §4 says resolves to the inner one
|
|
;;; and only ever the inner one. The outer clause is therefore on every list a
|
|
;;; break loop prints and reachable from no name at all, which is why a restart
|
|
;;; is taken by *index* now. 900 is the proof: it is the only value in this
|
|
;;; file that by-name lookup cannot produce.
|
|
(defn shadowed [n i32] i32
|
|
(restart-case
|
|
(+ (restart-case (do (error (Missing {:id n})) 0)
|
|
(retry [] 5))
|
|
100)
|
|
(retry [] 900)))
|
|
|
|
(defn main [] i32
|
|
(agent/start "/tmp/flan-break.sock")
|
|
(print-i64 (i64 (fetch 1))) (newline)
|
|
(print-i64 (i64 (fetch 2))) (newline)
|
|
(print-i64 (i64 (shadowed 3))) (newline)
|
|
0)
|