ts-data-forge 13.0.0 → 14.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/array/impl/array-utils-creation.d.mts +2 -2
- package/dist/array/impl/array-utils-creation.d.mts.map +1 -1
- package/dist/array/impl/array-utils-creation.mjs.map +1 -1
- package/dist/array/impl/array-utils-length-bounded-array-cast.d.mts +56 -29
- package/dist/array/impl/array-utils-length-bounded-array-cast.d.mts.map +1 -1
- package/dist/array/impl/array-utils-length-bounded-array-cast.mjs +23 -15
- package/dist/array/impl/array-utils-length-bounded-array-cast.mjs.map +1 -1
- package/dist/array/impl/array-utils-length-bounded-array-guard.d.mts +126 -28
- package/dist/array/impl/array-utils-length-bounded-array-guard.d.mts.map +1 -1
- package/dist/array/impl/array-utils-length-bounded-array-guard.mjs +39 -5
- package/dist/array/impl/array-utils-length-bounded-array-guard.mjs.map +1 -1
- package/dist/array/impl/array-utils-length-bounded-tuple-cast.d.mts +167 -0
- package/dist/array/impl/array-utils-length-bounded-tuple-cast.d.mts.map +1 -0
- package/dist/array/impl/array-utils-length-bounded-tuple-cast.mjs +87 -0
- package/dist/array/impl/array-utils-length-bounded-tuple-cast.mjs.map +1 -0
- package/dist/array/impl/array-utils-length-bounded-tuple-guard.d.mts +135 -0
- package/dist/array/impl/array-utils-length-bounded-tuple-guard.d.mts.map +1 -0
- package/dist/array/impl/array-utils-length-bounded-tuple-guard.mjs +41 -0
- package/dist/array/impl/array-utils-length-bounded-tuple-guard.mjs.map +1 -0
- package/dist/array/impl/array-utils-modification.d.mts +32 -18
- package/dist/array/impl/array-utils-modification.d.mts.map +1 -1
- package/dist/array/impl/array-utils-modification.mjs.map +1 -1
- package/dist/array/impl/array-utils-reducing-value.d.mts +3 -3
- package/dist/array/impl/array-utils-reducing-value.d.mts.map +1 -1
- package/dist/array/impl/array-utils-reducing-value.mjs +1 -1
- package/dist/array/impl/array-utils-reducing-value.mjs.map +1 -1
- package/dist/array/impl/array-utils-slicing.d.mts +11 -11
- package/dist/array/impl/array-utils-slicing.d.mts.map +1 -1
- package/dist/array/impl/array-utils-slicing.mjs +1 -1
- package/dist/array/impl/array-utils-slicing.mjs.map +1 -1
- package/dist/array/impl/array-utils-transformation.d.mts +35 -23
- package/dist/array/impl/array-utils-transformation.d.mts.map +1 -1
- package/dist/array/impl/array-utils-transformation.mjs +2 -2
- package/dist/array/impl/array-utils-transformation.mjs.map +1 -1
- package/dist/array/impl/array-utils-validation.d.mts +2 -124
- package/dist/array/impl/array-utils-validation.d.mts.map +1 -1
- package/dist/array/impl/array-utils-validation.mjs +1 -7
- package/dist/array/impl/array-utils-validation.mjs.map +1 -1
- package/dist/array/impl/index.d.mts +2 -0
- package/dist/array/impl/index.d.mts.map +1 -1
- package/dist/array/impl/index.mjs +5 -3
- package/dist/array/impl/index.mjs.map +1 -1
- package/dist/object/object.mjs +1 -1
- package/dist/string/str.d.mts +44 -44
- package/dist/string/str.d.mts.map +1 -1
- package/dist/string/str.mjs +17 -17
- package/dist/string/str.mjs.map +1 -1
- package/package.json +2 -2
- package/src/array/impl/array-utils-creation.mts +7 -3
- package/src/array/impl/array-utils-length-bounded-array-cast.mts +88 -50
- package/src/array/impl/array-utils-length-bounded-array-cast.test.mts +40 -13
- package/src/array/impl/array-utils-length-bounded-array-guard.mts +248 -39
- package/src/array/impl/array-utils-length-bounded-array-guard.test.mts +135 -24
- package/src/array/impl/array-utils-length-bounded-tuple-cast.mts +364 -0
- package/src/array/impl/array-utils-length-bounded-tuple-cast.test.mts +207 -0
- package/src/array/impl/array-utils-length-bounded-tuple-guard.mts +266 -0
- package/src/array/impl/array-utils-length-bounded-tuple-guard.test.mts +441 -0
- package/src/array/impl/array-utils-length-constraint-preservation.test.mts +287 -0
- package/src/array/impl/array-utils-modification.mts +72 -57
- package/src/array/impl/array-utils-modification.test.mts +93 -6
- package/src/array/impl/array-utils-reducing-value.mts +5 -5
- package/src/array/impl/array-utils-slicing.mts +65 -43
- package/src/array/impl/array-utils-slicing.test.mts +55 -0
- package/src/array/impl/array-utils-transformation.mts +66 -39
- package/src/array/impl/array-utils-transformation.test.mts +62 -16
- package/src/array/impl/array-utils-validation.mts +2 -162
- package/src/array/impl/array-utils-validation.test.mts +0 -371
- package/src/array/impl/index.mts +2 -0
- package/src/collections/queue.test.mts +1 -1
- package/src/string/str.mts +69 -69
- package/src/string/str.test.mts +40 -40
|
@@ -0,0 +1,287 @@
|
|
|
1
|
+
import {
|
|
2
|
+
type BoundedLengthArray,
|
|
3
|
+
type FixedLengthArray,
|
|
4
|
+
type FixedLengthTuple,
|
|
5
|
+
type MaxLengthArray,
|
|
6
|
+
type MinLengthArray,
|
|
7
|
+
type Seq,
|
|
8
|
+
} from 'ts-type-forge';
|
|
9
|
+
import { expectType } from '../../expect-type.mjs';
|
|
10
|
+
import { copy, create, seq, zeros } from './array-utils-creation.mjs';
|
|
11
|
+
import {
|
|
12
|
+
asBoundedLengthArray,
|
|
13
|
+
asFixedLengthArray,
|
|
14
|
+
asMaxLengthArray,
|
|
15
|
+
asMinLengthArray,
|
|
16
|
+
} from './array-utils-length-bounded-array-cast.mjs';
|
|
17
|
+
import {
|
|
18
|
+
set,
|
|
19
|
+
toFilled,
|
|
20
|
+
toRangeFilled,
|
|
21
|
+
toUpdated,
|
|
22
|
+
} from './array-utils-modification.mjs';
|
|
23
|
+
import {
|
|
24
|
+
map,
|
|
25
|
+
partition,
|
|
26
|
+
toReversed,
|
|
27
|
+
toSorted,
|
|
28
|
+
toSortedBy,
|
|
29
|
+
} from './array-utils-transformation.mjs';
|
|
30
|
+
|
|
31
|
+
describe('map preserves length constraints', () => {
|
|
32
|
+
test('a branded input keeps its constraint', () => {
|
|
33
|
+
const input: MinLengthArray<3, number> = asMinLengthArray(3, [0, 1, 2, 3]);
|
|
34
|
+
|
|
35
|
+
const result = map(input, (value) => `#${String(value)}`);
|
|
36
|
+
|
|
37
|
+
expectType<typeof result, MinLengthArray<3, string>>('~=');
|
|
38
|
+
|
|
39
|
+
// The structural prefix survives, so in-range access is not `undefined`.
|
|
40
|
+
const first: string = result[0];
|
|
41
|
+
|
|
42
|
+
assert.deepStrictEqual(first, '#0');
|
|
43
|
+
|
|
44
|
+
assert.deepStrictEqual<readonly string[]>(result, ['#0', '#1', '#2', '#3']);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
test('a fixed-length branded input keeps its exact length', () => {
|
|
48
|
+
const input: FixedLengthArray<3, number> = asFixedLengthArray(
|
|
49
|
+
3,
|
|
50
|
+
[255, 128, 0],
|
|
51
|
+
);
|
|
52
|
+
|
|
53
|
+
const result = map(input, (value) => value / 255);
|
|
54
|
+
|
|
55
|
+
expectType<typeof result, FixedLengthArray<3, number>>('~=');
|
|
56
|
+
|
|
57
|
+
const length: 3 = result.length;
|
|
58
|
+
|
|
59
|
+
assert.deepStrictEqual(length, 3);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
test('a minimum above the structural prefix cap survives, clamped like MinLengthArray itself', () => {
|
|
63
|
+
const input: MinLengthArray<12, number> = asMinLengthArray(
|
|
64
|
+
12,
|
|
65
|
+
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11],
|
|
66
|
+
);
|
|
67
|
+
|
|
68
|
+
const result = map(input, String);
|
|
69
|
+
|
|
70
|
+
expectType<typeof result, MinLengthArray<12, string>>('~=');
|
|
71
|
+
|
|
72
|
+
assert.deepStrictEqual<readonly string[]>(result, [
|
|
73
|
+
'0',
|
|
74
|
+
'1',
|
|
75
|
+
'2',
|
|
76
|
+
'3',
|
|
77
|
+
'4',
|
|
78
|
+
'5',
|
|
79
|
+
'6',
|
|
80
|
+
'7',
|
|
81
|
+
'8',
|
|
82
|
+
'9',
|
|
83
|
+
'10',
|
|
84
|
+
'11',
|
|
85
|
+
]);
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
test('a plain tuple still maps element-wise', () => {
|
|
89
|
+
const result = map([1, 2, 3] as const, String);
|
|
90
|
+
|
|
91
|
+
expectType<typeof result, readonly [string, string, string]>('=');
|
|
92
|
+
|
|
93
|
+
assert.deepStrictEqual(result, ['1', '2', '3']);
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
test('a plain array still maps to a plain array', () => {
|
|
97
|
+
const input: readonly number[] = [1, 2, 3] as const;
|
|
98
|
+
|
|
99
|
+
const result = map(input, String);
|
|
100
|
+
|
|
101
|
+
expectType<typeof result, readonly string[]>('=');
|
|
102
|
+
|
|
103
|
+
assert.deepStrictEqual(result, ['1', '2', '3']);
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
test('an upper bound survives too', () => {
|
|
107
|
+
const input: MaxLengthArray<5, number> = asMaxLengthArray(5, [1, 2]);
|
|
108
|
+
|
|
109
|
+
const result = map(input, String);
|
|
110
|
+
|
|
111
|
+
expectType<typeof result, MaxLengthArray<5, string>>('~=');
|
|
112
|
+
|
|
113
|
+
assert.deepStrictEqual<readonly string[]>(result, ['1', '2']);
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
test('both bounds survive together', () => {
|
|
117
|
+
const input: BoundedLengthArray<2, 5, number> = asBoundedLengthArray(
|
|
118
|
+
2,
|
|
119
|
+
5,
|
|
120
|
+
[1, 2],
|
|
121
|
+
);
|
|
122
|
+
|
|
123
|
+
const result = map(input, String);
|
|
124
|
+
|
|
125
|
+
expectType<typeof result, BoundedLengthArray<2, 5, string>>('~=');
|
|
126
|
+
|
|
127
|
+
assert.deepStrictEqual<readonly string[]>(result, ['1', '2']);
|
|
128
|
+
});
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
describe('other length-preserving utilities preserve length constraints', () => {
|
|
132
|
+
test('toSorted keeps the brand', () => {
|
|
133
|
+
const input: FixedLengthArray<3, number> = asFixedLengthArray(3, [3, 1, 2]);
|
|
134
|
+
|
|
135
|
+
const result = toSorted(input, (x, y) => x - y);
|
|
136
|
+
|
|
137
|
+
expectType<typeof result, FixedLengthArray<3, number>>('~=');
|
|
138
|
+
|
|
139
|
+
assert.deepStrictEqual<readonly number[]>(result, [1, 2, 3]);
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
test('toSortedBy keeps the brand', () => {
|
|
143
|
+
const input: MinLengthArray<3, number> = asMinLengthArray(3, [3, 1, 2]);
|
|
144
|
+
|
|
145
|
+
const result = toSortedBy(input, (x) => x);
|
|
146
|
+
|
|
147
|
+
expectType<typeof result, MinLengthArray<3, number>>('~=');
|
|
148
|
+
|
|
149
|
+
assert.deepStrictEqual<readonly number[]>(result, [1, 2, 3]);
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
test('set keeps the brand and widens only the element type', () => {
|
|
153
|
+
const input: MinLengthArray<3, number> = asMinLengthArray(3, [0, 1, 2]);
|
|
154
|
+
|
|
155
|
+
const result = set(input, 1, 'x');
|
|
156
|
+
|
|
157
|
+
expectType<typeof result, MinLengthArray<3, number | 'x'>>('~=');
|
|
158
|
+
|
|
159
|
+
assert.deepStrictEqual<readonly (number | string)[]>(result, [0, 'x', 2]);
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
test('toUpdated keeps the brand and widens only the element type', () => {
|
|
163
|
+
const input: MinLengthArray<3, number> = asMinLengthArray(3, [0, 1, 2]);
|
|
164
|
+
|
|
165
|
+
const result = toUpdated(input, 1, (prev) => `#${String(prev)}`);
|
|
166
|
+
|
|
167
|
+
expectType<typeof result, MinLengthArray<3, number | `#${string}`>>('~=');
|
|
168
|
+
|
|
169
|
+
assert.deepStrictEqual<readonly (number | string)[]>(result, [0, '#1', 2]);
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
test('toRangeFilled keeps the brand', () => {
|
|
173
|
+
const input: MinLengthArray<3, number> = asMinLengthArray(3, [0, 1, 2]);
|
|
174
|
+
|
|
175
|
+
const result = toRangeFilled(input, 'x', [0, 2]);
|
|
176
|
+
|
|
177
|
+
expectType<typeof result, MinLengthArray<3, number | 'x'>>('~=');
|
|
178
|
+
|
|
179
|
+
assert.deepStrictEqual<readonly (number | string)[]>(result, ['x', 'x', 2]);
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
test('toFilled keeps the brand with the fill type', () => {
|
|
183
|
+
const input: MinLengthArray<3, number> = asMinLengthArray(3, [0, 1, 2]);
|
|
184
|
+
|
|
185
|
+
const result = toFilled(input, 'x');
|
|
186
|
+
|
|
187
|
+
expectType<typeof result, MinLengthArray<3, 'x'>>('~=');
|
|
188
|
+
|
|
189
|
+
assert.deepStrictEqual<readonly string[]>(result, ['x', 'x', 'x']);
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
test('toReversed keeps the brand', () => {
|
|
193
|
+
const input: MinLengthArray<3, number> = asMinLengthArray(3, [1, 2, 3, 4]);
|
|
194
|
+
|
|
195
|
+
const result = toReversed(input);
|
|
196
|
+
|
|
197
|
+
expectType<typeof result, MinLengthArray<3, number>>('~=');
|
|
198
|
+
|
|
199
|
+
// The structural prefix survives the reversal too.
|
|
200
|
+
const first: number = result[0];
|
|
201
|
+
|
|
202
|
+
assert.deepStrictEqual(first, 4);
|
|
203
|
+
|
|
204
|
+
assert.deepStrictEqual<readonly number[]>(result, [4, 3, 2, 1]);
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
test('toReversed still reverses a plain tuple at the type level', () => {
|
|
208
|
+
const result = toReversed([1, 'two', true] as const);
|
|
209
|
+
|
|
210
|
+
expectType<typeof result, readonly [true, 'two', 1]>('=');
|
|
211
|
+
|
|
212
|
+
assert.deepStrictEqual(result, [true, 'two', 1]);
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
test('copy keeps the brand, because it returns the input type', () => {
|
|
216
|
+
const input: FixedLengthArray<3, number> = asFixedLengthArray(3, [1, 2, 3]);
|
|
217
|
+
|
|
218
|
+
const result = copy(input);
|
|
219
|
+
|
|
220
|
+
expectType<typeof result, FixedLengthArray<3, number>>('=');
|
|
221
|
+
|
|
222
|
+
assert.deepStrictEqual<readonly number[]>(result, [1, 2, 3]);
|
|
223
|
+
});
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
describe('partition reports the bounds of each chunk', () => {
|
|
227
|
+
test('a chunk is non-empty and at most the chunk size long', () => {
|
|
228
|
+
const result = partition([1, 2, 3, 4, 5] as const, 2);
|
|
229
|
+
|
|
230
|
+
expectType<
|
|
231
|
+
typeof result,
|
|
232
|
+
readonly BoundedLengthArray<1, 2, 1 | 2 | 3 | 4 | 5>[]
|
|
233
|
+
>('~=');
|
|
234
|
+
|
|
235
|
+
// The lower bound is the point: the first element of a chunk is never
|
|
236
|
+
// `undefined`, unlike with the plain `readonly E[]` this used to return.
|
|
237
|
+
const [firstChunk] = result;
|
|
238
|
+
|
|
239
|
+
// The outer array is unbounded, so this narrows away its `undefined`; the
|
|
240
|
+
// chunk's own first element needs no such check.
|
|
241
|
+
assert.isTrue(firstChunk !== undefined);
|
|
242
|
+
|
|
243
|
+
const first: 1 | 2 | 3 | 4 | 5 = firstChunk[0];
|
|
244
|
+
|
|
245
|
+
assert.deepStrictEqual(first, 1);
|
|
246
|
+
|
|
247
|
+
assert.deepStrictEqual<readonly (readonly number[])[]>(result, [
|
|
248
|
+
[1, 2],
|
|
249
|
+
[3, 4],
|
|
250
|
+
[5],
|
|
251
|
+
]);
|
|
252
|
+
});
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
describe('creation utilities report an exact length past their tuple range', () => {
|
|
256
|
+
const zeros12 = zeros(12);
|
|
257
|
+
|
|
258
|
+
const seq12 = seq(12);
|
|
259
|
+
|
|
260
|
+
test('create is branded with the exact length', () => {
|
|
261
|
+
const result = create(12, 'x');
|
|
262
|
+
|
|
263
|
+
expectType<typeof result, FixedLengthArray<12, 'x'>>('~=');
|
|
264
|
+
|
|
265
|
+
assert.deepStrictEqual(result.length, 12);
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
test('a length still inside the tuple range keeps its exact tuple type', () => {
|
|
269
|
+
const result = create(3, 'x');
|
|
270
|
+
|
|
271
|
+
expectType<typeof result, readonly ['x', 'x', 'x']>('=');
|
|
272
|
+
|
|
273
|
+
assert.deepStrictEqual(result, ['x', 'x', 'x']);
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
test('zeros and seq keep their tuple type across their whole literal range', () => {
|
|
277
|
+
// `SizeType.ArgArr` only carries literals up to `SmallUint`, which these
|
|
278
|
+
// two already expand into a tuple, so there is no branded case to add.
|
|
279
|
+
expectType<typeof zeros12, FixedLengthTuple<12, 0>>('=');
|
|
280
|
+
|
|
281
|
+
expectType<typeof seq12, Seq<12>>('=');
|
|
282
|
+
|
|
283
|
+
assert.deepStrictEqual(zeros12.length, 12);
|
|
284
|
+
|
|
285
|
+
assert.deepStrictEqual(seq12.length, 12);
|
|
286
|
+
});
|
|
287
|
+
});
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import {
|
|
2
|
+
type ChangeArrayElement,
|
|
3
|
+
type ConstrainedList,
|
|
2
4
|
type FixedLengthTuple,
|
|
5
|
+
type HasLengthConstraint,
|
|
3
6
|
type Increment,
|
|
4
7
|
type IsFixedLengthList,
|
|
5
8
|
type NonEmptyArray,
|
|
@@ -27,27 +30,17 @@ import { copy, create } from './array-utils-creation.mjs';
|
|
|
27
30
|
* assert.deepStrictEqual(updated, [10, 25, 30]);
|
|
28
31
|
* ```
|
|
29
32
|
*/
|
|
30
|
-
export function set<
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
):
|
|
35
|
-
? Readonly<{ [K in keyof Ar]: Ar[K] | V }>
|
|
36
|
-
: Ar extends NonEmptyTuple<unknown>
|
|
37
|
-
? NonEmptyArray<Ar[number] | V>
|
|
38
|
-
: readonly (Ar[number] | V)[];
|
|
33
|
+
export function set<
|
|
34
|
+
const Ar extends readonly unknown[],
|
|
35
|
+
const I extends ArgArrayIndex<Ar>,
|
|
36
|
+
const V = Ar[number],
|
|
37
|
+
>(array: Ar, index: I, newValue: V): ReplacedAt<Ar, I, V>;
|
|
39
38
|
|
|
40
39
|
// curried version
|
|
41
|
-
export function set<const V>(
|
|
42
|
-
index:
|
|
40
|
+
export function set<const I extends SizeType.ArgArr, const V>(
|
|
41
|
+
index: I,
|
|
43
42
|
newValue: V,
|
|
44
|
-
): <const Ar extends readonly unknown[]>(
|
|
45
|
-
array: Ar,
|
|
46
|
-
) => IsFixedLengthList<Ar> extends true
|
|
47
|
-
? Readonly<{ [K in keyof Ar]: Ar[K] | V }>
|
|
48
|
-
: Ar extends NonEmptyTuple<unknown>
|
|
49
|
-
? NonEmptyArray<Ar[number] | V>
|
|
50
|
-
: readonly (Ar[number] | V)[];
|
|
43
|
+
): <const Ar extends readonly unknown[]>(array: Ar) => ReplacedAt<Ar, I, V>;
|
|
51
44
|
|
|
52
45
|
export function set<E, const V = E>(
|
|
53
46
|
...args:
|
|
@@ -79,7 +72,7 @@ const setImpl = <E, const V = E>(
|
|
|
79
72
|
*
|
|
80
73
|
* const increased = Arr.toUpdated(temperatures, 1, (value) => value + 5);
|
|
81
74
|
*
|
|
82
|
-
* const incrementLast = Arr.toUpdated<number>(
|
|
75
|
+
* const incrementLast = Arr.toUpdated<2, number>(
|
|
83
76
|
* 2,
|
|
84
77
|
* (value) => value + 1,
|
|
85
78
|
* )(temperatures);
|
|
@@ -91,28 +84,15 @@ const setImpl = <E, const V = E>(
|
|
|
91
84
|
*/
|
|
92
85
|
export function toUpdated<
|
|
93
86
|
const Ar extends readonly unknown[],
|
|
87
|
+
const I extends ArgArrayIndex<Ar>,
|
|
94
88
|
const V = Ar[number],
|
|
95
|
-
>(
|
|
96
|
-
array: Ar,
|
|
97
|
-
index: ArgArrayIndex<Ar>,
|
|
98
|
-
updater: (prev: Ar[number]) => V,
|
|
99
|
-
): IsFixedLengthList<Ar> extends true
|
|
100
|
-
? Readonly<{ [K in keyof Ar]: Ar[K] | V }>
|
|
101
|
-
: Ar extends NonEmptyTuple<unknown>
|
|
102
|
-
? NonEmptyArray<Ar[number] | V>
|
|
103
|
-
: readonly (Ar[number] | V)[];
|
|
89
|
+
>(array: Ar, index: I, updater: (prev: Ar[number]) => V): ReplacedAt<Ar, I, V>;
|
|
104
90
|
|
|
105
91
|
// curried version
|
|
106
|
-
export function toUpdated<E, const V = E>(
|
|
107
|
-
index:
|
|
92
|
+
export function toUpdated<const I extends SizeType.ArgArr, E, const V = E>(
|
|
93
|
+
index: I,
|
|
108
94
|
updater: (prev: E) => V,
|
|
109
|
-
): <const Ar extends readonly E[]>(
|
|
110
|
-
array: Ar,
|
|
111
|
-
) => IsFixedLengthList<Ar> extends true
|
|
112
|
-
? Readonly<{ [K in keyof Ar]: Ar[K] | V }>
|
|
113
|
-
: Ar extends NonEmptyTuple<unknown>
|
|
114
|
-
? NonEmptyArray<Ar[number] | V>
|
|
115
|
-
: readonly (Ar[number] | V)[];
|
|
95
|
+
): <const Ar extends readonly E[]>(array: Ar) => ReplacedAt<Ar, I, V>;
|
|
116
96
|
|
|
117
97
|
export function toUpdated<E, V = E>(
|
|
118
98
|
...args:
|
|
@@ -379,22 +359,26 @@ const toUnshiftedImpl = <Ar extends readonly unknown[], const V>(
|
|
|
379
359
|
export function toFilled<const Ar extends readonly unknown[], const V>(
|
|
380
360
|
array: Ar,
|
|
381
361
|
value: V,
|
|
382
|
-
):
|
|
383
|
-
?
|
|
384
|
-
: Ar extends
|
|
385
|
-
?
|
|
386
|
-
:
|
|
362
|
+
): HasLengthConstraint<Ar> extends true
|
|
363
|
+
? ChangeArrayElement<Ar, V>
|
|
364
|
+
: IsFixedLengthList<Ar> extends true
|
|
365
|
+
? FixedLengthTuple<Ar['length'], V>
|
|
366
|
+
: Ar extends NonEmptyTuple<unknown>
|
|
367
|
+
? NonEmptyArray<V>
|
|
368
|
+
: readonly V[];
|
|
387
369
|
|
|
388
370
|
// curried version
|
|
389
371
|
export function toFilled<const V>(
|
|
390
372
|
value: V,
|
|
391
373
|
): <const Ar extends readonly unknown[]>(
|
|
392
374
|
array: Ar,
|
|
393
|
-
) =>
|
|
394
|
-
?
|
|
395
|
-
: Ar extends
|
|
396
|
-
?
|
|
397
|
-
:
|
|
375
|
+
) => HasLengthConstraint<Ar> extends true
|
|
376
|
+
? ChangeArrayElement<Ar, V>
|
|
377
|
+
: IsFixedLengthList<Ar> extends true
|
|
378
|
+
? FixedLengthTuple<Ar['length'], V>
|
|
379
|
+
: Ar extends NonEmptyTuple<unknown>
|
|
380
|
+
? NonEmptyArray<V>
|
|
381
|
+
: readonly V[];
|
|
398
382
|
|
|
399
383
|
export function toFilled<E>(
|
|
400
384
|
...args: readonly [array: readonly E[], value: E] | readonly [value: E]
|
|
@@ -435,11 +419,13 @@ export function toRangeFilled<const Ar extends readonly unknown[], const V>(
|
|
|
435
419
|
start: ArgArrayIndexWithNegative<Ar>,
|
|
436
420
|
end: ArgArrayIndexWithNegative<Ar>,
|
|
437
421
|
],
|
|
438
|
-
):
|
|
439
|
-
?
|
|
440
|
-
: Ar extends
|
|
441
|
-
?
|
|
442
|
-
:
|
|
422
|
+
): HasLengthConstraint<Ar> extends true
|
|
423
|
+
? ChangeArrayElement<Ar, V | Ar[number]>
|
|
424
|
+
: IsFixedLengthList<Ar> extends true
|
|
425
|
+
? FixedLengthTuple<Ar['length'], V | Ar[number]>
|
|
426
|
+
: Ar extends NonEmptyTuple<unknown>
|
|
427
|
+
? NonEmptyArray<V | Ar[number]>
|
|
428
|
+
: readonly (V | Ar[number])[];
|
|
443
429
|
|
|
444
430
|
// curried version
|
|
445
431
|
export function toRangeFilled<const V>(
|
|
@@ -450,11 +436,13 @@ export function toRangeFilled<const V>(
|
|
|
450
436
|
],
|
|
451
437
|
): <const Ar extends readonly unknown[]>(
|
|
452
438
|
array: Ar,
|
|
453
|
-
) =>
|
|
454
|
-
?
|
|
455
|
-
: Ar extends
|
|
456
|
-
?
|
|
457
|
-
:
|
|
439
|
+
) => HasLengthConstraint<Ar> extends true
|
|
440
|
+
? ChangeArrayElement<Ar, V | Ar[number]>
|
|
441
|
+
: IsFixedLengthList<Ar> extends true
|
|
442
|
+
? FixedLengthTuple<Ar['length'], V | Ar[number]>
|
|
443
|
+
: Ar extends NonEmptyTuple<unknown>
|
|
444
|
+
? NonEmptyArray<V | Ar[number]>
|
|
445
|
+
: readonly (V | Ar[number])[];
|
|
458
446
|
|
|
459
447
|
export function toRangeFilled<E, const V>(
|
|
460
448
|
...args:
|
|
@@ -499,3 +487,30 @@ const toRangeFilledImpl = <E, const V>(
|
|
|
499
487
|
|
|
500
488
|
return mut_cp;
|
|
501
489
|
};
|
|
490
|
+
|
|
491
|
+
/**
|
|
492
|
+
* The result of replacing the element at `I` of `Ar` with a `V`.
|
|
493
|
+
*
|
|
494
|
+
* `ConstrainedList.SetAt` replaces exactly one position when the index pins
|
|
495
|
+
* one, which is what makes `Arr.set(xs, 1, v)` report `readonly [a, V, c]`
|
|
496
|
+
* instead of widening every position to `a | V`.
|
|
497
|
+
*
|
|
498
|
+
* A union index needs the weaker answer, and this used to guard for it here:
|
|
499
|
+
* `ArgArrayIndex<Ar>` for a tuple is the union of its indices, so
|
|
500
|
+
* `Arr.set(tuple, someIndex, v)` with `someIndex: 0 | 2` binds `I = 0 | 2`, and
|
|
501
|
+
* the upstream `List.SetAt` used to answer `readonly [V, b, V]` — a type that
|
|
502
|
+
* *neither* possible runtime result satisfies, since the call replaces one
|
|
503
|
+
* position or the other and never both.
|
|
504
|
+
*
|
|
505
|
+
* ts-type-forge 9.1.1 fixes that at the source and widens the candidate
|
|
506
|
+
* positions itself, so the guard is gone. Upstream is the more precise of the
|
|
507
|
+
* two: it widens only the positions the index can name, where the guard here
|
|
508
|
+
* widened all of them — `Arr.set(t, i, 'x')` with `i: 0 | 2` on
|
|
509
|
+
* `readonly [1, 2, 3]` now keeps the middle element at `2` rather than
|
|
510
|
+
* reporting `2 | 'x'`.
|
|
511
|
+
*/
|
|
512
|
+
type ReplacedAt<
|
|
513
|
+
Ar extends readonly unknown[],
|
|
514
|
+
I extends number,
|
|
515
|
+
V,
|
|
516
|
+
> = ConstrainedList.SetAt<I, V, Ar>;
|
|
@@ -1,6 +1,13 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
type BoundedLengthArray,
|
|
3
|
+
type FixedLengthArray,
|
|
4
|
+
type FixedLengthTuple,
|
|
5
|
+
type MinLengthArray,
|
|
6
|
+
type NonEmptyArray,
|
|
7
|
+
} from 'ts-type-forge';
|
|
2
8
|
import { expectType } from '../../expect-type.mjs';
|
|
3
9
|
import { asUint32 } from '../../number/index.mjs';
|
|
10
|
+
import { type SizeType } from '../../types.mjs';
|
|
4
11
|
import {
|
|
5
12
|
set,
|
|
6
13
|
toFilled,
|
|
@@ -18,7 +25,7 @@ describe('Arr modifications', () => {
|
|
|
18
25
|
|
|
19
26
|
const result = toUpdated(xs, 1, (x) => x + 2);
|
|
20
27
|
|
|
21
|
-
expectType<typeof result,
|
|
28
|
+
expectType<typeof result, readonly [1, number, 3]>('=');
|
|
22
29
|
|
|
23
30
|
test('case 1', () => {
|
|
24
31
|
assert.deepStrictEqual(result, [1, 4, 3]);
|
|
@@ -305,7 +312,7 @@ describe('Arr modifications', () => {
|
|
|
305
312
|
describe(set, () => {
|
|
306
313
|
const result = set([1, 2, 3], 1, 4);
|
|
307
314
|
|
|
308
|
-
expectType<typeof result, readonly [1
|
|
315
|
+
expectType<typeof result, readonly [1, 4, 3]>('=');
|
|
309
316
|
|
|
310
317
|
test('case 1', () => {
|
|
311
318
|
assert.deepStrictEqual(result, [1, 4, 3]);
|
|
@@ -316,9 +323,7 @@ describe('Arr modifications', () => {
|
|
|
316
323
|
|
|
317
324
|
const withString = set(nums, 1, 'two');
|
|
318
325
|
|
|
319
|
-
expectType<typeof withString, readonly [1
|
|
320
|
-
'=',
|
|
321
|
-
);
|
|
326
|
+
expectType<typeof withString, readonly [1, 'two', 3]>('=');
|
|
322
327
|
|
|
323
328
|
assert.deepStrictEqual(withString, [1, 'two', 3]);
|
|
324
329
|
});
|
|
@@ -348,3 +353,85 @@ describe('Arr modifications', () => {
|
|
|
348
353
|
});
|
|
349
354
|
});
|
|
350
355
|
});
|
|
356
|
+
|
|
357
|
+
/* `Arr.set` / `Arr.toUpdated` across the input shapes the positional
|
|
358
|
+
`ConstrainedList.SetAt` annotation has to cope with; the suite previously
|
|
359
|
+
covered only the plain-tuple case. Written as an unexported-by-value function
|
|
360
|
+
so the assertions get real overload resolution — it is never called, and the
|
|
361
|
+
branded shapes have no runtime representation to build. */
|
|
362
|
+
export const setTypeChecks = (
|
|
363
|
+
shapeTuple: readonly [1, 2, 3],
|
|
364
|
+
shapeArray: readonly number[],
|
|
365
|
+
shapeMutableArray: readonly number[],
|
|
366
|
+
shapeBranded: MinLengthArray<3, string>,
|
|
367
|
+
shapeBounded: BoundedLengthArray<2, 5, string>,
|
|
368
|
+
shapeFixed: FixedLengthArray<3, string>,
|
|
369
|
+
shapeBrandedTuple: MinLengthArray<3, number> & readonly [1, 2, 3, 4, 5],
|
|
370
|
+
unionIndex: 0 | 2,
|
|
371
|
+
widenedIndex: SizeType.ArgArr,
|
|
372
|
+
): void => {
|
|
373
|
+
/* Plain tuple — the point of the positional form: only the touched position
|
|
374
|
+
changes, the rest keep their literal types. */
|
|
375
|
+
const _onTuple = set(shapeTuple, 1, 'x');
|
|
376
|
+
|
|
377
|
+
expectType<typeof _onTuple, readonly [1, 'x', 3]>('=');
|
|
378
|
+
|
|
379
|
+
// Plain array: no positions to pin, so the element type widens.
|
|
380
|
+
const _onArray = set(shapeArray, widenedIndex, 'x');
|
|
381
|
+
|
|
382
|
+
expectType<typeof _onArray, readonly (number | 'x')[]>('=');
|
|
383
|
+
|
|
384
|
+
// A mutable array is not brand-carrying, and behaves like the readonly one.
|
|
385
|
+
const _onMutable = set(shapeMutableArray, widenedIndex, 'x');
|
|
386
|
+
|
|
387
|
+
expectType<typeof _onMutable, readonly (number | 'x')[]>('=');
|
|
388
|
+
|
|
389
|
+
/* Branded with no exact tuple: the length constraint survives, and with no
|
|
390
|
+
positions to pin the element type absorbs the new value. */
|
|
391
|
+
const _onBranded = set(shapeBranded, 1, 'x');
|
|
392
|
+
|
|
393
|
+
expectType<typeof _onBranded, MinLengthArray<3, string>>('<=');
|
|
394
|
+
|
|
395
|
+
const _onBounded = set(shapeBounded, 1, 'x');
|
|
396
|
+
|
|
397
|
+
expectType<typeof _onBounded, BoundedLengthArray<2, 5, string>>('<=');
|
|
398
|
+
|
|
399
|
+
// Fixed length: brand *and* exact positions both survive.
|
|
400
|
+
const _onFixed = set(shapeFixed, 1, 'x');
|
|
401
|
+
|
|
402
|
+
expectType<
|
|
403
|
+
typeof _onFixed,
|
|
404
|
+
FixedLengthArray<3, string> & readonly [string, 'x', string]
|
|
405
|
+
>('~=');
|
|
406
|
+
|
|
407
|
+
/* Brand intersected with an exact tuple — the shape `Arr.isMinLengthArray`
|
|
408
|
+
produces. All three survive: the brand, the exact length, and the
|
|
409
|
+
positions, the last as a structural conjunct beside the widened one. */
|
|
410
|
+
const _onBrandedTuple = set(shapeBrandedTuple, 1, 'x');
|
|
411
|
+
|
|
412
|
+
expectType<(typeof _onBrandedTuple)[0], 1>('=');
|
|
413
|
+
|
|
414
|
+
expectType<(typeof _onBrandedTuple)[1], 'x'>('=');
|
|
415
|
+
|
|
416
|
+
expectType<(typeof _onBrandedTuple)[4], 5>('=');
|
|
417
|
+
|
|
418
|
+
expectType<(typeof _onBrandedTuple)['length'], 5>('=');
|
|
419
|
+
|
|
420
|
+
/* A union index must NOT be replaced positionally. The call sets one of the
|
|
421
|
+
positions and never both, so `readonly ['x', 2, 'x']` — what the upstream
|
|
422
|
+
`List.SetAt` used to answer for a union index — is a type that no possible
|
|
423
|
+
result satisfies. ts-type-forge 9.1.1 widens the candidate positions
|
|
424
|
+
instead, and only those: index 1 is not among `0 | 2`, so it stays `2`. */
|
|
425
|
+
const _onUnionIndex = set(shapeTuple, unionIndex, 'x');
|
|
426
|
+
|
|
427
|
+
expectType<typeof _onUnionIndex, readonly [1 | 'x', 2, 3 | 'x']>('=');
|
|
428
|
+
|
|
429
|
+
// `toUpdated` follows `set`, with the updater's return as the value.
|
|
430
|
+
const _updatedTuple = toUpdated(shapeTuple, 1, (x) => x + 1);
|
|
431
|
+
|
|
432
|
+
expectType<typeof _updatedTuple, readonly [1, number, 3]>('=');
|
|
433
|
+
|
|
434
|
+
const _updatedUnion = toUpdated(shapeTuple, unionIndex, (x) => x + 1);
|
|
435
|
+
|
|
436
|
+
expectType<typeof _updatedUnion, readonly [number, 2, number]>('=');
|
|
437
|
+
};
|
|
@@ -11,7 +11,7 @@ import {
|
|
|
11
11
|
type SizeType,
|
|
12
12
|
type Some,
|
|
13
13
|
} from '../../types.mjs';
|
|
14
|
-
import { isNonEmpty } from './array-utils-
|
|
14
|
+
import { isNonEmpty } from './array-utils-length-bounded-array-guard.mjs';
|
|
15
15
|
|
|
16
16
|
/**
|
|
17
17
|
* Finds the minimum value in an array.
|
|
@@ -543,22 +543,22 @@ export function sum(array: readonly number[]): number {
|
|
|
543
543
|
*/
|
|
544
544
|
export function sumBy(array: readonly [], mapFn: (item: unknown) => number): 0;
|
|
545
545
|
|
|
546
|
-
export function sumBy<N extends Uint
|
|
546
|
+
export function sumBy<E, N extends Uint>(
|
|
547
547
|
array: readonly E[],
|
|
548
548
|
mapFn: (item: E) => N,
|
|
549
549
|
): Uint;
|
|
550
550
|
|
|
551
|
-
export function sumBy<N extends Int
|
|
551
|
+
export function sumBy<E, N extends Int>(
|
|
552
552
|
array: readonly E[],
|
|
553
553
|
mapFn: (item: E) => N,
|
|
554
554
|
): Int;
|
|
555
555
|
|
|
556
|
-
export function sumBy<N extends number
|
|
556
|
+
export function sumBy<E, N extends number>(
|
|
557
557
|
array: readonly E[],
|
|
558
558
|
mapFn: (item: E) => N,
|
|
559
559
|
): number;
|
|
560
560
|
|
|
561
|
-
export function sumBy<N extends number
|
|
561
|
+
export function sumBy<E, N extends number>(
|
|
562
562
|
array: readonly E[],
|
|
563
563
|
mapFn: (item: E) => N,
|
|
564
564
|
): number {
|