Confusion about generic return type based on input parameters

Viewed 179

I'm going to present a couple of examples with and without generic return types and I'm confused as to why typescript doesn't treat all the examples the same.

Example 1: base case - function overload (works correctly)

function test(data:string):string
function test(data:string[]):string[]
function test(data:string | string[]): string | string[] {

  if(Array.isArray(data)){
     return ['']
  } 

  return data
}

const testArr = test([]) // return type is Array
const testStr=test('a') // return type is string

Now I'm gonna try to do the same with a generic data type:

Example 2: inferred return types from the function are wrong

function testGeneric<T extends string | string[]>(data:T){
  if(Array.isArray(data)){
     return ['']
  } 

  return data
}

const testArrGen = testGeneric([])
const testStrGen=testGeneric('a')

Example 3: The function itself has the return types wrong.

function testGeneric2<T extends string | string[]>(data:T): T extends string? T : T[]{
  if(Array.isArray(data)){
     return [''] // error
  } 

  return data // error
}

const testArrGen2 = testGeneric2([]) // string[] || never[]
const testStrGen2=testGeneric2('a') //  "a"| string[]

Now I would like to understand why examples 2 and 3 are wrong, and how could I create the correct implementation using generics.

TS Playground

1 Answers

In example 2, no type information is declared for the result, no type inference is performed on the result.

The problem with Example 3 is that the returned array (string []) does not match the specified return type, which in case of an array type is T[] and T itself must not be of type string. It could have extended the string type. Furthermore the bound parameter type has to be used to let the parameter type T match the return type. You don't have to redeclare the type parameter T for the result.

I think this is how it should work:

function testGeneric2<T extends string | string[]>(data: T): T {
  if (Array.isArray(data)) {
    const res: T = Object.create(data);

    return res;
  }

  return data;
}

Since no new object can be created from the type parameter out of the box (see here), I created one from the array parameter using with Object.create.

Related