Description
🔎 Search Terms
"distribution union"
I have a type DistributeOverTupleElementTypes that is intended to operate on a tuple type and to distribute over the component union elements. Example:
DistributeOverTupleElementTypes<[number | boolean, string | 5]> is expected to evaluate to [number, string] | [number, 5] | [boolean, string] | [boolean, 5] and it works as expected (see t1 below). However, when replacing type "5" with type "[5]", the expected evaluation result misses the components [boolean, string] and [boolean, [5]] (see t2, t3, t4 and t5). Why? Is the behavioral difference intended?
type DistributeOverTupleElementTypes<T> =
T extends [infer SingleComponent]
? (SingleComponent extends unknown // enforce distribution
? [SingleComponent]
: never)
: (T extends [infer First, ...infer Rest]
? (First extends unknown // enforce distribution
? (DistributeOverTupleElementTypes<Rest> extends infer EvaluatedRestU
? (EvaluatedRestU extends infer FixedEvaluatedRestTuple // enforce distribution
? (FixedEvaluatedRestTuple extends unknown[]
? [First, ...FixedEvaluatedRestTuple]
: never)
: never)
: never)
: never)
: never);
const t1: [number, string] | [number, 5] | [boolean, string] | [boolean, 5] extends DistributeOverTupleElementTypes<[number | boolean, string | 5]> ? true : false = true;
const t2: [number, string] | [number, [5]] | [boolean, string] | [boolean, [5]] extends DistributeOverTupleElementTypes<[number | boolean, string | [5]]> ? true : false = true; //!! error
const t3: [number, string] | [number, [5]] | [boolean, string] | [boolean, [5]] extends DistributeOverTupleElementTypes<[number | boolean, string | [5]]> | [boolean, string] ? true : false = true; // !!error
const t4: [number, string] | [number, [5]] | [boolean, string] | [boolean, [5]] extends DistributeOverTupleElementTypes<[number | boolean, string | [5]]> | [boolean, [5]] ? true : false = true; // !!error
const t5: [number, string] | [number, [5]] | [boolean, string] | [boolean, [5]] extends DistributeOverTupleElementTypes<[number | boolean, string | [5]]> | [boolean, string] | [boolean, [5]] ? true : false = true;
// Thus, the evaluation result of DistributeOverTupleElementTypes<[number | boolean, string | [5]]> misses the union components [boolean, string] and [boolean, [5]]. Why?
const t6: [number, string | [5]] | [boolean, string | [5]] extends DistributeOverTupleElementTypes<[number | boolean, string | 5]> ? true : false = true; // !!error
Output
"use strict";
const t1 = true;
const t2 = true; //!! error
const t3 = true; // !!error
const t4 = true; // !!error
const t5 = true;
// Thus, the evaluation result of DistributeOverTupleElementTypes<[number | boolean, string | [5]]> misses the union components [boolean, string] and [boolean, [5]]. Why?
const t6 = true; // !!error
Compiler Options
{
"compilerOptions": {
"strict": true,
"noImplicitAny": true,
"strictNullChecks": true,
"strictFunctionTypes": true,
"strictPropertyInitialization": true,
"strictBindCallApply": true,
"noImplicitThis": true,
"noImplicitReturns": true,
"alwaysStrict": true,
"esModuleInterop": true,
"declaration": true,
"target": "ES2017",
"jsx": "react",
"module": "ESNext",
"moduleResolution": "node"
}
}
Playground Link: Provided
🕗 Version & Regression Information
- This is the behavior in every version I tried starting with version 5.04 up to 5.8.3 and the Nightly version as of 2025-05-22.
⏯ Playground Link
💻 Code
type DistributeOverTupleElementTypes<T> =
T extends [infer SingleComponent]
? (SingleComponent extends unknown // enforce distribution
? [SingleComponent]
: never)
: (T extends [infer First, ...infer Rest]
? (First extends unknown // enforce distribution
? (DistributeOverTupleElementTypes<Rest> extends infer EvaluatedRestU
? (EvaluatedRestU extends infer FixedEvaluatedRestTuple // enforce distribution
? (FixedEvaluatedRestTuple extends unknown[]
? [First, ...FixedEvaluatedRestTuple]
: never)
: never)
: never)
: never)
: never);
const t1: [number, string] | [number, 5] | [boolean, string] | [boolean, 5] extends DistributeOverTupleElementTypes<[number | boolean, string | 5]> ? true : false = true;
const t2: [number, string] | [number, [5]] | [boolean, string] | [boolean, [5]] extends DistributeOverTupleElementTypes<[number | boolean, string | [5]]> ? true : false = true; //!! error
const t3: [number, string] | [number, [5]] | [boolean, string] | [boolean, [5]] extends DistributeOverTupleElementTypes<[number | boolean, string | [5]]> | [boolean, string] ? true : false = true; // !!error
const t4: [number, string] | [number, [5]] | [boolean, string] | [boolean, [5]] extends DistributeOverTupleElementTypes<[number | boolean, string | [5]]> | [boolean, [5]] ? true : false = true; // !!error
const t5: [number, string] | [number, [5]] | [boolean, string] | [boolean, [5]] extends DistributeOverTupleElementTypes<[number | boolean, string | [5]]> | [boolean, string] | [boolean, [5]] ? true : false = true;
// Thus, the evaluation result of DistributeOverTupleElementTypes<[number | boolean, string | [5]]> misses the union components [boolean, string] and [boolean, [5]]. Why?
🙁 Actual behavior
Assignments to variables t2, t3 and t4 are flagged as faulty (Type 'true' is not assignable to type 'false'.(2322)).
🙂 Expected behavior
Assignments to variables t2, t3 and t4 are OK because the respectively declared types should evaluate to "true".
Additional information about the issue
No response