Currently, TypeScript is grappling with the complexities of merging generics with control flow analysis. The issue arises when handling the variable multiple
, where TypeScript can narrow it down from type T
to something like T & true
. However, the challenge intensifies when attempting to further refine or limit T
itself. Ideally, if multiple
is set to true
, then T
should also be true
. Unfortunately, the actual behavior doesn't align with this expectation, as evident in the example below:
foo(Math.random() < 0.99, 1); // no compiler error, 99% chance of failure
The core of the issue stems from the fact that Math.random() < 0.99
yields a type
boolean</code, which is a union of <code>true | false
. Consequently, the conditional type
T extends true ? number[] : number
results in the union
number[] | number
, potentially causing compatibility concerns with the arguments accepted by
foo()
.
A feature request is currently in progress to tackle this by enabling a generic function where T
can only be true
or false
, excluding the union true | false
. Until this improvement materializes, users will need to explore alternative strategies.
To effectively address this issue, utilizing control flow analysis in place of generics can be a strategic workaround. By accepting a rest parameter of a destructured discriminated union of tuple types, the function foo()
becomes more versatile in handling diverse scenarios:
function foo(...[multiple, value]:
[multiple: true, value: number[]] |
[multiple: false, value: number]
) {
if (multiple) {
let test: number[] = value; // works fine
}
}
This approach leverages TypeScript's improved comprehension of narrowing the discriminated union represented by [multiple, value]
. Consequently, invoking foo()
now mirrors an overloaded function setup, offering flexibility in how arguments are passed.
By following this methodology, potential errors can be minimized by ensuring that foo()
is invoked with the correct parameters. Any deviation would trigger a type mismatch error, especially in scenarios like the example using Math.random()
:
foo(true, 5); // error
foo(Math.random() < 0.99, 1); // error
This strict adherence guarantees that [boolean, number]
cannot be assigned to
[true, number[]] | [false, number]
.
View and Edit the Code in TypeScript Playground