🔎 Search Terms
recursive object literal getter accessor constraint check circular "implicitly has return type" mapped type zod
🕗 Version & Regression Information
⏯ Playground Link
No response
💻 Code
type output<T> = T extends { _zod: { output: any } } ? T["_zod"]["output"] : unknown;
type input<T> = T extends { _zod: { input: any } } ? T["_zod"]["input"] : unknown;
type NoUndefined<T> = T extends undefined ? never : T;
type Writeable<T> = { -readonly [P in keyof T]: T[P] };
type Prettify<T> = { [K in keyof T]: T[K] } & {};
interface $ZodTypeInternals<out O = unknown, out I = unknown> { def: unknown; output: O; input: I; optin?: "optional" | undefined; optout?: "optional" | undefined }
interface StandardProps<I, O> { readonly types?: { readonly input: I; readonly output: O } | undefined }
interface $ZodType<O = unknown, I = unknown, Internals extends $ZodTypeInternals<O, I> = $ZodTypeInternals<O, I>> {
_zod: Internals;
"~standard": StandardProps<input<this>, output<this>>;
}
interface ZodType<out Internals extends $ZodTypeInternals = $ZodTypeInternals> extends $ZodType<any, any, Internals> {
default(def: NoUndefined<output<this>>): ZodDefault<this>;
default(def: () => NoUndefined<output<this>>): ZodDefault<this>;
}
type Shape = Readonly<{ [k: string]: $ZodType }>;
type OptionalOut = { _zod: { optout: "optional" } };
type OptionalIn = { _zod: { optin: "optional" } };
type $InferObjectOutput<T extends Shape> = Prettify<
{ -readonly [k in keyof T as T[k] extends OptionalOut ? never : k]: T[k]["_zod"]["output"] } &
{ -readonly [k in keyof T as T[k] extends OptionalOut ? k : never]?: T[k]["_zod"]["output"] }
>;
type $InferObjectInput<T extends Shape> = Prettify<
{ -readonly [k in keyof T as T[k] extends OptionalIn ? never : k]: T[k]["_zod"]["input"] } &
{ -readonly [k in keyof T as T[k] extends OptionalIn ? k : never]?: T[k]["_zod"]["input"] }
>;
interface $ZodStringInternals extends $ZodTypeInternals { def: { type: "string" }; output: string; input: string }
interface ZodString extends ZodType<$ZodStringInternals> {}
declare function string(): ZodString;
interface $ZodObjectInternals<S extends Shape> extends $ZodTypeInternals { def: { type: "object"; shape: S }; output: $InferObjectOutput<S>; input: $InferObjectInput<S> }
interface ZodObject<S extends Shape> extends ZodType<$ZodObjectInternals<S>> { shape: S }
declare function object<T extends Shape>(shape: T): ZodObject<Writeable<T>>;
interface $ZodArrayInternals<T extends $ZodType> extends $ZodTypeInternals { def: { type: "array"; element: T }; output: output<T>[]; input: input<T>[] }
interface ZodArray<T extends $ZodType> extends ZodType<$ZodArrayInternals<T>> { element: T }
declare function array<T extends $ZodType>(element: T): ZodArray<T>;
interface $ZodDefaultInternals<T extends $ZodType> extends $ZodTypeInternals { def: { type: "default"; inner: T }; output: NoUndefined<output<T>>; input: input<T> | undefined; optin: "optional" }
interface ZodDefault<T extends $ZodType> extends ZodType<$ZodDefaultInternals<T>> {}
const Tree = object({
name: string(),
get children() {
return array(Tree).default([]);
},
});
🙁 Actual behavior
children is reported as circular, TS7023 on the getter and TS2615 in Writeable.
🙂 Expected behavior
The getter infers ZodDefault<ZodArray<typeof Tree>>, as it does when array's constraint omits output.
Additional information about the issue
The check of array's type argument against $ZodType compares _zod, whose output property instantiates Tree's inferred output type, and the mapped property of Writeable for children reads the getter while its return type is still being inferred. The same happens through the outer call when object constrains its shape to Readonly<{ [k: string]: $ZodType }>. Zod avoids both today by constraining its builders to a looser type that omits output. #64413 defers those checks until the file's deferred nodes have run.
🔎 Search Terms
recursive object literal getter accessor constraint check circular "implicitly has return type" mapped type zod
🕗 Version & Regression Information
typescript@7.1.0-dev.20260918.1with Suppress type argument constraint checks in recursive call resolution #64311⏯ Playground Link
No response
💻 Code
🙁 Actual behavior
childrenis reported as circular, TS7023 on the getter and TS2615 inWriteable.🙂 Expected behavior
The getter infers
ZodDefault<ZodArray<typeof Tree>>, as it does whenarray's constraint omitsoutput.Additional information about the issue
The check of
array's type argument against$ZodTypecompares_zod, whoseoutputproperty instantiatesTree's inferred output type, and the mapped property ofWriteableforchildrenreads the getter while its return type is still being inferred. The same happens through the outer call whenobjectconstrains its shape toReadonly<{ [k: string]: $ZodType }>. Zod avoids both today by constraining its builders to a looser type that omitsoutput. #64413 defers those checks until the file's deferred nodes have run.