Search code examples
typescript

how properly overload function definition to cover specific type for T


Here is simple synthetic function

function test(input) {
    if (typeof input === 'number') {
        return 'a'
    } else {
        return input
    }
}

I want to type it but cannot figure out approach. The best I was able to get is:

function test<T extends number>(input: T): string;
function test<T>(input: T): T;
function test(input) {
    if (typeof input === 'number') {
        return 'a'
    } else {
        return input
    }
}

const a = [test('2'), test(2), test(null), test(true)] as const
//    ^? a: readonly ['2', string, null, true]
// looks valid

const b = [test<string>('2'), test<boolean>(true)] as const
//    ^? b: readonly [string, boolean]
// I can control/generalize types!

The blot on the landscape for that solution looks like that:

Parameter 'input' implicitly has an 'any' type.

What am I missing here? How to make Typescript happy while keeping types consistent and reliable?


Solution

  • Each individual overload signature represents a possible variation of parameter types and associated return type for the function. The implementation of the function must recognize all of these possible variations, so the appropriate type for any given parameter in the implementation signature is a union of all of the types used for that parameter in the overload signatures.

    Your function has two overload signatures, one with a generic type that is constrained by number (so input is expected to be some kind of number type)…

    function test<T extends number>(input: T): string;
    

    …and one that uses an unconstrained generic type parameter…

    function test<T>(input: T): T;
    

    …which means that it could be any type, and is effectively unknown.

    This means that the type for the input parameter in the implementation signature should be unknown (because unknown absorbs other types in a union) — and you can simply annotate it to satisfy the compiler:

    function test<T extends number>(input: T): string;
    function test<T>(input: T): T;
    function test(input: unknown) {
    //            ^^^^^^^^^^^^^^
    // Annotate the parameter in the implementation signature
      if (typeof input === 'number') {
        return 'a'
      } else {
        return input
      }
    }
    

    TS Playground