perf(types): match object schemas structurally in object methods by fabian-hiller · Pull Request #1676 · open-circle/valibot · GitHub
Skip to content

perf(types): match object schemas structurally in object methods - #1676

Open
fabian-hiller wants to merge 3 commits into
mainfrom
perf/types-object-methods-structural
Open

fabian-hiller wants to merge 3 commits into
mainfrom
perf/types-object-methods-structural

Conversation

@fabian-hiller

Copy link
Copy Markdown
Member

Part of #1663 (finding 11, part 1). Independent of the other PRs from that investigation.

Problem

The result types of partial, partialAsync, required, requiredAsync, pick and omit pick their branch with TSchema extends ObjectSchema<infer TEntries, ErrorMessage<ObjectIssue> | undefined> | StrictObjectSchema<infer TEntries, …> (and likewise for loose objects and objects with rest, sync and async). Inferring from and relating to another instantiation of the same interface forces TypeScript to measure the variance of each object schema interface. ObjectSchema's measured variance is "invariant | unreliable", so TS still falls back to a structural comparison afterwards.

Change

All 20 infer sites now match structurally on the discriminants plus the inferred properties:

TSchema extends {
  readonly type: 'object' | 'strict_object';
  readonly async: false;
  readonly entries: infer TEntries extends ObjectEntries;
}

TSchema is already constrained to Valibot object schemas without a pipe, so type + async select exactly the same branch as before. The pick/omit object-with-rest branches keep their existing rest: BaseSchema<…> requirement unchanged, including in the async branch.

Measurements

TypeScript 6.0.3, schema-benchmarks probe (schema declaration):

Case Before After
partial, pick, omit, required(partial(…)) on a sync object 24,387 18,253
The same on objectAsync, plus objectWithRest, strictObject, looseObject 39,358 30,030

Behavior

The displayed InferInput/InferOutput types of both cases are byte-identical, and all runtime and type tests pass.

Follow-up

The Schema constraints of these methods (and ObjectKeys) still reference ObjectSchema<ObjectEntries, …>, which still triggers the variance measurement. Making them structural as well cuts the two cases above further, to 13,470 and 21,877. It loosens the public constraints slightly, so that will be a separate PR stacked on this one.

🤖 Generated with Claude Code

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Copilot AI lite review requested due to automatic review settings September 27, 2026 00:15
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Sep 27, 2026 •

Copy link
Copy Markdown
Contributor

Warning

Review limit reached

Next included review available in 39 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used all 8 included reviews currently available.

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Learn how review limits work.

Review configuration:

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Advanced

Run ID: 61082731-ed12-48ba-8425-de3d9836ef96

📥 Commits

Reviewing files that changed from the base of the PR and between d4da61a and 95e0e4a.

📒 Files selected for processing (7)
  • library/CHANGELOG.md
  • library/src/methods/omit/omit.ts
  • library/src/methods/partial/partial.ts
  • library/src/methods/partial/partialAsync.ts
  • library/src/methods/pick/pick.ts
  • library/src/methods/required/required.ts
  • library/src/methods/required/requiredAsync.ts

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@pkg-pr-new

pkg-pr-new Bot commented Sep 27, 2026 •

Copy link
Copy Markdown

Open in StackBlitz

npm i https://pkg.pr.new/valibot@1676

commit: 95e0e4a

@pkg-pr-new

pkg-pr-new Bot commented Sep 27, 2026

Copy link
Copy Markdown

Open in StackBlitz

npm i https://pkg.pr.new/valibot@1676

commit: abe3e4d

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No issues found across 7 files

Re-trigger cubic

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🟢 Approval recommended

No blocking issues were identified; only minor test-coverage nits remain.

Review effort: Lite
Findings: 1 Low severity

Open (1)
What changed in this PR

Refactors object-method result types to use structural schema matching, reducing TypeScript variance-analysis overhead while preserving inferred types.

Changes:

  • Updated partial, required, pick, and omit type branches.
  • Preserved sync/async and object-with-rest behavior.
  • Documented the performance improvement.
File Summary
library/​src/​methods/​required/​requiredAsync.ts Structural async matching
library/​src/​methods/​required/​required.ts Structural sync matching
library/​src/​methods/​pick/​pick.ts Structural matching for pick branches
library/​src/​methods/​partial/​partialAsync.ts Structural async matching
library/​src/​methods/​partial/​partial.ts Structural sync matching
library/​src/​methods/​omit/​omit.ts Structural matching for omit branches
library/​CHANGELOG.md Documents the performance improvement

💡 Configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment on lines +132 to +136
: TSchema extends {
readonly type: 'object' | 'strict_object';
readonly async: true;
readonly entries: infer TEntries extends ObjectEntriesAsync;
}

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

2 issues found and verified against the latest diff

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="library/src/methods/omit/omit.ts">

<violation number="1" location="library/src/methods/omit/omit.ts:134">
P2: Add type assertions for async, loose, and strict schemas in `pick.test-d.ts`, and cover the corresponding omit cases so misclassified structural branches fail the type tests.</violation>

<violation number="2" location="library/src/methods/omit/omit.ts:324">
P3: The async `object_with_rest` branch keeps the sync-only `BaseSchema` rest requirement, so `omit(objectWithRestAsync(entries, anyAsyncSchema), keys)` — where `objectWithRestAsync` accepts `BaseSchema | BaseSchemaAsync` rests (see `objectWithRestAsync.ts` and the local `Schema` union) — matches no branch and resolves to the `: never` fallback, even though the runtime `omit` implementation never touches `rest` and works fine. `partialAsync`/`requiredAsync` in this same batch accept `BaseSchema | BaseSchemaAsync` for the identical branch, so this is the only async object schema that omitting/picking rejects. This was also broken before the change (the old `ObjectWithRestSchemaAsync<…, BaseSchema<…>, …>` pattern had the same gap), so it's not a regression, but while re-writing these infer sites it's a one-line alignment with the async variants: allow `BaseSchema | BaseSchemaAsync` in the rest requirement. Consider a type test like `expectNever`/`expectTypeOf` for `omit(objectWithRestAsync(entries, stringAsync()), ['key'])` since none currently covers the async-rest case.</violation>
</file>

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

>
: TSchema extends {
readonly type: 'object' | 'strict_object';
readonly async: true;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Add type assertions for async, loose, and strict schemas in pick.test-d.ts, and cover the corresponding omit cases so misclassified structural branches fail the type tests.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At library/src/methods/omit/omit.ts, line 134:

<comment>Add type assertions for async, loose, and strict schemas in `pick.test-d.ts`, and cover the corresponding omit cases so misclassified structural branches fail the type tests.</comment>

<file context>
@@ -125,15 +129,11 @@ export type SchemaWithOmit<
-          >
+  : TSchema extends {
+        readonly type: 'object' | 'strict_object';
+        readonly async: true;
+        readonly entries: infer TEntries extends ObjectEntriesAsync;
+      }
</file context>

readonly type: 'object_with_rest';
readonly async: false;
readonly entries: infer TEntries extends ObjectEntries;
readonly rest: BaseSchema<unknown, unknown, BaseIssue<unknown>>;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3: The async object_with_rest branch keeps the sync-only BaseSchema rest requirement, so omit(objectWithRestAsync(entries, anyAsyncSchema), keys) — where objectWithRestAsync accepts BaseSchema | BaseSchemaAsync rests (see objectWithRestAsync.ts and the local Schema union) — matches no branch and resolves to the : never fallback, even though the runtime omit implementation never touches rest and works fine. partialAsync/requiredAsync in this same batch accept BaseSchema | BaseSchemaAsync for the identical branch, so this is the only async object schema that omitting/picking rejects. This was also broken before the change (the old ObjectWithRestSchemaAsync<…, BaseSchema<…>, …> pattern had the same gap), so it's not a regression, but while re-writing these infer sites it's a one-line alignment with the async variants: allow BaseSchema | BaseSchemaAsync in the rest requirement. Consider a type test like expectNever/expectTypeOf for omit(objectWithRestAsync(entries, stringAsync()), ['key']) since none currently covers the async-rest case.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At library/src/methods/omit/omit.ts, line 324:

<comment>The async `object_with_rest` branch keeps the sync-only `BaseSchema` rest requirement, so `omit(objectWithRestAsync(entries, anyAsyncSchema), keys)` — where `objectWithRestAsync` accepts `BaseSchema | BaseSchemaAsync` rests (see `objectWithRestAsync.ts` and the local `Schema` union) — matches no branch and resolves to the `: never` fallback, even though the runtime `omit` implementation never touches `rest` and works fine. `partialAsync`/`requiredAsync` in this same batch accept `BaseSchema | BaseSchemaAsync` for the identical branch, so this is the only async object schema that omitting/picking rejects. This was also broken before the change (the old `ObjectWithRestSchemaAsync<…, BaseSchema<…>, …>` pattern had the same gap), so it's not a regression, but while re-writing these infer sites it's a one-line alignment with the async variants: allow `BaseSchema | BaseSchemaAsync` in the rest requirement. Consider a type test like `expectNever`/`expectTypeOf` for `omit(objectWithRestAsync(entries, stringAsync()), ['key'])` since none currently covers the async-rest case.</comment>

<file context>
@@ -315,11 +317,12 @@ export type SchemaWithOmit<
+              readonly type: 'object_with_rest';
+              readonly async: false;
+              readonly entries: infer TEntries extends ObjectEntries;
+              readonly rest: BaseSchema<unknown, unknown, BaseIssue<unknown>>;
+            }
           ? Omit<TSchema, 'entries' | '~standard' | '~run' | '~types'> & {
</file context>
Suggested change
readonly rest: BaseSchema<unknown, unknown, BaseIssue<unknown>>;
readonly rest:
| BaseSchema<unknown, unknown, BaseIssue<unknown>>
| BaseSchemaAsync<unknown, unknown, BaseIssue<unknown>>;

@yslpn
yslpn self-requested a review September 29, 2026 15:27
@yslpn

yslpn commented Sep 29, 2026

Copy link
Copy Markdown
Member

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants