Only a place has an address in the inspector, and a watched dyn value and string are pinned armed

This commit is contained in:
Joseph Ferano 2026-09-25 07:32:25 +07:00
parent 377ada8d27
commit cda939c8be
5 changed files with 69 additions and 25 deletions

View File

@ -1724,10 +1724,11 @@ with the selected frame's locals.
** DONE Hex, binary and an address on a primitive in the inspector ** DONE Hex, binary and an address on a primitive in the inspector
CLOSED: [2026-09-25] CLOSED: [2026-09-25]
Hex and binary were already drawn under every integer. The slot root's reply Hex and binary were already drawn under every integer. The slot root's reply
now carries =:addr=, the address of the place it read (a field or element down now carries =:addr=, the address of the place it read, a field, element or
a path included), and the inspector shows it as =at 0x…=. An expression root has option payload down a path included, and the inspector shows it as =at 0x…=. A
no address, because its value is not stored anywhere. A stack address is not data case's field and an expression root's value are not places and carry none,
offered to =flan-inspect-address=, since the registry does not follow one. rather than the address of a copy. A stack address is not offered to
=flan-inspect-address=, since the registry does not follow one.
** DONE A defclass is not on the definitions list as a type, and a sum's cases are not drawn ** DONE A defclass is not on the definitions list as a type, and a sum's cases are not drawn
CLOSED: [2026-09-25] CLOSED: [2026-09-25]

View File

@ -778,6 +778,7 @@ drawn from."
(get-text-property pos 'flan-cnr-abort) (get-text-property pos 'flan-cnr-abort)
(get-text-property pos 'flan-cnr-frame) (get-text-property pos 'flan-cnr-frame)
(get-text-property pos 'flan-cnr-hidden) (get-text-property pos 'flan-cnr-hidden)
(get-text-property pos 'flan-cnr-loc)
(get-text-property pos 'flan-cnr-inspect))) (get-text-property pos 'flan-cnr-inspect)))
(defun flan-cnr-tab () (defun flan-cnr-tab ()

View File

@ -1681,28 +1681,33 @@ let render_slot ?(origin = "<inspect>") t ~frame ~(fn : Tast.fn) ~slot ~path
(match Render.render c 0 v with (match Render.render c 0 v with
| exception Loc.Error { Loc.dmsg = why; _ } -> Error (name ^ path_text path ^ ": " ^ why) | exception Loc.Error { Loc.dmsg = why; _ } -> Error (name ^ path_text path ^ ": " ^ why)
| parts -> | parts ->
(* Where the value lives, first and on a line of its own: [v] is (* Where the value lives, first and on a line of its own, when [v]
a place in the stopped frame, so its address is the storage is a place in the stopped frame: its address is then the
the listing is reading. The caller splits it off at the first storage the listing is reading. A data case's field is not a
newline; a rendering has none, because [Render] quotes a place — [AddrOf] on it would answer the address of a copy — so
it has no address line. The caller splits the line off at the
first newline; a rendering has none, because [Render] quotes a
string's. *) string's. *)
let addr =
{ Tast.e =
Tast.Prim
(Tast.Cast (Types.Int Types.I64),
[ { Tast.e = Tast.Prim (Tast.AddrOf, [ v ]);
ty = Types.Ptr v.Tast.ty; loc } ]);
ty = Types.Int Types.I64; loc }
in
let newline =
{ Tast.e =
Tast.Prim
(Tast.Bytes, [ { Tast.e = Tast.Str "\n"; ty = Types.String; loc } ]);
ty = Types.Slice (Types.Int Types.U8); loc }
in
let parts = let parts =
dev_emitter.Render.ei64 addr :: dev_emitter.Render.ebytes newline if not (Emit.addr_is_place v) then parts
:: parts else
let addr =
{ Tast.e =
Tast.Prim
(Tast.Cast (Types.Int Types.I64),
[ { Tast.e = Tast.Prim (Tast.AddrOf, [ v ]);
ty = Types.Ptr v.Tast.ty; loc } ]);
ty = Types.Int Types.I64; loc }
in
let newline =
{ Tast.e =
Tast.Prim
(Tast.Bytes,
[ { Tast.e = Tast.Str "\n"; ty = Types.String; loc } ]);
ty = Types.Slice (Types.Int Types.U8); loc }
in
dev_emitter.Render.ei64 addr :: dev_emitter.Render.ebytes newline
:: parts
in in
let nullary n = { Tast.e = Tast.Call (n, []); ty = Types.Unit; loc } in let nullary n = { Tast.e = Tast.Call (n, []); ty = Types.Unit; loc } in
t.thunks <- t.thunks + 1; t.thunks <- t.thunks + 1;

View File

@ -41,6 +41,10 @@
(watch "pos" (Pos {.x 3 .y 1.5})) (watch "pos" (Pos {.x 3 .y 1.5}))
(watch "row" [1 2 3]) (watch "row" [1 2 3])
(watch "t2" (* ticks 2)) (watch "t2" (* ticks 2))
;; A dyn value renders through the dyn printer into the slot, and a string
;; through the form is quoted.
(watch "d" {:a 1})
(watch "s" "x")
;; A hot inner loop, and the value *varies* across it — which is what makes ;; A hot inner loop, and the value *varies* across it — which is what makes
;; the row a test of the accumulator rather than of the plumbing. A slot that ;; the row a test of the accumulator rather than of the plumbing. A slot that
;; only kept [last] would report 21 and no range; n, min and max are each ;; only kept [last] would report 21 and no range; n, min and max are each

View File

@ -1368,7 +1368,9 @@ let () =
(* The site, on LLVM: an arith trap publishes its loc around the (* The site, on LLVM: an arith trap publishes its loc around the
hook exactly as a bounds trap does. *) hook exactly as a bounds trap does. *)
(match Wire.string_field (ask "(:op \"break\")") "site" with (match Wire.string_field (ask "(:op \"break\")") "site" with
| Some site when contains_sub site "dev-break.flan:" -> () (* Absolute, because the break buffer opens the file it names and
an editor is not in this process's working directory. *)
| Some site when contains_sub site "dev-break.flan:" && site.[0] = '/' -> ()
| Some site -> fail "the arith site points at %s" site | Some site -> fail "the arith site points at %s" site
| None -> fail "a division by zero carries no :site"); | None -> fail "a division by zero carries no :site");
let r = ask "(:op \"restart\" :name \"use-zero\")" in let r = ask "(:op \"restart\" :name \"use-zero\")" in
@ -2385,6 +2387,29 @@ let () =
if Int64.sub y x <> 4L then if Int64.sub y x <> 4L then
fail "mark.y is %Ld bytes past mark.x, not 4" (Int64.sub y x) fail "mark.y is %Ld bytes past mark.x, not 4" (Int64.sub y x)
| _ -> fail "inspect on a slot answered with no :addr")); | _ -> fail "inspect on a slot answered with no :addr"));
(let addr name path =
match slot_of listing name with
| None -> None
| Some slot ->
(match Wire.field (inspect ~path slot) "addr" with
| Some { Form.v = Form.Int a; _ } -> Some a
| _ -> None)
in
(* An element is its index's worth of elements past the first. *)
(match addr "xs" "()", addr "xs" "(1)" with
| Some a, Some b when Int64.sub b a = 4L -> ()
| a, b ->
fail "xs[1] is not one i32 past xs: %s, %s"
(Option.fold ~none:"none" ~some:Int64.to_string a)
(Option.fold ~none:"none" ~some:Int64.to_string b));
(* An option's payload is inside the option's own storage. *)
(match addr "box" "()", addr "box" "(some)" with
| Some a, Some b when b > a && Int64.sub b a < 16L -> ()
| _ -> fail "box's payload is not inside box's storage");
(* A data case's field has no accessor place to take the address
of, and says nothing rather than a copy's address. *)
if addr "s" "(\"Shape.Rect.w\")" <> None then
fail "a data case's field answered with an address");
(* Emacs prints an empty list as `nil' and has no other spelling for (* Emacs prints an empty list as `nil' and has no other spelling for
one, so a client in that language cannot send `()'. *) one, so a client in that language cannot send `()'. *)
(match slot_of listing "mark" with (match slot_of listing "mark" with
@ -3921,6 +3946,14 @@ let () =
| Some v when int_of_string_opt v <> None -> () | Some v when int_of_string_opt v <> None -> ()
| Some v -> fail "watch rendered a computed i64 as %s" v | Some v -> fail "watch rendered a computed i64 as %s" v
| None -> fail "the (watch ...) form never wrote a scalar"); | None -> fail "the (watch ...) form never wrote a scalar");
(match List.assoc_opt "d" t with
| Some "{ :a 1}" -> ()
| Some v -> fail "watch rendered a dyn map as %s" v
| None -> fail "the (watch ...) form never wrote a dyn value");
(match List.assoc_opt "s" t with
| Some "\"x\"" -> ()
| Some v -> fail "the (watch ...) form rendered a string as %s" v
| None -> fail "the (watch ...) form never wrote a string");
(* The accumulator, which is the other half of the watch and the (* The accumulator, which is the other half of the watch and the
half a scalar row cannot stand in for. [loop-cells] samples "cell" half a scalar row cannot stand in for. [loop-cells] samples "cell"
eight times per step at 0, 3, ... 21, so the row has to show a eight times per step at 0, 3, ... 21, so the row has to show a