# V3 step 3.4 — records + WITH (done 2026-09-20) Suite 56/56 (23 run — incl. 3 trap-aborts — + 33 reject). LL(1)-clean (only the known-benign TypeBlock warning). ## Layout: pointer fields (locked amendment) The plan said array headers sit inline in records, but 3.1 shipped nested *separate* objects — one type would then have two layouts (silent corruption on cross-assigns). Locked instead: records are flat blobs with scalars/sets inline, array fields as 8-byte pointers to static descriptors, nested records inline, static declaration-order offsets. Whole-record assignment is deep (per-field copy, arrays via `CopyArray`), so no aliasing. ## Grammar (`M2.atg`) - `RecordType` (+ `RecField`/`RecIdents` reusing the field machinery, so classes share offsets for free); class fields widened to full `Type` for the same reason. - `.` suffixes mixing freely with `[...]` (`r.tags[i]`, `m[i].x`); 215 non-record, 216 unknown field. - `KindField` designators resolve through the WITH stacks (base + static offset, array fields auto-dereferenced like nested levels); field use outside `WITH`/methods is still 230. - `WITH v [, ...] DO` (215 on non-records): pushes fields (inner wins) + bases, pops both exactly; nested and field-designator (`WITH r.org`) forms work. - `Assign` accepts field targets and copies records deep (`CopyRecord`); whole-class assign is explicit 230; record/set `=` is 213 (added to `EqCheck`); partial composites flow as addresses in `Fact` (never scalar-loaded). ## SymTab (additive) `TypeSize` (inline footprint: arrays/pointers 8), lazy declaration-order `FieldOffset` (+ rank for declaration-order enumeration, invalidated on new fields), `FieldOwner` via a new scope→record link, `FieldCount/FieldName`; nominal-cycle depth guard (223 proper still deferred). ## Backend (`QbeGen`) `DeclRec` (statics pre-pass + flat items; reverse-chain emission walks declaration order via ranks), `FieldAddr`, WITH base stack, `CopyRecord` (scalars/sets inline, arrays deep, records recurse). ## Bugs caught 1. **Reversed emission**: the field chain builds prepend (reverse declaration) but offsets are declaration-order — first cut emitted backwards while addressing forwards (SIGSEGV via a NULL slot read as pointer). Fixed with declaration ranks. 2. **Inline-vs-pointer size split**: `TypeSize` counted full array sizes inside records while layout stores pointers — offsets went wrong whenever an array field preceded others. `TypeSize` is now the inline footprint (arrays 8) everywhere. 3. Silent partial-composite loads/stores (record/set/class through scalar paths) closed by construction; whole-class assign plugged with an explicit 230. ## Tests `t_record` → 160 (nesting, array fields, deep-copy independence via post-copy mutation, field-designator `WITH`), `t_with` → 114 (multi-designator shadowing, nested `WITH`); `t_bad_field` (216), `t_bad_with` (215), `t_bad_recassign` (210). Total 56. Deferred: variant records (Wirth has none — correctly absent), `WITH`-field `VAR` actuals (needs calls, step 4), class lowering (vtable/`THIS`), `HIGH`/`LEN`, 223 cycle errors.