Skip to content

Commit

Permalink
LastArrayElement: Fix handling of tuple with spread elements (#727)
Browse files Browse the repository at this point in the history
  • Loading branch information
Emiyaaaaa authored Oct 25, 2023
1 parent 89e2993 commit 9630089
Show file tree
Hide file tree
Showing 2 changed files with 31 additions and 8 deletions.
26 changes: 18 additions & 8 deletions source/last-array-element.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,16 +13,26 @@ const array = ['foo', 2];
typeof lastOf(array);
//=> number
const array = ['foo', 2] as const;
typeof lastOf(array);
//=> 2
```
@category Array
@category Template literal
*/
export type LastArrayElement<Elements extends readonly unknown[]>
= number extends Elements['length']
? Elements extends ReadonlyArray<infer Element>
? Element
: never
: Elements extends readonly [...any, infer Target]
? Target
: never;
export type LastArrayElement<Elements extends readonly unknown[], ElementBeforeTailingSpreadElement = never> =
// If the last element of an array is a spread element, the `LastArrayElement` result should be `'the type of the element before the spread element' | 'the type of the spread element'`.
Elements extends readonly []
? ElementBeforeTailingSpreadElement
: Elements extends readonly [...infer U, infer V]
? V
: Elements extends readonly [infer U, ...infer V]
// If we return `V[number] | U` directly, it would be wrong for `[[string, boolean, object, ...number[]]`.
// So we need to recurse type `V` and carry over the type of the element before the spread element.
? LastArrayElement<V, U>
: Elements extends ReadonlyArray<infer U>
? U | ElementBeforeTailingSpreadElement
: never;
13 changes: 13 additions & 0 deletions test-d/last-array-element.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,16 @@ expectType<2>(lastOf(mixedArray));
expectType<string>(lastOf(['a', 'b', 'c']));
expectType<string | number>(lastOf(['a', 'b', 1]));
expectType<1>(lastOf(['a', 'b', 1] as const));

declare const leadingSpreadTuple: [...string[], object, number];
expectType<number>(lastOf(leadingSpreadTuple));

declare const trailingSpreadTuple1: [string, ...number[]];
expectType<number | string>(lastOf(trailingSpreadTuple1));

declare const trailingSpreadTuple2: [string, boolean, ...number[]];
expectType<number | boolean>(lastOf(trailingSpreadTuple2));

// eslint-disable-next-line @typescript-eslint/array-type
declare const trailingSpreadTuple3: ['foo', true, ...(1 | '2')[]];
expectType<true | 1 | '2'>(lastOf(trailingSpreadTuple3));

0 comments on commit 9630089

Please sign in to comment.