Using a conditional type to sometimes return the value passed as a function argument

Viewed 82

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

TypeScript Playground

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;
        }
    }
};

TypeScript Playground

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

TypeScript Playground

0 Answers
Related