A union's fields have no accessor, so RET refuses them where none exists
The capability lists were written before the code held the line they claim. Under an expression root, RET on a field of a union built `(.at s)' and sent it, and the checker refused it — "a union's fields belong to a case ... they are reached by (match ...)". A refusal from the far end of a socket is exactly what this buffer's own comment says not to do: every refusal is by name, here, with the reason, because RET working on some lines and erroring on others teaches nothing about the language. It is a refusal of the *parent* and not of the value at point, which is why it is not in `flan-inspect-refusal': a struct field that merely holds a union is an ordinary accessor and has to stay enterable. It is a field of the union itself that cannot be written. The two cases are one test each. The slot root steps into it by offset and is unaffected, which is the difference the manual now claims and the tests now show. `lib/dev.ml' cited DISCUSS.md item 1 as a hole; item 1 is the answer now, so it cites BUILT.md instead. And the item 1 stub is two sentences and a pointer — everything else in it is in BUILT.md verbatim, and DISCUSS.md's own header says nothing in it is a decision.
This commit is contained in:
parent
0389c2282c
commit
306fc88094
16
DISCUSS.md
16
DISCUSS.md
@ -10,19 +10,9 @@ Settled decisions live in `NEXT.md`. Reasons for what already exists live in `BU
|
||||
|
||||
## 1. `i`, the inspector, and the frame it cannot see — answered and built
|
||||
|
||||
Option 2 was taken: the inspector has a second rooting mode, at a frame and a slot index. `BUILT.md`'s "Two ways to
|
||||
root a walk" says what each root can and cannot do and why both are kept. The number stays here because other files
|
||||
cite these by number; the question itself is no longer open.
|
||||
|
||||
One thing the original entry recorded turned out to be half right, and is worth keeping here rather than deleting with it. "Root the inspector
|
||||
at the slot's address does not work, because an address is not an expression and the first `RET` has nothing to build
|
||||
from" — the premise is true and the conclusion was wrong. What changes it is that the *step* need not be an expression
|
||||
either: the daemon holds the frame's address and every slot's type, so a field is an address plus an offset with that
|
||||
field's type, which is the arithmetic the locals listing already does. The navigation objection was to rooting at an
|
||||
address while still stepping by source.
|
||||
|
||||
`l` crossing between the modes was listed as a cost of option 2 and it is not one. A stack entry carries its own root
|
||||
and `RET` only extends the path under the root it already has, so a mixed stack cannot be built at all.
|
||||
Answered, and built as option 2. `BUILT.md`'s "Two ways to root a walk, and why neither subsumes the other" is where
|
||||
it lives now, including the correction to what this entry said about rooting at an address. The number is kept because
|
||||
other files cite these by number; nothing here is open.
|
||||
|
||||
## 2. Annotating the IR and the disassembly with the source
|
||||
|
||||
|
||||
@ -623,6 +623,25 @@ root, which is what makes a mixed stack unconstructible."
|
||||
(unless step (user-error "flan: nothing to inspect on this line"))
|
||||
(let ((why (flan-inspect-refusal node flan-inspect--root)))
|
||||
(when why (user-error "flan: %s" why)))
|
||||
;; A union case's field, under an expression root. This is a refusal of
|
||||
;; the *parent* and not of the value at point, which is why it is here and
|
||||
;; not in `flan-inspect-refusal\=': a struct field that happens to hold a
|
||||
;; union is reached by an ordinary accessor and must stay enterable; it is
|
||||
;; a field *of the union itself* that has no accessor. `(match ...)\=' is
|
||||
;; how a union is opened in the language, and it binds names rather than
|
||||
;; producing a value to send, so there is nothing to build here. The
|
||||
;; renderer wrote the head as `Union.case\=', which is the one type spelling
|
||||
;; with a dot in it — a package qualifies with a slash.
|
||||
(let ((ty (plist-get flan-inspect--node :type)))
|
||||
(when (and (not (eq (car-safe flan-inspect--root) :slot))
|
||||
(stringp ty)
|
||||
(string-match-p "\\." ty))
|
||||
(user-error
|
||||
"flan: %s"
|
||||
(concat "a union case's field: it is reached by (match ...) in the "
|
||||
"language, not by an accessor, so there is no expression to "
|
||||
"send. `i' on a local in the break buffer roots at the frame's "
|
||||
"slot instead, and that root steps into it by offset"))))
|
||||
;; The line carries the step that names the field; what the wire needs
|
||||
;; beyond the name is the type it is a field *of*, and that is this
|
||||
;; buffer's own node — the parent of the one at point. A union's payload
|
||||
|
||||
@ -457,6 +457,37 @@ unwind would send the next one to a daemon that is not there."
|
||||
(test-flan--check "an element is its number"
|
||||
(equal (flan-inspect-wire-step '(:index 2)) 2))
|
||||
|
||||
|
||||
;; And the other half of that pair: under an *expression* root there is no
|
||||
;; accessor to send, so RET refuses there rather than sending `(.at s)' for
|
||||
;; the checker to reject. A union's fields are reached by `(match ...)' in
|
||||
;; the language, which binds names rather than producing a value. It is a
|
||||
;; refusal of the parent, not of the value at point — a struct field that
|
||||
;; merely *holds* a union is an ordinary accessor and stays enterable.
|
||||
(let ((buf (test-flan--inspect "s" "(Shape.circle {.at (V {.x 1 .y 2})})")))
|
||||
(with-current-buffer buf
|
||||
(goto-char (point-min))
|
||||
(flan-inspect-next)
|
||||
(test-flan--check "an expression root refuses a union case's field"
|
||||
(string-match-p
|
||||
"reached by (match"
|
||||
(or (test-flan--caught #'flan-inspect-into) "")))))
|
||||
|
||||
(let ((asked nil))
|
||||
(let ((flan-inspect-request-function
|
||||
(lambda (form) (push (plist-get form :code) asked)
|
||||
(list :status "ok"
|
||||
:value "(Cell {.id 1 .s (Shape.circle {.at (V {.x 1 .y 2})})})")))
|
||||
(flan-inspect-buffer " *test-inspect*"))
|
||||
(when (get-buffer " *test-inspect*") (kill-buffer " *test-inspect*"))
|
||||
(save-window-excursion
|
||||
(flan-inspect--show '(:expr "c") nil)
|
||||
(with-current-buffer " *test-inspect*"
|
||||
(goto-char (point-min))
|
||||
(flan-inspect-next) (flan-inspect-next) ; .s, which holds the union
|
||||
(test-flan--check "but a struct field that merely holds one is enterable"
|
||||
(progn (flan-inspect-into) (equal (car asked) "(.s c)")))))))
|
||||
|
||||
(test-flan--slot
|
||||
0 1 "s"
|
||||
(lambda (form)
|
||||
|
||||
@ -921,7 +921,8 @@ let locals t ~frame =
|
||||
refused) ]))
|
||||
|
||||
(* [(:op "inspect" :frame N :slot I :path (...))] — the inspector's second
|
||||
rooting mode, and the answer to the hole [DISCUSS.md] item 1 named.
|
||||
rooting mode. [BUILT.md]'s "Two ways to root a walk" says what each root
|
||||
can and cannot do; this is the half that names a frame.
|
||||
|
||||
[i] in the break buffer used to send a local's *name* to be evaluated as an
|
||||
expression. On the innermost frame that happens to be right; on any other
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user