feat(irverify): reject a union that declares no variants - #360
Open
OmarAlJarrah wants to merge 1 commit into
Open
feat(irverify): reject a union that declares no variants#360OmarAlJarrah wants to merge 1 commit into
OmarAlJarrah wants to merge 1 commit into
Conversation
An ir.Union carrying an empty Variants slice was reported by nothing. irverify had no rule reading Variants at all, and the one place pass reads them (checkUnionDiscriminator) folds them into a membership set and returns immediately when the union declares no discriminator. A union is the choice between its variants, so a union of none is a type no value inhabits, and no source format expresses one. A union that reaches the IR with none was built by a lowering that dropped every variant it meant to add -- our bug, which is what makes it a Violation rather than an ir.Diagnostic. Downstream it is worse than the missing variants: an emitter switching over the variants renders a type with no arms and no error, so the loss surfaces as generated code that compiles and can never be constructed. The two neighbouring shapes the issue raised are settled by what the compiler actually produces rather than by inspection. oneOf with one $ref lowers to a union of exactly one variant, and oneOf naming one $ref twice lowers to two variants sharing a target; both come from documents the specification allows, so neither is evidence of a compiler defect and neither is reported here. Both are pinned as clean so a later tightening has to argue with a test.
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
An
ir.Unioncarrying an emptyVariantsslice was reported by nothing.irverifyhad no rule that readVariantsat all, and the one placepassreads them is
checkUnionDiscriminator, which folds them into a membership setfor the discriminator mapping and returns immediately when the union declares no
discriminator:
So this verified clean and validated clean:
A union is the choice between its variants, so a union of none is a type no
value inhabits. It is not a shape any source format can express, so a union that
reaches the IR with none was built by a lowering that dropped every variant it
meant to add. That is our bug rather than a spec-author problem, which is what
makes it an
irverify.Violationand not anir.Diagnostic. Downstream it isworse than the missing variants are on their own: an emitter switching over a
union's variants renders a type with no arms and no error, so the loss surfaces
as generated code that compiles and can never be constructed.
checkUnionsreportsir/union-no-variantsper offending union. Unions liveonly in the type registry — invariant #3 keeps every named entity there and lets
no node embed another — so it iterates the registry and needs no walk, which is
also why it is not a
walkChecksentry: those all owe a truncation flag, and aregistry iteration has nothing to truncate.
The two adjacent questions, settled by probe rather than by inspection
The issue asked whether a one-variant union and a repeated variant target are
the same defect. Compiling them decides it:
Both come out of legal documents. Invariant #2 is what forbids a compiler
collapsing the single-variant case, and a repeated target is degenerate rather
than impossible. A
Violationclaims a compiler defect, so neither belongs inthis channel — if the repeated target is worth reporting at all it is a
spec-author problem for
pass.Validate, which is deliberately out of scope hereand recorded as such in
checkUnions' doc comment. Both are pinned as clean, soa later tightening has to argue with a test rather than slip through.
Test plan
Four tests in
ir/irverify, each confirmed to redden against a planted defect:TestVerify_UnionWithNoVariantsIsAViolation— removing thecheckUnionscallfrom
Verifyfails it with"[]" should have 1 item(s), but has 0.TestVerify_SingleVariantUnionIsClean— widening the predicate tolen(u.Variants) > 1fails it withShould be empty, but was [{ir/union-no-variants ...}].TestVerify_RepeatedVariantTargetIsClean— pins the second passed shape.TestVerify_NilTypeBesideAUnionDoesNotPanic— holds the report-only guaranteeat this check; a nil registry entry stays
checkRegistryKeys' to report.unionViolationsfilters by code, so a test asserting "clean" is not satisfiedby some unrelated violation being absent.
Coverage note, stated rather than implied: no committed fixture reaches this
check, because
oneOf: []is refused before it lowers and nothing else producesan empty union. That is the intended state — the check guards against a future
lowering bug, not against a spec shape — so the unit fixtures above are what
exercise it, and the corpus is expected to stay silent.
Full gate green:
gofmt,go vet ./...,golangci-lint run(0 issues),go build ./...,./scripts/check-coverage.sh(all 4952 statements covered).Closes #318