Simple function with conditional type

1.7k Views Asked by At

The following function was largely lifted from the typescript handbook section on using conditional types, yet it doesn't work:

function test<T extends boolean>(a: T): T extends true ? string : number {
  return a ? '1' : 1
}

Typescript is reporting that:

Type '1 | "1"' is not assignable to type 'T extends true ? string : number'.
  Type '1' is not assignable to type 'T extends true ? string : number'.

I imagine I'm missing something obvious. How can I construct this function so that typescript correctly infers the type based on the function's argument?

I realize that this specific problem could be solved using function signature overloading, but I'd like to learn more about conditional types.

3

There are 3 best solutions below

2
On BEST ANSWER

The short a answer is you can't. No value will be assignable to an unresolved conditional type (a conditional type that still depends on a free generic type variable). The only thing you can do is use a type assertion.

function test<T extends boolean>(a: T): T extends true ? string : number {
  return (a ? '1' : 1)  as any
}

Conditional types are useful to express relations between parameters but they don't help when it comes to implementing the function. Another approach would be to use a more permissive implementation signature.

function test<T extends boolean>(a: T): T extends true ? string : number
function test(a: boolean): number | string {
    return (a ? '1' : 1)
}
1
On

TypeScript does correctly infer return type. What it does not do is that it does not check that runtime logic follows the condition specified in conditional type, and in your case it causes compile-time error. You can circumvent the error by using indexed type access to get the type you want based on condition.

It will have different behavior compared to the test that's declared in your question, namely it will infer union type in case when type is not known at compile time. The implementation will still not be checked for conformance with conditional type logic, but there is no error, and no type assertions necessary:

interface Selector {
    t: string;
    f: number;
}

function test<T extends boolean>(a: T): Selector[T extends true ? 't' : 'f'] {
  // NOTE: not checked that is returns correct type actually
  return a ? '1' : 1
}


const t1 = test(true);  // string
const t2 = test(false); // number
declare var b: boolean;
const t3 = test(b); // string | number, which may or may not be what you want
0
On

function keyword doesn't work for me but it works in arrow function definition

 type counterType<T = boolean> = (obj:T) => T extends true? 
 string:number;

 let counter: counterType = (obj) =>{
   if(obj)
    return "abc";
    return 123
}

console.log(counter(true)) //returns ABC (string)