A union arm is a field line: the arm framing for any field type, on both wires (#396 item 1, #392) - #481
Conversation
#396, #392) SPEC-TABLES §2.6 and SPEC §4.8 lift the declared-types-only restriction on a union arm: an arm is an ordinary field line, so an arm's type is any type a field's is — a scalar with its bounds, an enum, flags, string(N), bytes(N), a bounded array, a pointer, another union, a `type`, a `table`. The framing question #436's F19 names is answered here rather than left to nine ports: the arm payload is exactly the bytes a FIELD of that type puts after its own framing prefix, under the arm's existing `L`. No kind is added, no per-arm kind byte rides, and a table arm's bytes do not move. What follows from having no kind byte is stated with it: an `L` that is not a fixed-width arm's declared width is a kind mismatch, a damaged length-shaped payload is that arm's own framing damage, and an arm retyped under one width is SILENT — §4.1's class grows to five, and §18's baseline is what refuses it. The arm's baseline line and the arm's cook-projection line gain the FIELD tokens for a general arm and keep `payload=<Name>` for an arm naming a declaration, so no committed baseline and no build version moves for a unit that has not changed. The packet wire's rule is fixed in SPEC §4.8 so no port guesses it — an arm rides after the tag as the field encoding its type already has — and the nine backends' packet codecs plus the arm's field facts in the wire-shape projection are the named follow-on. A union with a general arm is a table-closure construct today, refused by name outside one, exactly as a table-armed union is. Refused on an arm, each by name and each with its reason: a specified default, `?`, `was`, `json`, an enum-keyed array, a map, an `if` guard. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
8935407 to
e89f460
Compare
Rulings on the #481 cold read (union arms, docs/SPEC-TABLES.md and docs/SPEC.md §4.8)Numbers follow the reader's report. Line numbers there are at e89f460 on union-arms-spec. Every ruling is stated on the page as present fact, in the page's voice, with no history, no plan and no owner quotation. 1. Underdetermined1a. A body's terminator is the end of its payload. A body arm, and a body field, whose terminator is not the last two bytes of its L (one byte under schema#435) is malformed: the payload stops, the union reads None or the field reads its default, and the enclosing body continues past it by L. State it once on §3's body row for fields and arms alike, and add the fuzzer row for it. 1b to 1d. The silent class is stated exactly, once, in §3, and cited from §2.6, §4, §4.1 item 5 and the three-frames row. It is: a retype between two fixed-width arms of one width; any arm retyped to a string arm, at every width (clamped fires only past N); a scalar and a pointer at four bytes (an in-range index reads a node, an out-of-range one is malformed and the slot reads null); a scalar arm retyped to a bytes or array arm at fewer than five bytes (inert: the arm is selected at its defaults and nothing is counted). At five bytes and above the element kind check applies at an arm exactly as at a field. 1e. The build version's arm line carries the field line's tokens in the field line's EMITTED order, with size= unconditional and offset= the arm's offset within the union's storage. Fix the documented field grammar to the emitted order in the same edit. State on both lines that elem= is the element's size in this projection and the element's kind in the baseline. 1f. The baseline's arm line carries every judged token the field line carries, min= and max= included, and a tightened arm range warns as a field's does. Strike "resolution" from §2.6: the baseline judges what it records. 1g. The page states the rule the compiler already holds: a new judged token bumps the baseline rendering version. This page's arm tokens bump it. The number on the page is main's constant plus one at merge time (5 today); the maps page bumps it too, and whichever of the two merges second rebases to plus two. 1h. ViewVariant gains the field row member. §8.1 and §8.3 name it, and the exactly-one-non-NULL rule names both members. 1i. Descriptor offsets are from the start of the union's storage, the tag at offset 0. 1j and 2b. In the text form an arm value of the wrong shape is kind_mismatch: the union reads None and the enclosing object continues. This is what §16.2's report paragraph and the manifest rows already say. 1k. Every arm projects into the protocol id as the field line it is. Adding a scalar arm to a union moves the id as adding a field does. Strike "projects nothing". 1l. Mode derivation cites §2.2's rule for variable length instead of listing cases. 2. Contradictions2a. The arm id 0 rides in two places: a None element of an array of unions, and a nested-union arm holding None (L = 2, inner id 0). Fix the sentence. Also: an arm admits every type a field admits, a map included once #479's field lands. Strike "refuses a map". bits(N) is a scalar in both pages. 3. The #435 seamOne sentence at the arm table: these widths are today's; under schema#435 the arm header is a field header (id reference, kind, L, payload), so the arm carries its kind and the silent members this section names close by construction. Today's members stay stated as today's. The framing rule is width-free and stands. 4. TestsThe held-by-test sections name the ten single-reason controls the reader listed, each with its corpus row or instance and the one reason it goes red. The shared-node case gets stream_arm_first's construction. §18.2's four refusal sub-cases are listed with their fixture pairs. 5. ProseThe fixed-width, length-shaped and silent triad once, in §3. The kind-byte rationale once, in §14, not §13. "today" out. RuleFixed described, not named. "the arm column" named. The overlay excerpt marked as five of ten arms. armour becomes armor and the example ids are recomputed with the tool. No em dashes in rewritten prose. The PR body rewritten to the page as it is. |
…what an arm projects An arm may carry no payload at all: a bare name line in the grammar, the tag alone on the packet wire, a tag value with no member in generated code. A field and an arm are one shape under two productions, which is what §4.2 has. The refusal list moves to its one home in SPEC-TABLES §2.6, and `bits(N)` is spelled as a scalar in both pages. Every arm projects into the protocol id as the field line it is, so adding a scalar arm moves the id as adding a field does. The one union excluded whole is a union with a `table` arm, because a table has no packet wire for an arm to carry. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
… the arm lines the baseline and the build version render §3 owns the arm's read rules and states them once: a body's terminator is the end of its payload, so bytes left inside `L` are framing damage for a field and for an arm alike; and the silent class is four retypes, not one, since a string arm accepts every width, a scalar and a pointer share four bytes, and a `bytes` or array arm under five bytes is inert. §2.6, §4, §4.1 and the three-frames table cite it instead of restating it. The kind byte the framing does not spend is priced once, in §14. The baseline's arm line carries every judged token a field's does, `min=` and `max=` included, a payload-free arm carries `kind=none`, and a new judged token bumps the rendering version, which the example now shows at 5. The build version's arm line carries the field line's tokens in the field line's emitted order, with `size=` unconditional and `offset=` taken within the union's storage, and the documented field grammar is corrected to that same order. The registry's arm row gains its `field` descriptor beside `payload`, every descriptor offset is taken from the start of the union's storage with the tag at 0, and a wrong-shaped arm value in the text form is `kind_mismatch`. The backend status paragraph is gone: the held-by-test sections carry what the corpus holds, each control named with its row and the one reason it goes red. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…s the arm rule Both of COMPARISON-TABLES' counts read five, matching §4.1. USAGE's union section says what an arm is now: any type a field's is, or no type at all, with the table-closure boundary named. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The maps specification (#479) landed first, so the baseline rendering version on the page is main's constant plus two, and the four overlaps resolve to both sides: the pointer walk lists a map entry beside the set arm, §18.2 warns on a tightened range whether it is a field's or an arm's, and §20.2's field grammar keeps `array=map` while dropping the `stride=` the emitter puts on `slot` lines only. `elem=` is stated for an array, which is what an `arm` line carries. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Spec first. This is the page for #396's adopt item 1, union arms of any field type, and for #392's closure seen from the arm side. It carries
docs/SPEC.mdanddocs/SPEC-TABLES.md, with the two doc-family corrections the change makes owed. The C++ reference, the tool, the corpus and the goldens follow in the implementation PR, built against this page.What the pages say
An arm is an ordinary field line (SPEC §4.8, SPEC-TABLES §2.6). An arm's type is any type a field's is, with nothing added and nothing held back: a scalar with its bounds, a compressed float,
fixed(I, F), a 128-bit integer, anenum, aflagsmask,string(N),bytes(N), a bounded array, a pointer, another union, a declaredtype, and inside a table closure atable. A field and an arm are one shape under two productions,FieldandUnionVariant, and the second carries no default and no?.An arm may carry no payload at all, written as its name alone. It has no storage, the table wire gives it its arm id and
L = 0, the packet wire gives it the tag and nothing else, the text form spells it{ "ping": null }, and C++ gives it a tag value with no member and no accessor. It is notNone, which says no arm was selected.The framing. An arm payload is exactly the bytes a field of that type puts after its own framing prefix, under the arm's existing
L. §3's kind-15 row states it per type, and says once that those widths are this wire's: under #435 an arm header becomes a field header and the silent members below close by construction, while the framing rule itself carries no width and stands under both forms.What a reader can see, and what it cannot. A fixed-width arm whose
Lis not its declared width iskind_mismatch. A length-shaped arm's damaged payload is that arm's own framing damage underL. §3 then names the whole silent class, which is four retypes and not one: two fixed-width arms of one width, any arm read as a string arm at every width, a scalar and a pointer at four bytes, and a scalar read as abytesor array arm under five bytes. §2.6, §4, §4.1's fifth member and the three-frames table cite that list rather than restating it.A body's terminator is the end of its payload, for a kind-13 field and for a body arm alike. Bytes left inside
Lafter the terminator are framing damage, so a four-byte payload whose low two bytes are zero no longer decodes as an empty body with nothing counted. §4.2's fuzzer carries the mutator for it, and one for an arm'sLmoved off its declared width.The baseline (§18.1, §18.2, §18.3). An arm line has three disjoint spellings:
payload=<Name>for an arm naming a declaredtypeortable,kind=nonefor a payload-free arm, and the field tokens for everything else, in the field line's column order and judged by the field line's rules,min=andmax=included. A tightened arm range warns as a field's does. The page also states the rule the compiler already holds, that a new judged token bumps the rendering version, and the example file readsschema-tables-baseline 6, which is main's constant plus the two renderings that bump it, this page and the maps page (#479).The build version (§20.1, §20.2, §20.4, §20.8). The
armline carries the field line's tokens in the field line's emitted order, withsize=unconditional andoffset=taken within the union's storage, whose tag sits at 0. The documented field grammar is corrected to that same emitted order, andelem=is stated as the element's size in this projection where the baseline records the element's kind.The protocol id (SPEC §3.1, §4.8). Every arm projects as the field line it is, so adding a scalar arm to a union moves the unit's id exactly as adding a field to a
typedoes. A union with atablearm is excluded whole, as the table is, because a table has no packet wire for an arm to carry.Storage, reflection, text and the walks. An arm is one overlay member, and an arm needing a companion rides as one member of an unnamed struct type, because the dialect refuses an anonymous struct member, which is now a row in §13.9 where the dialect's refusals live.
ViewVariantgains afielddescriptor besidepayload, and every offset in §8.1 is taken from the start of the union's storage. An arm's value takes the arm's own row of §16.2's mapping table, and a value of the wrong shape iskind_mismatchwith the union readingNoneand the enclosing object reading on. A pointer arm is a pointer edge visited in the union field's declaration-order position (§3.1).Refused on an arm, stated once in §2.6 with the reasons and registered in §11 by name: a specified default,
?,was,json, an enum-keyed array[E]T, and anifguard. Amapis not among them, because an arm admits every type a field admits.The kind byte the framing does not spend is priced once, in §14, as the rejected shape it is.
Held by test
The held-by-test sections carry what the corpus holds as the requirement the implementation PR meets, each control named with its corpus row or instance and the one reason it goes red: the seven arm evolution rows in §4, the arm mutators in §4.2, the shared-node pointer arm built the way
stream_arm_firstis in §3.1, the general arm's two descriptor columns in §8.7, the hostile arm rows in §16.5, the five arm refusal sub-cases with their fixture pairs in §18.6, and in §20.8 the arm's own protocol-id control beside the unchanged-unit twin that regenerates both projections byte-identically.Doc family
docs/COMPARISON-TABLES.mdreads "exactly five" in both places, matching §4.1.docs/USAGE.md's union section states the arm rule.Issues: #396 (adopt item 1), #392 (the closure), #436 (F19, F24).
🤖 Generated with Claude Code