Arguments of same length Typescript
I'd like to write a function in Typescript as follows
x, y => void
Where x
is an array of type string[]
and y
is an array of type string[][]
, but I'd like to constrain y
so that the inner arrays must have the same length as array x
.
Is this possible?
UPDATE 2022 It might be done in a much easier and less verbose way:
function array<X extends unknown[], Y extends X[]>(x: [...X], y: [...Y]) {
return [1, 2, 3] as any
}
const result = array([1, 2, 3], [[1, 1, 1]]) // ok
const result2 = array([1, 2, 3], [[1, 1, 1], [1, 1]]) // expected error
const result3 = array([1, 2, 3], [[1, 1, 1], [1]]) // expected error
const result4 = array([1, 2], [[1, 1], [1, 1]]) // ok
const result5 = array([1, 2], [[1, 1], [1]]) // expected error
I just was not aware about variadic tuple types 1 year ago :D
Yes, this is possible.
CONSIDER THIS SOLUTION AS A DEPRECATED ONE
Here is my solution:
/**
* Second approach, is more advanced and it is exactly what you are looking for
*/
type ArrayElement = number
type Array1D = ReadonlyArray<ArrayElement>
type Array2D = ReadonlyArray<Array1D>
type MutableLength = unknown[]['length'] // number
/**
* Get length of the array
* Allow only immutable arrays
*/
export type Length<T extends ReadonlyArray<any>> = T extends { length: infer L } ? MutableLength extends L ? MutableLength : L : MutableLength;
/**
* Compare length of the arrays
*/
type CompareLength<X extends ReadonlyArray<any>, Y extends ReadonlyArray<any>> =
MutableLength extends Length<X> ? false : MutableLength extends Length<Y> ? false : Length<X> extends Length<Y> ? true : false;
/**
* Check if all arrays (union type) have same length as X
*/
type Filter<X extends ReadonlyArray<any>, Y extends ReadonlyArray<any>> =
X['length'] extends Y['length']
? Y['length'] extends X['length'] ? Y : never : never
{
/**
* CompareLength, compares length of X and filtered Y,
* if true - return zero index element - ReadonlyArray<ArrayElement>
* if false - return never
*
* So, if it will return never, then you can't pass second argument,
* but if you did not provide second argument, you will receive another error -
* function expects two arguments
*/
function array<X extends Array1D, Y extends {
0: readonly ArrayElement[]
}[CompareLength<X, Filter<X, Y>> extends true ? 0 : never]>(x: X, y: readonly Y[]): 'put here your returned type'
function array<X extends Array1D, Y extends readonly ArrayElement[], Z extends CompareLength<X, Y>>(x: X, y: readonly Y[]) {
return [1, 2, 3] as any
}
const result = array([1, 2, 3] as const, [[1, 1, 1], [1, 2, 3]] as const) // ok
const result0 = array([1, 2, 3] as const, [[1, 1, 1]] as const) // ok
const arr = [1, 2, 3] as const
const result1 = array([1, 2, 3], [[1, 1, 1], [1]]) // error
const result2 = array([1, 2, 3] as const, [[1, 1, 1], [1, 2]] as const) // error
const result3 = array([1, 2, 3] as const, [[1, 2]] as const) // error
const result4 = array([1, 2, 3] as const, [[1], [1, 2], [1, 2, 3]] as const) // error
const result5 = array([1, 2, 3] as const, [1] as const) // error
const result6 = array([1, 2, 3] as const, [[1, 2, 3], []] as const) // error
const result7 = array(arr, [[1, 1, 1]]) // error, because TS is unable to fidure out length of mutable array.
}
I used number
instead of string
, because it is easier to type numbers instead of strings.
Feel free to change it ArrayElement
to string
Please keep in mind, that you should use every time as const
to infer array length
Playground