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:
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:
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:
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:
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);
}