I see a few answers with questions around inferring the length of the array literals. The issue is that when you pass in an array literal to a function, the compiler generally widens it to an array and does not interpret it as a fixed-length tuple. This is often what you want; often arrays change length. When you want the compiler to see [1, 2] as a pair and not as an array, you can give the compiler a hint:
function requireTwoSameLengthArrays<
T extends readonly [] | readonly any[]
>(t: T, u: { [K in keyof T]: any }): void { }
Notice that the generic type parameter T's generic constraint is a union of an empty tuple type [] and an array type any[]. (Don't worry about readonly; this modifier makes the function more general, not more specific, since string[] is assignable to readonly string[] and not vice versa.) Having the empty tuple type in a union doesn't change the kinds of things that T can be (after all, any[] already includes the empty tuple []). But it does give the compiler a hint that tuple types are desired.
So the compiler will infer [1, 2] as [number, number] instead of as number[].
Examining the signature above, you see that the u argument is a mapped array/tuple type. If T is an tuple, {[K in keyof T]: any} is a tuple of the same length as T.
So let's see it in action:
requireTwoSameLengthArrays([1, 2], [3, 4]); // okay
requireTwoSameLengthArrays([1, 2], [3]); // error! property 1 is missing in [number]!
requireTwoSameLengthArrays([1, 2], [3, 4, 5]); // error! length is incompatible!
Hooray!
Note that if the compiler has already forgotten the length of the tuple, this will not work:
const oops = [1, 2]; // number[]
requireTwoSameLengthArrays(oops, [1, 2, 3]); // okay because both are of unknown length
The type of oops is inferred as number[], and passing it into requireTwoSameLengthArrays() can't undo that inference. It's too late. If you want the compiler to just reject arrays of completely unknown length, you can do it:
function requireTwoSameLengthTuples<
T extends (readonly [] | readonly any[]) & (
number extends T["length"] ? readonly [] : unknown
)>(t: T, u: { [K in keyof T]: any }): void { }
This is uglier, but what it's doing is checking to see if T has a length of number instead of some specific numeric literal. If so, it prevents the match by demanding an empty tuple. This is a little weird, but it works:
requireTwoSameLengthTuples([1, 2], [3, 4]); // okay
requireTwoSameLengthTuples([1, 2], [3]); // error! [number] not [any, any]
requireTwoSameLengthTuples([1, 2], [3, 4, 5]); // error! ]number, number, number]
requireTwoSameLengthTuples(oops, [1, 2, 3]); // error on oops!
// Types of property 'length' are incompatible.
Okay, hope that helps; good luck!
Playground link to code
[Tab, Panel]as Daniel suggested below or a new type that requires individual Tab and a Panel fields provided i.e.{ readonly tab: Tab, readonly panel: Panel }).