In Typescript, define a type for an array where the first element is more specific than the rest

In current versions of Typescript this is possible using array spread:

type FAs = [Function, ...Array<{}>]

It supports any length from 1 to n (the first element is required).


If you define

type FAs = [Function, {}];

Then values of type FAs will require a first element of type Function, a second element of type {}, and succeeding elements of Function | {}. That is how TypeScript literal array types work. From the TS docs:

When accessing an element outside the set of known indices, a union type is used instead:

This should do everything you want except for the fact that you will be able to pass in a Function-typed value as the third element etc. of the array. But actually that would be the case anyway, since Function is compatible with {}.

There is no way around this. There is no way in TS to define an array type where the first n elements are of some specific type(s), and there are an arbitrary number of remaining elements of some other specific type.

I also wonder if the type system supports what is arguably an arbitrary-length tuple.

Actually, the type system only supports arbitrary-length tuples. If you say

type Tuple = [number, number];

this type is compatible with any array, of length two or greater, that contains numbers. If you say

type Tuple = [string, number];

this type is compatible with any array, of length two or longer, that has a string as its first element, a number as its second, and either a string or number as its third etc. I would not call the reasons for this behavior "computer-science based"; it's more a matter of what it's feasible for TS to check.

An alternate approach

interface Arglist {
  [index: number]: object;
  0: Function;
}

const a1: Arglist = [func];
const a2: Arglist = [22];                  // fails
const a3: Arglist = [func, "foo"];         // fails
const a4: Arglist = [func, obj];
const a5: Arglist = [func, obj, obj];

I also wonder if the type system supports what is arguably an arbitrary-length tuple.

Since TS 4.0, you can use variadic tuple types. For example, assert [func, ...<proper func args>] in a type-safe way:

type FAs<A extends unknown[], R> = [(...args: A) => R, ...A]

const myCaller = <A extends unknown[], R>([fn, ...args]: FAs<A, R>) =>
    fn.apply(null, args)
Example:
const fn1 = (a1: string, a2: number) => true

const r1 = myCaller([fn1, "foo", 42]) // OK, r1 has type `boolean`
const r2 = myCaller([fn1, "foo", "bar"]) // error, `bar` has wrong type
const r3 = myCaller([fn1, "foo"]) // error, not enough arguments

Playground