0
votes

Description

I would like like to define a Generic function in TypeScript so that it would apply a transformation to the array argument and then return an array of same type. (TypeScript version used: v3.24)

Simplified code snippet as follow:

describe("generic functions", function () {
    it("should not give transpilation error", function () {
        // given
        function myFunction<T>(arr: T[]): T[] {
            // ...... apply some transformation to the array here
            return arr; // or return a copy of the array of same type
        }

        let arrays = [
            ["a"],
            [1]
        ]; // Just for demonstration purpose, the actual input has more varieties of types

        // These can be transpiled without error
        myFunction(["b"]);
        myFunction([2]);
        arrays.map(myFunction);

        // This gives transpilation error, see following for the detailed message
        arrays.map(arr => myFunction(arr));
    });
});

In particular I am not sure why the TS compiler would fail to recognize the type only when I am applying the function to an array of mixed type and calling it explicitly.

The transpilation error message is:

TS2345: Argument of type 'string[]| number[]' is not assignable to parameter of type 'string[]'. Type 'number[]' is not assignable to type 'string[]'. Type 'number' is not assignable to type 'string'.

Similar question

  1. Assigning generics function to delegate in Typescript - But I would like to keep the generic signature as I want my function to be more flexible
  2. Typescript Generic type not assignable error - But my function is returning the generic type.

(Apologies and please let me know if this duplicates with another question - I had tried to search but failed to find question that could answer this)

1

1 Answers

1
votes

The compiler is having trouble unwrapping the (string[] | number[]) type to infer the type argument. In this case, explicitly passing the type argument will work:

arrays.map(arr => myFunction<string | number>(arr));

This is one of those cases where the compiler treats:

(string | number)[][]

Differently to:

(string[] | number[])[]

If you use the latter, it infers the type argument as string[], which then fails for number[].

You can help the compiler with a type guard, but this example is really thought exercise and I can't imagine wanting to use this in real life:

function isStringArray(arr: any[]): arr is string[] {
    return (typeof arr[0] === 'string');
}

arrays.map(arr => (isStringArray(arr)) ? myFunction(arr) : myFunction(arr));

Even though it calls myFunction either way, it can infer the type argument as string[] and number[] in each case.