Search code examples
typescriptstringtypesnumbersreturn

ReturnType won't catch return type of a fuction that's returning string or number values


Can anyone please explain to me why ReturnType can infer a function's exact return values when there's an if statement, but only can infer types of the value when the function returns one guaranteed string or number value?

In testA, ReturnType<typeof testA> could successfully infer the return values, 1 and 2.

However, in the case of testB(return one value), ReturnType<typeof testB> can only infer the type, number.

Why a function that returns only can guess literally the type of the return value which is number

It behaves the same with string values. (second screenshot)

the case with number

the case with string


Solution

  • This is related to type widening

    The following resources will be useful:

    • SO answer explaining the behaviour
    • The TypeScript PR (referenced in the above answer) which explains the rules for when a literal type is widened or not

    An explicit excerpt from the above PR outlines the behaviour observed

    In a function with no return type annotation, if the inferred return type is a literal type (but not a literal union type) and the function does not have a contextual type with a return type that includes literal types, the return type is widened to its widened literal type:

    function foo() {
        return "hello";
    }
    
    function bar() {
        return cond ? "foo" : "bar";
    }
    
    const c1 = foo();  // string
    const c2 = bar();  // "foo" | "bar"
    

    A couple of simple ways to address this are:

    • Leverage as const on the functions return value
    • Provide an explicit return type on the function (as your function definition, e.g. return 1 satisfies the return type constraint, there is no need for inferrence and thus potential widening)
    const testA = (v: boolean) => v ? 1 : 2;         // (v: boolean) => 1 | 2
    const testB = (v: boolean) => 1;                 // (v: boolean) => number
    const constTestB = (v: boolean) => 1 as const;   // (v: boolean) => 1
    const typedTestB = (v: boolean): 1 => 1;         // (v: boolean) => 1
    

    https://tsplay.dev/w2pyzm