Home > Software design >  How to use generic type with extends to pass parameters into more specific typed function
How to use generic type with extends to pass parameters into more specific typed function

Time:12-06

I wrote this ts playground and I can't understand why it isn't possible to do this.

Here's the code as well:

type Foo = {
    t: string;
}

type Bar = string | {
    date: Date;
    list: string;
}

function test<T extends Bar | Foo>(items: T[]) {
    console.log(items);
    test2(items);
}

function test2(items: Bar[] | Foo[]) {
    console.log(items);
}

The error I get is when calling test2 in test saying that T[] isn't a valid type for the type of test2.

Why's that?

CodePudding user response:

Here's the compiler error for the code you've shown:

function test <T extends Bar | Foo>(items: T[]): void {
  console.log(items);
  test2(items);
  //    ~~~~~
}
Argument of type 'T[]' is not assignable to parameter of type 'Bar[] | Foo[]'.
  Type 'T[]' is not assignable to type 'Bar[]'.
    Type 'T' is not assignable to type 'Bar'.
      Type 'Foo | Bar' is not assignable to type 'Bar'.
        Type 'Foo' is not assignable to type 'Bar'.
          Type 'T' is not assignable to type '{ date: Date; list: string; }'.
            Type 'Foo | Bar' is not assignable to type '{ date: Date; list: string; }'.
              Type 'string' is not assignable to type '{ date: Date; list: string; }'. (2345)

What this means is that — currently — your test function has a signature that expects an array of values. Each individual value can be of a type that is assignable to either Foo or Bar: it's acceptable to provide an array value with mixed elements of Foo and Bar. For example:

TS Playground

const array = [
  { t: 'hello' },
  'world',
  { date: new Date(), list: 'str' },
];

test(array); // ok

However, your test2 function has a signature which expects an array argument of either exclusively Foo elements or exclusively Bar elements:

function test2 (items: Bar[] | Foo[]): void;

So, when providing the same array to test2, the error occurs:

TS Playground

const array = [
  { t: 'hello' },
  'world',
  { date: new Date(), list: 'str' },
];

test2(array);
//    ~~~~~
// A very similar compiler error happens here

You can mitigate the error by modifying the parameter of the test2 function to accept an array of mixed Foo and Bar elements, like this:

(Bar | Foo)[]

which, when put all together, would look like this:

TS Playground

type Foo = { t: string };

type Bar = string | {
  date: Date;
  list: string;
};

function test <T extends Bar | Foo>(items: T[]): void {
  console.log(items);
  test2(items); // ok
}

// function test2 (items: Bar[] | Foo[]) {
function test2 (items: (Bar | Foo)[]): void {
  console.log(items);
}

Alternatively, if your intention was to have a more strict test function that accepts a generic array of exclusively Foo elements or exclusively Bar elements, you can rewrite it like this and achieve the same compatibility with your existing test2 function:

TS Playground

function test <T extends Bar[] | Foo[]>(items: T): void {
  console.log(items);
  test2(items); // ok
}

CodePudding user response:

If you change test2 to following signature, it will be acceptable

function test2(items: Array<Bar|Foo>)

Its because

test<T extends Bar | Foo> (items: T[])

says that items is an array can be of both types A and B in the same time, (and your intention is probably to have either array of A or array of B), and test2 requires unified array of one or the other type.

This would work as well

function test<A extends Bar, B extends Foo> (items: A[] | B[]) {
    console.log(items);
    test2(items);
}

function test2(items: Foo[] | Bar[]) {
    console.log(items);
}
  • Related