Skip to content

Tighten preprocess return types#5908

Open
colinhacks wants to merge 1 commit intomainfrom
investigate-preprocess-types-5733
Open

Tighten preprocess return types#5908
colinhacks wants to merge 1 commit intomainfrom
investigate-preprocess-types-5733

Conversation

@colinhacks
Copy link
Copy Markdown
Owner

Adds a type-level compatibility check for z.preprocess() callback returns so clear mismatches are caught before the value reaches the target schema, while preserving the existing <A, U, B> generic shape and validation-boundary patterns like unknown, unknown[], object wrappers, void, and z.NEVER.

Covers the regression reported in #5733 with focused type assertions for sync, async, union, array, object, and default-schema cases.

Validated with pnpm exec tsc -p packages/zod/tsconfig.test.json --noEmit --extendedDiagnostics and pnpm vitest run packages/zod/src/v4/classic/tests/preprocess.test.ts.

@pullfrog
Copy link
Copy Markdown
Contributor

pullfrog Bot commented Apr 30, 2026

Reviewed PR #5908 — approved. The type-level machinery correctly constrains preprocess return types while preserving backward compatibility for void returns, unknown pass-throughs, and async patterns. All existing tests pass, and I verified additional edge cases (optional properties, missing required properties, explicit unknown returns).

Task list (3/3 completed)
  • Checkout PR and read the diff
  • Read changed files and trace data flow
  • Self-critique and submit review

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

@pullfrog
Copy link
Copy Markdown
Contributor

pullfrog Bot commented Apr 30, 2026

TL;DR — Adds a compile-time compatibility check to z.preprocess() so the callback's return type must be assignable to the target schema's input type, catching obvious mismatches at the type level rather than at runtime. Addresses the regression reported in #5733.

Key changes

  • Add _IsPreprocessAssignableTo recursive type utility — deep structural check that walks arrays and object properties to determine if a preprocess return is compatible with a schema's input, with escape hatches for unknown, any, void, and empty-key types.
  • Constrain the fn parameter of preprocess() — the callback return type now resolves to never when it fails the assignability check, surfacing a type error at the call site.
  • Add type-level assertions for preprocess return checking — covers sync, async, union, array, object, and default-schema cases with @ts-expect-error guards.

Summary | 2 files | 1 commit | base: maininvestigate-preprocess-types-5733


Structural return-type constraint for z.preprocess()

Before: z.preprocess(() => 4, z.string()) compiled without error — the mismatch was only caught at runtime.
After: The same call produces a type error because number is not assignable to string (the schema's input).

The new _PreprocessReturn<A, U> conditional type resolves to A when the (awaited) return is structurally compatible with core.input<U>, and to never otherwise. Compatibility is checked recursively via _IsPreprocessAssignableTo, which handles arrays element-wise, objects property-wise, and short-circuits on any/unknown/empty-key types to preserve existing patterns where the preprocess intentionally returns a loose type for validation-boundary use.

Why not a simple [A] extends [input<U>] check?

A naive extends check rejects common patterns: returning unknown[] for a z.string().array(), returning an object literal whose properties are wider than declared, or using void as a sentinel (e.g. with .default()). The recursive utility mirrors what TypeScript's structural checker does but with explicit opt-outs for the boundary types Zod users rely on.

schemas.ts · preprocess.test.ts

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

Copy link
Copy Markdown
Contributor

@pullfrog pullfrog Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed — no issues found.

Task list (3/3 completed)
  • Checkout PR and read the diff
  • Read changed files and trace data flow
  • Self-critique and submit review

Pullfrog  | View workflow run | Using Claude Opus𝕏

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