Typescript return type depending on parameter

TypescriptTyping

Typescript Problem Overview


I am trying to write a function which takes a parameter of type boolean and returns one of two types, depending on the value of the input. I have found two approaches:

function dependsOnParameter<B extends boolean>(x: B): B extends true ? number : string {
    if (x) {
        return 3;
    } else {
        return "string";
    }
}

Here, TypeScript says that Type '3'/'"string"' is not assignable to type 'B extends true ? number : string'.

My other approach looks like this:

function dependsOnParameter(x: true): number;
function dependsOnParameter(x: false): string;
function dependsOnParameter(x: boolean): number | string {
    if (x) {
        return 3;
    } else {
        return "string";
    }
}

This compiles; however, if I try to use my function:

function calling(x: boolean) {
    dependsOnParameter(x);
}

I get Argument of type 'boolean' is not assignable to parameter of type 'false'.

Is there any way to achieve what I want without using any?

Typescript Solutions


Solution 1 - Typescript

Both approaches are valid. If your function uses conditional types in the return it will need to use type assertions, as typescript will not try to reason about the conditional type since it contains a free type parameter:

function dependsOnParameter<B extends boolean>(x: B): B extends true ? number : string {
    if (x) {
        return 3 as any;
    } else {
        return "string"as any;
    }
}

This approach uses any which you want to avoid.

The second approach we can get to work without resorting to type assertions by just duplicating the last signature:

function dependsOnParameter(x: true): number;
function dependsOnParameter(x: false): string;
function dependsOnParameter(x: boolean): number | string
function dependsOnParameter(x: boolean): number | string {
    if (x) {
        return 3;
    } else {
        return "string";
    }
}

function calling(x: boolean) {
    dependsOnParameter(x); // returns number| string
    dependsOnParameter(true); // returns number
    dependsOnParameter(false); // returns string
}

The last signature is the implementation signature and is not publicly accessible. You can make it accessible by duplicating it. The compiler is not smart enough to combine the two overloads with true/false and decide the return type is string|number

Edit

We can also combine the two approaches for fewer signatures:

function dependsOnParameter<B extends boolean>(x: B): B extends true ? number : string 
function dependsOnParameter(x: boolean): number | string{
    if (x) {
        return 3;
    } else {
        return "string";
    }
}

Solution 2 - Typescript

This is the right way:

function dependsOnParameter<B extends boolean>(x: B): B extends true ? number : string {
    return (x === true ? 3 : "string") as B extends true ? number : string;
}

Here, the condition itself (B extends true ? number : string) is considered as a type. This type is called a Conditional Type.

Solution 3 - Typescript

You can write it like this

function dependsOnParameter<B extends boolean, C = B extends true ? number : string>(x: B): C {
    if (x) {
        return 3 as unknown as C;
    } else {
        return "string" as unknown as C;
    }
}

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionLukorView Question on Stackoverflow
Solution 1 - TypescriptTitian Cernicova-DragomirView Answer on Stackoverflow
Solution 2 - Typescriptaxell-brendowView Answer on Stackoverflow
Solution 3 - TypescriptNurbol AlpysbayevView Answer on Stackoverflow