ts-data-forge 14.0.1 → 14.2.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 +5 -5
- package/dist/array/impl/array-utils-transformation.d.mts +4 -1
- package/dist/array/impl/array-utils-transformation.d.mts.map +1 -1
- package/dist/array/impl/array-utils-transformation.mjs +4 -5
- package/dist/array/impl/array-utils-transformation.mjs.map +1 -1
- package/dist/array/impl/array-utils-validation.d.mts +8 -3
- package/dist/array/impl/array-utils-validation.d.mts.map +1 -1
- package/dist/array/impl/array-utils-validation.mjs.map +1 -1
- package/dist/collections/imap-mapped.mjs +1 -1
- package/dist/collections/imap-mapped.mjs.map +1 -1
- package/dist/collections/imap.mjs +1 -1
- package/dist/collections/imap.mjs.map +1 -1
- package/dist/functional/match.d.mts.map +1 -1
- package/dist/functional/match.mjs.map +1 -1
- package/dist/functional/optional/impl/optional-map.d.mts +1 -1
- package/dist/functional/optional/impl/optional-map.d.mts.map +1 -1
- package/dist/functional/optional/impl/optional-map.mjs.map +1 -1
- package/dist/functional/result/impl/result-map-err.d.mts +1 -1
- package/dist/functional/result/impl/result-map-err.d.mts.map +1 -1
- package/dist/functional/result/impl/result-map-err.mjs.map +1 -1
- package/dist/functional/result/impl/result-map.d.mts +1 -1
- package/dist/functional/result/impl/result-map.d.mts.map +1 -1
- package/dist/functional/result/impl/result-map.mjs.map +1 -1
- package/dist/number/branded-types/int.d.mts +1 -1
- package/dist/number/branded-types/int16.d.mts +1 -1
- package/dist/number/branded-types/int32.d.mts +1 -1
- package/dist/number/branded-types/non-negative-int16.d.mts +1 -1
- package/dist/number/branded-types/non-negative-int32.d.mts +1 -1
- package/dist/number/branded-types/non-positive-finite-number.mjs +1 -1
- package/dist/number/branded-types/non-positive-finite-number.mjs.map +1 -1
- package/dist/number/branded-types/positive-int.d.mts +1 -1
- package/dist/number/branded-types/positive-int16.d.mts +1 -1
- package/dist/number/branded-types/positive-int32.d.mts +1 -1
- package/dist/number/branded-types/positive-safe-int.d.mts +1 -1
- package/dist/number/branded-types/positive-uint16.d.mts +1 -1
- package/dist/number/branded-types/positive-uint32.d.mts +1 -1
- package/dist/number/branded-types/safe-int.d.mts +1 -1
- package/dist/number/branded-types/safe-uint.d.mts +1 -1
- package/dist/number/branded-types/uint.d.mts +1 -1
- package/dist/number/branded-types/uint16.d.mts +1 -1
- package/dist/number/branded-types/uint32.d.mts +1 -1
- package/dist/number/enum/int8.d.mts +2 -2
- package/dist/number/enum/int8.d.mts.map +1 -1
- package/dist/number/enum/int8.mjs.map +1 -1
- package/dist/number/enum/uint8.d.mts +2 -2
- package/dist/number/enum/uint8.d.mts.map +1 -1
- package/dist/number/enum/uint8.mjs.map +1 -1
- package/dist/number/num-arithmetic-types.d.mts +8 -9
- package/dist/number/num-arithmetic-types.d.mts.map +1 -1
- package/dist/number/refined-number-utils.d.mts +2 -2
- package/dist/number/refined-number-utils.d.mts.map +1 -1
- package/dist/number/refined-number-utils.mjs.map +1 -1
- package/dist/object/object.d.mts +12 -12
- package/dist/object/object.d.mts.map +1 -1
- package/dist/object/object.mjs +5 -1
- package/dist/object/object.mjs.map +1 -1
- package/dist/others/fast-deep-equal.d.mts.map +1 -1
- package/dist/others/fast-deep-equal.mjs +2 -2
- package/dist/others/fast-deep-equal.mjs.map +1 -1
- package/package.json +2 -2
- package/src/array/impl/array-utils-modification.test.mts +26 -5
- package/src/array/impl/array-utils-shape-invariants.test.mts +259 -17
- package/src/array/impl/array-utils-slicing.test.mts +11 -3
- package/src/array/impl/array-utils-transformation.mts +41 -18
- package/src/array/impl/array-utils-transformation.test.mts +10 -3
- package/src/array/impl/array-utils-validation.mts +54 -10
- package/src/array/impl/array-utils-validation.test.mts +153 -3
- package/src/collections/imap-mapped.mts +1 -1
- package/src/collections/imap-mapped.test.mts +2 -2
- package/src/collections/imap.mts +1 -1
- package/src/collections/iset-mapped.test.mts +1 -1
- package/src/functional/match.mts +5 -4
- package/src/functional/optional/impl/optional-map.mts +16 -1
- package/src/functional/optional.test.mts +48 -1
- package/src/functional/result/impl/result-map-err.mts +16 -1
- package/src/functional/result/impl/result-map.mts +16 -1
- package/src/functional/result.test.mts +83 -3
- package/src/guard/is-primitive.test.mts +1 -1
- package/src/guard/is-record.test.mts +4 -4
- package/src/guard/is-type.test.mts +2 -2
- package/src/number/branded-types/finite-number.test.mts +2 -2
- package/src/number/branded-types/non-negative-finite-number.test.mts +2 -2
- package/src/number/branded-types/non-positive-finite-number.mts +1 -1
- package/src/number/branded-types/non-positive-finite-number.test.mts +5 -9
- package/src/number/branded-types/non-zero-finite-number.test.mts +2 -2
- package/src/number/branded-types/positive-finite-number.test.mts +2 -2
- package/src/number/enum/int8.mts +2 -1
- package/src/number/enum/uint8.mts +6 -2
- package/src/number/num-arithmetic-types.mts +125 -24
- package/src/number/num.test.mts +9 -9
- package/src/number/refined-number-utils.mts +2 -1
- package/src/object/object.mts +29 -17
- package/src/object/object.test.mts +2 -1
- package/src/others/fast-deep-equal.mts +6 -4
|
@@ -14,8 +14,26 @@ import {
|
|
|
14
14
|
asMaxLengthArray,
|
|
15
15
|
asMinLengthArray,
|
|
16
16
|
} from './array-utils-length-bounded-array-cast.mjs';
|
|
17
|
-
import {
|
|
18
|
-
|
|
17
|
+
import {
|
|
18
|
+
toFilled,
|
|
19
|
+
toInserted,
|
|
20
|
+
toPushed,
|
|
21
|
+
toRangeFilled,
|
|
22
|
+
toRemoved,
|
|
23
|
+
toUnshifted,
|
|
24
|
+
} from './array-utils-modification.mjs';
|
|
25
|
+
import { skip, skipLast, take, takeLast } from './array-utils-slicing.mjs';
|
|
26
|
+
import {
|
|
27
|
+
concat,
|
|
28
|
+
filter,
|
|
29
|
+
filterNot,
|
|
30
|
+
map,
|
|
31
|
+
toReversed,
|
|
32
|
+
toSorted,
|
|
33
|
+
toSortedBy,
|
|
34
|
+
uniq,
|
|
35
|
+
uniqBy,
|
|
36
|
+
} from './array-utils-transformation.mjs';
|
|
19
37
|
|
|
20
38
|
/**
|
|
21
39
|
* Invariant-based coverage for the array -> array transforms.
|
|
@@ -31,12 +49,14 @@ import { map, toReversed, toSorted } from './array-utils-transformation.mjs';
|
|
|
31
49
|
* 1. **Length** — the result reports the same `length` as the input.
|
|
32
50
|
* 2. **Bounds** — a length brand on the input survives onto the result.
|
|
33
51
|
* 3. **Generic context** — from a caller that is itself generic over the
|
|
34
|
-
* array, the result is assignable to the caller's own
|
|
52
|
+
* array, the result is assignable to the caller's own annotation.
|
|
35
53
|
*
|
|
36
|
-
* Property 3 is the one
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
*
|
|
54
|
+
* Property 3 is the one that keeps regressing, and the one a per-combination
|
|
55
|
+
* table cannot express: a conditional a generic `Ar` cannot decide stays
|
|
56
|
+
* deferred, and a deferred conditional carrying a brand-intersection branch is
|
|
57
|
+
* not an array type at all — so the result has no `entries()`, is not
|
|
58
|
+
* assignable to the caller's annotation, and in the worst case the bound
|
|
59
|
+
* arithmetic behind the brand exceeds the instantiation limit.
|
|
40
60
|
*/
|
|
41
61
|
|
|
42
62
|
// The sample inputs. Between them they cover the axes: empty / singleton /
|
|
@@ -71,15 +91,15 @@ const exactLength: FixedLengthArray<3, number> = asFixedLengthArray(
|
|
|
71
91
|
* Property 3 — generic context.
|
|
72
92
|
*
|
|
73
93
|
* Each of these is an assertion by construction: the annotated return type is
|
|
74
|
-
* the caller
|
|
94
|
+
* what the caller is entitled to expect, so the declaration only compiles while
|
|
75
95
|
* the transform reports something assignable to it. There is nothing to run.
|
|
76
96
|
*
|
|
77
|
-
*
|
|
78
|
-
*
|
|
79
|
-
*
|
|
80
|
-
* here as they are fixed; that is what makes this list the coverage record.
|
|
97
|
+
* This list is the coverage record. Every array -> array transform that
|
|
98
|
+
* resolves under a generic array parameter appears here; the ones that do not
|
|
99
|
+
* are listed at the bottom of this file, with the reason.
|
|
81
100
|
*/
|
|
82
101
|
|
|
102
|
+
// Element-replacing transforms report the caller's own homomorphic mapping.
|
|
83
103
|
const mapInGenericContext = <const T extends readonly number[]>(
|
|
84
104
|
xs: T,
|
|
85
105
|
): Readonly<{ [K in keyof T]: string }> => map(xs, String);
|
|
@@ -100,6 +120,85 @@ const toRangeFilledInGenericContext = <const T extends readonly number[]>(
|
|
|
100
120
|
xs: T,
|
|
101
121
|
): Readonly<{ [K in keyof T]: number }> => toRangeFilled(xs, 0, [0, 1]);
|
|
102
122
|
|
|
123
|
+
// Reordering transforms keep the elements, so the caller is entitled to an
|
|
124
|
+
// array of them.
|
|
125
|
+
const toReversedInGenericContext = <const T extends readonly number[]>(
|
|
126
|
+
xs: T,
|
|
127
|
+
): readonly T[number][] => toReversed(xs);
|
|
128
|
+
|
|
129
|
+
const toSortedInGenericContext = <const T extends readonly number[]>(
|
|
130
|
+
xs: T,
|
|
131
|
+
): readonly T[number][] => toSorted(xs, (a, b) => a - b);
|
|
132
|
+
|
|
133
|
+
// Numbers sort without a comparator. This is the overload that makes it
|
|
134
|
+
// optional, which a generic caller could not reach at all while the parameter
|
|
135
|
+
// list was itself a conditional type.
|
|
136
|
+
const toSortedDefaultInGenericContext = <const T extends readonly number[]>(
|
|
137
|
+
xs: T,
|
|
138
|
+
): readonly T[number][] => toSorted(xs);
|
|
139
|
+
|
|
140
|
+
const toSortedNonNumberInGenericContext = <const T extends readonly string[]>(
|
|
141
|
+
xs: T,
|
|
142
|
+
): readonly T[number][] => toSorted(xs, (a, b) => a.localeCompare(b));
|
|
143
|
+
|
|
144
|
+
const toSortedByInGenericContext = <const T extends readonly number[]>(
|
|
145
|
+
xs: T,
|
|
146
|
+
): readonly T[number][] => toSortedBy(xs, (n) => n);
|
|
147
|
+
|
|
148
|
+
// Shortening transforms lose the length but keep the element type.
|
|
149
|
+
const takeInGenericContext = <const T extends readonly number[]>(
|
|
150
|
+
xs: T,
|
|
151
|
+
): readonly T[number][] => take(xs, 2);
|
|
152
|
+
|
|
153
|
+
const takeLastInGenericContext = <const T extends readonly number[]>(
|
|
154
|
+
xs: T,
|
|
155
|
+
): readonly T[number][] => takeLast(xs, 2);
|
|
156
|
+
|
|
157
|
+
const skipInGenericContext = <const T extends readonly number[]>(
|
|
158
|
+
xs: T,
|
|
159
|
+
): readonly T[number][] => skip(xs, 2);
|
|
160
|
+
|
|
161
|
+
const skipLastInGenericContext = <const T extends readonly number[]>(
|
|
162
|
+
xs: T,
|
|
163
|
+
): readonly T[number][] => skipLast(xs, 2);
|
|
164
|
+
|
|
165
|
+
const toRemovedInGenericContext = <const T extends readonly number[]>(
|
|
166
|
+
xs: T,
|
|
167
|
+
): readonly T[number][] => toRemoved(xs, 0);
|
|
168
|
+
|
|
169
|
+
const filterInGenericContext = <const T extends readonly number[]>(
|
|
170
|
+
xs: T,
|
|
171
|
+
): readonly T[number][] => filter(xs, (n) => n > 0);
|
|
172
|
+
|
|
173
|
+
const filterNotInGenericContext = <const T extends readonly number[]>(
|
|
174
|
+
xs: T,
|
|
175
|
+
): readonly T[number][] => filterNot(xs, (n) => n > 0);
|
|
176
|
+
|
|
177
|
+
const uniqInGenericContext = <const T extends readonly number[]>(
|
|
178
|
+
xs: T,
|
|
179
|
+
): readonly T[number][] => uniq(xs);
|
|
180
|
+
|
|
181
|
+
const uniqByInGenericContext = <const T extends readonly number[]>(
|
|
182
|
+
xs: T,
|
|
183
|
+
): readonly T[number][] => uniqBy(xs, (n) => n);
|
|
184
|
+
|
|
185
|
+
// Lengthening transforms widen the element type by whatever was added.
|
|
186
|
+
const toPushedInGenericContext = <const T extends readonly number[]>(
|
|
187
|
+
xs: T,
|
|
188
|
+
): readonly (T[number] | 'x')[] => toPushed(xs, 'x');
|
|
189
|
+
|
|
190
|
+
const toUnshiftedInGenericContext = <const T extends readonly number[]>(
|
|
191
|
+
xs: T,
|
|
192
|
+
): readonly (T[number] | 'x')[] => toUnshifted(xs, 'x');
|
|
193
|
+
|
|
194
|
+
const toInsertedInGenericContext = <const T extends readonly number[]>(
|
|
195
|
+
xs: T,
|
|
196
|
+
): readonly (T[number] | 'x')[] => toInserted(xs, 0, 'x');
|
|
197
|
+
|
|
198
|
+
const concatInGenericContext = <const T extends readonly number[]>(
|
|
199
|
+
xs: T,
|
|
200
|
+
): readonly T[number][] => concat(xs, xs);
|
|
201
|
+
|
|
103
202
|
describe('Arr shape invariants', () => {
|
|
104
203
|
// The type-level assertions below are checked by `tsc`; this pins the same
|
|
105
204
|
// property at runtime, so a transform that silently drops or adds an element
|
|
@@ -107,19 +206,20 @@ describe('Arr shape invariants', () => {
|
|
|
107
206
|
test('every shape-preserving transform keeps the length at runtime', () => {
|
|
108
207
|
const input = [3, 1, 2] as const;
|
|
109
208
|
|
|
110
|
-
assert.deepStrictEqual<FixedLengthTuple<
|
|
209
|
+
assert.deepStrictEqual<FixedLengthTuple<6, 3>>(
|
|
111
210
|
[
|
|
112
211
|
map(input, String).length,
|
|
113
212
|
toFilled(input, 0).length,
|
|
114
213
|
toRangeFilled(input, 0, [1, 2]).length,
|
|
115
214
|
toSorted(input).length,
|
|
215
|
+
toSortedBy(input, (n) => n).length,
|
|
116
216
|
toReversed(input).length,
|
|
117
217
|
] as const,
|
|
118
|
-
[3, 3, 3, 3, 3] as const,
|
|
218
|
+
[3, 3, 3, 3, 3, 3] as const,
|
|
119
219
|
);
|
|
120
220
|
});
|
|
121
221
|
|
|
122
|
-
test('the
|
|
222
|
+
test('the element-replacing callers agree with their annotations', () => {
|
|
123
223
|
const input = [3, 1, 2] as const;
|
|
124
224
|
|
|
125
225
|
assert.deepStrictEqual<FixedLengthTuple<3, string>>(
|
|
@@ -147,6 +247,105 @@ describe('Arr shape invariants', () => {
|
|
|
147
247
|
[0, 1, 2],
|
|
148
248
|
);
|
|
149
249
|
});
|
|
250
|
+
|
|
251
|
+
test('the reordering callers agree with their annotations', () => {
|
|
252
|
+
const input = [3, 1, 2] as const;
|
|
253
|
+
|
|
254
|
+
assert.deepStrictEqual<readonly number[]>(
|
|
255
|
+
toReversedInGenericContext(input),
|
|
256
|
+
[2, 1, 3],
|
|
257
|
+
);
|
|
258
|
+
|
|
259
|
+
assert.deepStrictEqual<readonly number[]>(
|
|
260
|
+
toSortedInGenericContext(input),
|
|
261
|
+
[1, 2, 3],
|
|
262
|
+
);
|
|
263
|
+
|
|
264
|
+
assert.deepStrictEqual<readonly number[]>(
|
|
265
|
+
toSortedDefaultInGenericContext(input),
|
|
266
|
+
[1, 2, 3],
|
|
267
|
+
);
|
|
268
|
+
|
|
269
|
+
assert.deepStrictEqual<readonly string[]>(
|
|
270
|
+
toSortedNonNumberInGenericContext(['c', 'a', 'b'] as const),
|
|
271
|
+
['a', 'b', 'c'],
|
|
272
|
+
);
|
|
273
|
+
|
|
274
|
+
assert.deepStrictEqual<readonly number[]>(
|
|
275
|
+
toSortedByInGenericContext(input),
|
|
276
|
+
[1, 2, 3],
|
|
277
|
+
);
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
test('the shortening callers agree with their annotations', () => {
|
|
281
|
+
const input = [3, 1, 2] as const;
|
|
282
|
+
|
|
283
|
+
assert.deepStrictEqual<readonly number[]>(
|
|
284
|
+
takeInGenericContext(input),
|
|
285
|
+
[3, 1],
|
|
286
|
+
);
|
|
287
|
+
|
|
288
|
+
assert.deepStrictEqual<readonly number[]>(
|
|
289
|
+
takeLastInGenericContext(input),
|
|
290
|
+
[1, 2],
|
|
291
|
+
);
|
|
292
|
+
|
|
293
|
+
assert.deepStrictEqual<readonly number[]>(skipInGenericContext(input), [2]);
|
|
294
|
+
|
|
295
|
+
assert.deepStrictEqual<readonly number[]>(
|
|
296
|
+
skipLastInGenericContext(input),
|
|
297
|
+
[3],
|
|
298
|
+
);
|
|
299
|
+
|
|
300
|
+
assert.deepStrictEqual<readonly number[]>(
|
|
301
|
+
toRemovedInGenericContext(input),
|
|
302
|
+
[1, 2],
|
|
303
|
+
);
|
|
304
|
+
|
|
305
|
+
assert.deepStrictEqual<readonly number[]>(
|
|
306
|
+
filterInGenericContext(input),
|
|
307
|
+
[3, 1, 2],
|
|
308
|
+
);
|
|
309
|
+
|
|
310
|
+
assert.deepStrictEqual<readonly number[]>(
|
|
311
|
+
filterNotInGenericContext(input),
|
|
312
|
+
[],
|
|
313
|
+
);
|
|
314
|
+
|
|
315
|
+
assert.deepStrictEqual<readonly number[]>(
|
|
316
|
+
uniqInGenericContext([1, 1, 2] as const),
|
|
317
|
+
[1, 2],
|
|
318
|
+
);
|
|
319
|
+
|
|
320
|
+
assert.deepStrictEqual<readonly number[]>(
|
|
321
|
+
uniqByInGenericContext([1, 1, 2] as const),
|
|
322
|
+
[1, 2],
|
|
323
|
+
);
|
|
324
|
+
});
|
|
325
|
+
|
|
326
|
+
test('the lengthening callers agree with their annotations', () => {
|
|
327
|
+
const input = [3, 1, 2] as const;
|
|
328
|
+
|
|
329
|
+
assert.deepStrictEqual<readonly (number | string)[]>(
|
|
330
|
+
toPushedInGenericContext(input),
|
|
331
|
+
[3, 1, 2, 'x'],
|
|
332
|
+
);
|
|
333
|
+
|
|
334
|
+
assert.deepStrictEqual<readonly (number | string)[]>(
|
|
335
|
+
toUnshiftedInGenericContext(input),
|
|
336
|
+
['x', 3, 1, 2],
|
|
337
|
+
);
|
|
338
|
+
|
|
339
|
+
assert.deepStrictEqual<readonly (number | string)[]>(
|
|
340
|
+
toInsertedInGenericContext(input),
|
|
341
|
+
['x', 3, 1, 2],
|
|
342
|
+
);
|
|
343
|
+
|
|
344
|
+
assert.deepStrictEqual<readonly number[]>(
|
|
345
|
+
concatInGenericContext(input),
|
|
346
|
+
[3, 1, 2, 3, 1, 2],
|
|
347
|
+
);
|
|
348
|
+
});
|
|
150
349
|
});
|
|
151
350
|
|
|
152
351
|
// `map` — property 1 (length) across every unbranded sample.
|
|
@@ -235,8 +434,7 @@ describe('Arr shape invariants', () => {
|
|
|
235
434
|
expectType<MinLengthOf<typeof _c>, MinLengthOf<typeof lowerBounded>>('=');
|
|
236
435
|
}
|
|
237
436
|
|
|
238
|
-
// `toSorted` — properties 1 and 2. Sorting neither adds nor drops elements
|
|
239
|
-
// it is held to the same contract even though it is not fixed for property 3.
|
|
437
|
+
// `toSorted` — properties 1 and 2. Sorting neither adds nor drops elements.
|
|
240
438
|
{
|
|
241
439
|
const _a = toSorted(fixedTuple);
|
|
242
440
|
|
|
@@ -261,6 +459,27 @@ describe('Arr shape invariants', () => {
|
|
|
261
459
|
expectType<MaxLengthOf<typeof _e>, MaxLengthOf<typeof bounded>>('=');
|
|
262
460
|
}
|
|
263
461
|
|
|
462
|
+
// `toSortedBy` — properties 1 and 2.
|
|
463
|
+
{
|
|
464
|
+
const _a = toSortedBy(fixedTuple, (n) => n);
|
|
465
|
+
|
|
466
|
+
const _b = toSortedBy(plainArray, (n) => n);
|
|
467
|
+
|
|
468
|
+
const _c = toSortedBy(lowerBounded, (n) => n);
|
|
469
|
+
|
|
470
|
+
const _d = toSortedBy(bounded, (n) => n);
|
|
471
|
+
|
|
472
|
+
expectType<(typeof _a)['length'], (typeof fixedTuple)['length']>('=');
|
|
473
|
+
|
|
474
|
+
expectType<(typeof _b)['length'], (typeof plainArray)['length']>('=');
|
|
475
|
+
|
|
476
|
+
expectType<MinLengthOf<typeof _c>, MinLengthOf<typeof lowerBounded>>('=');
|
|
477
|
+
|
|
478
|
+
expectType<MinLengthOf<typeof _d>, MinLengthOf<typeof bounded>>('=');
|
|
479
|
+
|
|
480
|
+
expectType<MaxLengthOf<typeof _d>, MaxLengthOf<typeof bounded>>('=');
|
|
481
|
+
}
|
|
482
|
+
|
|
264
483
|
// `toReversed` — properties 1 and 2. Reversal permutes, so the length and the
|
|
265
484
|
// bounds are exactly what must not move.
|
|
266
485
|
{
|
|
@@ -282,3 +501,26 @@ describe('Arr shape invariants', () => {
|
|
|
282
501
|
|
|
283
502
|
expectType<MaxLengthOf<typeof _d>, MaxLengthOf<typeof bounded>>('=');
|
|
284
503
|
}
|
|
504
|
+
|
|
505
|
+
/**
|
|
506
|
+
* The transforms that do **not** resolve under a generic array parameter, and
|
|
507
|
+
* therefore have no property-3 entry above:
|
|
508
|
+
*
|
|
509
|
+
* | transform | reports | symptom under a generic `Ar` |
|
|
510
|
+
* | :------------------ | :---------------------- | :--------------------------- |
|
|
511
|
+
* | `tail` / `butLast` | `ConstrainedList.Tail` | TS2590 |
|
|
512
|
+
* | `zip` | `ConstrainedList.Zip` | TS2589, and no `entries()` |
|
|
513
|
+
* | `set` / `toUpdated` | `ConstrainedList.SetAt` | TS2589 |
|
|
514
|
+
*
|
|
515
|
+
* The cause is the same in all three, and it is upstream. Their brand branch
|
|
516
|
+
* does arithmetic on the input's own bounds (`SubLength`, `SmallerLength`), and
|
|
517
|
+
* that arithmetic goes through `MakeTuple`. For a concrete branded array the
|
|
518
|
+
* bound is a single number and the expansion is cheap; for a type parameter it
|
|
519
|
+
* is the whole `SupportedLength` union, which `MakeTuple` distributes over.
|
|
520
|
+
*
|
|
521
|
+
* `map`, `toFilled` and `toRangeFilled` were fixable in 14.0.1 precisely
|
|
522
|
+
* because `ChangeArrayElement`'s brand branch does no such arithmetic. The
|
|
523
|
+
* `UnknownBrand`-keyed overload split that fixed them does not transfer: the
|
|
524
|
+
* constrained overload makes the checker attempt that arithmetic at every call
|
|
525
|
+
* site, generic or not.
|
|
526
|
+
*/
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { type MinLengthArray } from 'ts-type-forge';
|
|
2
2
|
import { expectType } from '../../expect-type.mjs';
|
|
3
|
+
import { asMinLengthArray } from './array-utils-length-bounded-array-cast.mjs';
|
|
3
4
|
import {
|
|
4
5
|
butLast,
|
|
5
6
|
skip,
|
|
@@ -224,9 +225,9 @@ describe('Arr slicing', () => {
|
|
|
224
225
|
/* Length-constrained inputs. `List.*` reports what it can see from a
|
|
225
226
|
fixed-length tuple, and a branded array's `length` is `number`, so it handed
|
|
226
227
|
the input type straight back — `tail` of an "at least 5" array claimed the
|
|
227
|
-
four-element result was still at least five.
|
|
228
|
-
`array-utils-modification.test.mts`. */
|
|
229
|
-
|
|
228
|
+
four-element result was still at least five. Shape-per-parameter; see the
|
|
229
|
+
note in `array-utils-modification.test.mts`. */
|
|
230
|
+
const slicingTypeChecks = (
|
|
230
231
|
brandedFive: MinLengthArray<5, string>,
|
|
231
232
|
brandedAndTuple: MinLengthArray<3, number> & readonly [1, 2, 3, 4, 5],
|
|
232
233
|
): void => {
|
|
@@ -274,3 +275,10 @@ export const slicingTypeChecks = (
|
|
|
274
275
|
|
|
275
276
|
expectType<typeof _takenTuple, readonly [1, 2]>('=');
|
|
276
277
|
};
|
|
278
|
+
|
|
279
|
+
test('slicing type-level checks', () => {
|
|
280
|
+
slicingTypeChecks(
|
|
281
|
+
asMinLengthArray(5, ['a', 'b', 'c', 'd', 'e'] as const),
|
|
282
|
+
asMinLengthArray(3, [1, 2, 3, 4, 5] as const),
|
|
283
|
+
);
|
|
284
|
+
});
|
|
@@ -245,28 +245,51 @@ export const toReversed = <const Ar extends readonly unknown[]>(
|
|
|
245
245
|
* assert.deepStrictEqual(alphabetical, expectedWords);
|
|
246
246
|
* ```
|
|
247
247
|
*/
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
248
|
+
// Numbers sort without a comparator, so it is optional for them and required
|
|
249
|
+
// for everything else.
|
|
250
|
+
//
|
|
251
|
+
// Stated as two overloads rather than one conditional rest-parameter tuple.
|
|
252
|
+
// The conditional form reads well, but a *generic* `Ar` cannot decide it, so
|
|
253
|
+
// the parameter list stayed unresolved and `Arr.toSorted(xs, cmp)` inside a
|
|
254
|
+
// function that is itself generic over the array was rejected outright:
|
|
255
|
+
//
|
|
256
|
+
// Argument of type '[T, (a: number, b: number) => number]' is not
|
|
257
|
+
// assignable to parameter of type 'T extends readonly number[] ? ... : ...'
|
|
258
|
+
//
|
|
259
|
+
// Overload resolution picks a branch per call instead, so both concrete and
|
|
260
|
+
// generic callers work. The *return* type stays conditional — that one a
|
|
261
|
+
// generic caller handles, as `toSortedBy` (same shape) already showed.
|
|
262
|
+
export function toSorted<const Ar extends readonly number[]>(
|
|
263
|
+
array: Ar,
|
|
264
|
+
comparator?: (x: Ar[number], y: Ar[number]) => number,
|
|
265
|
+
): SortedResult<Ar>;
|
|
266
|
+
|
|
267
|
+
export function toSorted<const Ar extends readonly unknown[]>(
|
|
268
|
+
array: Ar,
|
|
269
|
+
comparator: (x: Ar[number], y: Ar[number]) => number,
|
|
270
|
+
): SortedResult<Ar>;
|
|
271
|
+
|
|
272
|
+
export function toSorted(
|
|
273
|
+
array: readonly unknown[],
|
|
274
|
+
comparator?: (x: never, y: never) => number,
|
|
275
|
+
): readonly unknown[] {
|
|
276
|
+
return array.toSorted(
|
|
265
277
|
// eslint-disable-next-line total-functions/no-unsafe-type-assertion
|
|
266
278
|
(comparator as ((x: unknown, y: unknown) => number) | undefined) ??
|
|
267
279
|
// eslint-disable-next-line total-functions/no-unsafe-type-assertion
|
|
268
280
|
((x, y) => (x as number) - (y as number)),
|
|
269
|
-
)
|
|
281
|
+
);
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
/** The result of sorting `Ar`: same elements, same length, same bounds. */
|
|
285
|
+
type SortedResult<Ar extends readonly unknown[]> =
|
|
286
|
+
HasLengthConstraint<Ar> extends true
|
|
287
|
+
? ChangeArrayElement<Ar, Ar[number]>
|
|
288
|
+
: IsFixedLengthList<Ar> extends true
|
|
289
|
+
? FixedLengthTuple<Ar['length'], Ar[number]>
|
|
290
|
+
: Ar extends NonEmptyTuple<unknown>
|
|
291
|
+
? NonEmptyArray<Ar[number]>
|
|
292
|
+
: readonly Ar[number][];
|
|
270
293
|
|
|
271
294
|
/**
|
|
272
295
|
* Sorts an array by a mapped value.
|
|
@@ -1207,9 +1207,9 @@ describe('Arr transformations', () => {
|
|
|
1207
1207
|
|
|
1208
1208
|
/* `toReversed` is a known permutation, so the tuple half can be reversed
|
|
1209
1209
|
alongside the brand. It used to fall back to `ChangeArrayElement`, which kept
|
|
1210
|
-
the length but collapsed the positions to their union.
|
|
1211
|
-
note in `array-utils-modification.test.mts`. */
|
|
1212
|
-
|
|
1210
|
+
the length but collapsed the positions to their union. Shape-per-parameter;
|
|
1211
|
+
see the note in `array-utils-modification.test.mts`. */
|
|
1212
|
+
const reversedTypeChecks = (
|
|
1213
1213
|
brandedFive: MinLengthArray<5, string>,
|
|
1214
1214
|
brandedAndTuple: MinLengthArray<3, number> & readonly [1, 2, 3, 4, 5],
|
|
1215
1215
|
): void => {
|
|
@@ -1235,3 +1235,10 @@ export const reversedTypeChecks = (
|
|
|
1235
1235
|
|
|
1236
1236
|
expectType<typeof _sortedTuple, FixedLengthTuple<3, 1 | 2 | 3>>('~=');
|
|
1237
1237
|
};
|
|
1238
|
+
|
|
1239
|
+
test('toReversed / toSorted type-level checks', () => {
|
|
1240
|
+
reversedTypeChecks(
|
|
1241
|
+
asMinLengthArray(5, ['a', 'b', 'c', 'd', 'e'] as const),
|
|
1242
|
+
asMinLengthArray(3, [1, 2, 3, 4, 5] as const),
|
|
1243
|
+
);
|
|
1244
|
+
});
|
|
@@ -1,4 +1,10 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
type BoolOr,
|
|
3
|
+
type ChangeArrayElement,
|
|
4
|
+
type IsAny,
|
|
5
|
+
type IsUnknown,
|
|
6
|
+
type UnknownBrand,
|
|
7
|
+
} from 'ts-type-forge';
|
|
2
8
|
import { asUint32, Num } from '../../number/index.mjs';
|
|
3
9
|
import { type ArrayIndex, type SizeType } from '../../types.mjs';
|
|
4
10
|
|
|
@@ -55,15 +61,30 @@ type Cast<A, B> = A extends B ? A : never;
|
|
|
55
61
|
* assert.isFalse(allStartWithA);
|
|
56
62
|
* ```
|
|
57
63
|
*/
|
|
58
|
-
// Type guard overloads - narrow the entire array type
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
64
|
+
// Type guard overloads - narrow the entire array type.
|
|
65
|
+
//
|
|
66
|
+
// Both narrowed types are intersected with `Ar`: a type predicate's type has
|
|
67
|
+
// to be assignable to the parameter's type, and neither the deferred
|
|
68
|
+
// conditional `ChangeArrayElement<Ar, S>` nor the mapped type over a generic
|
|
69
|
+
// `Ar` is (TS2677). The intersection also keeps whatever the caller already
|
|
70
|
+
// knew about the array — a branded five-tuple stays a five-tuple.
|
|
71
|
+
export function every<
|
|
72
|
+
const Ar extends readonly unknown[] & UnknownBrand,
|
|
73
|
+
S extends Ar[number],
|
|
74
|
+
>(
|
|
75
|
+
array: Ar,
|
|
76
|
+
predicate: (a: Ar[number], index: ArrayIndex<Ar>) => a is S,
|
|
77
|
+
): array is ChangeArrayElement<Ar, S> & Ar;
|
|
78
|
+
|
|
79
|
+
// A plain array or tuple carries no length brand, so the homomorphic mapping
|
|
80
|
+
// is the whole answer — same reasoning as in `map`.
|
|
81
|
+
export function every<
|
|
82
|
+
const Ar extends readonly unknown[],
|
|
83
|
+
S extends Ar[number],
|
|
84
|
+
>(
|
|
85
|
+
array: Ar,
|
|
86
|
+
predicate: (a: Ar[number], index: ArrayIndex<Ar>) => a is S,
|
|
87
|
+
): array is Readonly<{ [K in keyof Ar]: S }> & Ar;
|
|
67
88
|
|
|
68
89
|
// Regular boolean predicate overloads
|
|
69
90
|
export function every<const Ar extends readonly unknown[]>(
|
|
@@ -71,6 +92,29 @@ export function every<const Ar extends readonly unknown[]>(
|
|
|
71
92
|
predicate: (a: Ar[number], index: ArrayIndex<Ar>) => boolean,
|
|
72
93
|
): boolean;
|
|
73
94
|
|
|
95
|
+
// curried version
|
|
96
|
+
//
|
|
97
|
+
// As in `map`, the branded and the plain case have to be overloads *of the
|
|
98
|
+
// returned function*, not two overloads of `every` itself: both curried
|
|
99
|
+
// signatures take the same single predicate, so overload resolution would
|
|
100
|
+
// always pick the first and the second would be dead — which is what left a
|
|
101
|
+
// plain `readonly unknown[]` unable to satisfy the `UnknownBrand` constraint
|
|
102
|
+
// at the call site of the returned guard.
|
|
103
|
+
//
|
|
104
|
+
// The intersection is also what keeps the group compatible with the
|
|
105
|
+
// implementation signature. A lone `<Ar extends readonly E[] & UnknownBrand>`
|
|
106
|
+
// signature is not something `(array: readonly E[]) => boolean` can implement
|
|
107
|
+
// (TS2394); the unbranded member of the intersection is, and one matching
|
|
108
|
+
// member is enough.
|
|
109
|
+
export function every<E, S extends E>(
|
|
110
|
+
predicate: (a: E, index: SizeType.Arr) => a is S,
|
|
111
|
+
): (<const Ar extends readonly E[] & UnknownBrand>(
|
|
112
|
+
array: Ar,
|
|
113
|
+
) => array is ChangeArrayElement<Ar, S> & Ar) &
|
|
114
|
+
(<const Ar extends readonly E[]>(
|
|
115
|
+
array: Ar,
|
|
116
|
+
) => array is Readonly<{ [K in keyof Ar]: S }> & Ar);
|
|
117
|
+
|
|
74
118
|
export function every<E>(
|
|
75
119
|
predicate: (a: E, index: SizeType.Arr) => boolean,
|
|
76
120
|
): (array: readonly E[]) => boolean;
|