Simple function with conditional type
Asked Answered
C

3

8

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.

Centare answered 3/9, 2018 at 7:13 Comment(0)
M
9

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)
}
Murdoch answered 3/9, 2018 at 7:18 Comment(2)
I wonder if there's any good documentation for this issue, since it comes up a bunch. I see microsoft/TypeScript#23132 as a suggestion to do something about this, and maybe microsoft/TypeScript#13995 is also related, but I wish I had some good canonical information to give people.Lastex
Hey, microsoft/TypeScript#33912 is probably the canonical issue to point to now.Lastex
R
5

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
Recessional answered 3/9, 2018 at 7:35 Comment(1)
Interesting use of type queries and conditional types :-)Murdoch
T
0

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)
Transmittal answered 9/10, 2022 at 14:24 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.