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.
54 lines
1.8 KiB
Plaintext
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)
|