The inspector walks expressions, which is why it cannot see a frame

This commit is contained in:
Joseph Ferano 2026-09-12 13:36:34 +07:00
parent f4f61ae8c9
commit b97a3dbbf1

View File

@ -8,20 +8,42 @@ Settled decisions live in `NEXT.md`. Reasons for what already exists live in `BU
---
## 1. What is `i` supposed to inspect in the stack section?
## 1. `i`, the inspector, and the frame it cannot see
Raised as a question about intent, and it deserves one, because the current answer may be wrong rather than merely
undocumented.
Two things got conflated here and they should be separated.
`i` on a local passes that local's **name** to the inspector, which evaluates it as an expression inside the running
program. That is correct for the innermost frame, where the name is in scope. **It is not obviously correct for any
other frame** — evaluating `y` while looking at frame 3 evaluates `y` wherever the evaluator stands, not in frame 3.
It may resolve to a global, to a different binding, or fail.
**What the inspector already does.** Most of what was asked for is built. `flan-inspect` opens its own buffer, lays a
value's fields one per line, `RET` walks into one, `l` comes back, `g` re-reads. The renderer bounds its walk at depth 4
and span 8, and entering a field renders *that field* from depth 0 — so the elision moves with you rather than
truncating permanently. It is CIDER's inspector adapted, and the file says what the adaptation changed.
The locals *listing* does not have this problem: it renders from the frame's own slot addresses. So the display is
frame-accurate and the inspector may not be. Options: root the inspector at the slot's address the way the listing is
rooted; refuse `i` outside the innermost frame; or make the inspector frame-aware. Worth settling before anyone relies
on it.
**What is actually missing** is detail on the leaves: a number shows in decimal only, with no hex and no binary, and a
pointer does not show its address. Purely additive, small, and worth doing.
**The real problem, and it is sharper than "a bug".** The locals listing renders from each frame's own slot addresses,
so it is frame-accurate. The inspector is built on a stack of **expressions** — going into a field means sending a
different expression (`(.pos b)` where the last was `b`), and `l` works by popping back to the previous one. That design
is forced: a Flan value has no header, the thunk that rendered it is `dlclose`d as soon as it returns, and there is no
heap to retain anything in, so nothing can be held server-side the way CIDER holds a JVM object.
The consequence is that `i` evaluates a name wherever the evaluator stands, **not in the frame being looked at**. On the
innermost frame that happens to be right. On any other it may resolve to a global, to a different binding, or fail —
with nothing saying so.
**An earlier suggestion in this conversation — "root the inspector at the slot's address" — does not work**, and the
reason is worth keeping: an address is not an expression, so the first `RET` has nothing to build the next expression
from and navigation dies at step one. Recorded because it is the obvious fix and it is wrong.
So the options are genuinely three, and none is free:
1. **Teach the program to evaluate an expression relative to a frame.** The most useful and the most work: the frame's
slots would have to be in scope for a compiled thunk, which means the daemon building a thunk whose free names bind
to that frame's addresses. It would also fix `C-x C-e` while stopped, which has the same blindness.
2. **Give the inspector a second rooting mode** — an address root that can still walk, by carrying a type alongside the
address and stepping to a field's address rather than to a sub-expression. Navigation then works, but the two modes
have different capabilities and `l` has to cross between them.
3. **Refuse `i` outside the innermost frame**, honestly and by name. Cheapest, and it gives up the feature exactly where
it is most wanted, since the innermost frame is the one already fully visible.
## 2. Annotating the IR and the disassembly with the source