Skip to content

openapi: keywords the elected lowering never reads are dropped without a trace #268

Description

@OmarAlJarrah

Summary

lower() elects one keyword family per position (constenumallOf, then the type set) and lowers it. #35 covered the competing families the election passes over. A second, narrower class remains: keywords the elected lowering simply never reads. They are neither modelled nor kept, and nothing is reported.

Two shapes reproduce today:

# 1. type beside allOf — lowerAllOf builds a Model (an object); a non-object
#    type set is never read.
S:
  allOf: [{$ref: '#/components/schemas/Base'}]
  type: string
# → Model{Base: Base}; `type: string` gone, zero diagnostics.

# 2. format beside const — hoistLiteral produces a Literal, which has no
#    Encoding field, and unhomedKeywords only flags `format` when no type is
#    declared beside it.
T: {const: 5, type: integer, format: int32}
# → Literal{5}; `format: int32` gone, zero diagnostics.

Why this is separate from #35

#35's mechanism is "which of several competing families wins", and the answer is a property of the dispatch order alone — the losing families are always losing, whatever the winner produced. This one is a different question: did the lowering that won actually consume this keyword? Answering it needs a per-winner, value-dependent rule rather than a keyword list:

  • allOf + type: object is extremely common and loses nothing (a Model is an object), so a rule keyed on node kind alone would emit a bogus Unmodeled entry and diagnostic on a large share of real specs. Only a non-object type set is lossy.
  • const + a consistent type is likewise redundant rather than lossy; only a contradictory one, or a format the Literal cannot carry, loses anything.

unhomedKeywords/applicatorHome is the existing home for "the node has nowhere to put this", and its contract is deliberately keyed on node kind so it cannot drift from lower()'s dispatch. Extending it here would break that contract, since a *ir.Model reached through lowerAllOf and one reached through lowerModel differ in whether type was read.

So the fix wants its own design decision about what "consumed" means for each winner, taken on its own terms rather than as a side effect of the co-declared-family work.

Expected

Whatever the position elects, a keyword the elected lowering does not read is kept verbatim under Unmodeled with a degraded-construct diagnostic, and one that is read stays silent — in particular allOf + type: object must produce neither an entry nor a diagnostic.

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