Skip to content

openapi: the {X, null} collapse hints a branch pointer differently from a $ref to it #281

Description

@OmarAlJarrah

The {X, null} oneOf/anyOf collapse (nullUnionCollapselowerOneOfAnyOf) 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> (subSchemaHintbranchPointerHint). 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions