For typing practice purpose, I'm trying to apply the following typing to get the first element of an array :
type First<T extends unknown[]> = T extends [infer P, ...unknown[]] ? P : never
I have the following implementation :
const getFirstOrThrow = <T extends unknown[]>(arr: T): First<T> => {
if(arr.length === 0) {
throw new Error("Empty array")
}
return arr[0]; // Type 'unknown' is not assignable to type 'First<T>'.(2322)
}
For some reason typescript is not able to understand that the array should have at least 1 element.
I tried with the following type too :
type First<T extends unknown[]> = T["length"] extends 0 ? never : T[0];
type A = First<[]> // never
type B = First<[1]> // 1
type C = First<[1,2]> // 1
Is something like this possible with typescript ?
This typing seems ok to me :
declare function getFirst <T extends unknown[]>(arr: T): First<T>;
const A = getFirst([]) // never
const B = getFirst([1]) // number
const C = getFirst([1, 2]) //number
CodePudding user response:
Just as an alternative to Matthieu's good answer, a couple of other options for you (well, perhaps a couple of variations on another option):
You can make the type of the element generic instead of the type of the array, which avoids the need for an
extends
in the type parameter entirely:type First<T extends unknown[]> = T["length"] extends 0 ? never : T[0]; const getFirstOrThrow = <ElementType>(arr: ElementType[]): First<ElementType[]> => { // −−−−−−−−−−−−−−−−−−−−−−^^^^^^^^^^^−−−−−−−^^^^^^^^^^^^^−−−−−−−−−^^^^^^^^^^^^^ if (arr.length === 0) { throw new Error("Empty array"); } return arr[0]; };
I think it's fundamentally the same thing, though, since the generic type parameter is unconstrained (and thus effectively
extends any
).(This is obsoleted by your edit to the question saying you were explicitly trying to use
First
, but I'll leave it here for future readers.) And if you use the element type as the type parameter rather than an array type, you don't needFirst
anymore, you can just useElementType
directly (or even leave it off; it'll be inferred from thereturn
):const getFirstOrThrow = <ElementType>(arr: ElementType[]): ElementType => { // −−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−^^^^^^^^^^^ if (arr.length === 0) { throw new Error("Empty array"); } return arr[0]; };
or (inferring from
return
):const getFirstOrThrow = <ElementType>(arr: ElementType[]) => { // −−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−^ if (arr.length === 0) { throw new Error("Empty array"); } return arr[0]; };
I'd include it explicitly so that if I messed up the code inside the function, TypeScript would catch me, but I've seen it argued well both ways. :-)
CodePudding user response:
Make it T extends any
:
const getFirstOrThrow = <T extends any[]>(arr: T): First<T> => {
if (arr.length === 0) {
throw new Error("Empty array")
}
return arr[0]; // ok
}