Skip to content

openapi: a single null branch lowers to a union with no variants #416

Description

@OmarAlJarrah

Problem

An anyOf whose only branch is {"type": "null"} lowers to a Union type node that declares no
variants at all. irverify rejects the result, so the compiler produces a document its own
invariant checker calls invalid, and morphic compile still exits 0.

Minimal spec:

{"components":{"schemas":{"Fuzzed":{"anyOf":[{"type":"null"}]}}},
 "info":{"title":"FuzzSchema","version":"1.0.0"},"openapi":"3.1.0","paths":{}}

The type it produces has no variants key:

$ go run ./cmd/morphic compile spec.json | jq -c '.types["t/openapi/components/schemas/Fuzzed"]'
{"kind":"union","id":"t/openapi/components/schemas/Fuzzed","name":{"source":"Fuzzed","canonical":"fuzzed"},
 "anonymous":false,"docs":{},"sensitive":false,
 "provenance":{"source":0,"pointer":"/components/schemas/Fuzzed"},"exclusive":false,"wireTagged":false}

and the harness reports it:

$ go run ./cmd/morphic-harness spec.json
spec.json violations [{Code:ir/union-no-variants Message:union declares no variants, so no value
inhabits it Path:types[t/openapi/components/schemas/Fuzzed]}]
exit status 1

Both reproduce at 29801f7 on main.

The {X, null} collapse presumably drops the null branch as a nullability marker; with nothing
beside it there is no branch left, and nothing notices that the union is then empty. Either the
schema should not become a union at all, or the collapse must refuse to empty one.

How it surfaced

go test ./... is green — no committed seed reaches it. A bounded -fuzz search of
FuzzLowerSchema finds it in 1.45 s from a cleared corpus (go clean -fuzzcache, no committed
reproducer), minimizing to {"anyOf":[{"type":"null"}]}. That means any CI step that fuzzes this
target reddens on essentially every run until this is fixed.

Acceptance

  • The spec above either lowers to something other than an empty union, or raises a diagnostic.
  • go run ./cmd/morphic-harness on it reports no violation.
  • A conformance or golden case pins whichever behaviour is chosen, declared in the order that was
    wrong if the fix is order-sensitive.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions