feat(generator): unions expose the base every variant composes - #57
Merged
Conversation
When every arm of a oneOf/anyOf embeds the same schema through allOf, the generated wrapper only offered Value any, so reading a field present on all variants meant a type switch that goes stale as variants are added. The union now carries that base and generates an accessor for it: func (u Pet) Base() *PetBase The analyzer sets TypeDef.BaseType after cycle-breaking, when the embedded type sets of all variants intersect in exactly one struct. Unions whose variants share no base, share more than one, or reach it through a cycle-broken pointer keep their current shape. A discriminator is not required: Value holds the same variant values either way, and Base returns nil when it holds none of them.
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 #56.
Problem
When every branch of a discriminated
oneOfcomposes the same schema viaallOf, the branches come out right (the base is embedded in each), but the union discards that fact:Reading a field that is present on every variant by construction costs a type switch, and the compiler cannot tell you when a new variant makes that switch incomplete.
Fix
The union now carries the shared base and generates an accessor for it:
Valueis untouched, so existing call sites keep working.ir.TypeDef.BaseTyperecords, for a union, the type every variant embeds.analyzer.linkUnionBasesruns after cycle-breaking, so it sees each variant's final field shape, and setsBaseTypeonly when the embedded type sets of all variants intersect in exactly one struct. Variants that share no base, share more than one, or reach it through a cycle-broken pointer keep today's shape.distinctVariantskeeps the switch from repeating a case when a spec lists the same$reftwice.A discriminator is not required.
Valueholds the same variant values whether or not the union has one, so an untaggedoneOfwith a shared base gets the accessor too.Tests
internal/analyzer/unionbases_test.go: shared base, base only some variants compose, two shared bases, and a discriminator-free union.internal/generator/e2e_union_base_test.go: generates the issue's spec, asserts a union without a common base gets no accessor, then compiles and runs a test that readsBase().Nameoff decoded values, checksnilfor an unrecognizedkind, and confirms writing through the returned pointer does not mutateValue.gofmt,go vet ./..., andgo test ./...pass.