Repository navigation
fix(analyzer): concrete discriminator bases join their variant unions (#93) - #94
Conversation
…#93) Root cause: the registry only registered mapped and oneOf/anyOf-ref members, while translateRefThroughSeam rewrote every out-of-spine base ref to '{Base}Variant' without checking that the union is ever emitted, and same-family spine refs ignored base membership entirely. Combined, that produced the three reported failures: refs to an unmapped concrete base named an undeclared union type; a self-mapped base intersected its child's literal into 'never'; mapping-children-only families omitted the base from its own union. - register a concrete base (no oneOf/anyOf, or listed in its own mapping) as a family member; literal precedence: self-mapping key > own const > single-value enum > raw schema name - register implicit direct-allOf children as variants (grandchildren excluded); member order: mapping -> oneOf/anyOf -> allOf children, deduped by ref and type name - inside same-family spines a member base renders Omit<Base,'disc'> (membership checked before the bare-name fallback) and the '$disc: string' placeholder is skipped for member bases - empty families fall back to bare base refs and emit no union type - rules identical for 3.0 and 3.1 (registry and mapper are version-agnostic) Intentional snapshot updates: families with concrete bases now include the base in their '{Base}Variant' unions and strip the discriminant from children via Omit. Manual type-assertion replicas re-transcribed from regenerated contracts. Closes #93
|
Summary This PR fixes three discriminator bugs sharing one root cause: Findings
Verified independently: full suite 1667/1667, Verdict✅ Approve — the fix is correct, well-tested, and regression-free on classic patterns; the findings are a pre-existing edge case and documentation nits, none blocking. |
|
Thanks for the review — all four findings addressed in
Suite now 1669/1669 (two new warning tests); check/lint/format clean. |
Summary
Three discriminator bugs shared one root cause: the registry never registered a concrete base as a family member, while the ref seam rewrote every out-of-spine base
$refto{Base}Variant— a union that wasn't guaranteed to exist, didn't contain the base, and collided with children's literals when the base was self-mapped.Concrete-base literal precedence: self-mapping key > own
const> single-valueenum> raw schema name.Evidence
tsc --stricton the output):error TS2304: Cannot find name 'PetVariant'— 3 sites; noPetVarianttype was ever emittedEntityVariantrendered asEntityDetailsalone →FindtypedEntityDetails[]EntityDetails reduced to 'never' because property '"$type"' has conflicting typesnpx vitest run tests/integration/discriminator-concrete-base.test.ts— 20/20, full suite 1667/1667):export type PetVariant = Pet | Cat | Dog;export type EntityVariant = Entity | EntityDetails;,Find → EntityVariant[], details endpoint → exactlyEntityDetailstsc --strict --noEmit, plus P1–P4 payload/narrowing assertions (tests/integration/discriminator-concrete-base.test.ts)Intentional snapshot updates: existing families with concrete bases now include the base in their
{Base}Variantunions and strip the discriminant from children viaOmit(discriminator-allof,mega-spec,data-types-keywords,discriminator-cross-variant).Merge Danger
Door: two-way — analyzer-only codegen change, reverted by reverting the commit; no runtime/CLI surface changes.
Blast radius: generated output shape. Any consumer whose specs have concrete discriminator bases will see their variant unions gain the base as a member and children switch to
Omit<Base, '$disc'>— type-narrowing code written against the old (buggy) unions may need updating. The manual type-assertion replicas were re-transcribed from regenerated contracts to match.Closes #93