fix(compilers/openapi): stop an empty enum widening the type - #351
Open
OmarAlJarrah wants to merge 1 commit into
Open
fix(compilers/openapi): stop an empty enum widening the type#351OmarAlJarrah wants to merge 1 commit into
OmarAlJarrah wants to merge 1 commit into
Conversation
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.
Summary
enum: []is legal JSON Schema, and it fixes the value space to the empty set: the position it iswritten at accepts no instance. The compiler read the keyword off
len(enum) > 0, which cannottell an empty member list from an absent one, so the enum was elected by no lowering and preserved
by none either. The position then widened to whatever its siblings admitted — the top type where
nothing else was written, the declared type where something was — with no diagnostic and nothing
under
Unmodeled. A schema matching nothing became a type admitting everything, silently.The same
len(...) > 0test was spelled at three sites, so this is one predicate rather than onesite: the family election (
declaresFamily), the union-sibling test (declaresShape), and thedistribution test (
composesAsModel). All three now ask whether the keyword is written at all(
enumWritten), which is the distinction the parser already keeps — an absent keyword leaves thefield nil, an empty list leaves it non-nil and empty.
What it lowers to. A closed
Enumadmits its members and nothing else, so a closedEnumwithno members is the empty value space — exact, not an approximation, and reached without inventing an
IR node. The IR has no bottom
TypeKind; this is how it spells one, andir-design.md§4.5 nowsays so, since an emitter reading the node needs the rule (TypeScript's
never, and no widening tothe top type). The neighbouring construct settles for less: a boolean
falseschema also matchesnothing and lowers to a closed empty
Model, which still admits{}— filed as #350 rather thanchanged here, because that rule is normative in §4.8 and reaches composition.
A
warningannounces it either way. The document is well-formed, so this is not a refusal, butnobody writes an empty member list on purpose — it is what a generator emitting a list it never
filled produces, and every position reaching it is uncallable.
Nothing is traded away for the new node. An
Enumhas no home for a property set or a composition,so
{type: object, properties: {...}, enum: []}and{allOf: [...], enum: []}keep what theydeclare beside the enum through the existing unhomed-keyword and skipped-family paths, each with
its own diagnostic.
On #318 (nothing rejects a
Unionwith no variants): this lowering cannot produce one. Theempty case returns before
enumMembers, soenumAsUnion— which mints one variant per member — isnever reached with an empty list. No
irverifyrule is added and no part of #318 is closed; thegap it names is still open for whatever else can reach it.
Merge ordering. #333 rewrites the nullability predicate in the same file and expects a textual
conflict here; the two are independent. It deliberately left an empty enum silent in that
predicate so this decision stayed open, and this change does not settle it: whether a reference
to an empty enum admits null is still computed from the type keyword, so
{type: [T, "null"], enum: []}reads as nullable at its uses. That is #288's shape exactly — anullable type array beside an enum listing no null member — and belongs there, not here. The code
comment at
emptyEnumsays so in the place the next reader reaches.Test plan
TestEnum_EmptyMemberListMatchesNoValue— one row per position the widening followed (bare,beside a type keyword, beside a property set, beside an
allOf, beside a nullable type array),asserting the closed memberless Enum, its
ValueType, one warning, and the co-declared keywordkept under
Unmodeled. The last row is a populated enum, so a change reaching every enum ratherthan the degenerate one fails.
TestEnum_EmptyMemberListIsAUnionSibling— the site that never reaches the family dispatch:{enum: [], oneOf: [...]}must not lower as the union alone.empty-enum+ golden: the corpus had noenum: []anywhere, so the goldenscould not have moved. Verified the fixture bites — deleting only the
enum: []line fromNothingreddensTestConformance/empty-enum(got *ir.Scalar). The corpus sweeps also put itthrough the harness oracles (irverify, round-trip, determinism, two-order):
ok.lowerEnumarm reverted and every testkept, all six subtests plus the conformance case go red.
gofmt,go vet,golangci-lint,go build, coverage at 100%.Closes #278