Skip to content

Optimize distributive conditional type instantiation for trivial extends types#2711

Open
DukeDeSouth wants to merge 1 commit intomicrosoft:mainfrom
DukeDeSouth:fix/reduce-instantiation-count-for-big-unions
Open

Optimize distributive conditional type instantiation for trivial extends types#2711
DukeDeSouth wants to merge 1 commit intomicrosoft:mainfrom
DukeDeSouth:fix/reduce-instantiation-count-for-big-unions

Conversation

@DukeDeSouth
Copy link

@DukeDeSouth DukeDeSouth commented Feb 7, 2026

Human View

Summary

Adds a fast path in getConditionalTypeInstantiation for distributive conditional types where the extends type resolves to never, any, or unknown. In these cases, the branch outcome is trivially determinable for all union members, so we skip the expensive per-member getConditionalType call (which involves type instantiation, permissive instantiation, and structural comparison) and directly apply the predetermined branch.

The Problem

When distributing a conditional type like Exclude<BigUnion, never> (= T extends never ? never : T) over a large union (e.g., React HTML props with 250+ members), the current code calls getConditionalType for each member of the union. Each call:

  1. Instantiates the check type (+1 instantiateType call)
  2. Instantiates the extends type (+1 instantiateType call)
  3. Computes permissive instantiations
  4. Runs isTypeAssignableTo structural comparison
  5. Instantiates the result branch (+1 instantiateType call)

For extends never, every member goes to the false branch — all this work is redundant. In complex HOC patterns (withRouter + generic enhance + React props), multiple layers of Omit/Pick/Exclude compound this, potentially exhausting the 5M instantiationCount budget and producing a false positive TS2589 error.

The Fix

Before distributing over union members, resolve the extends type. If it's:

  • never: All non-never members go to false branch → apply false branch directly
  • any/unknown: All members go to true branch → apply true branch directly

Safety guards (to prevent false negatives on genuinely infinite types):

  • Only applies when !forConstraint (constraint resolution has different semantics)
  • Only applies when len(root.inferTypeParameters) == 0 (infer types need inference logic)
  • Only applies when distribution type is a union
  • Falls through to the original getConditionalType path for all other cases
  • Does not change any instantiation limits

Testing

  • All existing submodule tests pass (0 regressions)
  • limitDeepInstantiations still correctly errors on genuinely infinite types
  • All recursiveConditionalTypes / recursiveConditionalCrash tests pass
  • All conditionalType* and mappedType* tests pass

Fixes microsoft/TypeScript#34933
Related: #988, #2609


AI View (DCCE Protocol v1.0)

Metadata

  • Generator: Claude (Anthropic) via Cursor IDE
  • Methodology: AI-assisted development with human oversight and review

AI Contribution Summary

  • Solution design and implementation

Verification Steps Performed

  1. Reproduced the reported issue
  2. Analyzed source code to identify root cause
  3. Implemented and tested the fix

Human Review Guidance

  • Review the implementation for correctness and completeness
  • Verify test coverage matches the described scenarios

Made with M7 Cursor

…nds types

When distributing a conditional type T extends U ? X : Y over a union,
if the extends type U resolves to 'never', 'any', or 'unknown', the
branch outcome is trivially determinable for all union members without
running the full getConditionalType machinery per member.

- When U is 'never': T extends never is false for all non-never T,
  so we directly apply the false branch to each member.
- When U is 'any' or 'unknown': T extends any/unknown is true for all T,
  so we directly apply the true branch to each member.

This skips expensive per-member operations including type instantiation,
permissive instantiation, and structural comparison (isTypeAssignableTo)
that are redundant when the branch is predetermined.

The optimization primarily benefits patterns like Exclude<BigUnion, never>
(commonly triggered by Omit<T, never>) where hundreds of union members
would each trigger the same branch outcome. In complex generic patterns
with large React prop types (250+ properties), this significantly reduces
the total instantiation count, helping avoid false positive TS2589 errors.

Safety guards: only applies when !forConstraint, no infer type parameters,
and distribution type is a union. Falls through to the original path for
all other cases.

Fixes microsoft/TypeScript#34933

Co-authored-by: Cursor <cursoragent@cursor.com>
@DukeDeSouth
Copy link
Author

@microsoft-github-policy-service agree

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.

“Type instantiation is excessively deep and possibly infinite” but only in a large codebase

1 participant