TypeScript: conditional types and using a boolean parameter to control the return type
Asked Answered
J

3

14

How do I rewrite this without overload signatures, using conditional types instead?

function foo(returnString: true): string;
function foo(returnString: false): number;
function foo(returnString: boolean) {
  return returnString ? String(Math.random()) : Math.random();
}

I tried the following code, but it doesn't compile without as any:

function foo<T extends boolean>(returnString: T): T extends true ? string : number {
  return (returnString ? String(Math.random()) : Math.random()) as any;
}

How can I get rid of as any?

The error message is super-unhelpful:

Type 'string | number' is not assignable to type 'T extends true ? string : number'.
  Type 'string' is not assignable to type 'T extends true ? string : number'.
Jany answered 19/7, 2018 at 21:40 Comment(2)
Looks like this is a known issue, and they mention in that issue that an any cast or overloads are the current workarounds. Additionally in their June Design Meeting Notes they mention: Today, a type isn't assignable to a conditional type unless it's another conditional type., so it seems they are aware of this and a fix may be coming in the future.Gutter
@Gutter Thanks! Before asking my question, I spent quite some time trying to find a corresponding issue, but it hid too well.Jany
A
9

I'm not exactly sure why the compiler can't accept this as is (not extremely familiar with TypeScript), but here's what you could do:

function foo<T extends boolean>(returnString: T): T extends true ? string : number;
function foo<T extends boolean>(returnString: T): string | number {
  return returnString ? String(Math.random()) : Math.random();
}

Basically you separate the declaration (public signature) and the implementation, giving the more accurate signature to the declaration and the broader one to the implementation.

Arriaga answered 19/7, 2018 at 22:55 Comment(2)
This works, but I thought conditional types would allow us to get rid of declaration signatures altogether in cases like this.Jany
My real signature is, of course, more complex than the example. Keeping two copies of it feels wrong.Jany
V
0

I think you should write your functions like this:

export class MyService {

    request(param: false): string; // must a defination;

    request(param: true): number;

    request(param: any): string | number {
        return null;
    }

}

borrowed from https://github.com/angular/angular/blob/master/packages/common/http/src/client.ts

with typescript 2.8, you can write func like this:

function fun<T extends true | false>(t: T): T extends true ? string : number {
    return null;
}
Vallombrosa answered 20/7, 2018 at 0:4 Comment(4)
Did you try compiling this code? It doesn't seem to be able to work. If so, please add the example which compiles.Matchboard
I see. This is just what OP explicitly stated as undesirable.Matchboard
sorry, it's my fault, It is a feature of typescript 2.8, you can write like this: function fun<T extends true | true>(t: T): T extends true ? string : number { return null; }Vallombrosa
I've added return statement into your function (like in question) - it doesn't seem to distinguish true and false. T extends boolean holds independently of value.Matchboard
H
0

I also faced that issue , and in the fact I did not solved it. But I found a way to avoid using type any by assigning the conditional return type to a generic. Then can be used instead of any.

function foo<T extends boolean, Z = T extends true ? string : number >(returnString?: T ): Z  {
    if(returnString) {
        return String(Math.random()) as Z ;
    }
    return Math.random() as Z ;
}
Holocaine answered 23/2, 2023 at 10:12 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.