You can't narrow the type of the generic parameter within the function. So when you test a this will not tell the compiler what the type of b is. And more importantly it will not tell the compiler what the return type of the function needs to be
function add<T extends (number | string)>(a: T, b: T): T {
if (typeof a === 'string' && typeof b === 'string') {
let result = a + b; // result is string, we can apply +
return result as T; // still an error without the assertion, string is not T
} else if (typeof a === 'number' && typeof b === 'number') {
let result = a + b; // result is number, we can apply +
return result as T; // still an error without the assertion, number is not T
}
throw "Unsupported parameter type combination"; // default case should not be reached
}
In this case though maybe having a dedicated implementation signature that works on the union instead (meaning no assertion is required) and public signature being the one you previously used.:
function add<T extends number | string>(a: T, b: T): T
function add(a: number | string, b: number | string): number | string {
if (typeof a === 'string' && typeof b === 'string') {
return a + b;
} else if (typeof a === 'number' && typeof b === 'number') {
return a + b;
}
throw "Unsupported parameter type combination"; // default case should not be reached
}