microsoft / microsoft/TypeScript

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

Open
#62,812 1 comment 0 reactions 0 assignees View on GitHub
Bug Domain: Conditional Types Help Wanted
Dominant language
Go
Stars
111k
Forks
14.3k
PR merge metrics
PR metrics pending

Description

### 🔎 Search Terms

distribution, spread

### 🕗 Version & Regression Information

This is the behavior in every version I tried from version 4.1.5 upwards to 5.9.3 and in nightly v6.0.0-dev.20251127

### ⏯ Playground Link

https://www.typescriptlang.org/play/?ts=5.9.3#code/C4TwDgpgBAwgTgewM5IAqICYFcDGwA8AqgHYCWCxANLAlscBHFBAB4PEZJR0DWxCAd2IBtALoA+KAF4AUFHk06DJq3acow0sQBmjKAC1GCagDozW3UwBKEJMFFyFAfigAKEuWLM2EDlwt6ALIQALYARoyOCs4aweGMpmbwyGiYuAQeFNQ2duIO0dEAXFDEEABujACUUVDFYgDcMgA2EMBQDHYAjMXJKOgI2Hj4xFjxTAA+UHZwWgDm1MJ0GBDaWhAYEvXyAPTbGtNzolCTwiNjDqCQUAAiEGDAABad0rCIfWlDZxETU8AzxPMNEsVmsNpJdvs-odjhovowHC02h1gAAmYquOE-A4A7xqfw6PSZLwudxkCi43zqXj8IRQFzCImJEy3e5PUTFUoVOCVWolcpVLYQ4TYwEio4nTHUTHiyH-QHSmGnUbfahi5qtdq2YAAZnRmJhIopfigASYRLpbnNqkpXGpgmJGkZUDMJl6qQG6WGyr0kxFC2Bq1KYPZfK5PI5-O5Wx2e2FUIBqvjsxlSrGUu9cARGuRABY9RmDUmjepTVBzSSrT5jXbafSnS7XHG5SnpZUQ5yqryO1GoEK-b9m4rJSUMyn+wqJRnE82ZEA

### 💻 Code

```ts
type CrossProduct =
Counter extends [infer Zero, ...infer Rest]
? (Union extends infer Member
? [Member, ...CrossProduct]
: never)
: [];
let test1: CrossProduct; // [string] | [number]
type Depth1 = CrossProduct // [string] | [number]
let test2: (number | string extends infer Union ? (Union extends unknown ? [Union, ...Depth1]: never) : never); // [string, string] | [number, number] | [string, number] | [number, string]
let test3: (number | string extends infer Union ? (Union extends unknown ? [Union, ...CrossProduct]: never) : never); // [string, string] | [number, number]
let test4: (number | string extends infer Union ? (Union extends unknown ? [Union, ...([string] | [number])]: never) : never); // [string, string] | [number, number] | [string, number] | [number, string]
```

### 🙁 Actual behavior

The types of test2 and test4 evaluate to the correct, expected, fully distributed type, while the type of test3 is incompletely distributed (as displayed by IntelliSense in the Playground).

The only difference between test2 and test3 is that in test3 the type that is aliased as Depth1 in test2 is inlined in test3. Thus, both should evaluate to the same type. The type of test2 is correct.

test4 also evaluates correctly. Here the alias is replaced by the literal evaluation result.

### 🙂 Expected behavior

The type of variable test3 should evaluate to

[string, string] | [number, number] | [string, number] | [number, string]

But it evaluates to

[string, string] | [number, number]

### Additional information about the issue

This problem matters since the recursive call in test3 is what I actually need for the construction of a respective recursive type.

Contributor guide

Open the contributing guide

Research direction

Start with the linked TypeScript Playground reproduction and compare the evaluated types of test2, test3, and test4. Trace the recursive CrossProduct call and spread handling in the type checker; done means test3 evaluates to the same fully distributed union as test2 and test4.

Written by the indexing model from the issue text.

Assessment

Tech stack
typescript
Domain
compilers
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
38/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.