-
-
Notifications
You must be signed in to change notification settings - Fork 1.7k
fix: Fix optional chaining for innerType in $ZodLazy #5371
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
@colinhacks, Can you please take a look into this PR and let me know if anything further required. Also please feel free to edit the PR if required. Thanks |
WalkthroughThe Pre-merge checks✅ Passed checks (3 passed)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
packages/zod/src/v4/core/schemas.ts
(1 hunks)
🧰 Additional context used
📓 Path-based instructions (11)
**/*.{js,jsx,ts,tsx,mjs,cjs,json}
📄 CodeRabbit inference engine (CLAUDE.md)
Enforce line width of 120 characters via Biome formatting
Files:
packages/zod/src/v4/core/schemas.ts
**/*.{js,jsx,ts,tsx,mjs,cjs}
📄 CodeRabbit inference engine (CLAUDE.md)
Use ES5-style trailing commas in JavaScript/TypeScript code
Files:
packages/zod/src/v4/core/schemas.ts
**/*.{ts,tsx}
📄 CodeRabbit inference engine (CLAUDE.md)
**/*.{ts,tsx}
: Allow the any type in TypeScript (noExplicitAny off)
Allow non-null assertions in TypeScript (noNonNullAssertion off)
Write TypeScript to pass strict mode with exactOptionalPropertyTypes enabled
Use NodeNext module resolution semantics for imports in TypeScript
Target ES2020 language features in TypeScript source
Files:
packages/zod/src/v4/core/schemas.ts
**/*.{ts,tsx,js,jsx,mjs,cjs}
📄 CodeRabbit inference engine (CLAUDE.md)
Allow parameter reassignment for performance-sensitive code (noParameterAssign off)
Files:
packages/zod/src/v4/core/schemas.ts
**/*.{js,mjs,ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/development-setup.mdc)
**/*.{js,mjs,ts,tsx}
: Use .js extensions in import specifiers (e.g., import { z } from "./index.js")
Don’t use require(); use ESM import statements
Files:
packages/zod/src/v4/core/schemas.ts
**/*.{js,mjs,cjs,jsx,ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/guidelines.mdc)
Do not leave log statements (e.g., console.log, debugger) in tests or production code
Files:
packages/zod/src/v4/core/schemas.ts
packages/zod/src/v4/core/{schemas.ts,core.ts}
📄 CodeRabbit inference engine (.cursor/rules/zod-internals.mdc)
Use the custom constructor system via core.$constructor() and initialize instances with $ZodType.init() when creating schemas
Files:
packages/zod/src/v4/core/schemas.ts
packages/zod/src/v4/core/schemas.ts
📄 CodeRabbit inference engine (.cursor/rules/zod-internals.mdc)
packages/zod/src/v4/core/schemas.ts
: Wrapper schemas (e.g., $ZodOptional, $ZodNullable, $ZodReadonly) must pass through internal properties from their inner type using util.defineLazy for propValues, values, optin, and optout
Define computed internal properties using util.defineLazy() to avoid circular dependencies
Implement schema parse functions following the standard structure: type check, push invalid_type issue on mismatch, optionally coerce/transform, and return payload
For $ZodDiscriminatedUnion, compute and merge propValues lazily from options; ensure each option provides the discriminator key and that values are unique
Ensure readonly wrapper types (e.g., $ZodReadonly) pass through values for discriminator support in unions
Files:
packages/zod/src/v4/core/schemas.ts
packages/zod/src/v4/core/{schemas.ts,checks.ts}
📄 CodeRabbit inference engine (.cursor/rules/zod-internals.mdc)
When adding issues, push well-formed payload.issues entries including code, expected (when applicable), input, inst, and optional path/message/continue
Files:
packages/zod/src/v4/core/schemas.ts
packages/**/src/**/*.{ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/zod-project-guide.mdc)
Write source code in TypeScript (TypeScript-first codebase)
Files:
packages/zod/src/v4/core/schemas.ts
packages/zod/**
📄 CodeRabbit inference engine (.cursor/rules/zod-project-guide.mdc)
Make core Zod library changes in the main package at packages/zod/
Files:
packages/zod/src/v4/core/schemas.ts
🧠 Learnings (4)
📓 Common learnings
Learnt from: CR
PR: colinhacks/zod#0
File: .cursor/rules/zod-internals.mdc:0-0
Timestamp: 2025-10-21T17:27:32.477Z
Learning: Applies to packages/zod/src/v4/core/schemas.ts : Wrapper schemas (e.g., $ZodOptional, $ZodNullable, $ZodReadonly) must pass through internal properties from their inner type using util.defineLazy for propValues, values, optin, and optout
Learnt from: CR
PR: colinhacks/zod#0
File: .cursor/rules/zod-internals.mdc:0-0
Timestamp: 2025-10-21T17:27:32.477Z
Learning: Applies to packages/zod/src/v4/core/schemas.ts : Define computed internal properties using util.defineLazy() to avoid circular dependencies
Learnt from: CR
PR: colinhacks/zod#0
File: .cursor/rules/zod-internals.mdc:0-0
Timestamp: 2025-10-21T17:27:32.477Z
Learning: Applies to packages/zod/src/v4/core/{schemas.ts,core.ts} : Use the custom constructor system via core.$constructor() and initialize instances with $ZodType.init() when creating schemas
Learnt from: CR
PR: colinhacks/zod#0
File: .cursor/rules/zod-internals.mdc:0-0
Timestamp: 2025-10-21T17:27:32.477Z
Learning: Applies to packages/zod/src/v4/core/schemas.ts : Ensure readonly wrapper types (e.g., $ZodReadonly) pass through values for discriminator support in unions
Learnt from: CR
PR: colinhacks/zod#0
File: .cursor/rules/zod-internals.mdc:0-0
Timestamp: 2025-10-21T17:27:32.477Z
Learning: Applies to packages/zod/src/v4/core/schemas.ts : For $ZodDiscriminatedUnion, compute and merge propValues lazily from options; ensure each option provides the discriminator key and that values are unique
📚 Learning: 2025-10-21T17:27:32.477Z
Learnt from: CR
PR: colinhacks/zod#0
File: .cursor/rules/zod-internals.mdc:0-0
Timestamp: 2025-10-21T17:27:32.477Z
Learning: Applies to packages/zod/src/v4/core/schemas.ts : Wrapper schemas (e.g., $ZodOptional, $ZodNullable, $ZodReadonly) must pass through internal properties from their inner type using util.defineLazy for propValues, values, optin, and optout
Applied to files:
packages/zod/src/v4/core/schemas.ts
📚 Learning: 2025-10-21T17:27:32.477Z
Learnt from: CR
PR: colinhacks/zod#0
File: .cursor/rules/zod-internals.mdc:0-0
Timestamp: 2025-10-21T17:27:32.477Z
Learning: Applies to packages/zod/src/v4/core/schemas.ts : Define computed internal properties using util.defineLazy() to avoid circular dependencies
Applied to files:
packages/zod/src/v4/core/schemas.ts
📚 Learning: 2025-10-21T17:27:32.477Z
Learnt from: CR
PR: colinhacks/zod#0
File: .cursor/rules/zod-internals.mdc:0-0
Timestamp: 2025-10-21T17:27:32.477Z
Learning: Applies to packages/zod/src/v4/core/{schemas.ts,core.ts} : Use the custom constructor system via core.$constructor() and initialize instances with $ZodType.init() when creating schemas
Applied to files:
packages/zod/src/v4/core/schemas.ts
util.defineLazy(inst._zod, "optin", () => inst._zod.innerType?._zod.optin ?? undefined); | ||
util.defineLazy(inst._zod, "optout", () => inst._zod.innerType?._zod.optout ?? undefined); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
Inconsistent optional chaining - consider applying to all innerType
accesses.
The optional chaining here prevents the TypeError for optin
and optout
, but lines 4167-4168 directly above access innerType._zod.pattern
and innerType._zod.propValues
without the same protection. If innerType
can be undefined during lazy evaluation (which this PR fixes), shouldn't all property accesses be guarded consistently?
Based on learnings, wrapper schemas should pass through propValues
, values
, optin
, and optout
similarly. The partial fix might leave other code paths vulnerable to the same error.
Consider applying optional chaining to lines 4167-4168 as well:
- util.defineLazy(inst._zod, "pattern", () => inst._zod.innerType._zod.pattern);
- util.defineLazy(inst._zod, "propValues", () => inst._zod.innerType._zod.propValues);
+ util.defineLazy(inst._zod, "pattern", () => inst._zod.innerType?._zod.pattern);
+ util.defineLazy(inst._zod, "propValues", () => inst._zod.innerType?._zod.propValues);
util.defineLazy(inst._zod, "optin", () => inst._zod.innerType?._zod.optin ?? undefined);
util.defineLazy(inst._zod, "optout", () => inst._zod.innerType?._zod.optout ?? undefined);
Run this script to verify how pattern and propValues are used across other wrapper schemas:
🏁 Script executed:
#!/bin/bash
# Check how other wrapper schemas handle innerType property access
rg -n -A2 -B2 'defineLazy.*\b(pattern|propValues)\b' packages/zod/src/v4/core/schemas.ts
Length of output: 2567
I need to see the actual code context around the parse function to understand how it handles innerType.
---
🏁 Script executed:
```shell
#!/bin/bash
# View the complete $ZodLazy constructor including parse function and all property definitions
sed -n '4160,4180p' packages/zod/src/v4/core/schemas.ts
Length of output: 78
🏁 Script executed:
#!/bin/bash
# View the complete $ZodLazy constructor including parse function and all property definitions
sed -n '4160,4180p' packages/zod/src/v4/core/schemas.ts
Length of output: 876
Incomplete fix — optional chaining needed on pattern
and propValues
too.
The PR adds optional chaining for optin
and optout
(lines 4169-4170), but lines 4167-4168 directly above still access innerType._zod.pattern
and innerType._zod.propValues
without protection. Since innerType
is lazily computed here (line 4166 via def.getter()
), it could be undefined during evaluation, making pattern
and propValues
vulnerable to the same TypeError.
The parse function (lines 4172-4173) also accesses innerType
without guards. For consistency and safety, all these accesses should be protected:
- util.defineLazy(inst._zod, "pattern", () => inst._zod.innerType._zod.pattern);
- util.defineLazy(inst._zod, "propValues", () => inst._zod.innerType._zod.propValues);
+ util.defineLazy(inst._zod, "pattern", () => inst._zod.innerType?._zod.pattern);
+ util.defineLazy(inst._zod, "propValues", () => inst._zod.innerType?._zod.propValues);
util.defineLazy(inst._zod, "optin", () => inst._zod.innerType?._zod.optin ?? undefined);
util.defineLazy(inst._zod, "optout", () => inst._zod.innerType?._zod.optout ?? undefined);
Also verify the parse function (lines 4172-4173) won't crash if innerType
is undefined.
Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In packages/zod/src/v4/core/schemas.ts around lines 4167 to 4173, accesses to
inst._zod.innerType._zod.pattern and inst._zod.innerType._zod.propValues (and
the parse function that uses innerType) are not protected and can throw if
innerType is undefined; update those lines to use optional chaining (e.g.,
innerType?._zod?.pattern and innerType?._zod?.propValues) and add a guard in the
parse function to handle the case when innerType is undefined (return
appropriate default/failure or skip inner parsing) so no property access occurs
on undefined.
Fix TypeError when creating self-referencing lazy schemas
Problem
When creating a lazy schema that self-references itself, Zod throws a TypeError:
This results in the following error:
Solution
This PR fixes the issue by adding proper optional chaining to handle cases where
innerType
might be undefined during the lazy evaluation of self-referencing schemas.