Skip to content

Commit 5267f90

Browse files
committed
Improve recursive types
1 parent 75657a3 commit 5267f90

File tree

2 files changed

+56
-1
lines changed

2 files changed

+56
-1
lines changed

packages/zod/src/v4/core/schemas.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2106,6 +2106,8 @@ export interface $ZodIntersectionInternals<A extends SomeType = $ZodType, B exte
21062106
extends $ZodTypeInternals<core.output<A> & core.output<B>, core.input<A> & core.input<B>> {
21072107
def: $ZodIntersectionDef<A, B>;
21082108
isst: never;
2109+
optin: A["_zod"]["optin"] | B["_zod"]["optin"];
2110+
optout: A["_zod"]["optout"] | B["_zod"]["optout"];
21092111
}
21102112

21112113
export interface $ZodIntersection<A extends SomeType = $ZodType, B extends SomeType = $ZodType> extends $ZodType {
@@ -2418,6 +2420,8 @@ export interface $ZodRecordInternals<Key extends $ZodRecordKey = $ZodRecordKey,
24182420
extends $ZodTypeInternals<$InferZodRecordOutput<Key, Value>, $InferZodRecordInput<Key, Value>> {
24192421
def: $ZodRecordDef<Key, Value>;
24202422
isst: errors.$ZodIssueInvalidType | errors.$ZodIssueInvalidKey<Record<PropertyKey, unknown>>;
2423+
optin?: "optional" | undefined;
2424+
optout?: "optional" | undefined;
24212425
}
24222426

24232427
export type $partial = { "~~partial": true };
@@ -2551,6 +2555,8 @@ export interface $ZodMapInternals<Key extends SomeType = $ZodType, Value extends
25512555
extends $ZodTypeInternals<Map<core.output<Key>, core.output<Value>>, Map<core.input<Key>, core.input<Value>>> {
25522556
def: $ZodMapDef<Key, Value>;
25532557
isst: errors.$ZodIssueInvalidType | errors.$ZodIssueInvalidKey | errors.$ZodIssueInvalidElement<unknown>;
2558+
optin?: "optional" | undefined;
2559+
optout?: "optional" | undefined;
25542560
}
25552561

25562562
export interface $ZodMap<Key extends SomeType = $ZodType, Value extends SomeType = $ZodType> extends $ZodType {
@@ -2650,6 +2656,8 @@ export interface $ZodSetInternals<T extends SomeType = $ZodType>
26502656
extends $ZodTypeInternals<Set<core.output<T>>, Set<core.input<T>>> {
26512657
def: $ZodSetDef<T>;
26522658
isst: errors.$ZodIssueInvalidType;
2659+
optin?: "optional" | undefined;
2660+
optout?: "optional" | undefined;
26532661
}
26542662

26552663
export interface $ZodSet<T extends SomeType = $ZodType> extends $ZodType {
@@ -3060,6 +3068,7 @@ export interface $ZodDefaultInternals<T extends SomeType = $ZodType>
30603068
extends $ZodTypeInternals<util.NoUndefined<core.output<T>>, core.input<T> | undefined> {
30613069
def: $ZodDefaultDef<T>;
30623070
optin: "optional";
3071+
optout?: "optional" | undefined; // required
30633072
isst: never;
30643073
values: T["_zod"]["values"];
30653074
}
@@ -3120,6 +3129,7 @@ export interface $ZodPrefaultInternals<T extends SomeType = $ZodType>
31203129
extends $ZodTypeInternals<util.NoUndefined<core.output<T>>, core.input<T> | undefined> {
31213130
def: $ZodPrefaultDef<T>;
31223131
optin: "optional";
3132+
optout?: "optional" | undefined;
31233133
isst: never;
31243134
values: T["_zod"]["values"];
31253135
}
@@ -3162,6 +3172,8 @@ export interface $ZodNonOptionalInternals<T extends SomeType = $ZodType>
31623172
def: $ZodNonOptionalDef<T>;
31633173
isst: errors.$ZodIssueInvalidType;
31643174
values: T["_zod"]["values"];
3175+
optin: "optional" | undefined;
3176+
optout: "optional" | undefined;
31653177
}
31663178

31673179
export interface $ZodNonOptional<T extends SomeType = $ZodType> extends $ZodType {
@@ -3255,6 +3267,8 @@ export interface $ZodSuccessDef<T extends SomeType = $ZodType> extends $ZodTypeD
32553267
export interface $ZodSuccessInternals<T extends SomeType = $ZodType> extends $ZodTypeInternals<boolean, core.input<T>> {
32563268
def: $ZodSuccessDef<T>;
32573269
isst: never;
3270+
optin: T["_zod"]["optin"];
3271+
optout: "optional" | undefined;
32583272
}
32593273

32603274
export interface $ZodSuccess<T extends SomeType = $ZodType> extends $ZodType {

play.ts

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,44 @@
1-
import { z } from "zod/v4-mini";
1+
import { z } from "zod/v4";
22

33
z;
4+
5+
// import * as z from "zod/v4";
6+
7+
export type SomeRecursiveUnion1 = z.ZodUnion<
8+
[
9+
z.ZodObject<{
10+
foo: z.ZodReadonly<z.ZodPrefault<SomeRecursiveUnion1>>;
11+
}>,
12+
]
13+
>;
14+
15+
// type Foo = z.core.$ZodRecord<z.ZodString, B>;
16+
// type B = z.ZodPipe<B, Foo>;
17+
18+
const Category = z.object({
19+
name: z.string(),
20+
get subcategories() {
21+
return z.array(z.union([z.string(), Category]));
22+
},
23+
});
24+
25+
const err = new z.core.$ZodError([
26+
{
27+
code: "invalid_type",
28+
expected: "string",
29+
path: [],
30+
message: "Expected string, received number",
31+
input: 1,
32+
},
33+
]);
34+
console.log(err.toString());
35+
// But it works without `z.ZodReadonly` or without recursive reference:
36+
37+
// export type SomeRecursiveUnion2 = z.ZodUnion<
38+
// [
39+
// z.ZodObject<{
40+
// foo: z.ZodRecord<z.ZodString, SomeRecursiveUnion2>;
41+
// bar: z.ZodReadonly<z.ZodRecord<z.ZodString, z.ZodString>>;
42+
// }>,
43+
// ]
44+
// >;

0 commit comments

Comments
 (0)