flan/test/programs/dev-inspect.flan
Joseph Ferano 404c810958 Which frame the inspector answered from, asserted rather than reasoned about
The `inspect' verb had no coverage. The discriminating case is not a path
step, it is the frame: dev-inspect.flan gives `mark' to a global holding 99
and to a local of the OUTER frame holding a Point, so evaluating the name and
rooting at the frame answer differently and not even with the same type. One
`eval-expr' and one `inspect' of that name is the bug and the fix in a pair.

The slot index comes off the locals listing's fourth element rather than being
written as a literal, which exercises the field the editor depends on and
keeps the test from passing for the wrong reason if slot allocation shifts.

The rest is what a path can and cannot do: a struct field, an array element,
an option's payload and a union case's field — the last two having offsets but
no accessor form in the language — and four refusals, each checked for naming
the step and saying why. A `:path' of `nil' is read as the slot itself,
because Emacs has no other spelling for an empty list.

Two claims about the frame, since `stopped_frame' being shared is an assertion
about code rather than about behaviour until something proves it: the frame
whose body was redefined under it is refused, and so is the whole stack once
the program resumes.
2026-09-12 20:34:32 +07:00

54 lines
1.8 KiB
Plaintext

;;;; A stopped stack whose OUTER frame holds a local the evaluator cannot see.
;;;;
;;;; dev-locals.flan is about what one frame holds; this one is about which
;;;; frame the answer came from. The whole of the bug the `inspect' verb
;;;; exists for is that an expression is evaluated where the evaluator stands,
;;;; so a local's *name* reaches the right storage only when the frame is the
;;;; innermost one. `mark' below is a global AND a local of the outer frame,
;;;; holding different things of different types: evaluating the name answers
;;;; the global, and rooting at the frame and slot answers the frame.
;;;;
;;;; The other locals are the shapes a path step has to walk and that an
;;;; expression cannot reach at all: an option's payload, which has no
;;;; accessor form in the language, and a union case's field, whose offset
;;;; depends on which case the value is in.
(import agent "vendor:agent")
(defstruct Point [x f32 y f32])
(defstruct Boom [why i32])
(defunion Shape
[Empty
(Dot [x f64 y f64])
(Rect [w i32 h i32])])
;; The discriminator. `outer' binds a local of this name to something else, so
;; every claim about which frame answered is visible in the value itself.
(defvar mark i64)
;; The innermost frame, and it is deliberately dull: it holds nothing worth
;; inspecting, so that the frame worth inspecting is not the one an expression
;; would have found by luck.
(defn deeper [] i64
(restart-case
(do (error (Boom {.why 7})) 1)
(carry-on [] 5)))
(defn outer [] i64
(let [mark (Point {.x 1.5 .y 2.5})
xs [10 20 30]
box (Some (Point {.x 4.5 .y 5.5}))
s (Shape.Rect {.w 3 .h 6})]
(deeper)))
(defvar ticks i64)
(defn main [] i32
(set mark 99)
(agent/start "/tmp/flan-dev-inspect-fallback.sock")
(print (outer)) (println "")
(dotimes [i 4000]
(agent/wait 5)
(set ticks (+ ticks 1)))
0)