Repository: colinhacks/zod (language: python)

Issue:
Prisma XOR with Zod  > 3.21.1 not working
We encounterd a bug when using generated prisma types with versions greater than `3.21.1`. 

You can reproduce the issue in the following repo:  https://github.com/chrishoermann/zod-prisma-types-bug

As you can read in the following discussion we first thought it was an issue with prisma, but after digging around and finding that the suspected type has been this way for a long time now @njdowdy found that this is a zod issue that happend somwhere in `3.21.2`

### Discussed in https://github.com/colinhacks/zod/discussions/2171

<div type='discussions-op-text'>

<sup>Originally posted by **chrishoermann** March  9, 2023</sup>
I've built a generator for prisma ([zod-prisma-types](https://github.com/chrishoermann/zod-prisma-types)) that throws a type error since prisma updated their `XOR` type in 4.11:

```ts
  type Without<T, U> = { [P in Exclude<keyof T, keyof U>]?: never };

  /**
   * XOR is needed to have a real mutually exclusive union type
   * https://stackoverflow.com/questions/42123407/does-typescript-support-mutually-exclusive-types
   */
  type XOR<T, U> =
    T extends object ?
    U extends object ?
      (Without<T, U> & U) | (Without<U, T> & T)
    : U : T
```

before this type update the following zod schema worked without problems:

```ts
export const UserCreateInputSchema: z.ZodType<Prisma.UserCreateInput> = z.object({
  id: z.string().cuid().optional(),
  email: z.string().email({ message: "Invalid email address" }),
  name: z.string().min(1).max(100).optional().nullable(),
  role: z.union([ z.lazy(() => UserCreateroleInputSchema),z.lazy(() => RoleSchema).array() ]).optional(),
  enum: z.lazy(() => AnotherEnumSchema).optional(),
  scalarList: z.union([ z.lazy(() => UserCreatescalarListInputSchema),z.string().array() ]).optional(),
  posts: z.lazy(() => PostCreateNestedManyWithoutAuthorInputSchema).optional(),
  profile: z.lazy(() => ProfileCreateNestedOneWithoutUserInputSchema).optional(),
  location: z.lazy(() => LocationCreateNestedOneWithoutUserInputSchema).optional(),
}).strict();

export const UserUncheckedCreateInputSchema: z.ZodType<Prisma.UserUncheckedCreateInput> = z.object({
  id: z.string().cuid().optional(),
  email: z.string().email({ message: "Invalid email address" }),
  name: z.string().min(1).max(100).optional().nullable(),
  role: z.union([ z.lazy(() => UserCreateroleInputSchema),z.lazy(() => RoleSchema).array() ]).optional(),
  enum: z.lazy(() => AnotherEnumSchema).optional(),
  scalarList: z.union([ z.lazy(() => UserCreatescalarListInputSchema),z.string().array() ]).optional(),
  lat: z.number(),
  lng: z.number(),
  posts: z.lazy(() => PostUncheckedCreateNestedManyWithoutAuthorInputSchema).optional(),
  profile: z.lazy(() => ProfileUncheckedCreateNestedOneWithoutUserInputSchema).optional(),
}).strict();

export const UserCreateArgsSchema: z.ZodType<Prisma.UserCreateArgs> = z.object({
  select: UserSelectSchema.optional(),
  include: UserIncludeSchema.optional(),
  data: z.union([ UserCreateInputSchema,UserUncheckedCreateInputSchema ]),
}).strict()
```

so a simple union was sufficient to satisfy typescript. in Prisma 4.11 typescript now throws the following error because, as I see it, the `Without<...>` type can not be satisfied with a union anymore:
```
Type 'ZodObject<{ select: ZodOptional<ZodType<UserSelect, ZodTypeDef, UserSelect>>; include: ZodOptional<ZodType<UserInclude, ZodTypeDef, UserInclude>>; data: ZodUnion<...>; }, "strict", ZodTypeAny, { ...; }, { ...; }>' is not assignable to type 'ZodType<UserCreateArgs, ZodTypeDef, UserCreateArgs>'.
  The types of '_type.data' are incompatible between these types.
    Type '(UserCreateInput | UserUncheckedCreateInput) & (UserCreateInput | UserUncheckedCreateInput | undefined)' is not assignable to type '(Without<UserCreateInput, UserUncheckedCreateInput> & UserUncheckedCreateInput) | (Without<...> & UserCreateInput)'.
      Type 'UserCreateInput & UserUncheckedCreateInput' is not assignable to type '(Without<UserCreateInput, UserUncheckedCreateInput> & UserUncheckedCreateInput) | (Without<...> & UserCreateInput)'.
        Type 'UserCreateInput & UserUncheckedCreateInput' is not assignable to type 'Without<UserUncheckedCreateInput, UserCreateInput> & UserCreateInput'.
          Type 'UserCreateInput & UserUncheckedCreateInput' is not assignable to type 'Without<UserUncheckedCreateInput, UserCreateInput>'.
            Types of property 'lat' are incompatible.
              Type 'number' is not assignable to type 'undefined'.
```

I'm currently a bit stuck on how to solve this issue because I don't really know how to implement the `Without<...>` type especially because the `z.ZodType<...>` does not provide the object helper methods that would have been my first guess on how to approach this. Any help is appreciated.
</div>
Change in 3.21.2 breaks assignability of schema created from generics' parsed and inferred type
I have a need to create a zod object schema that only has one key-value pair. This may not be exactly the best way to do something like this (and this is a contrived example), but in version 3.21.1, it was possible to assign the parsed data to the inferred type of a schema created through generic parameters:

```ts
import { z } from 'zod';

const createSchemaAndParse = <K extends string, VS extends z.ZodString>(key: K, valueSchema: VS, data: unknown) => {
  const schema = z.object({
    [key]: vSchema
  });
  const parsed = schema.parse(data);
  const inferred: z.infer<z.ZodObject<{ [k in K]: VS }>> = parsed; // this line errors in 3.21.2
  return inferred;
};
createSchemaAndParse('foo', z.string(), { foo: '' });
```

Unfortunately, with 3.21.2 (and above), the above line now produces a type error.


Discussion / hints:
Thanks for creating the issue @chrishoermann ! I'm working [zenstack](https://github.com/zenstackhq/zenstack) and hit by the same thing 😄.
We have the same problem. Since almost 3 month now we are stuck to using zod in version 3.21.1. 
We put a resolutions entry at the top of our monorepo:
```json5
// package.json
{
  // ...
  "resolutions": {
    "zod": "<=3.21.1",
  }
}
```

This issue has been here for a while 😄. Just wondering if it's on the radar for a fix soon.
It seems like this bug does not occur in v4.16.1 of Prisma 👍🏼 
@Intevel No, I can confirm same error with `prisma@4.16.1` and `zod@3.21.4`
> @Intevel No, I can confirm same error with `prisma@14.16.1` and `zod@3.21.4`

Did you updated your @prisma/client aswell? 
> Did you updated your @prisma/client aswell?

Yes, sure
Waiting for an update as well. Stuck here too

any news on this one? Is it confirmed as a bug yet?
If it's confirmed to be a bug, I'd like to try making a fix.
Hi @colinhacks , we need some attention here 😄. Many people build Prisma-related tools using zod and all likely blocked to some extent.
This issue needs to get more attention!
I do agree. I sadly have switched to dirzzle for new projetcs by now.
Zod sure has a lot of open issues for a validation library. No wonder its the most used these days.

This is probably gonna take some time to get picked up without a random dev stepping in. 
Not only prisma, we recently encountered the same error with nextjs react-hook-forms zodResolver.
Hope this problem can be solved .
Here to add some weight to the issue
I've got that same problem. Is there any workaround in the meantime (that isn't downgrading)?
We need this fixed :-)
Any news on this topic ? This is a blocking issue that prevent us to generate types from Prisma schema with latest Zod release
setting up the following rule in `tsconfig.json` might be a solution.
```javascript
{
    "compilerOptions": {
        "strictNullChecks": true   
    }
}
``` 
Maybe related to this #2184?
This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.


Fix patch:
diff --git a/deno/lib/helpers/util.ts b/deno/lib/helpers/util.ts
index 70d71394f..39e1e8025 100644
--- a/deno/lib/helpers/util.ts
+++ b/deno/lib/helpers/util.ts
@@ -100,18 +100,23 @@ export namespace objectUtil {
     [k in Exclude<keyof U, keyof V>]: U[k];
   } & V;
 
-  // type optionalKeys<T extends object> = {
-  //   [k in keyof T]: undefined extends T[k] ? k : never;
-  // }[keyof T];
+  type optionalKeys<T extends object> = {
+    [k in keyof T]: undefined extends T[k] ? k : never;
+  }[keyof T];
 
   type requiredKeys<T extends object> = {
     [k in keyof T]: undefined extends T[k] ? never : k;
   }[keyof T];
 
+  // export type addQuestionMarks<
+  //   T extends object,
+  //   R extends keyof T = requiredKeys<T>
+  // > = Pick<Required<T>, R> & Partial<T>;
   export type addQuestionMarks<
     T extends object,
-    R extends keyof T = requiredKeys<T>
-  > = Pick<Required<T>, R> & Partial<T>;
+    R extends keyof T = requiredKeys<T>,
+    O extends keyof T = optionalKeys<T>
+  > = Pick<T, R> & Partial<Pick<T, O>> & { [k in keyof T]?: unknown };
 
   export type identity<T> = T;
   export type flatten<T> = identity<{ [k in keyof T]: T[k] }>;
diff --git a/playground.ts b/playground.ts
index 6e008be42..4e01473b6 100644
--- a/playground.ts
+++ b/playground.ts
@@ -1,6 +1,3 @@
-import { z, ZodNativeEnum } from "./src";
+import { z } from "./src";
 
 z;
-
-const A = z.object({}).catchall(z.string());
-type A = z.infer<typeof A>;
diff --git a/src/helpers/util.ts b/src/helpers/util.ts
index 70d71394f..39e1e8025 100644
--- a/src/helpers/util.ts
+++ b/src/helpers/util.ts
@@ -100,18 +100,23 @@ export namespace objectUtil {
     [k in Exclude<keyof U, keyof V>]: U[k];
   } & V;
 
-  // type optionalKeys<T extends object> = {
-  //   [k in keyof T]: undefined extends T[k] ? k : never;
-  // }[keyof T];
+  type optionalKeys<T extends object> = {
+    [k in keyof T]: undefined extends T[k] ? k : never;
+  }[keyof T];
 
   type requiredKeys<T extends object> = {
     [k in keyof T]: undefined extends T[k] ? never : k;
   }[keyof T];
 
+  // export type addQuestionMarks<
+  //   T extends object,
+  //   R extends keyof T = requiredKeys<T>
+  // > = Pick<Required<T>, R> & Partial<T>;
   export type addQuestionMarks<
     T extends object,
-    R extends keyof T = requiredKeys<T>
-  > = Pick<Required<T>, R> & Partial<T>;
+    R extends keyof T = requiredKeys<T>,
+    O extends keyof T = optionalKeys<T>
+  > = Pick<T, R> & Partial<Pick<T, O>> & { [k in keyof T]?: unknown };
 
   export type identity<T> = T;
   export type flatten<T> = identity<{ [k in keyof T]: T[k] }>;


Test patch:
diff --git a/deno/lib/__tests__/generics.test.ts b/deno/lib/__tests__/generics.test.ts
index 388e147e7..911b28629 100644
--- a/deno/lib/__tests__/generics.test.ts
+++ b/deno/lib/__tests__/generics.test.ts
@@ -23,3 +23,19 @@ test("generics", () => {
   const result = stripOuter(z.object({ a: z.string() }), { a: "asdf" });
   util.assertEqual<typeof result, Promise<{ a: string }>>(true);
 });
+
+test("assignability", () => {
+  const createSchemaAndParse = <K extends string, VS extends z.ZodString>(
+    key: K,
+    valueSchema: VS,
+    data: unknown
+  ) => {
+    const schema = z.object({
+      [key]: valueSchema,
+    });
+    const parsed = schema.parse(data);
+    const inferred: z.infer<z.ZodObject<{ [k in K]: VS }>> = parsed;
+    return inferred;
+  };
+  createSchemaAndParse("foo", z.string(), { foo: "" });
+});
diff --git a/deno/lib/__tests__/object.test.ts b/deno/lib/__tests__/object.test.ts
index 5af579d4d..ff636a078 100644
--- a/deno/lib/__tests__/object.test.ts
+++ b/deno/lib/__tests__/object.test.ts
@@ -451,3 +451,23 @@ test("passthrough index signature", () => {
   type b = z.infer<typeof b>;
   util.assertEqual<{ a: string } & { [k: string]: unknown }, b>(true);
 });
+
+test("xor", () => {
+  type Without<T, U> = { [P in Exclude<keyof T, keyof U>]?: never };
+  type XOR<T, U> = T extends object
+    ? U extends object
+      ? (Without<T, U> & U) | (Without<U, T> & T)
+      : U
+    : T;
+
+  type A = { name: string; a: number };
+  type B = { name: string; b: number };
+  type C = XOR<A, B>;
+  type Outer = { data: C };
+  const Outer: z.ZodType<Outer> = z.object({
+    data: z.union([
+      z.object({ name: z.string(), a: z.number() }),
+      z.object({ name: z.string(), b: z.number() }),
+    ]),
+  });
+});
diff --git a/src/__tests__/generics.test.ts b/src/__tests__/generics.test.ts
index 56460f26f..a28cc9631 100644
--- a/src/__tests__/generics.test.ts
+++ b/src/__tests__/generics.test.ts
@@ -22,3 +22,19 @@ test("generics", () => {
   const result = stripOuter(z.object({ a: z.string() }), { a: "asdf" });
   util.assertEqual<typeof result, Promise<{ a: string }>>(true);
 });
+
+test("assignability", () => {
+  const createSchemaAndParse = <K extends string, VS extends z.ZodString>(
+    key: K,
+    valueSchema: VS,
+    data: unknown
+  ) => {
+    const schema = z.object({
+      [key]: valueSchema,
+    });
+    const parsed = schema.parse(data);
+    const inferred: z.infer<z.ZodObject<{ [k in K]: VS }>> = parsed;
+    return inferred;
+  };
+  createSchemaAndParse("foo", z.string(), { foo: "" });
+});
diff --git a/src/__tests__/object.test.ts b/src/__tests__/object.test.ts
index b0b222ebe..879351f83 100644
--- a/src/__tests__/object.test.ts
+++ b/src/__tests__/object.test.ts
@@ -450,3 +450,23 @@ test("passthrough index signature", () => {
   type b = z.infer<typeof b>;
   util.assertEqual<{ a: string } & { [k: string]: unknown }, b>(true);
 });
+
+test("xor", () => {
+  type Without<T, U> = { [P in Exclude<keyof T, keyof U>]?: never };
+  type XOR<T, U> = T extends object
+    ? U extends object
+      ? (Without<T, U> & U) | (Without<U, T> & T)
+      : U
+    : T;
+
+  type A = { name: string; a: number };
+  type B = { name: string; b: number };
+  type C = XOR<A, B>;
+  type Outer = { data: C };
+  const Outer: z.ZodType<Outer> = z.object({
+    data: z.union([
+      z.object({ name: z.string(), a: z.number() }),
+      z.object({ name: z.string(), b: z.number() }),
+    ]),
+  });
+});