typescriptunion-typesfunction-parametertype-definition

TypeScript Array Union Type in function parameters


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?

TS playground

Example:

const people: {
    name: string,
    address: Address,
    work: (numbers: [ number ] | [ number, number ] | [ number, number, number, number ]) => any
}[] = [
    {
        name: "Bob",
        address: new Address(),
        work: function(numbers: [ number ]): number {
            // Implementation returning number
        }
    },
    {
        name: "Ashley",
        address: new Address(),
        work: function(numbers: [ number, number, number, number ]): boolean {
            // Implementation returning boolean
        }
    },
    {
        name: "Michael",
        address: new Address(),
        work: function(numbers: [ number, number ]): number {
            // Implementation returning number
        }
    },
]

Currently, it's giving me the error:

Error message

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:

Type number is not assignable to type never

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."


Solution

  • 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;
        }
      },
    ]
    

    TypeScript playground

    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