flan/test/programs/struct-ergonomics.flan
Joseph Ferano c1b18cb181 Two struct spellings the parser was never able to decide
A bare {.field v} had its refusal in Parse.expr, before any checking, so a
defn whose return type was the only place the struct's name appeared could
not build one. The refusal moves to Check: Parse builds an Ast.Bare out of
the same struct_fields the named form uses, and check_bare reads the type
name off the expectation and hands that very list to check_struct. ZII, the
unknown-field refusal and the duplicate-field refusal are therefore not
copies of the named form's rules but the named form's rules.

Braces at a dyn want are the dyn map literal and stay exactly that. A
.field-keyed brace was never part of that spelling, and at a dyn want it is
refused by name rather than given a second meaning.

(Cell 1 2) is the other half, and it is character-for-character an ordinary
call, so only the symbol table separates them. It is decided on the last arm
of named_call, after a local of function type, a generic and the function
table -- so a defclass constructor, which is a real defn, resolves above it
and is untouched. Arity is exact: ZII is what the braces do, and a positional
list cannot say which field it left out, so it is not allowed to leave one
out. The refusal names the first field it did not reach and points at the
spelling that does mean "zero the rest".

Both are gone before any backend sees them -- Tast.Make either way -- and the
three acceptance rows print the same lines to say so.
2026-09-20 18:12:45 +07:00

71 lines
2.9 KiB
Plaintext

;;;; Two spellings a struct value gained, both decided by the checker.
;;;;
;;;; A bare {.field v} has no type written in front of it. The fields alone do
;;;; not name one, but the position usually does: a defn's return type, a
;;;; parameter, a field of an enclosing literal, a place being set. The parser
;;;; cannot see any of that -- it has no symbol table and no expectation -- so
;;;; it builds the field list and the checker reads the type off the want and
;;;; hands the very same list to the named form's own code. Everything below
;;;; that follows -- ZII for an omitted field, the unknown-field and
;;;; duplicate-field refusals -- is therefore not a copy of the named form's
;;;; rules, it is the named form's rules.
;;;;
;;;; (Cell 1 2) is the other half, and it is a call until the checker looks the
;;;; head up: a struct name where a function name would be. Arity is exact.
;;;; ZII is still available and is what the braces do; what a positional list
;;;; cannot do is SAY which field it left out, so it is not allowed to leave
;;;; one out.
(defstruct Cell [row i32 col i32])
(defstruct Grid [a Cell b Cell])
;; The case from the notes: a defn whose return type is the only place the
;; type is written. This is what used to fail at parse, before any checking.
(defn origin [] Cell {.row 0 .col 0})
;; An omitted field is zeroed, exactly as (Cell {.row 3}) zeroes .col.
(defn just-row [r i32] Cell {.row r})
;; A parameter is an expectation too -- as the only argument and as a later
;; one, which are two different paths into the checker and both arrive here.
(defn sum [c Cell] i32 (+ (.row c) (.col c)))
(defn offset-sum [n i32 c Cell] i32 (+ n (sum c)))
;; A field of an enclosing literal. The inner braces are bare and the outer
;; ones are not, so both readings stand side by side in one form.
(defn grid [] Grid (Grid {.a {.row 1 .col 2} .b (Cell 3 4)}))
;; Positional, in the position that names the type anyway.
(defn diag [n i32] Cell (Cell n n))
;; Nested positional, and positional feeding a bare literal's field.
(defn pair [] Grid (Grid {.a (Cell 5 6) .b (Cell 7 8)}))
(defn main [] ()
(println (sum (origin)))
(println (sum (just-row 9)))
(println (sum {.row 10 .col 20}))
(println (offset-sum 1 {.row 2}))
(let [g (grid)]
(println (.row (.a g)))
(println (.col (.a g)))
(println (.row (.b g)))
(println (.col (.b g))))
(println (sum (diag 11)))
(let [p (pair)]
(println (sum (.a p)))
(println (sum (.b p))))
;; A bare literal set into a typed place: the place's type is the want.
(let [c (Cell 0 0)]
(set c {.row 7 .col 8})
(println (sum c))
;; And into a field, whose type is the want one level down.
(let [g (Grid {.a c .b c})]
(set (.b g) {.row 100})
(println (sum (.b g)))))
;; The dyn map literal is untouched by any of this: keyword keys, and a
;; .field-keyed brace was never part of that spelling.
(let [m {:a 1 :b 2}]
(println (len m))
(println (get m :b))))