spec: unbounded []T and []*T in the variable class (#523) - #531
Merged
Merged
Conversation
Contributor
Author
|
Rulings from the cold read, under the owner's word of 2026-09-04:
The named page changes from the read land on this branch before it leaves draft. |
rowan-claude
pushed a commit
that referenced
this pull request
Sep 4, 2026
Ruling 3 on schema#523: the map's storage, walks and measure without the key and the sort, wire-neutral under the existing array kind and the wire's 64-bit counts. Plus the three owner rulings and the thirteen named changes from the cold read of #531. New §2.9. The declaration is `[]T` and `[]*T`, legal in a table body only, refused in a `type` body and therefore on the packet wire, and refused as a union arm. Storage is the map's slot exactly: an `int64` self-relative reference and an `int32` count, sixteen bytes, with the elements laid by value in the holder's node extent, lists and maps ordered as one population pre-order. The wire is kind `14` at the element's own kind with the live count, so `[]T` and `[..N]T` are the same bytes and a bound is added or removed without touching a stored file. The text form is a JSON array with no bound to clamp against. The owner rulings: - ERASE EXISTS, by the map's own mechanism, addressed by the element's POINTER: dead bit, live count, the four walks skip it. The save-edit cycle is the tool's path and a game's inventory removes in slot order, which a map by id cannot keep. Retention then restates §6.6's hazard honestly: a region's count and elements are ordinary writable memory, the hazard applies unchanged, and the builder is not on that path. - `LoadMeasure`'s `-1` CARRIES A REASON, from the enum ruling 8 gives `Open` and `BlockOpen`, one value per clause on §7's own rule, stated once in §6.5: `unknown_form`, `count_over_length`, `count_over_extent_cap`, `blob_over_size_cap`, `data_cycle`. - The index accessor is BOUNDS-CHECKED IN EVERY BUILD, on §2.4's rule: the extent came from a file, so there is no undefined-behavior path in release. The named changes: the two overflow outcomes stated per path, region against builder, with the agreement claim scoped to what either path decodes; the `[][]T` fix spelled as a TABLE wrapper, since a `type` body refuses a list; the four `LoadMeasure` refusals moved to a named unit test with synthetic counts, because a refusal produces no counters and no golden can carry two gigabytes; the baseline rule stated as the capacity fact `bound=` carries when it appears or vanishes, with `elem=`, `type=` and `kind=` still judging; `array=unbounded` added to §20.2's grammar and §20.1's fact table; the map's descriptor columns documented in §8.1 for the first time, with `array_bound = 0` as the tell that a field's offset names a reference rather than the first element, and the list taking them unchanged; the clamp control pinned above 2^16; `list_migrates` and `list_erased` added to the corpus; one clause in SPEC.md §1 scoping "no unbounded collections" to the type wire; the §4.2 and §4.3 cites fixed, `?[..N]T` distinguished from `[]?T` with `[]*T` and a null slot named as what serves; `string(N)` and `bytes(N)` dropped from the element-set sentence, which is now the rule that `[]T` admits exactly what `[..N]T` admits; and the additions count corrected against the bullets that follow it. Specification only, no code. Ports are a named follow-on (§15). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
rowan-claude
force-pushed
the
spec-unbounded-arrays
branch
from
September 4, 2026 15:49
13c169a to
77960b8
Compare
Ruling 3 on schema#523: the map's storage, walks and measure without the key and the sort, wire-neutral under the existing array kind and the wire's 64-bit counts. Plus the four owner rulings and the thirteen named changes from the cold read of #531. New §2.9. The declaration is `[]T` and `[]*T`, legal in a table body only, refused in a `type` body and therefore on the packet wire, and refused as a union arm. Storage is the map's slot exactly: an `int64` self-relative reference and an `int32` count, sixteen bytes, with the elements laid by value in the holder's node extent, lists and maps ordered as one population pre-order. The wire is kind `14` at the element's own kind with the live count, so `[]T` and `[..N]T` are the same bytes and a bound is added or removed without touching a stored file. The text form is a JSON array with no bound to clamp against. The owner rulings: - ERASE EXISTS, by the map's own mechanism, addressed by the element's POINTER: dead bit, live count, the four walks skip it. The save-edit cycle is the tool's path and a game's inventory removes in slot order, which a map by id cannot keep. Retention then restates §6.6's hazard honestly: a region's count and elements are ordinary writable memory, the hazard applies unchanged, and the builder is not on that path. - `LoadMeasure`'s `-1` CARRIES A REASON, from the enum ruling 8 gives `Open` and `BlockOpen`, one value per clause on §7's own rule, stated once in §6.5: `unknown_form`, `count_over_length`, `count_over_extent_cap`, `blob_over_size_cap`, `data_cycle`. The name is #532's to define, so this page cites it by description and section. - The index accessor is BOUNDS-CHECKED IN EVERY BUILD, on §2.4's rule: the extent came from a file, so there is no undefined-behavior path in release. - A MAP IS NOT A UNION ARM EITHER, refused by name in §2.6 and §11 on the same ground `[]T` is: both put their elements in the holder's node extent through a placement walk over what the record reaches BY VALUE, and an arm is reached by value only when its tag says so, so an arm's array would make the extent depend on a discriminant. §2.6's admission of a map at an arm predates this PR and is corrected here, because §2.9's refusal made it load-bearing. No corpus schema declares one. The named changes: the two overflow outcomes stated per path, region against builder, with the agreement claim scoped to what either path decodes; the `[][]T` fix spelled as a TABLE wrapper, since a `type` body refuses a list; the four `LoadMeasure` refusals moved to a named unit test with synthetic counts, because a refusal produces no counters and no golden can carry two gigabytes; the baseline rule stated as the capacity fact `bound=` carries when it appears or vanishes, with `elem=`, `type=` and `kind=` still judging; `array=unbounded` added to §20.2's grammar and §20.1's fact table, and the arm line's own token set widened to name what it refuses; the map's descriptor columns documented in §8.1 for the first time, with `array_bound = 0` as the tell that a field's offset names a reference rather than the first element, and the list taking them unchanged; the clamp control pinned above 2^16; `list_migrates` and `list_erased` added to the corpus; one clause in SPEC.md §1 scoping "no unbounded collections" to the type wire; the §4.2 and §4.3 cites fixed, `?[..N]T` distinguished from `[]?T` with `[]*T` and a null slot named as what serves; `string(N)` and `bytes(N)` dropped from the element-set sentence, which is now the rule that `[]T` admits exactly what `[..N]T` admits; and the additions count corrected against the bullets that follow it. Specification only, no code. Ports are a named follow-on (§15). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
rowan-claude
force-pushed
the
spec-unbounded-arrays
branch
from
September 4, 2026 15:55
77960b8 to
ca6972a
Compare
gafferongames
marked this pull request as ready for review
September 4, 2026 15:56
rowan-claude
pushed a commit
that referenced
this pull request
Sep 4, 2026
rowan-claude
pushed a commit
that referenced
this pull request
Sep 4, 2026
rowan-claude
pushed a commit
that referenced
this pull request
Sep 4, 2026
… its stated scope (#531)
rowan-claude
pushed a commit
that referenced
this pull request
Sep 4, 2026
gafferongames
added a commit
that referenced
this pull request
Sep 4, 2026
…omments (#523) (#532) * spec: the wstring table kind, widening, float16, Open reasons, text comments (#523) Six rulings from #523, specification only. 1. wstring on the table wire: kind 33, `*wstring` under the reserved blob id fnv1a64("wstring"), the cooked storage row, the text row, the LoadMeasure term, the map-key refusal by name, and the #510 closure refusal deleted. 2. Widening on read: an integer kind into a wider one of the same signedness, and f32 into f64, decodes exactly under a new `widened` counter. Every other pair stays kind_mismatch. The silent class does not move. 3. Flags storage stays uint64 in every target, with the reason. 4. float16 declined, kind 34 reserved by name. 5. Open and BlockOpen fill a TableOpenReason beside the null. 6. The text form accepts // and /* */ comments on read and never writes them. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * spec: keep the wstring refusal clause off the byte-buffers bullet's opening lines * spec: ill-formed text is framing-class damage on both text kinds, and the cold read's named changes (#523) * spec: reconcile with the message form (#530), and fix its form-byte fuzzer pin * spec: keep the three-spelling list edits clear of the unbounded-array lines (#531) * spec: the wide-text writer rows are storage built in code, not loaded * spec: name TableOpenReason from the measure's reason table, and widen its stated scope (#531) * spec: the refusal enum is TableRefuseReason, since LoadMeasure's -1 carries it too (#531) * spec: the arm companion list carries wstring, three reserved-id refusals, one header on the refusal table --------- Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
This was referenced Sep 4, 2026
gafferongames
added a commit
that referenced
this pull request
Sep 4, 2026
…sals (#531) The `[]T` and `[]*T` spellings reach the IR as ArrayList, the near-miss spellings `[..]T` and `[0..]T` are refused by name with `[]T` as the fix, and arrays of arrays are refused where a second bracket stands. The construct's own refusals land beside the element's: a `type` body, a union arm, `?[]T`, a specified default, a qualification, and the three claimed names `<Table><Field>{Add,Each,Erase}`. `TableList` joins the unit-level runtime claim. Every port refuses a unit that declares one, naming the fields and cpp as the carrier, and the tool's cook surfaces refuse one on the map's terms. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
gafferongames
added a commit
that referenced
this pull request
Sep 4, 2026
…sals (#531) The `[]T` and `[]*T` spellings reach the IR as ArrayList, the near-miss spellings `[..]T` and `[0..]T` are refused by name with `[]T` as the fix, and arrays of arrays are refused where a second bracket stands. The construct's own refusals land beside the element's: a `type` body, a union arm, `?[]T`, a specified default, a qualification, and the three claimed names `<Table><Field>{Add,Each,Erase}`. `TableList` joins the unit-level runtime claim. Every port refuses a unit that declares one, naming the fields and cpp as the carrier, and the tool's cook surfaces refuse one on the map's terms. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
gafferongames
added a commit
that referenced
this pull request
Sep 4, 2026
…ions (#531) The wire engine writes and reads a `[]T` as the kind 14 body a `[..N]T` writes: the same element kind, the same count, the same elision of an empty one. A count above the int32 storage cap is the refusal LoadBuilder answers NULL for, and the slots are grown against the body's own length so a count no body can cover allocates nothing. `clamped` cannot fire on the count, because there is no bound to clamp against. The text form is the JSON array it already was, with every element the text carries read. The node walk reaches a list at its field's position, in index order, so a `[]*T` declared before a pointer field numbers the shared node first. The baseline renders `array=unbounded` with no `bound=`, which is what makes a bound added warn and a bound removed pass, and the cook projection renders `kind=14 array=unbounded elem=` the element's own storage size beside the sixteen-byte slot's `size=`. Proved end to end through `pack` and `unpack` over §2.9's own example, and the same content under `[..8]T` packs to byte-identical bytes. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
gafferongames
added a commit
that referenced
this pull request
Sep 4, 2026
…531) Every claim §2.9 makes about the halves this branch carries is a test with a negative control behind it: the checker's refusals, the wire's index order and its shared nodes, the empty list's elision, the element-kind rule, a count the body cannot cover, the walk order at a `[]*T` declared before a pointer, the baseline's `array=unbounded` with no `bound=` in both directions, and the cook projection's sixteen-byte slot. No code generator carries the construct, so every target refuses a unit that declares one by name and the tool's cook surfaces refuse on the map's terms. §2.9's and USAGE's status lines say what is now true: the front end takes the spelling and holds every refusal, `pack` and `unpack` read and write one, and the C++ reference lands the codec next. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
gafferongames
added a commit
that referenced
this pull request
Sep 4, 2026
…#531) The gate the struct-array walk needed: a `[]Row` whose element holds a `*Leaf` reaches that node from inside each element, in index order, so two elements naming one node hold one node and a null slot stays null. The control that drops the list from the struct-array case goes red on the sharing. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
gafferongames
added a commit
that referenced
this pull request
Sep 4, 2026
RefuseTableLists, UnitHasList and ListFieldsOf had no caller: the port refusal lives in compiler/tableslists.go beside the map's, and the two gates the codegen would use land with the codegen. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
gafferongames
added a commit
that referenced
this pull request
Sep 4, 2026
…531) Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
gafferongames
added a commit
that referenced
this pull request
Sep 4, 2026
…531) Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
gafferongames
added a commit
that referenced
this pull request
Sep 4, 2026
…arrier (#531) golangci-lint named registerListCarrier unused, which it is: no code generator carries the construct, so the registry the map's file keeps lands here with the first carrier and the refusal names the target directly. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
gafferongames
added a commit
that referenced
this pull request
Sep 4, 2026
…sals (#531) The `[]T` and `[]*T` spellings reach the IR as ArrayList, the near-miss spellings `[..]T` and `[0..]T` are refused by name with `[]T` as the fix, and arrays of arrays are refused where a second bracket stands. The construct's own refusals land beside the element's: a `type` body, a union arm, `?[]T`, a specified default, a qualification, and the three claimed names `<Table><Field>{Add,Each,Erase}`. `TableList` joins the unit-level runtime claim. Every port refuses a unit that declares one, naming the fields and cpp as the carrier, and the tool's cook surfaces refuse one on the map's terms. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
gafferongames
added a commit
that referenced
this pull request
Sep 4, 2026
…ions (#531) The wire engine writes and reads a `[]T` as the kind 14 body a `[..N]T` writes: the same element kind, the same count, the same elision of an empty one. A count above the int32 storage cap is the refusal LoadBuilder answers NULL for, and the slots are grown against the body's own length so a count no body can cover allocates nothing. `clamped` cannot fire on the count, because there is no bound to clamp against. The text form is the JSON array it already was, with every element the text carries read. The node walk reaches a list at its field's position, in index order, so a `[]*T` declared before a pointer field numbers the shared node first. The baseline renders `array=unbounded` with no `bound=`, which is what makes a bound added warn and a bound removed pass, and the cook projection renders `kind=14 array=unbounded elem=` the element's own storage size beside the sixteen-byte slot's `size=`. Proved end to end through `pack` and `unpack` over §2.9's own example, and the same content under `[..8]T` packs to byte-identical bytes. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
gafferongames
added a commit
that referenced
this pull request
Sep 4, 2026
…531) Every claim §2.9 makes about the halves this branch carries is a test with a negative control behind it: the checker's refusals, the wire's index order and its shared nodes, the empty list's elision, the element-kind rule, a count the body cannot cover, the walk order at a `[]*T` declared before a pointer, the baseline's `array=unbounded` with no `bound=` in both directions, and the cook projection's sixteen-byte slot. No code generator carries the construct, so every target refuses a unit that declares one by name and the tool's cook surfaces refuse on the map's terms. §2.9's and USAGE's status lines say what is now true: the front end takes the spelling and holds every refusal, `pack` and `unpack` read and write one, and the C++ reference lands the codec next. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
gafferongames
added a commit
that referenced
this pull request
Sep 4, 2026
…#531) The gate the struct-array walk needed: a `[]Row` whose element holds a `*Leaf` reaches that node from inside each element, in index order, so two elements naming one node hold one node and a null slot stays null. The control that drops the list from the struct-array case goes red on the sharing. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
gafferongames
added a commit
that referenced
this pull request
Sep 4, 2026
RefuseTableLists, UnitHasList and ListFieldsOf had no caller: the port refusal lives in compiler/tableslists.go beside the map's, and the two gates the codegen would use land with the codegen. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
gafferongames
added a commit
that referenced
this pull request
Sep 4, 2026
…531) Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
gafferongames
added a commit
that referenced
this pull request
Sep 4, 2026
…531) Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
gafferongames
added a commit
that referenced
this pull request
Sep 4, 2026
…arrier (#531) golangci-lint named registerListCarrier unused, which it is: no code generator carries the construct, so the registry the map's file keeps lands here with the first carrier and the refusal names the target directly. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
gafferongames
added a commit
that referenced
this pull request
Sep 4, 2026
…nd text (#531) (#548) * wip: unbounded arrays, the front end and the ir (uncommitted at the stall) * tool: the unbounded-array front end, the IR facts and the ported refusals (#531) The `[]T` and `[]*T` spellings reach the IR as ArrayList, the near-miss spellings `[..]T` and `[0..]T` are refused by name with `[]T` as the fix, and arrays of arrays are refused where a second bracket stands. The construct's own refusals land beside the element's: a `type` body, a union arm, `?[]T`, a specified default, a qualification, and the three claimed names `<Table><Field>{Add,Each,Erase}`. `TableList` joins the unit-level runtime claim. Every port refuses a unit that declares one, naming the fields and cpp as the carrier, and the tool's cook surfaces refuse one on the map's terms. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * tool: the unbounded array on the wire, in the text and in the projections (#531) The wire engine writes and reads a `[]T` as the kind 14 body a `[..N]T` writes: the same element kind, the same count, the same elision of an empty one. A count above the int32 storage cap is the refusal LoadBuilder answers NULL for, and the slots are grown against the body's own length so a count no body can cover allocates nothing. `clamped` cannot fire on the count, because there is no bound to clamp against. The text form is the JSON array it already was, with every element the text carries read. The node walk reaches a list at its field's position, in index order, so a `[]*T` declared before a pointer field numbers the shared node first. The baseline renders `array=unbounded` with no `bound=`, which is what makes a bound added warn and a bound removed pass, and the cook projection renders `kind=14 array=unbounded elem=` the element's own storage size beside the sixteen-byte slot's `size=`. Proved end to end through `pack` and `unpack` over §2.9's own example, and the same content under `[..8]T` packs to byte-identical bytes. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * tool: the unbounded array's gates, and the backend status it leaves (#531) Every claim §2.9 makes about the halves this branch carries is a test with a negative control behind it: the checker's refusals, the wire's index order and its shared nodes, the empty list's elision, the element-kind rule, a count the body cannot cover, the walk order at a `[]*T` declared before a pointer, the baseline's `array=unbounded` with no `bound=` in both directions, and the cook projection's sixteen-byte slot. No code generator carries the construct, so every target refuses a unit that declares one by name and the tool's cook surfaces refuse on the map's terms. §2.9's and USAGE's status lines say what is now true: the front end takes the spelling and holds every refusal, `pack` and `unpack` read and write one, and the C++ reference lands the codec next. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * tool: a list of tables is descended for the edges inside its elements (#531) The gate the struct-array walk needed: a `[]Row` whose element holds a `*Leaf` reaches that node from inside each element, in index order, so two elements naming one node hold one node and a null slot stays null. The control that drops the list from the struct-array case goes red on the sharing. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * tool: the unused half of the wip IR file, removed (#531) RefuseTableLists, UnitHasList and ListFieldsOf had no caller: the port refusal lives in compiler/tableslists.go beside the map's, and the two gates the codegen would use land with the codegen. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * docs: the comparison table says which half of the construct is live (#531) Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * test: the list text case ranges over int, as the modernize pin wants (#531) Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * tool: the list refusal carries no carrier registry until there is a carrier (#531) golangci-lint named registerListCarrier unused, which it is: no code generator carries the construct, so the registry the map's file keeps lands here with the first carrier and the refusal names the target directly. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
rowan-claude
pushed a commit
that referenced
this pull request
Sep 5, 2026
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
gafferongames
added a commit
that referenced
this pull request
Sep 5, 2026
* tables: the C++ codec for unbounded arrays, work in progress (#531) Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * tables: cook-check refuses a map slot by name where its scan meets one The unit-level map refusal at cook-check refused every cook from a unit that declares a map anywhere, which is the whole tables/lists corpus. The scan now refuses the slot it cannot bound, naming the field, the reference that reads it and schema#380 as the PR that lands the clause, and a cook of a map-free root in the same unit checks as any other does. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * tables: the list controls run as one target, and the element-kind control's script survives make A blank line inside the aggregate target's continuation ended its dependency list, and the element-kind control's sed script carried commas and an unbalanced parenthesis that make's call split and cut. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * tables: pin the header goldens of the lists corpus Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * tables: re-pin the C++ table goldens under the list adapters and the §8.1 columns Every list-free unit gains the JSON walk's three list adapter stubs, as it carries the map's, and the map units take the descriptor columns §8.1 now names, the TableRefuseReason enum and the extent carve. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * tables: the list test's loop ranges over the count Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * tables: the cold read's five changes on the list codec The update-goldens recipe's for-loop is whole again: the blank line after its first continuation is gone, and GNU Make 3.81 runs it through. The preorder negative control sabotages both writers of a list whose element holds a map, the pack's extent walk and the cook's extent writer, and runs schema cook-check on the cook the sabotaged gate wrote. Both of the instruments the page names go red: the pinned list_of_maps_cook byte compare, and the containment clause, the array leaves the node. The page states the cook-check stopgap under 7.4 item 4: the tool refuses a map slot by name where its scan meets one until schema#380 lands the clause, and the #380 entry in 15 names that clause as the piece owed. The map's wire extent tests the int32 cap before the body's L, so a map count above the cap answers count_over_extent_cap exactly as a list's does, one rule for both constructs. The measure-refusals battery builds a synthetic Army wire with the map's count as its knob and asserts both reasons, with a clean map-holding wire beside them. The eight Table.h goldens the emission moves are re-pinned. The em dash in TestToolRefusesMapsByName's comment is a period. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5.1 <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.
Ruling 3 on #523: unbounded
[]Tand[]*Tin the variable class, yes. The map's storage, walks and measure without the key and the sort, wire-neutral under the existing kind and 64-bit lengths.Specification only, no code. New §2.9, plus the clauses the construct needs in §2, §2.2, §2.6, §2.8, §3, §4, §4.1, §6.5, §6.6, §7.2, §7.4, §8.1, §11, §15, §16.2, §18.1, §18.2, §20.1 and §20.2, and one clause in SPEC.md §1.
Updated for the cold read of #531: three owner rulings and thirteen named changes, all made.
The construct
[]Tand[]*T, legal in a table body only. Atypebody refuses one by name, which keeps SPEC.md's "no unbounded collections" true of the type wire and refuses one on a packet. A UNION ARM refuses one too (ruling below).int64self-relativeTableRefand anint32count, sixteen bytes, with the elements laid by value in the holder's node extent atalignof( T ), zero slack. Lists and maps are one population in that extent, placed pre-order in the declaration order of the fields that hold them.14at the element's own element kind with the live count, elided at zero.[]Tand[..N]Tare the same bytes, and a bound is added or removed without touching a stored file.LoadMeasure's term isN × sizeof( T )rounded up toalignof( T ), at every depth, reached by the same header walk a map'sNneeds. The block form is none, by absence.The three owner rulings from the cold read
LoadBuilder, edit,Save) is the tool's path, and a game's inventory removes items in slot order, which amap[K]Tkeyed by an id cannot keep. Indices are not stable across an erase and the pointer is, which is why the pointer is the handle.The retention paragraph now states §6.6's hazard honestly: on the region path a list's count and elements are ordinary writable memory like any array's, §6.6's rule applies unchanged, and the builder is irrelevant to retention because retention is a region round trip.
LoadMeasure's-1carries a reason, from the same enum ruling 8 givesOpenandBlockOpen(§7, §19.2), with the same spellings in every target. Stated once in §6.5 as a table, on §7's own rule of one value per clause:unknown_form,count_over_length,count_over_extent_cap,blob_over_size_cap,data_cycle(the authoring side's). §2.9 cites it rather than restating it. A refusal still moves no counter.size()came from a file,NDEBUGdoes not remove the compare, and there is no undefined-behavior path in any configuration. C++ asserts then aborts, C# throws, Rust and Go panic.mapis not a union arm either, refused by name in §2.6 and §11 on the same ground[]Tis: both put their elements in the holder's node extent through a placement walk over what the record reaches by value, and an arm is reached by value only when its tag says so, so an arm's array would make the extent's contents and every offset after it depend on a discriminant — a layout no cook can be byte-stable under and nocook-checkclause can bound. §2.6's admission of amapat an arm predates this PR; it is corrected here because §2.9's refusal made it load-bearing. No corpus schema declares one, so the refusal breaks nothing, and §20.2's arm grammar already excludedarray=map— the prose now says why.The thirteen named changes
LoadMeasureanswers-1with its reason, noLoad, no report; into a builder, a count the body cannot cover is §4'smalformedwith the covered prefix kept, and a count above theint32cap is a NULL fromLoadBuilderwith the partial builder discarded and the report as it stood. "Never disagree about a wire" is scoped: the two paths agree on every wire either of them decodes, and where they differ is at a refusal, which is not a decode. The fiveLoadMeasurecontrols moved off thereportsurface.[][]Tfix is a TABLE wrapper, spelled — SPEC.md's "wrap the inner array in a type" does not reach here, because atypebody refuses a list:int32cap control is a named unit test,make tables-list-measure-refusals, with synthetic counts: no golden can carry two gigabytes, and a refusal produces no counters for areportrow to pin. It covers the cap, the count overL, both at depth, and a clean wire beside them.bound=appearing or vanishing on anarray=move is the capacity fact, judged as a shrink (warn) or a growth (silence);elem=,type=,enum=,union=andkind=keep judging exactly as before. It still bumps no rendering version.unboundedadded to §20.2'sarray=grammar, with the prose and a row in §20.1's fact table. It carries nobound=, for the map's reason.kind/element kind,element_sizeas the pitch,countedwithcount_offset,tablenaming the element's descriptor, andarray_bound = 0as the one tell that the field'soffsetnames anint64reference rather than the first element. Zero is free because every other shape's bound is at least one, so it costs no new column in nine ports.Backend status is stated on the page, because the reference does not carry it yet. The C++ map descriptor emitted today leaves
kindat0andis_arrayfalse and describes the map through the entry's ownTableTypeInfobeside three map-specific function columns (map_count,map_at,map_insert). It moves to the columns above when the list lands, and they land together for one reason: a second out-of-line shape would otherwise need a second set of function columns, and three per construct is how a descriptor becomes a per-construct API instead of a vocabulary. The resolver the text walk genuinely cannot spell for itself stays; what changes is that the SHAPE is read from the array columns like every other array's rather than inferred from a non-NULLentry.[]Tas a union arm: refused by name in §2.6 and §11. An arm's storage is overlaid and a list's elements live in the holder's node extent for every list the record reaches by value, so an arm's array would make the extent depend on the tag. An arm holding a list is a table arm's job.list_migratesgolden added: one content, two declarations of the holder ([..N]Tand[]T), one pinned wire both write byte for byte and both read into equal values, report silent both directions.list_erasedadded beside it for the dead-element control.mapand[]Tand why neither is reachable from a packet.?[..N]T(a presence bit on the array, landed) distinguished from[]?T(an element's presence bit, a follow-on); and the additions count corrected against the bullets that follow it.string(N)andbytes(N)dropped from the element-set sentence, which is now the rule rather than a list: whatever[..N]Tadmits,[]Tadmits, and whatever it refuses,[]Trefuses on the bounded array's own diagnostic.[]*Twith a null slot named as what serves the[]?Tcase today.The decisions the page made beyond the ruling
The element set is
[..N]T's and not the map's value list, so every element refusal is one an array already carries; no entry type is generated, which is where four of the map's six framing bytes an entry go;clampedcannot fire on the count; what bounds a load is the arena, the caller's allocation underLoadMeasure, and theint32count slot; the construct claims three names against its field (Add,Each,Erase) where a map claims eight; §4.1's silent class stays at four; retain-unknown addresses an element by its ordinal (§6.6); and position here is not a vocabulary, so §2.4's[E.Max]Trefusal is not reopened.Ports
Backend status is the map's: the C++ reference and the tool carry it, every other backend refuses a unit that declares one by name, and the ports are a named follow-on (§15). What a port needs is smaller than what a map needed, by exactly the key.
Merge check
Rebased onto
mainat 8fd34f1 (after #507, #527, #529, #530).git merge-tree --write-treereports zero conflicts againstmainand against #532's head. #530's §3.3, its reserved-id rule and its §5/§11 edits touch nothing this PR touches: an unbounded array spends no reserved id and adds no framing, so the message form carries it exactly as the file form does. The §11 and §6.6 edits were re-checked in place after the rebase.Two edits were deliberately relocated to keep them off lines #532 touches: the §11 runtime-name claim and the §16.2 text row.
One landing-order note: §6.5's refusal-reason table shares the enum ruling 8 gives
OpenandBlockOpen, which #532 defines asTableOpenReason. This page refers to it by description and by section rather than by type name, so nothing here claims that name or conflicts with it. Whichever of the two merges second adds the back-reference; a build with one has the other.🤖 Generated with Claude Code