check: the enum-bound refusal follows provenance through a table closure (#605) - #657
Merged
Conversation
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>
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-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>
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>
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>
House style takes none in anything added here, and the heading carried one. 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.
Closes #605.
[E.Max]Twas refused in a table body and a union arm by its SPELLING, while SPEC-TABLES.md §2.4 and §11 state the refusal on the bound's PROVENANCE.[E.Count]Tand[N]Tunder aconst Nthat folds from either compiled clean, and each one reopens the positional class §4.1 counts as closed.Red first (86d0073)
test/tables/enumboundholds one unit a shape. Against that commit,./bin/schema checkon each file:TestEnumBoundProvenanceCorpusreads the directory and holds each file to its answer. At the red-first commit all ten refused rows fail and the three controls pass:The checker (66e4cdc)
enumBoundProvenancefollows 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: aflagsbound is refused by its own rule, and a bound reaching neither is a plain positional array that stands wherever it is spelled.checkPositionalEnumBoundInClosureruns 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 atypein.The walk is the fence. It starts at TABLE bodies and descends UNIONS alone.
typea table closure reaches is never visited, so Owner ruling wanted: an enum-extent array in a type rides a different table-wire kind depending on whether a table holds the type #606 keeps its ruling. Widening this walk totypebodies is the one edit that would decide it, and it is not made here.typeno table reaches is not in the closure at all, so the packet wire is untouched.The diagnostics, green:
exprSpellingnow renders constant arithmetic, so a bound like[Grade.Max + 1]is quoted back as the source spells it instead of as the placeholderN.What the refusal caught in the tree
test/tables/V1.schemaandV2.schemasizedtallyin both generations andledgerin V1 as[Grade.Max + 1]int32: a positional array in a table body whose bound folds from an enum, which is exactly the shape the page says is refused. Two evolution fixtures rested on the gap.Both keep their extents, 3 in V1 and 4 in V2, spelled now as the plain constants
TallySlotsandLedgerSlots, so every generation the evolution test exercises is unmoved: the growing bound, the clamped count on the narrow read, and V1-positional against V2-keyed forledger.schema fmtre-canonicalized the attribute column in both files. No wire byte and no golden moved.Controls (53bb48d)
make check-enum-bound-negative-control, wired intomake testbesidecheck-zero-range-negative-control. It compiles no C++.The gate: every refused shape is refused, each diagnostic names the field or the arm, the enum, the constant where the bound reaches the enum through one, and
[E]Tas the fix; each arm row names the table that reaches the union.The three positive controls compile:
ControlPacket(a packet-only unit spelling all five bounds),ControlPlain(a bound that folds from no enum, in a table body and a union arm),ControlTypeHeld(atypea table reaches, #606's case).The negative control is §2.4's own: it removes the CONSTANT FOLD from the bound check through
go build -overlay, writing no tracked file. It is targeted rather than blanket, so the four direct spellings must stay refused under the same sabotage.The page (ea4a622)
Silences for the owner
type-held case is untouched and undecided.ControlTypeHeld.schemapins that atypea table reaches keeps the spelling, so whichever way Owner ruling wanted: an enum-extent array in a type rides a different table-wire kind depending on whether a table holds the type #606 goes, the fixture names the case rather than leaving it implicit. Deleting that control is the edit that reopens the question.tally's fixture intent narrowed. Its comment read "sized off Grade.Max + 1, which grows when Grade does". The bound can no longer fold from Grade in a table body, so the fixture now grows 3 to 4 by a plain constant. If the intent was specifically "the bound tracks the enum", the table-wire answer for that is[Grade]int32, keyed, which is a different fixture and a wire move; I did not make it.ledgerwas the "a keyed array made positional" evolution fixture and its V1 side is now a positional array with a non-enum bound. The wire evolution it exercises, a positional kind read as a keyed one, is unchanged; what it no longer demonstrates is that side being spelled from the enum, because that spelling is now refused.checkUnionArmBoundsdescends an arm that is itself a union, but a corpus unit for it is refused first by "no table reaches" for an unrelated reason, so no row holds that path.Toolchain:
go test ./...green,make checkgreen, the diagnostics suite green,make testrun to the C# leg (#599, dotnet absent).🤖 Generated with Claude Code
Two things the merge carried, and neither is this issue's
goldens: re-pin maps/TextTable.cpp. main is red at 19a2372 on its own CI run 34081461383 and was green at bb5f3bd. #625 movedinternal/codegen/cpptable/json.goand re-pinned the map goldens it knew about,DepthTable.cpp,FleetTable.cppandRowsTable.cpp, each by the same 91 lines;Text.schemaand its golden landed in #626, which merged after #625's branch was cut, somaps/TextTable.cppnever got the re-pin andmake tables-block-zero-costreports it moved. The file here is the generator's own output, copied by the pathmake update-goldenscopies 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. This commit belongs to #625/#626 and is here only because this branch cannot be green without it.No golden moved for this change.
go test ./internal/goldensis green, the C++ tables leg byte-compares every pinned wire golden and passes, and the tree is clean after generation, somake update-goldenswas not needed for anything in this PR.