Repository: colinhacks/zod
Pull request #4111 (2025-04-11 22:43:51)

Fix improper function definition in migration guide
I noticed a function definition improperly using `const` in place of `function` when working through the migration guide.

coderabbitai[bot]: <!-- This is an auto-generated comment: summarize by coderabbit.ai -->
<!-- This is an auto-generated comment: skip review by coderabbit.ai -->

> [!IMPORTANT]
> ## Review skipped
> 
> Auto reviews are disabled on base/target branches other than the default branch.
> 
> 
> 
> Please check the settings in the CodeRabbit UI or the `.coderabbit.yaml` file in this repository. To trigger a single review, invoke the `@coderabbitai review` command.
> 
> You can disable this status message by setting the `reviews.review_status` to `false` in the CodeRabbit configuration file.

<!-- end of auto-generated comment: skip review by coderabbit.ai -->
<!-- tips_start -->

---



<details>
<summary>🪧 Tips</summary>

### Chat

There are 3 ways to chat with [CodeRabbit](https://coderabbit.ai?utm_source=oss&utm_medium=github&utm_campaign=colinhacks/zod&utm_content=4111):

- Review comments: Directly reply to a review comment made by CodeRabbit. Example:
  - `I pushed a fix in commit <commit_id>, please review it.`
  - `Generate unit testing code for this file.`
  - `Open a follow-up GitHub issue for this discussion.`
- Files and specific lines of code (under the "Files changed" tab): Tag `@coderabbitai` in a new review comment at the desired location with your query. Examples:
  - `@coderabbitai generate unit testing code for this file.`
  -	`@coderabbitai modularize this function.`
- PR comments: Tag `@coderabbitai` in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
  - `@coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.`
  - `@coderabbitai read src/utils.ts and generate unit testing code.`
  - `@coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.`
  - `@coderabbitai help me debug CodeRabbit configuration file.`

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

### CodeRabbit Commands (Invoked using PR comments)

- `@coderabbitai pause` to pause the reviews on a PR.
- `@coderabbitai resume` to resume the paused reviews.
- `@coderabbitai review` to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
- `@coderabbitai full review` to do a full review from scratch and review all the files again.
- `@coderabbitai summary` to regenerate the summary of the PR.
- `@coderabbitai generate docstrings` to [generate docstrings](https://docs.coderabbit.ai/finishing-touches/docstrings) for this PR.
- `@coderabbitai resolve` resolve all the CodeRabbit review comments.
- `@coderabbitai plan` to trigger planning for file edits and PR creation.
- `@coderabbitai configuration` to show the current CodeRabbit configuration for the repository.
- `@coderabbitai help` to get help.

### Other keywords and placeholders

- Add `@coderabbitai ignore` anywhere in the PR description to prevent this PR from being reviewed.
- Add `@coderabbitai summary` to generate the high-level summary at a specific location in the PR description.
- Add `@coderabbitai` anywhere in the PR title to generate the title automatically.

### CodeRabbit Configuration File (`.coderabbit.yaml`)

- You can programmatically configure CodeRabbit by adding a `.coderabbit.yaml` file to the root of your repository.
- Please see the [configuration documentation](https://docs.coderabbit.ai/guides/configure-coderabbit) for more information.
- If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: `# yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json`

### Documentation and Community

- Visit our [Documentation](https://docs.coderabbit.ai) for detailed information on how to use CodeRabbit.
- Join our [Discord Community](http://discord.gg/coderabbit) to get help, request features, and share feedback.
- Follow us on [X/Twitter](https://twitter.com/coderabbitai) for updates and announcements.

</details>

<!-- tips_end -->

Patch:
diff --git a/packages/core/src/doc.ts b/packages/core/src/doc.ts
index 4f1d046f..ced22d4d 100644
--- a/packages/core/src/doc.ts
+++ b/packages/core/src/doc.ts
@@ -1,12 +1,12 @@
 type ModeWriter = (doc: Doc, modes: { execution: "sync" | "async" }) => void;
 
 export class Doc {
-  args: string[];
+  args!: string[];
   content: string[] = [];
   indent = 0;
 
-  constructor(args: string[]) {
-    this.args = args;
+  constructor(args: string[] = []) {
+    if (this) this.args = args;
   }
 
   indented(fn: (doc: Doc) => void) {
diff --git a/packages/core/src/schemas.ts b/packages/core/src/schemas.ts
index 0f1c539f..e3807edf 100644
--- a/packages/core/src/schemas.ts
+++ b/packages/core/src/schemas.ts
@@ -2047,16 +2047,23 @@ export interface $ZodDiscriminatedUnion<T extends readonly $ZodType[] = readonly
 }
 
 function matchDiscriminators(input: any, discs: util.DiscriminatorMap): boolean {
+  let matched = true;
   for (const [key, value] of discs) {
     const data = input?.[key];
-    if (value.values.has(data)) return true;
+
+    if (value.values.size && !value.values.has(data)) {
+      matched = false;
+    }
     if (value.maps.length > 0) {
       for (const m of value.maps) {
-        if (matchDiscriminators(data, m)) return true;
+        if (!matchDiscriminators(data, m)) {
+          matched = false;
+        }
       }
     }
   }
-  return false;
+
+  return matched;
 }
 
 export const $ZodDiscriminatedUnion: core.$constructor<$ZodDiscriminatedUnion> =
@@ -3633,8 +3640,6 @@ export interface $ZodCustom<O = unknown, I = unknown> extends $ZodType {
 }
 
 export const $ZodCustom: core.$constructor<$ZodCustom> = /*@__PURE__*/ core.$constructor("$ZodCustom", (inst, def) => {
-  // if (def.checks?.length) console.warn("Can't add custom checks to z.custom()");
-
   checks.$ZodCheck.init(inst, def);
   $ZodType.init(inst, def);
 
diff --git a/packages/core/src/versions.ts b/packages/core/src/versions.ts
index 259a54b0..7d2699b2 100644
--- a/packages/core/src/versions.ts
+++ b/packages/core/src/versions.ts
@@ -1,5 +1,5 @@
 export const version = {
   major: 0,
   minor: 4,
-  patch: 0 as number,
+  patch: 1 as number,
 } as const;
diff --git a/packages/docs/content/v4/changelog.mdx b/packages/docs/content/v4/changelog.mdx
index 91f79d33..d2d66567 100644
--- a/packages/docs/content/v4/changelog.mdx
+++ b/packages/docs/content/v4/changelog.mdx
@@ -419,7 +419,7 @@ The result of `z.function()` is no longer a Zod schema. Instead, it acts as a st
 <Tabs groupId="lib" items={["Zod 4", "Zod 3"]} persist>
 <Tab value="Zod 4">
 ```ts
-const myFunction = z.function().args({
+const myFunction = z.function({
   input: [z.object({
     name: z.string(),
     age: z.number().int(),
@@ -594,7 +594,7 @@ class ZodType<Output = unknown, Input = unknown> {
 The second generic `Def` has been entirely removed. Instead the base class now only tracks `Output` and `Input`. While previously the `Input` value defaulted to `Output`, it now defaults to `unknown`. This allows generic functions involving `z.ZodType` to behave more intuitively in many cases.
 
 ```ts
-const inferSchema<T extends z.ZodType>(schema: T): T {
+function inferSchema<T extends z.ZodType>(schema: T): T {
   return schema;
 };
 
diff --git a/play.ts b/play.ts
index c7e06682..49082dbb 100644
--- a/play.ts
+++ b/play.ts
@@ -1,3 +1,27 @@
 import * as z from "zod";
 
-z.templateLiteral([z.string().min(5), "@", z.string().min(5)]).parse("asdf@asdf");
+const a = z.object({
+  type: z.object({ key: z.literal("A") }),
+  name: z.string(),
+});
+
+const b = z.object({
+  type: z.object({ key: z.literal("B") }),
+  age: z.number(),
+});
+
+const c = z.discriminatedUnion([a, b]);
+
+console.log(
+  c.safeParse({
+    type: { key: "A" },
+    name: "hello",
+  })
+);
+
+console.log(
+  c.safeParse({
+    type: { key: "B" },
+    age: 12,
+  })
+);


Test patch:
diff --git a/packages/zod/tests/discriminated-unions.test.ts b/packages/zod/tests/discriminated-unions.test.ts
index 11844674..7c786bc1 100644
--- a/packages/zod/tests/discriminated-unions.test.ts
+++ b/packages/zod/tests/discriminated-unions.test.ts
@@ -476,3 +476,33 @@ test("optional and nullable", () => {
   if (value.key === "b") value.b;
   if (value.key === null) value.b;
 });
+
+test("multple discriminators", () => {
+  const FreeConfig = z.object({
+    type: z.literal("free"),
+    min_cents: z.null(),
+  });
+
+  // console.log(FreeConfig.shape.type);
+  const PricedConfig = z.object({
+    type: z.literal("fiat-price"),
+    // min_cents: z.int().nullable(),
+    min_cents: z.null(),
+  });
+
+  const Config = z.discriminatedUnion([FreeConfig, PricedConfig]);
+
+  Config.parse({
+    min_cents: null,
+    type: "fiat-price",
+    name: "Standard",
+  });
+
+  expect(() => {
+    Config.parse({
+      min_cents: null,
+      type: "not real",
+      name: "Standard",
+    });
+  }).toThrow();
+});