Repository: colinhacks/zod
Pull request #4137 (2025-04-13 19:01:45)

fix (v4): check for null prototype in parsedType function
When passing a Object.create(null) object (a " null -prototype object") the parseType function errors because it cannot read the name from the constructor.

Fastify test suite uses these null prototype objects for some reason.

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 -->

---

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.

<details>
<summary>❤️ Share</summary>

- [X](https://twitter.com/intent/tweet?text=I%20just%20used%20%40coderabbitai%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20the%20proprietary%20code.%20Check%20it%20out%3A&url=https%3A//coderabbit.ai)
- [Mastodon](https://mastodon.social/share?text=I%20just%20used%20%40coderabbitai%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20the%20proprietary%20code.%20Check%20it%20out%3A%20https%3A%2F%2Fcoderabbit.ai)
- [Reddit](https://www.reddit.com/submit?title=Great%20tool%20for%20code%20review%20-%20CodeRabbit&text=I%20just%20used%20CodeRabbit%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20proprietary%20code.%20Check%20it%20out%3A%20https%3A//coderabbit.ai)
- [LinkedIn](https://www.linkedin.com/sharing/share-offsite/?url=https%3A%2F%2Fcoderabbit.ai&mini=true&title=Great%20tool%20for%20code%20review%20-%20CodeRabbit&summary=I%20just%20used%20CodeRabbit%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20proprietary%20code)

</details>

<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=4137):

- 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/locales/en.ts b/packages/core/src/locales/en.ts
index 5de51ee6..55ecf50e 100644
--- a/packages/core/src/locales/en.ts
+++ b/packages/core/src/locales/en.ts
@@ -27,7 +27,8 @@ export const parsedType = (data: any): string => {
       if (data === null) {
         return "null";
       }
-      if (Object.getPrototypeOf(data) !== Object.prototype) {
+
+      if (Object.getPrototypeOf(data) !== Object.prototype && data.constructor) {
         return data.constructor.name;
       }
     }
diff --git a/packages/core/src/schemas.ts b/packages/core/src/schemas.ts
index 3863770c..96cb935b 100644
--- a/packages/core/src/schemas.ts
+++ b/packages/core/src/schemas.ts
@@ -199,8 +199,7 @@ export const $ZodType: core.$constructor<$ZodType> = /*@__PURE__*/ core.$constru
           throw new core.$ZodAsyncError();
         }
         if (asyncResult || _ instanceof Promise) {
-          asyncResult = asyncResult ?? Promise.resolve();
-          asyncResult.then(async () => {
+          asyncResult = (asyncResult ?? Promise.resolve()).then(async () => {
             await _;
             const nextLen = payload.issues.length;
             if (nextLen === currLen) return;


Test patch:
diff --git a/packages/core/tests/locales/en.test.ts b/packages/core/tests/locales/en.test.ts
new file mode 100644
index 00000000..b06aaddd
--- /dev/null
+++ b/packages/core/tests/locales/en.test.ts
@@ -0,0 +1,22 @@
+import { expect, test } from "vitest";
+import { parsedType } from "../../src/locales/en.js";
+
+test("parsedType", () => {
+  expect(parsedType("string")).toBe("string");
+  expect(parsedType(1)).toBe("number");
+  expect(parsedType(true)).toBe("boolean");
+  expect(parsedType(null)).toBe("null");
+  expect(parsedType(undefined)).toBe("undefined");
+  expect(parsedType([])).toBe("array");
+  expect(parsedType({})).toBe("object");
+  expect(parsedType(new Date())).toBe("Date");
+  expect(parsedType(new Map())).toBe("Map");
+  expect(parsedType(new Set())).toBe("Set");
+  expect(parsedType(new Error())).toBe("Error");
+
+  const nullPrototype = Object.create(null);
+  expect(parsedType(nullPrototype)).toBe("object");
+
+  const doubleNullPrototype = Object.create(Object.create(null));
+  expect(parsedType(doubleNullPrototype)).toBe("object");
+});
diff --git a/packages/mini/tests/object.test.ts b/packages/mini/tests/object.test.ts
index 3db8f214..d40735da 100644
--- a/packages/mini/tests/object.test.ts
+++ b/packages/mini/tests/object.test.ts
@@ -28,6 +28,12 @@ test("z.object", () => {
   // "test?" is required in ZodObject
   expect(() => z.parse(a, { name: "john", age: "30" })).toThrow();
   expect(() => z.parse(a, "hello")).toThrow();
+
+  // null prototype
+  const schema = z.object({ a: z.string() });
+  const obj = Object.create(null);
+  obj.a = "foo";
+  expect(schema.parse(obj)).toEqual({ a: "foo" });
 });
 
 test("z.strictObject", () => {
diff --git a/packages/zod/tests/async-parsing.test.ts b/packages/zod/tests/async-parsing.test.ts
index f45f1345..a2d2c213 100644
--- a/packages/zod/tests/async-parsing.test.ts
+++ b/packages/zod/tests/async-parsing.test.ts
@@ -331,7 +331,14 @@ test("async validation multiple errors 2", async () => {
     z.object({
       hello: z.string(),
       foo: z.object({
-        bar: z.number().refine(is_async ? async () => false : () => false),
+        bar: z.number().refine(
+          is_async
+            ? async () =>
+                new Promise((resolve) => {
+                  setTimeout(() => resolve(false), 500);
+                })
+            : () => false
+        ),
       }),
     });
 
diff --git a/packages/zod/tests/object.test.ts b/packages/zod/tests/object.test.ts
index 18344196..46283b7a 100644
--- a/packages/zod/tests/object.test.ts
+++ b/packages/zod/tests/object.test.ts
@@ -440,3 +440,10 @@ test("assignability", () => {
   z.interface({ "a?": z.string() }) satisfies z.ZodInterface;
   z.interface({ "?a": z.string() }) satisfies z.ZodInterface;
 });
+
+test("null prototype", () => {
+  const schema = z.object({ a: z.string() });
+  const obj = Object.create(null);
+  obj.a = "foo";
+  expect(schema.parse(obj)).toEqual({ a: "foo" });
+});