I have a situation where the generic type is constraint by a union type, but I find that doing this does not make the type narrowing to work as expected. The code snippet below shows what is going on.
function somefunc<T extends string | number>(input: T): T {
if (typeof input === "string") {
// expecting input to be of type "string"
// but input becomes of type T & "string"
input
} else {
// expecting input to be of type "number"
// but input becomes of type T extends string | number
input
}
}
If I do away with the generics and just annotate the function argument as string | number
it works, but for my use case I need to have the generic constraints.
Edit
The use case is basically an attempt to also use this with conditional types. Basically I want to have a result type be a conditional type that depends on the input type. So when the input type is number
, the result is also number
, when input is string
result becomes string
also. Basically this:
type Result<T> = T extends string ? string : number
function somefunc<T extends string | number>(input: T): Result<T> {
if (typeof input === "string") {
// expecting input to be of type "string"
// but input becomes of type T & "string"
input
} else {
// expecting input to be of type "number"
// but input becomes of type T extends string | number
input
}
}
I am probably missing something, but the question is, how do I have union based generic constraint and have type narrowing work as I expect. In the above code, that will mean, in the if
branch, input
becomes type string
while in the else
branch, it becomes number
(or at least it becomes T & number
)
** Edit **
I was able to achieve want I wanted using function overloading. I was only wondering if same thing can be achieved using generics and conditional types.
The reason why it's not narrowed is explained in this answer
Hacky way to narrow the type correctly:
type Result<T> = T extends string ? string : number;
function somefunc<T extends string | number>(input: T): Result<T> {
const inputNarrowed: string | number = input;
if (typeof inputNarrowed === "string") {
inputNarrowed; // string
} else {
inputNarrowed; // number
}
return inputNarrowed as Result<T>;
}
Alternative solution (which I prefer) with overload + conditional generic
type Result<T> = T extends string ? string : number;
function somefunc<T extends string | number>(input: T): Result<T>;
function somefunc(input: string | number) {
if (typeof input === "string") {
input; // string
} else {
input; // number
}
return input;
}
const str = somefunc("string"); // string
const num = somefunc(1); // number