The {X, null} oneOf/anyOf collapse (nullUnionCollapse → lowerOneOfAnyOf) lowers the surviving branch at the branch pointer but passes the enclosing schema's hint, while hoistSubSchema — reached by an outside $ref naming that same pointer — derives variant_<index> (subSchemaHint → branchPointerHint). Only the first lowering to arrive interns the node, so the same components compiled in two declaration orders produce two different documents, silently.
openapi: 3.1.0
info: {title: O, version: "1.0.0"}
paths: {}
components:
schemas:
S:
oneOf:
- {type: string, description: the branch}
- {type: "null"}
Outsider: {$ref: '#/components/schemas/S/oneOf/0'}
Compiled with Outsider first, t/anon/components/schemas/S/oneOf/0 is hinted variant_0; with S first, S. cmp.Diff over the two Documents shows only that field. The branch must declare something position-scoped (a description here) for it to own a node at all — a bare {type: string} branch resolves to the shared primitive and never competes.
Why this is filed on its own
This is #181's mechanism at a site #181 did not sweep. That issue closed the inline-branch half by giving hoistSubSchema branchPointerHint, so the union lowering and the pointer walk agree — but the agreement is between branchHint and subSchemaHint, and the collapse path uses neither. It hands the branch the hint belonging to the schema that encloses it.
It is also not what #35 was about, and #270 does not touch it: that change stops the collapse only when a schema co-declares anyOf, which removes one shape from the exposure and leaves the ordinary single-combinator {X, null} collapse — by far the common one — exactly as it was. Confirmed present on main and on fix/openapi-codeclared-composition alike.
Expected
The branch keeps the hint the composition would give it, so both paths agree whichever arrives first — the same resolution #181 took, extended to the collapse. orderInvariantIR() excludes Naming.Hint today and documents the exclusion as a live gap; closing this shrinks what that exclusion is covering for.
Method
Compiled both orders through openapi.New().Compile(...) and diffed the whole ir.Document with only SourceInfo.Hash ignored.
The
{X, null}oneOf/anyOfcollapse (nullUnionCollapse→lowerOneOfAnyOf) lowers the surviving branch at the branch pointer but passes the enclosing schema's hint, whilehoistSubSchema— reached by an outside$refnaming that same pointer — derivesvariant_<index>(subSchemaHint→branchPointerHint). Only the first lowering to arrive interns the node, so the same components compiled in two declaration orders produce two different documents, silently.Compiled with
Outsiderfirst,t/anon/components/schemas/S/oneOf/0is hintedvariant_0; withSfirst,S.cmp.Diffover the twoDocuments shows only that field. The branch must declare something position-scoped (adescriptionhere) for it to own a node at all — a bare{type: string}branch resolves to the shared primitive and never competes.Why this is filed on its own
This is #181's mechanism at a site #181 did not sweep. That issue closed the inline-branch half by giving
hoistSubSchemabranchPointerHint, so the union lowering and the pointer walk agree — but the agreement is betweenbranchHintandsubSchemaHint, and the collapse path uses neither. It hands the branch the hint belonging to the schema that encloses it.It is also not what #35 was about, and #270 does not touch it: that change stops the collapse only when a schema co-declares
anyOf, which removes one shape from the exposure and leaves the ordinary single-combinator{X, null}collapse — by far the common one — exactly as it was. Confirmed present onmainand onfix/openapi-codeclared-compositionalike.Expected
The branch keeps the hint the composition would give it, so both paths agree whichever arrives first — the same resolution #181 took, extended to the collapse.
orderInvariantIR()excludesNaming.Hinttoday and documents the exclusion as a live gap; closing this shrinks what that exclusion is covering for.Method
Compiled both orders through
openapi.New().Compile(...)and diffed the wholeir.Documentwith onlySourceInfo.Hashignored.