flan/test/programs/dev-inspect.flan
Joseph Ferano ff2c949361 The tagged sum is defdata, and the old spelling is an error by name
Flan's tagged sum has been spelled defunion since it landed, which was
accurate right up until the language wanted C's untagged union as well.
Both cannot be called the same thing, and the tagged one is the one with
an alternative name that says what it is: a case, its fields, and a tag
that steers which case is live is a data type, not a union.

So the form is defdata everywhere -- the parser, the AST, the checker,
both backends, the prelude's Form, the editor's font-locking and imenu,
the docs and every .flan file in the tree. The internal vocabulary moves
with it: Tast.union is Tast.data, uname is dname, the tables the checker
and the emitter keep are datas. Leaving them would have inverted the
words permanently, with surface defunion meaning one thing and
env.unions meaning the other, which is exactly the kind of drift the
comments in those files exist to prevent. What did not move is case,
variant and vfields: a tagged sum still has cases, and it still has one
live at a time.

defunion is not kept as an alias. An alias would compile the day the
untagged form lands and mean the opposite of what it used to -- the same
silent misparse that made defn's return type mandatory, and worse,
because the reader would have no reason to look. The old spelling is a
named refusal instead, parse/defunion-renamed, which says what it is now
called and that the name is reserved for something else. It fires on the
head alone, so (defunion U [A B]) -- which would otherwise have parsed
cleanly as one field A of type B -- is refused with the rest.
2026-09-17 19:03:27 +07:00

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])
(defdata 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)