Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Fix spread operator failing to distribute over union when type is inlined#62836

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 ourterms of service andprivacy statement. We’ll occasionally send you account related emails.

Already on GitHub?Sign in to your account

Open
thromel wants to merge1 commit intomicrosoft:main
base:main
Choose a base branch
Loading
fromthromel:fix/spread-tuple-union-distribution

Conversation

@thromel
Copy link

Fixes#62812

Problem

When a union/intersection type likenumber | string appears in the true branch of a conditional type that has the same type as its check type, the type was incorrectly being narrowed with a substitution constraint. This caused issues when the union type was used as a type argument to a generic type.

For example:

typeCrossProduct<Union,Counterextendsunknown[]>=Counterextends[inferZero, ...inferRest]    ?(Unionextends inferMember        ?[Member, ...CrossProduct<Union,Rest>]        :never)    :[];typeDepth1=CrossProduct<number|string,[undefined]>// [string] | [number]// This works correctly:lettest2:(number|stringextends inferUnion ?(Unionextendsunknown ?[Union, ...Depth1]:never) :never);// Result: [string, string] | [number, number] | [string, number] | [number, string]// But this was broken (inlined instead of aliased):lettest3:(number|stringextends inferUnion ?(Unionextendsunknown ?[Union, ...CrossProduct<number|string,[undefined]>]:never) :never);// Expected: [string, string] | [number, number] | [string, number] | [number, string]// Actual: [string, string] | [number, number]  (missing cross-product entries)

Root Cause

IngetConditionalFlowTypeOfType, when processing a type node in the true branch of a conditional type, the function checks if the type matches the conditional's check type and creates a substitution type with the implied constraint.

The issue is that for structural types likenumber | string, different occurrences in the code all resolve to the same canonical type. So whenCrossProduct<number | string, [undefined]> appeared inside the true branch ofnumber | string extends infer Union ? ..., the type argumentnumber | string was incorrectly being narrowed with the constraintUnion even though it was a completely independent occurrence.

Fix

Added a check to skip this narrowing for union/intersection types that don't contain type variables:

constisStructuralTypeWithoutTypeVariables=!!(type.flags&TypeFlags.UnionOrIntersection)&&!couldContainTypeVariables(type);if(!isStructuralTypeWithoutTypeVariables&&(covariant||type.flags&TypeFlags.TypeVariable)&& ...){

This ensures:

  • Named types (interfaces, classes, type aliases) still get narrowed - different references to the same named type refer to the same entity
  • Structural types (unions, intersections) without type variables are NOT narrowed - different occurrences are independent even if they structurally match

Test

Addedtests/cases/conformance/types/spread/spreadTupleUnionDistribution.ts to verify the fix.

@github-project-automationgithub-project-automationbot moved this toNot started inPR BacklogDec 4, 2025
@typescript-bottypescript-bot added the For Backlog BugPRs that fix a backlog bug labelDec 4, 2025
@thromel
Copy link
Author

@microsoft-github-policy-service agree

@thromel
Copy link
Author

@RyanCavanaugh

…inedFixesmicrosoft#62812When a union/intersection type like `number | string` appears in the truebranch of a conditional type that has the same type as its check type,the type was incorrectly being narrowed with a substitution constraint.This caused issues when the union type was used as a type argument toa generic type - different occurrences of `number | string` in the codewere being treated as the same entity when they should be independent.The fix adds a check to skip this narrowing for structural types(unions/intersections) that don't contain type variables. Named types(interfaces, classes, etc.) still get narrowed since different referencesto the same named type refer to the same entity.
@thromelthromelforce-pushed thefix/spread-tuple-union-distribution branch fromf44ce00 to8d6b841CompareDecember 15, 2025 18:13
Sign up for freeto join this conversation on GitHub. Already have an account?Sign in to comment

Reviewers

No reviews

Assignees

No one assigned

Labels

For Backlog BugPRs that fix a backlog bug

Projects

Status: Not started

Milestone

No milestone

Development

Successfully merging this pull request may close these issues.

Spread operator fails to distribute over union when recursive type call is inlined instead of aliased

2 participants

@thromel@typescript-bot

[8]ページ先頭

©2009-2025 Movatter.jp