Repository navigation
Expand file tree
/
Copy patharray-at.d.ts
More file actions
156 lines (127 loc) · 8.39 KB
/
Copy patharray-at.d.ts
File metadata and controls
156 lines (127 loc) · 8.39 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
import type {ExcludeRestElement} from './exclude-rest-element.d.ts';
import type {ExtractRestElement} from './extract-rest-element.d.ts';
import type {If} from './if.d.ts';
import type {IntRange} from './int-range.d.ts';
import type {IfNotAnyOrNever, IsExactOptionalPropertyTypesEnabled} from './internal/type.d.ts';
import type {LessThan} from './less-than.d.ts';
import type {IsNegative} from './numeric.d.ts';
import type {SplitOnRestElement} from './split-on-rest-element.d.ts';
import type {Subtract} from './subtract.d.ts';
import type {Sum} from './sum.d.ts';
import type {UnknownArray} from './unknown-array.d.ts';
/**
Get the type of the element at the given index of an array, like [`Array#at()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/at).
A negative index counts back from the end. If the array has optional elements or a rest element, the result is a union of every element the index can reach, and it includes `undefined` if the index can be out of bounds. If `exactOptionalPropertyTypes` is disabled, an optional element can also be `undefined`.
Use-case: Get the type of the last element of a tuple, or of the value returned by `array.at(index)`.
@example
```
import type {ArrayAt} from 'type-fest';
// Positive index
type A = ArrayAt<['a', 'b', 'c', 'd'], 1>;
//=> 'b'
// Negative index
type B = ArrayAt<['a', 'b', 'c', 'd'], -4>;
//=> 'a'
// Positive index with optional elements
type C = ArrayAt<['a', 'b', 'c'?, 'd'?], 3>;
//=> 'd' | undefined
// Negative index with optional elements
type D = ArrayAt<['a', 'b', 'c'?, 'd'?], -2>;
//=> 'a' | 'b' | 'c'
// Positive index with rest element and optional elements
type E = ArrayAt<['a', 'b', 'c'?, ...number[]], 3>;
//=> number | undefined
// Negative index with rest element and optional elements
type F = ArrayAt<['a', 'b', 'c'?, ...number[]], -1>;
//=> 'b' | 'c' | number
// Positive index with rest element in middle
type G = ArrayAt<['a', 'b', 'c', ...number[], 'd', 'e'], 4>;
//=> number | 'd' | 'e'
// Negative index with rest element in middle
type H = ArrayAt<['a', 'b', ...number[], 'c', 'd', 'e'], -5>;
//=> 'a' | 'b' | number
// Out-of-bounds positive index
type I = ArrayAt<['a', 'b'], 5>;
//=> undefined
// Out-of-bounds negative index
type J = ArrayAt<['a', 'b'], -3>;
//=> undefined
```
Note: A non-integer index is not supported, even though `Array#at()` truncates it.
@category Array
*/
export type ArrayAt<TArray extends UnknownArray, Index extends number> = IfNotAnyOrNever<TArray, {
ifNot: TArray extends unknown // For distributing `TArray`
? Index extends unknown // For distributing `Index`
? number extends Index
? TArray[number] | undefined
: IsNegative<Index> extends true
? ArrayAtNegativeIndex<TArray, Index>
: ArrayAtPositiveIndex<TArray, Index>
: never // Should never happen
: never; // Should never happen
}>;
type ArrayAtPositiveIndex<TArray extends UnknownArray, Index extends number> =
SplitOnRestElement<TArray> extends readonly [infer BeforeRest extends UnknownArray, infer Rest extends UnknownArray, infer AfterRest extends UnknownArray]
? LessThan<Index, Required<BeforeRest>['length']> extends true
? BeforeRest[Index]
: Subtract<Index, Required<BeforeRest>['length']> extends infer IndexAfterBeforeRest extends number
// The rest element can hold any number of elements, so the index can land on the rest element or on any element after it, up to `IndexAfterBeforeRest`.
? LessThan<IndexAfterBeforeRest, AfterRest['length']> extends true
? Rest[number] | AfterRest[IntRange<0, Sum<IndexAfterBeforeRest, 1>>]
// The index can also be past the last element, which is the only result for an array without a rest element. Checking this first avoids building a large `IntRange` for a large index.
: Rest[number] | AfterRest[number] | undefined
: never // Should never happen
: never; // Should never happen
/**
Recursion order for `ArrayAtNegativeIndex<['a', 'b', 'c', ...number[], 'd', 'e'], -5>`:
1. `ArrayAtNegativeIndex<['a', 'b', 'c', ...number[], 'd', 'e'], -5, 0, never>` // No match, increment `Index`.
2. `ArrayAtNegativeIndex<['a', 'b', 'c', ...number[], 'd'], -4, 0, never>` // No match, increment `Index`.
3. `ArrayAtNegativeIndex<['a', 'b', 'c', ...number[]], -3, 0, never>` // Found rest element, set `Index` to `-1`, `Left` to `Sum<Index, 1>` (i.e., `-2`), add rest element to result.
4. `ArrayAtNegativeIndex<['a', 'b', 'c'], -1, -2, number>` // Match found, `Left` not yet `0`, increment `Left`, add current element to result.
5. `ArrayAtNegativeIndex<['a', 'b'], -1, -1, 'c' | number>` // Match found, `Left` not yet `0`, increment `Left`, add current element to result.
6. `ArrayAtNegativeIndex<['a'], -1, 0, 'b' | 'c' | number>` // Match found, `Left` is `0`, add current element to result and return result.
Result: `'a' | 'b' | 'c' | number`
---
Recursion order for `ArrayAtNegativeIndex<['a', 'b', 'c'?, ...number[]], -1>`:
1. `ArrayAtNegativeIndex<['a', 'b', 'c'?, ...number[]], -1, 0, never>` // Found rest element, set `Index` to `-1`, `Left` to `Sum<Index, 1>` (i.e., `0`), add rest element to result.
2. `ArrayAtNegativeIndex<['a', 'b', 'c'?], -1, 0, number>` // Match found, current element is optional, add it to result without changing anything else.
3. `ArrayAtNegativeIndex<['a', 'b'], -1, 0, number | 'c'>` // Match found, current element is not optional, add it to result and return result.
Result: `'b' | 'c' | number`
---
Recursion order for `ArrayAtNegativeIndex<['a', 'b', 'c', 'd'?, 'e'?], -3>`:
1. `ArrayAtNegativeIndex<['a', 'b', 'c', 'd'?, 'e'?], -3, 0, never>` // No match, current element is optional, increment `Index` & decrement `Left`.
2. `ArrayAtNegativeIndex<['a', 'b', 'c', 'd'?], -2, -1, never>` // No match, current element is optional, increment `Index` & decrement `Left`.
3. `ArrayAtNegativeIndex<['a', 'b', 'c'], -1, -2, never>` // Match found, current element is not optional, `Left` is not `0`, increment `Left`, add current element to result.
4. `ArrayAtNegativeIndex<['a', 'b'], -1, -1, 'c'>` // Match found, current element is not optional, `Left` is not `0`, increment `Left`, add current element to result.
5. `ArrayAtNegativeIndex<['a'], -1, 0, 'b' | 'c'>` // Match found, current element is not optional, `Left` is `0`, add current element to result and return result.
Result: `'a' | 'b' | 'c'`
*/
type ArrayAtNegativeIndex<TArray extends UnknownArray, Index extends number, Left extends number = 0, Result = never> =
TArray extends readonly []
? Result | undefined // If the array is exhausted, return `Result` with `undefined`.
: number extends TArray['length']
// Enters this branch, if `TArray` contains a rest element.
? TArray extends readonly [...infer Rest, infer L]
? Index extends -1
? L // If an element after the rest element matches, return it.
: ArrayAtNegativeIndex<Rest, Sum<Index, 1>, Left, Result>
// Enters this branch, if `TArray` contains no elements after the rest element.
: ExcludeRestElement<TArray> extends infer TWithoutRest extends UnknownArray
// Remove the rest element and recurse further with the elements before the rest element,
// Set `Index` to `-1` & `Left` to `Sum<Index, 1>`, so that elements keep getting matched until `Left` reaches `0`.
? ArrayAtNegativeIndex<TWithoutRest, -1, Sum<Index, 1>, ExtractRestElement<TArray> | Result> // Also, add the rest element to `Result`.
: never // Should never happen
// Enters this branch, if `TArray` contains no rest element.
: TArray extends readonly [...infer Rest, (infer Last)?]
? Index extends -1
? TArray extends readonly [...unknown[], unknown] // If `Last` is not optional
? Left extends 0
? Last | Result // If there's a match, and `Left` is `0`, return `Result` with `Last`.
: ArrayAtNegativeIndex<Rest, Index, Sum<Left, 1>, Last | Result> // If there's a match, and `Left` is not `0`, increment `Left` and add `Last` to `Result`.
: ArrayAtNegativeIndex<Rest, Index, Left, Last | If<IsExactOptionalPropertyTypesEnabled, never, undefined> | Result> // If `Last` is optional, just add it to `Result` without changing anything else.
: TArray extends readonly [...unknown[], unknown]
? ArrayAtNegativeIndex<Rest, Sum<Index, 1>, Left, Result> // If `Last` is not optional, just increment `Index`.
: ArrayAtNegativeIndex<Rest, Sum<Index, 1>, Subtract<Left, 1>, Result> // If `Last` is optional, increment `Index` and decrement `Left`.
: never; // Should never happen
export {};