diff --git a/DISCUSS.md b/DISCUSS.md index 4f10b02..7791d98 100644 --- a/DISCUSS.md +++ b/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 diff --git a/emacs/flan-inspect.el b/emacs/flan-inspect.el index 2a68846..1475c28 100644 --- a/emacs/flan-inspect.el +++ b/emacs/flan-inspect.el @@ -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 diff --git a/emacs/test-flan-cider.el b/emacs/test-flan-cider.el index da9db11..2054e80 100644 --- a/emacs/test-flan-cider.el +++ b/emacs/test-flan-cider.el @@ -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) diff --git a/lib/dev.ml b/lib/dev.ml index b4a7ee9..3f9e49b 100644 --- a/lib/dev.ml +++ b/lib/dev.ml @@ -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