tables: text map values, string(N), wstring(N) and bytes(N), in the C++ reference (#619) - #626
Merged
Merged
Conversation
`tables/maps/Text.schema` declares the three text value kinds §2.8 names as
legal map values: `map[string(8)]string(16)`, `map[uint16]wstring(6)` and
`map[int32]bytes(10)`, with an `int32` past them so a stop is visible.
Against the emitter as it stands the generated header does not compile.
`internal/codegen/cpptable/codecs.go`'s `cppFieldType` has no case for
TString, TWString or TBytes, so `mapValueStorageType` answers `/* ? */` and
the entry surface reads:
inline const /* ? */ * TableEntryFound( const TextNamesEntry * entry )
inline /* ? */ * TableEntryValue( TextNamesEntry * entry )
clang, at the repo's own table flags, over a translation unit that includes
`TextTable.h` and nothing else, gives thirteen errors:
TextTable.h:4643:24: error: a type specifier is required for all declarations
TextTable.h:4643:81: error: cannot initialize return object of type
'const int *' with an rvalue of type 'const char (*)[17]'
TextTable.h:4719:80: error: cannot initialize return object of type
'const int *' with an rvalue of type 'const char16_t (*)[7]'
TextTable.h:4797:81: error: cannot initialize return object of type
'const int *' with an rvalue of type 'const uint8_t (*)[10]'
The entry's own storage is already right — `char value[16 + 1]` beside
`int32_t value_length` — so what is missing is the handle, not the layout.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
#619) `string(N)`, `wstring(N)` and `bytes(N)` are legal map values (§2.8, "A VALUE is anything a table field can hold"), and the generated entry already stored each correctly: `char value[N + 1]` beside `int32_t value_length`, the same storage row the field takes anywhere (§7.2). What was missing was the HANDLE. `mapValueStorageType` asked `cppFieldType` for a single type spelling, got `/* ? */` because that function answers types and not pairs, and the entry surface, Insert, Find and IndexFind all spelled it. A pair is two members, so no one member is the value. The handle is therefore the ENTRY, which reaches both: `TableEntryValue` and `TableEntryFound` answer `<Entry> *` and `const <Entry> *`, a caller fills `value` and `value_length`, and `key` stays the map's, which owns the order it carries. Nothing about the entry's layout, its descriptor, its codec or its text form moves: each of the three is an ordinary field of the entry and always was. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`test_text_values` builds the three text values through the entry handle, out
of key order, pins `testdata/wire/tables/map_text.bin`, holds measure == save,
loads the region and re-saves it byte for byte, and reads the text form back
to the same wire. The wide value is five UTF-16 code units ending in a
SURROGATE PAIR, so the transcode runs both ways and a clamp that split the
pair would show; the byte values carry a zero byte and a high byte, which
text cannot hold.
`maps_test.go` gains `{"map_text", "Text"}`, so the compiler's own engine
reads the reference's bytes, writes them back byte for byte, renders the text
and reads that text to the same bytes. The two engines' texts are the same
bytes as well:
{
"names": { "alpha": "first", "beta": "second" },
"wide": { "5": "Aé中😀" },
"blobs": { "-3": "3q2+7w==", "2": "AAGA/w==" },
"after": 9
}
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…ves (#619) NEW, one unit's worth: testdata/golden/tables/maps/TextTable.h, TextTable.cpp testdata/wire/tables/map_text.bin (pinned in the previous commit) MOVED, and every one of them is the UNIT'S SHARED VOCABULARY (§3.3) growing because `mapdemo` gained four tables — `Text` and the three entries its maps generate. The vocabulary is emitted identically into every header of a unit, which is why three unrelated headers move and why no per-type codec does: kCapacity 44 -> 50 the unit's distinct field ids kTableMessageEntriesHere 44 -> 52 the announced vocabulary's size kTableNodeTableFieldSlot 33 -> 40 the reserved node-table id's slot kTableAnnounceBytes 521 -> 615 the announcement, byte for byte BuildVersion 0xe763b68d79174b68 -> 0x28abc7f5927a7539 every `w.put( <slot>, kTableMessageRefBitsHere )` and `node.type_slot` The vocabulary is sorted, so new types shift the slots after them; the two message wires follow from the same fact. `map_conn` IS the announcement, 521 -> 615 bytes. `map_full_message` stays 172 bytes and repacks, because a body's type reference is an index into that vocabulary. `tables/maps/tables.baseline` is ADDITIVE: the `Text` line and the three anonymous entry lines §2.8 keys by the holder's and the field's wire id. The value lines record what the page says a value line records — kind 12 size 16, kind 33 size 6, and kind 14 elem 6 size 10 for the byte buffer. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`tables-maps-value-reset-negative-control` drops `TableResetMapValue` at its
call site in `TableMapPlace`, so a DUPLICATE key no longer resets the value
half §2.8 says is reset WHOLE. A TEXT value is where that bites hardest,
which is why the control arrives with `Text`: the storage is a PAIR, so the
repeat rides the first insert's bytes AND its length.
`test_text_values` gains the duplicate row the control needs, and the gate
answers by name.
RED under the sabotage, three of the four in the new rows:
FAIL test/tables/maps_main.cpp:257: again->health = 100, want 0
FAIL test/tables/maps_main.cpp:1453: repeat != NULL && repeat->value_length == 0
FAIL test/tables/maps_main.cpp:1454: repeat != NULL && repeat->value[0] == 0
FAIL test/tables/maps_main.cpp:1468: landed != NULL && landed->value_length == 0
4 map check(s) failed
negative control: valuereset turns the MAP GATE red — 4 failures
GREEN without it:
maps: all checks passed (docs/SPEC-TABLES.md §2.8)
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…619) `origin/main` moved under this branch: #615 landed retain-unknown in the C++ reference. `make update-goldens` over the merge, hand-merging nothing. `TextTable.h` is rewritten because it was generated before the merge and now carries #615's emitter whole. The other three maps headers move twenty lines each, and they are the SAME CLASS as the vocabulary rows this branch already moves: `kTableRetainKnownIds` is a unit-wide sorted list of known field ids, 44 -> 50, growing by the six the `Text` unit adds. The value handle's three-way choice is a switch. golangci-lint's gocritic reads the if-else chain as one, and it is. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
rowan-claude
added a commit
that referenced
this pull request
Sep 7, 2026
) The handle every map surface hands back now follows the STORAGE the page gives a field of the value's kind, so the six kinds #628 names emit C++ that compiles (docs/SPEC-TABLES.md §2.8, §4.2). The entry's own storage was right in all six: §2.8 makes the entry a real table whose `value` is an ordinary field, so `int32_t value[3]`, the element array beside `value_count`, `TableKeyed<int32_t, Slot>`, `TableList<Item>` and the eight-byte `TableRef` were all already emitted. What had no spelling was the handle, because `mapValueStorageType` asked `cppFieldType` for one type name and that function answers a field's ELEMENT type, not its array, its keyed slot, its list slot or its blob. - `[N]T` is ONE member, so the handle is a pointer to the ARRAY and keeps the extent. A return type cannot spell `T (*)[N]` without wrapping the declarator around the function name, so the entry gains one alias, `<Entry>Value`, and the handle is `<Entry>Value *`. - `[..N]T` is a PAIR, the elements beside `value_count`, so the handle is the ENTRY, which is #626's rule for a text value applied to the second storage shape that has two members. `mapValueIsText` becomes `mapValueIsPair` and covers both. - `[E]T` is `TableKeyed<T, E>` and `[]T` is `TableList<T>`, one member each, so the handle is that member. - `*string` and `*bytes` name a BLOB NODE (§2.5), which has no declared type name, so the const Find resolves through `TableBlobAt` and answers `const TableBlob *` where a `map[K]*T` answers `<T>At`'s pointer. The builder's handle stays the slot, which is what an Emplace fills. RED FIRST, against this commit's parent. clang at the repo's own table flags, over a translation unit that includes one generated header and nothing else: CellsTable.h:6144: cannot initialize return object of type 'const int32_t *' with an rvalue of type 'const int32_t (*)[3]' RunsTable.h:6134: cannot initialize return object of type 'const Item *' with an rvalue of type 'const Item (*)[4]' SlotsTable.h:6204: cannot initialize return object of type 'const int32_t *' with an rvalue of type 'const TableKeyed<int32_t, Slot> *' SpansTable.h:6141: cannot initialize return object of type 'const Item *' with an rvalue of type 'const TableList<Item> *' DocsTable.h:6154: a type specifier is required for all declarations inline const * TableEntryFound( const DocsPagesEntry * entry ) ChunksTable.h:6145: use of undeclared identifier 'At' BOTH ENGINES. `test/tables/maps_main.cpp` gains five rows, one per unit, each pinning its wire, holding `measure == save`, loading the region, re-saving it byte for byte, and reading the text form back to the same wire. `test/conformance/harness/maps_test.go` gains the six names, so the compiler's own engine reads the reference's bytes, writes them back byte for byte, renders the text and reads that text to the same bytes. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
rowan-claude
added a commit
that referenced
this pull request
Sep 7, 2026
NOT THIS ISSUE'S CHANGE, and it is here because the merge carries it. main is red at 19a2372 on its own CI run 34081461383, and it was green at bb5f3bd. #625 moved `internal/codegen/cpptable/json.go` and re-pinned the map goldens it knew about, DepthTable.cpp, FleetTable.cpp and RowsTable.cpp, each by the same 91 lines. Text.schema and its golden landed in #626, which merged after #625's branch was cut, so maps/TextTable.cpp never got the re-pin and `make tables-block-zero-cost` reports it moved. The gap is exactly the 91 lines the three siblings took, the TableJsonInterpretExact reader and the decimal band beside it, which every table holding a map now emits. The file here is the generator's own output, copied by the path `make update-goldens` copies it, with no hand edit: copying the whole maps directory moves this one file and nothing else, which is what says the other three were already current. block zero-cost gate: 105 Table sources byte-identical to their pins Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This was referenced Sep 7, 2026
Merged
make test refuses a missing pinned toolchain by name, and names a skip that is asked for (#599)
#660
Merged
rowan-claude
added a commit
that referenced
this pull request
Sep 7, 2026
…ero-cost gate on main) (#664) #625 moved the emitted JSON walker in every map-bearing unit and regenerated the goldens on its branch before #626's TextTable golden existed on it; the merge of main carried the golden in unchanged, so main's committed TextTable.cpp lags the compiler by the key-reader change and the block zero-cost gate refuses main. Regenerated with main's compiler; no header, wire pin or id moves. Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
rowan-claude
added a commit
that referenced
this pull request
Sep 7, 2026
…ure (#605) (#657) * red first: the enum-bound corpus, ten shapes and three controls (#605) SPEC-TABLES.md §2.4 and §11 state the refusal on the bound's PROVENANCE: a positional array whose bound folds from an enum is refused in a TABLE BODY and in a UNION ARM, in every spelling the bound has. The checker follows the SPELLING, so eight of the ten shapes compile clean today. test/tables/enumbound holds one unit a shape. Against this commit: ArmConstCount.schema COMPILES (exit 0) ArmConstMax.schema COMPILES (exit 0) ArmCount.schema COMPILES (exit 0) ArmFolded.schema COMPILES (exit 0) ArmMax.schema REFUSED, naming neither the arm nor the table BodyConstCount.schema COMPILES (exit 0) BodyConstMax.schema COMPILES (exit 0) BodyCount.schema COMPILES (exit 0) BodyFolded.schema COMPILES (exit 0) BodyMax.schema REFUSED, naming neither the table ControlPacket.schema COMPILES (exit 0) ControlPlain.schema COMPILES (exit 0) ControlTypeHeld.schema COMPILES (exit 0) TestEnumBoundProvenanceCorpus reads the directory and holds each file to its answer, so a shape is red the moment it compiles or the diagnostic stops naming the field, the enum, the constant the field spells and `[E]T` as the fix. All ten refused rows are red at this commit and the three controls are green. The controls hold the other edge: the packet wire, a bound that folds from no enum, and the `type`-held case schema#606 rules on, which this issue does not decide and this corpus does not move. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * check: the enum-bound refusal follows the bound's provenance (#605) The rule docs/SPEC-TABLES.md §2.4 and §11 state reads the bound's PROVENANCE and the checker read its SPELLING, so `[E.Count]T` and `[N]T` under a `const N` that folds from either compiled in a table body and in a union arm and carried the positional class §4.1 counts as closed. `enumBoundProvenance` follows a bound to the enum it folds from, through named constants and constant arithmetic at any depth (SPEC.md §4.2), carrying its own visiting guard over the constant graph. It looks at an ENUM alone: a `flags` bound is refused by its own rule, and a bound that reaches neither enum nor flags is a plain positional array. `checkPositionalEnumBoundInClosure` runs once the closure is known, which is what lets a union arm's diagnostic name the table that reaches the union, as #572's closure refusal names the edge that pulled a `type` in. THE WALK IS THE FENCE: it starts at TABLE bodies and descends UNIONS alone, so the `type` a table closure reaches is never visited and schema#606 keeps its ruling, and a `type` no table reaches is not in the closure at all, so the packet wire is untouched. The resolve-time spelling check is gone; the bound still evaluates there, because a bound that cannot be evaluated is a different diagnostic and belongs at the field. `exprSpelling` now renders constant arithmetic, so a bound like `[Grade.Max + 1]` is quoted back as the source spells it instead of as the placeholder `N`. test/tables/V1.schema and V2.schema: the refusal caught two live fields. `tally` in both generations and `ledger` in V1 were sized `[Grade.Max + 1]`, a positional array in a table body whose bound folds from an enum, which is the shape the page says is refused and the fixture rested on the gap. Both keep their extents, 3 in V1 and 4 in V2, spelled as the plain constants TallySlots and LedgerSlots, so every generation the evolution test exercises is unmoved: the growing bound, the clamped count, and V1-positional against V2-keyed for `ledger`. `schema fmt` re-canonicalized the attribute column in both files. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * check: the enum-bound gate and its negative control (#605) `check-enum-bound-negative-control` runs the compiler over test/tables/enumbound and reads three answers back. THE GATE: each of the ten refused shapes must be refused, and each diagnostic must name the field or the arm, the enum, the constant where the bound reaches the enum through one, and `[E]T` as the fix. The arm rows must name the table that reaches the union. The THREE POSITIVE CONTROLS must compile: the packet wire, whose `[E.Max]T` is a plain array the connect gate covers; a bound that folds from no enum; and the `type`-held case, which is schema#606's ruling and not this refusal's. THE NEGATIVE CONTROL is the one §2.4 names: it removes the CONSTANT FOLD from the bound check through `go build -overlay`, writing no tracked file, and every row whose bound reaches its enum through a constant must then compile clean. It is targeted rather than blanket, so the four direct spellings must stay refused under the same sabotage. A control that turned the whole rule off would go red for a reason that says nothing about the fold. Both halves are green: gate: ten shapes refused on the bound's provenance, three controls compile negative control: without the constant fold, six folded rows compile clean and the four direct spellings stay refused Wired into `make test` beside check-zero-range-negative-control. It compiles no C++ and needs no toolchain beyond Go. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * docs: the enum-bound refusal's status lines, on the tree (#605) SPEC-TABLES.md §2.4's CHECKER STATUS paragraph stated a split between the rule and the checker, and the paragraph carried its own instruction to be deleted by the PR that closes it. It now states one thing: the refusal reads the bound's provenance and not its text, in a table body and in a union arm, and it names what the diagnostic names. §11's checker-status line says the same in its own register. Both RULING STATUS lines for schema#606 stay exactly as they were: the type-held case is the owner's and this refusal does not reach it. VERSIONING.md: the #540 row leaves "Owed before 3.0.0", whose contract is a claim the page makes with the repository not yet behind it. The repository is behind it. The evolution table's "a keyed array made positional" row named the table body alone, which #605 flagged; it now names the union arm as well and states the provenance rule, which is the whole of what the checker refuses there. USAGE.md carried the same split as an italic caveat, and a caveat that is no longer true is worse than one out of scope, so it is replaced by what the diagnostic actually says. The #606 caveat beside it stays. Present tense, no history, no em dashes added. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * goldens: re-pin maps/TextTable.cpp, which main left behind (#625, #626) NOT THIS ISSUE'S CHANGE, and it is here because the merge carries it. main is red at 19a2372 on its own CI run 34081461383, and it was green at bb5f3bd. #625 moved `internal/codegen/cpptable/json.go` and re-pinned the map goldens it knew about, DepthTable.cpp, FleetTable.cpp and RowsTable.cpp, each by the same 91 lines. Text.schema and its golden landed in #626, which merged after #625's branch was cut, so maps/TextTable.cpp never got the re-pin and `make tables-block-zero-cost` reports it moved. The gap is exactly the 91 lines the three siblings took, the TableJsonInterpretExact reader and the decimal band beside it, which every table holding a map now emits. The file here is the generator's own output, copied by the path `make update-goldens` copies it, with no hand edit: copying the whole maps directory moves this one file and nothing else, which is what says the other three were already current. block zero-cost gate: 105 Table sources byte-identical to their pins Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * corpus: drop an em dash from the enum-bound README (#605) House style takes none in anything added here, and the heading carried one. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
rowan-claude
added a commit
that referenced
this pull request
Sep 7, 2026
… the C++ reference (#628) (#662) * tables: the map corpus for the six value kinds that do not compile (#628) Six units under tables/maps, one per value kind §2.8 lists that the C++ reference cannot spell today, each a map and a field past it so a stop is visible: Cells map[string(8)][3]int32, Runs map[uint16][..4]Item, Slots map[int32][Slot]int32, Spans map[uint8][]Item, Docs map[string(8)]*string and Chunks map[int32]*bytes. RED FIRST. Against this commit's emitter each generated header is refused alone, clang at the repo's own table flags over a translation unit that includes it and nothing else: CellsTable.h:6138: cannot initialize return object of type 'const int32_t *' with an rvalue of type 'const int32_t (*)[3]' RunsTable.h:6128: cannot initialize return object of type 'const Item *' with an rvalue of type 'const Item (*)[4]' SlotsTable.h:6198: cannot initialize return object of type 'const int32_t *' with an rvalue of type 'const TableKeyed<int32_t, Slot> *' SpansTable.h:6135: cannot initialize return object of type 'const Item *' with an rvalue of type 'const TableList<Item> *' DocsTable.h:6148: a type specifier is required for all declarations inline const * TableEntryFound( const DocsPagesEntry * entry ) ChunksTable.h:6139: use of undeclared identifier 'At' The entry's own storage is right in every one of the six: §2.8 makes the entry a real table whose `value` is an ordinary field, so the array, the count companion, the keyed slot, the list slot and the buffer reference are all already emitted. What has no spelling is the HANDLE. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * tables: the tool's numbering descends a map's entries (#628) `internal/tablewire`'s `visitEdges` had no case for a MAP field, so the node-table numbering never reached the pointer slots inside an entry. The corpus hid it: `Fleet.by_id`'s two keys name the node `Fleet.flagship` names, so every node reachable through a map was already numbered through a pointer field beside it. `Docs.pages` is `map[string(8)]*string` and `Chunks.blobs` is `map[int32]*bytes`, and each is the first slot to name a node NOTHING ELSE names. Against this commit's parent the engine read the reference's bytes clean and then re-encoded 64 bytes where the reference wrote 123, dropping both blob records and both pointer fields: map_docs: the engine re-encoded 64 bytes, the reference wrote 123 map_chunks: the engine re-encoded 61 bytes, the reference wrote 107 The fix is the page's own sentence: a map is a by-value edge of the one declaration-order walk, reached at its field's position, its entries visited in ascending key order, each entry descended before the next (docs/SPEC-TABLES.md §2.8, §3.1). `MapEntryOrder` is that order, lifted out of `encodeMap` so the numbering and the writer take it from one place and a wire cannot disagree with its own node table. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * tables: map values of array, enum-extent, unbounded and blob kinds (#628) The handle every map surface hands back now follows the STORAGE the page gives a field of the value's kind, so the six kinds #628 names emit C++ that compiles (docs/SPEC-TABLES.md §2.8, §4.2). The entry's own storage was right in all six: §2.8 makes the entry a real table whose `value` is an ordinary field, so `int32_t value[3]`, the element array beside `value_count`, `TableKeyed<int32_t, Slot>`, `TableList<Item>` and the eight-byte `TableRef` were all already emitted. What had no spelling was the handle, because `mapValueStorageType` asked `cppFieldType` for one type name and that function answers a field's ELEMENT type, not its array, its keyed slot, its list slot or its blob. - `[N]T` is ONE member, so the handle is a pointer to the ARRAY and keeps the extent. A return type cannot spell `T (*)[N]` without wrapping the declarator around the function name, so the entry gains one alias, `<Entry>Value`, and the handle is `<Entry>Value *`. - `[..N]T` is a PAIR, the elements beside `value_count`, so the handle is the ENTRY, which is #626's rule for a text value applied to the second storage shape that has two members. `mapValueIsText` becomes `mapValueIsPair` and covers both. - `[E]T` is `TableKeyed<T, E>` and `[]T` is `TableList<T>`, one member each, so the handle is that member. - `*string` and `*bytes` name a BLOB NODE (§2.5), which has no declared type name, so the const Find resolves through `TableBlobAt` and answers `const TableBlob *` where a `map[K]*T` answers `<T>At`'s pointer. The builder's handle stays the slot, which is what an Emplace fills. RED FIRST, against this commit's parent. clang at the repo's own table flags, over a translation unit that includes one generated header and nothing else: CellsTable.h:6144: cannot initialize return object of type 'const int32_t *' with an rvalue of type 'const int32_t (*)[3]' RunsTable.h:6134: cannot initialize return object of type 'const Item *' with an rvalue of type 'const Item (*)[4]' SlotsTable.h:6204: cannot initialize return object of type 'const int32_t *' with an rvalue of type 'const TableKeyed<int32_t, Slot> *' SpansTable.h:6141: cannot initialize return object of type 'const Item *' with an rvalue of type 'const TableList<Item> *' DocsTable.h:6154: a type specifier is required for all declarations inline const * TableEntryFound( const DocsPagesEntry * entry ) ChunksTable.h:6145: use of undeclared identifier 'At' BOTH ENGINES. `test/tables/maps_main.cpp` gains five rows, one per unit, each pinning its wire, holding `measure == save`, loading the region, re-saving it byte for byte, and reading the text form back to the same wire. `test/conformance/harness/maps_test.go` gains the six names, so the compiler's own engine reads the reference's bytes, writes them back byte for byte, renders the text and reads that text to the same bytes. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * tables: a negative control for the entry the numbering descends (#628) `tables-maps-entry-node-negative-control` short-circuits the per-entry `Number` call in the map's by-value edge, so a node named ONLY by a map entry takes no index. The `Docs` and `Chunks` rows are what meet it: their `*string` and `*bytes` values are the only slots in this corpus that name a node nothing else names, and `Fleet.by_id` cannot meet it because `Fleet.flagship` names the same node. RED under the sabotage, all six failures in the new rows, and the answer is the -1 §7.6 gives an unreached pointer rather than a wrong wire: FAIL table wire golden map_docs: -1 bytes written, 123 pinned FAIL test/tables/maps_main.cpp:1873: need > 0 FAIL test/tables/maps_main.cpp:1874: need = -1, past the 268435456 byte measure ceiling FAIL table wire golden map_chunks: -1 bytes written, 107 pinned FAIL test/tables/maps_main.cpp:1959: chunk_need > 0 FAIL test/tables/maps_main.cpp:1960: chunk_need = -1, past the 268435456 byte measure ceiling Its sabotage carries an unbalanced parenthesis a `$(call)` argument cannot, so the recipe is spelled out as the keylength one is. Two rows moved so the instrument survives the sabotages beside it. The `Chunks` half left the tail of `test_blob_values` for its own function, because a sabotage that refuses one unit's save must leave the other unit reporting. And the `Spans` element reads sit under their count, because a list whose elements were never placed has a NULL element pointer: under the `depth` sabotage that read faulted, and a fault takes the buffered output of every row with it, which is what "the gate went red, but not on a CHECK" was saying. The controls now red on the new rows by name, and each is the sabotage that owns that kind's runtime behaviour: - `valuereset` 4 to 10 failures, one per value kind's duplicate row: the fixed array reset whole, the counted array's elements AND its count, every keyed slot, the list slot back to empty, the blob slot to null. - `depth` names the `Spans` rows: an entry's list elements are a term of the map's extent at the entry's depth, so a measure summed at one depth only leaves them unplaced. - The compile-time half of this PR has NO runtime control by construction: a sabotage of the storage line produces no binary to run, and its evidence is the red-first quote in 4a2d885. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * docs: the storage and the handle per value kind under a map key (#628) §2.8 gains two paragraphs. THE VALUE'S STORAGE IS THE ROW ITS KIND TAKES AT A FIELD, kind by kind, because the entry is a real table and `value` is an ordinary field of it. THE HANDLE FOLLOWS THE STORAGE: a pointer to the `value` member where the storage is one member, the ENTRY where it is two, the ARRAY rather than its first element for a `[N]T`, and for a pointer value the SLOT on the builder against the RESOLVED node on the const `Find`. §4.2 gains one: a map's value is a FIELD POSITION, so the array element kind, the kind 17 node index and the kind 12 and kind 33 payloads the mutator list already names all land inside an entry without a strategy of their own, and what stays the map's is the KEY. §15 gains a named follow-on. `?T` and `?[N]T` store the value beside a `bool` presence companion, which is two members, so §2.8's rule answers the entry; the C++ reference answers the value member, so a `map[K]?T`'s presence cannot be set and the value is elided on every wire. `?[..N]T` is the exception by accident, its count companion already making it a pair. Found by probing every value kind §2.8 lists; left for its own issue rather than widened into this one. The one value kind refused BY NAME gains a diagnostics case: `map[uint32]*wstring`. The entry is where the refusal has to reach a map's value, because the value is a field of a table nobody wrote. Every other kind §2.8 lists compiles as the ordinary field it is, each confirmed by generating a unit and compiling the header alone: a scalar, an enum, a `flags` mask, a declared `type`, `?T`, `?[N]T`, `?[..N]T` and a union. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * tables: regenerate the six map units' walkers over main's key reader (#628) Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
map[K]string(N),map[K]wstring(N)andmap[K]bytes(N)now emit C++ thatcompiles, and the corpus, the goldens, the oracle and a control say so.
Closes #619.
What lands
The emitter.
internal/codegen/cpptable/maps.gogainsmapValueIsText,and
mapValueStorageTypeanswers the ENTRY for those three kinds. The entry'sown storage was never wrong: §2.8 makes the entry a real table whose
valueisan ordinary field, so it already emitted
char value[N + 1]besideint32_t value_length, the §7.2 row. What was missing was the HANDLE.mapValueStorageTypeaskedcppFieldTypefor one type spelling, and thatfunction answers types, not pairs, so it returned
/* ? */and the entrysurface,
Insert,FindandIndexFindall spelled it.TableEntryValueandTableEntryFoundnow answer<Entry> *andconst <Entry> *. Nothing about the entry's layout, its descriptor, its codec,its cook or its text form moves: each of the three was always an ordinary field
of the entry, and only the accessor was unspellable.
The corpus.
tables/maps/Text.schema, one table, three maps and a fieldpast them:
Both engines.
test/tables/maps_main.cpp'stest_text_valuespinstestdata/wire/tables/map_text.bin, holdsmeasure == save, loads the regionand re-saves it byte for byte, and reads the text form back to the same wire.
test/conformance/harness/maps_test.gogains{"map_text", "Text"}, so thecompiler's own engine reads the reference's bytes, writes them back byte for
byte, renders the text and reads that text to the same bytes. The two engines'
TEXTS are the same bytes too:
{ "names": { "alpha": "first", "beta": "second" }, "wide": { "5": "Aé中😀" }, "blobs": { "-3": "3q2+7w==", "2": "AAGA/w==" }, "after": 9 }The wide value is five UTF-16 code units ending in a SURROGATE PAIR, so the
transcode runs both ways and a clamp that split the pair would show; the byte
values carry a zero byte and a high byte, which text cannot hold.
Red first
tables/maps/Text.schemalanded on its own in 77403e2, before any emitterchange. Against main's emitter the generated header does not compile. clang at
the repo's own table flags, over a translation unit that includes
TextTable.hand nothing else, gives twenty-one errors (clang stops printing at twenty):
The pointer types in those three messages are the whole diagnosis: the storage
was already
char[17],char16_t[7]anduint8_t[10], and only the returntype was unspellable.
The control
tables-maps-value-reset-negative-controldropsTableResetMapValueat itscall site in
TableMapPlace, so a DUPLICATE key no longer resets the valuehalf §2.8 says is reset WHOLE. A TEXT value is where that bites hardest, which
is why the control arrives with
Text: the storage is a PAIR, so the repeatrides the first insert's bytes AND its length.
RED under the sabotage, three of the four in the new rows:
GREEN without it:
Stated plainly: that sabotage was reachable before this PR too, through the
Fleetrow at line 257. What the new rows add is the PAIR half of theevidence, the two members a text value's storage is.
The compile-time half of the fix has no runtime control, and cannot: a sabotage
of the storage line does not produce a binary to run. Its evidence is the
red-first quote above.
The goldens
NEW:
testdata/golden/tables/maps/TextTable.{h,cpp}andtestdata/wire/tables/map_text.bin.MOVED, and every one is the UNIT'S SHARED VOCABULARY (§3.3) growing because
mapdemogained four tables,Textand the three entries its maps generate.The vocabulary is emitted identically into every header of a unit, which is why
three unrelated headers move and why no per-type codec does:
TableIds::kCapacitykTableMessageEntriesHerekTableNodeTableFieldSlotkTableAnnounceBytesBuildVersion0xe763b68d79174b680x28abc7f5927a7539plus every
w.put( <slot>, kTableMessageRefBitsHere )andnode.type_slot,because the vocabulary is sorted and new types shift the slots after them. The
two message wires follow from the same fact:
map_connIS the announcement,521 to 615 bytes, and
map_full_messagestays 172 bytes and repacks, because abody's type reference is an index into that vocabulary. Those hunks are the
whole diff in
DepthTable.h,FleetTable.handRowsTable.h.AND SINCE THE MERGE with
origin/main(#615, retain-unknown), one more row of thesame class:
kTableRetainKnownIds, a unit-wide sorted list of known field ids,44 to 50, growing by the six the
Textunit adds.TextTable.his regeneratedwhole over that merge, because it was first generated before it.
tables/maps/tables.baselineis ADDITIVE, updated with its reason: theTextline and the three anonymous entry lines §2.8 keys by the holder's and the
field's wire id. The value lines record
kind=12 size=16,kind=33 size=6andkind=14 elem=6 size=10.Page silences decided, for the owner rather than quietly
What a text value's HANDLE is. §2.8 makes the entry a real table whose
valueis an ordinary field, and §7.2 gives that field a buffer beside anint32length. Neither says whatFind,InsertandEachhand back whenthe value's storage is TWO members. Decided: the ENTRY, which reaches both.
The two alternatives were rejected on the page's own words. A pointer to the
buffer alone loses the length an
Insertexists to let a caller set. Andwrapping the pair in an unnamed struct, the way §2.6 does for a union ARM,
would move
offsetof( Entry, value_length ), which the descriptor'scount_offsetspells (§8.1), and would contradict §2.8's "It has twofields". The cost of the decision, stated: the handle also reaches
key, soa caller CAN write it and break the order. The emitted comment says not to.
If the page would rather have a narrower handle, this is the line to rule on.
A
bytes(N)value's framing under a map key. §4.2's fuzz list nameskind 33 under a map key and says nothing about a byte buffer there. Decided:
the value takes the FIELD's framing, kind 14 over element kind 6, because
§2.8's rule is that a value is a field of the entry and nothing in it carves
an exception. The baseline records it that way.
The text form of a text value under a map key. §16.2's
map[K]Vrowsays the value takes its own row; §2.8's worked example only ever shows a
table value. Decided: the value's own §16.2 row verbatim, a string as
itself,
wstring(N)transcoded to UTF-8,bytes(N)base64 and padded. Bothengines produce the same bytes, quoted above, so this is now pinned either
way and worth a ruling if it should read otherwise.
Not a silence, checked and closed: a map value never carries a declared
default. The parser refuses a default on the map FIELD by name, and the grammar
has no place to spell one on the value, so the elision rule is exercised at the
empty end only.
Neighbours found and NOT fixed here
The same root cause reaches four more value kinds that #619 does not name. Each
was confirmed by generating and compiling, and each is left for its own issue
rather than widened into this one:
map[K][N]T,map[K][..N]T,map[K][E]Tandmap[K][]T:mapValueStorageTypeanswers the ELEMENT type, so&entry->valueis apointer to an array and the header does not compile. Same class, different
kinds.
map[K]*stringandmap[K]*bytes:mapValueIsPointeris true andTableEntryFoundspells%sAt( entry->value )fromvalue.Type.Name, whicha blob has none of, so the header carries
At( entry->value )and does notcompile.
map[K]*wstringis refused by name at the front end and is not in that set.Merged, not rebased
origin/mainmoved mid-branch when #615 landed. Integrated withgit merge origin/main, thenmake update-goldensover the merge. No goldenwas hand-merged.
Test
make testwhole, log kept. Five hard failures, allError 127, all theabsent toolchains this box was briefed to have:
dotnet(build-cs-cook,build-conformance-cs), dart (build/conformance-dart),elixirc(
build/elixir-tables-ebin), andjavacunder adist/that does not exist(
build-conformance-java). Nothing else failed: every otherFAILEDline inthe log is a negative control going red on purpose. The C++, C, Go and Rust
legs built and ran.
go test ./...is green across 30 packages.🤖 Generated with Claude Code