Skip to content

Drop z.undefined()'s optin/optout = "optional"#5899

Closed
colinhacks wants to merge 2 commits into
mainfrom
fix/z-undefined-optout
Closed

Drop z.undefined()'s optin/optout = "optional"#5899
colinhacks wants to merge 2 commits into
mainfrom
fix/z-undefined-optout

Conversation

@colinhacks

Copy link
Copy Markdown
Owner

Note: this PR description was drafted with AI assistance.

Drops the inst._zod.optin = "optional" / inst._zod.optout = "optional" assignments in $ZodUndefined.

These were bundled into #4769 alongside the .catch() JSON-schema fix from #4768 without separate justification. The test added in the same commit literally has // z.undefined should NOT be optional directly above an assertion saying it is. The comment was the principled position; the assignments snapshotted whatever the runtime values happened to be after the patch.

optout is read by the object parser's absent-key error-swallow path (#5589) and the tuple parser's optStart. Marking z.undefined() as optout-optional conflates "value type is undefined" with "key may be absent in inferred output," which is upstream of the confusion threading through #5654 and #5661. optin rides along on the input side — z.object({ a: z.undefined() }) infers as { a: undefined } (required key) under strict semantics, and the JSON-schema required array should agree.

Inference doesn't move. $ZodUndefinedInternals doesn't promote optin/optout to required fields, so neither z.object({ a: z.undefined() }) nor z.union([z.string(), z.undefined()]) ever extended OptionalOutSchema/OptionalInSchema structurally. Only the runtime values were disagreeing with the type.

Test changes: four runtime-value assertion flips in optional.test.ts for the z.undefined() and z.union([z.string(), z.undefined()]) blocks. expectTypeOf lines unchanged. 3,763/3,763 pass.

Bundled into #4769 alongside the legitimate `optin = "optional"` fix
for #4768 without separate justification. The test added in the same
commit literally has `// z.undefined should NOT be optional` directly
above an assertion saying it is.

`optout` is read by the object parser's absent-key error-swallow path
(#5589) and the tuple parser's `optStart`. Marking `z.undefined()`
optout-optional conflates "value type is undefined" with "key may be
absent in inferred output," which is upstream of the confusion in
#5654 and #5661.

Inference doesn't move: `\$ZodUndefinedInternals` doesn't promote
`optin`/`optout` to required fields, so `z.object({ a: z.undefined() })`
already infers as `{ a: undefined }` regardless. `optin = "optional"`
stays — that one is the JSON-schema `required`-array fix from #4768.
Same conflation as the previous commit, on the input side. The motivating
JSON-schema bug from #4768 is `.catch()`-specific; the analogous extension
to `z.undefined()` was opportunistic. Under strict semantics
`z.object({ a: z.undefined() })` infers as `{ a: undefined }` (required
key) and the JSON-schema `required` array should agree. Runtime stays
permissive — `z.undefined().parse(undefined)` succeeds whether the source
was an absent key or an explicit `undefined`.
@pullfrog

pullfrog Bot commented Apr 29, 2026

Copy link
Copy Markdown
Contributor

New pull request. Leaping into action...

Pullfrog  | View workflow run | via Pullfrog | Using Claude Opus𝕏

@colinhacks

Copy link
Copy Markdown
Owner Author

Folding the two z.undefined() optin/optout fixes into #5661 — they're upstream of the tuple-parser confusion that PR is resolving and reasoning about them in isolation from the tuple changes was creating a circular dependency between the two PRs. Cherry-picked as 4282b3e2 and 33c84535 over there. Closing this out.

@colinhacks colinhacks closed this Apr 29, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant