I have a union type of an Array of various specific lengths:
[ number ] | [ number, number ] | [ number, number, number, number ]
As you can see, there are requirements for an array with one element, two elements, or four elements.
I am trying to create an object which contains a function with one of these lengths. How do I write the type definition to allow for this?
Type '(numbers: [number]) => number' is not assignable to type '(numbers: [number] | [number, number] | [number, number, number, number]) => any'. Types of parameters 'numbers' and 'numbers' are incompatible. Type '[number] | [number, number] | [number, number, number, number]' is not assignable to type '[number]'. Type '[number, number]' is not assignable to type '[number]'. Source has 2 element(s) but target allows only 1.ts(2322)
------- Edit -------
I've applied a suggestion from the comments and made all the possible calls into separate function unions instead of an array union:
const people: {
name: string,
address: Address,
work: ((numbers: [ number ]) => any) | ((numbers: [ number, number ]) => any) | ((numbers: [ number, number, number, number ]) => any)
}[] = [
When trying to now call a function from this array:
people[1].work([2, 8, 6, 4])
It throws the following error now:
In VSCode I found out this is why:
"The intersection '[number] & [number, number] & [number, number, number, number]' was reduced to 'never' because property 'length' has conflicting types in some constituents."
CodePudding user response:
UPDATED You need to use bivariance here
class Address { }
type Tuple<
N extends number,
Item = number,
Result extends Array<unknown> = [],
> =
(Result['length'] extends N
? Result
: Tuple<N, Item, [...Result, number]>
)
interface WorkFn {
work(numbers: Tuple<1> | Tuple<2> | Tuple<4>): any
}
interface Person extends WorkFn {
name: string,
address: Address,
}
const people: Person[] = [
{
name: "Bob",
address: new Address(),
work(numbers: Tuple<1>) {
const [myNumber] = numbers;
return myNumber * 6
}
},
{
name: "Ashley",
address: new Address(),
work: function (numbers: Tuple<4>): boolean {
const [myNumber, anotherNumber, someNumber, replaceNumber] = numbers;
return myNumber === anotherNumber && someNumber === replaceNumber;
}
},
{
name: "Michael",
address: new Address(),
work: function (numbers: Tuple<2>): number {
const [myNumber, anotherNumber] = numbers;
return myNumber * anotherNumber;
}
},
]
Here you can find the difference between method type an arrow function type and about bivariance
Also, please be aware that it is not 100% safe
CodePudding user response:
My proposal would be to use a generic function to create the people
array.
function createPeople<
T extends {
name: string,
address: Address,
work: ((numbers: [ number ]) => any) | ((numbers: [ number, number ]) => any) | ((numbers: [number, number, number, number]) => any)
}[]
>(p: [...T]){
return p
}
const people = createPeople([
{
name: "Bob",
address: new Address(),
work: function(numbers: [ number ]): number {
const [ myNumber ]: [ number ] = numbers;
return myNumber * 6
}
},
{
name: "Ashley",
address: new Address(),
work: function(numbers: [ number, number, number, number ]): boolean {
const [ myNumber, anotherNumber, someNumber, replaceNumber ]: [ number, number, number, number ]= numbers;
return myNumber === anotherNumber && someNumber === replaceNumber;
}
},
{
name: "Michael",
address: new Address(),
work: function(numbers: [ number, number ]): number {
const [ myNumber, anotherNumber ]: [ number, number ] = numbers;
return myNumber * anotherNumber;
}
},
])
TypeScript now knows what the callback of each index is. That makes the following calls strictly typed.
people[1].work([2, 8, 6, 4])
people[2].work([1, 2])