I have a function that can take a number or a string, and always outputs either a number or null. If the function receives a number and returns a number, it will return the same number that it received.
Until now, I hadn't done anything to tell TypeScript that the same number would be returned, so I was losing some information but this worked just fine:
function getColNumber(colName: number | string): number | null {
But now I'd like to tell TypeScript about this restriction, so I've been trying to do this using a conditional type like this:
function getColNumber<T extends number | string>(colName: T): (T extends number ? T : number) | null {
However, TypeScript complains to me in each instance when I don't return null, telling me either "Type 'T & number' is not assignable to type 'T extends number ? T : number" or "Type 'number' is not assignable to type '(T extends number ? T : number) | null'"
I've tested this conditional type outside the function by creating some derived types using it, and TypeScript does seem to understand it in that circumstance. For example:
type SelfOrNumber<T> = T extends number ? T : number;
type Three = SelfOrNumber<3>; // type Three = 3
type Num = SelfOrNumber<'not a number'>; // type Num = number
So I'm not sure why it's not working in my example. Here's a minimal reproducible example:
function returnSelfRandOrNullConditional<T extends number | string>(arg: T): (T extends number ? T : number) | null {
if (typeof arg === 'number') {
if (arg > 0) {
return arg; // Type 'T & number' is not assignable to type 'T extends number ? T : number'.
} else {
return null;
}
} else {
const rand = Math.random();
if (rand > 0.5) {
return rand; // Type 'number' is not assignable to type '(T extends number ? T : number) | null'.
} else {
return null;
}
}
};
I have found that I can get the results I want using an overloaded function, so I know I can use that approach, but it's not clear to me why a conditional type doesn't work the way I had expected it to here.
function returnSelfRandOrNullOverloaded<T extends number>(arg: T): T | null
function returnSelfRandOrNullOverloaded<T extends string>(arg: T): number | null
function returnSelfRandOrNullOverloaded<T extends number | string>(arg: T): number | null
function returnSelfRandOrNullOverloaded<T extends number | string>(arg: T): number | null {
if (typeof arg === 'number') {
if (arg > 0) {
return arg;
} else {
return null;
}
} else {
const rand = Math.random();
if (rand > 0.5) {
return rand;
} else {
return null;
}
}
}
const a = returnSelfRandOrNullOverloaded(3); // 3 | null
const b = returnSelfRandOrNullOverloaded(-2); // -2 | null
const c = returnSelfRandOrNullOverloaded('test'); // number | null
let test = Math.random() > 0.5 ? 3 : 'test';
const d = returnSelfRandOrNullOverloaded(test); // number | null