When I encounter these types of issues, my go-to solution is to transfer the signature into an overload:
function f<T extends boolean>(value: T): T extends false ? 0 : 1;
function f(value: boolean) {
if (value === false) return 0;
else return 1;
}
This approach leverages the fact that TypeScript does not validate if the implementation "matches" the overloads, but rather confirms if the overloads can be assigned to the function declaration's signature (in this case, it is (value: boolean) => 0 | 1
).
It's important to note that this method still doesn't perform type-checking on the implementation. You could switch 0
and 1
in the body and it would still function, similar to using the as
operator.
Additionally, utilizing function declarations instead of function expressions is necessary as overloads are not supported for function expressions (refer to ms/TS#16731).
Playground
Addressing @AlexWayne's suggestion, another approach involves using two overloads and eliminating the conditional type:
function f(value: true): 1;
function f(value: false): 0;
function f(value: boolean) {
if (value === false) return 0;
else return 1;
}
Despite its advantages, this method still has some limitations mentioned earlier. If you pass a value of type boolean
, an error occurs since no overloads match. To address this, simply add another overload (at the bottom, as it is the least specific):
function f(value: true): 1;
function f(value: false): 0;
function f(value: boolean): 0 | 1;
function f(value: boolean) {
if (value === false) return 0;
else return 1;
}
Playground
An issue with using T extends false ? 0 : 1
or false extends T ? 0 : 1
arises when T
is boolean
, resulting in either 1
or 0
, respectively, instead of the preferred 0 | 1
. Resolving this requires checking for the general boolean
type first, which elongates the process considerably:
boolean extends T ? 0 | 1 : T extends false ? 0 : 1