ts-data-forge 13.0.0 → 14.0.1
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 +4 -3
- 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 +45 -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-slice-clamped.d.mts.map +1 -1
- package/dist/array/impl/array-utils-slice-clamped.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 +36 -19
- 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/collections/imap.d.mts +1 -1
- package/dist/collections/imap.mjs.map +1 -1
- package/dist/collections/iset-mapped.d.mts +2 -2
- package/dist/collections/iset-mapped.d.mts.map +1 -1
- package/dist/collections/iset-mapped.mjs.map +1 -1
- package/dist/collections/iset.d.mts +2 -2
- package/dist/collections/iset.d.mts.map +1 -1
- package/dist/collections/iset.mjs.map +1 -1
- package/dist/number/num-arithmetic-types.d.mts +2 -2
- package/dist/number/num-arithmetic-types.d.mts.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 +4 -5
- package/src/array/impl/array-utils-creation.mts +7 -3
- package/src/array/impl/array-utils-creation.test.mts +10 -8
- 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 +84 -61
- package/src/array/impl/array-utils-modification.test.mts +145 -6
- package/src/array/impl/array-utils-reducing-value.mts +5 -5
- package/src/array/impl/array-utils-shape-invariants.test.mts +284 -0
- package/src/array/impl/array-utils-slice-clamped.mts +2 -1
- 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 +91 -37
- package/src/array/impl/array-utils-transformation.test.mts +96 -23
- 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/imap.mts +1 -1
- package/src/collections/iset-mapped.mts +7 -3
- package/src/collections/iset.mts +7 -3
- package/src/collections/iset.test.mts +9 -6
- package/src/collections/queue.test.mts +1 -1
- package/src/number/num-arithmetic-types.mts +8 -4
- package/src/number/num.test.mts +2 -2
- package/src/string/str.mts +69 -69
- package/src/string/str.test.mts +40 -40
|
@@ -1,6 +1,14 @@
|
|
|
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';
|
|
11
|
+
import { asMinLengthArray } from './array-utils-length-bounded-array-cast.mjs';
|
|
4
12
|
import {
|
|
5
13
|
set,
|
|
6
14
|
toFilled,
|
|
@@ -18,7 +26,7 @@ describe('Arr modifications', () => {
|
|
|
18
26
|
|
|
19
27
|
const result = toUpdated(xs, 1, (x) => x + 2);
|
|
20
28
|
|
|
21
|
-
expectType<typeof result,
|
|
29
|
+
expectType<typeof result, readonly [1, number, 3]>('=');
|
|
22
30
|
|
|
23
31
|
test('case 1', () => {
|
|
24
32
|
assert.deepStrictEqual(result, [1, 4, 3]);
|
|
@@ -236,6 +244,45 @@ describe('Arr modifications', () => {
|
|
|
236
244
|
|
|
237
245
|
assert.deepStrictEqual(result, [0, 0, 0]);
|
|
238
246
|
});
|
|
247
|
+
|
|
248
|
+
test('should preserve the tuple shape under a generic parameter', () => {
|
|
249
|
+
// A caller that is itself generic over the tuple cannot resolve a
|
|
250
|
+
// conditional return type, so the unbranded overload has to state the
|
|
251
|
+
// homomorphic mapping directly.
|
|
252
|
+
const blank = <const T extends readonly number[]>(
|
|
253
|
+
xs: T,
|
|
254
|
+
): Readonly<{ [K in keyof T]: 0 }> => toFilled(xs, 0);
|
|
255
|
+
|
|
256
|
+
const result = blank([1, 2, 3] as const);
|
|
257
|
+
|
|
258
|
+
expectType<typeof result, FixedLengthTuple<3, 0>>('=');
|
|
259
|
+
|
|
260
|
+
assert.deepStrictEqual<FixedLengthTuple<3, 0>>(result, [0, 0, 0]);
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
test('should preserve the tuple shape under a generic parameter when curried', () => {
|
|
264
|
+
const blank = <const T extends readonly number[]>(
|
|
265
|
+
xs: T,
|
|
266
|
+
): Readonly<{ [K in keyof T]: 0 }> => toFilled(0)(xs);
|
|
267
|
+
|
|
268
|
+
const result = blank([1, 2] as const);
|
|
269
|
+
|
|
270
|
+
expectType<typeof result, FixedLengthTuple<2, 0>>('=');
|
|
271
|
+
|
|
272
|
+
assert.deepStrictEqual<FixedLengthTuple<2, 0>>(result, [0, 0]);
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
test('should keep the length brand of a branded array', () => {
|
|
276
|
+
const lower: MinLengthArray<2, number> = asMinLengthArray(2, [1, 2, 3]);
|
|
277
|
+
|
|
278
|
+
const filled = toFilled(lower, 0);
|
|
279
|
+
|
|
280
|
+
expectType<typeof filled, MinLengthArray<2, 0>>('~=');
|
|
281
|
+
|
|
282
|
+
// The brand is not part of the runtime value, so compare structurally —
|
|
283
|
+
// the type argument is what widens it, no intermediate binding needed.
|
|
284
|
+
assert.deepStrictEqual<readonly number[]>(filled, [0, 0, 0]);
|
|
285
|
+
});
|
|
239
286
|
});
|
|
240
287
|
|
|
241
288
|
describe(toRangeFilled, () => {
|
|
@@ -263,6 +310,18 @@ describe('Arr modifications', () => {
|
|
|
263
310
|
assert.deepStrictEqual(result, [1, 2, 3]);
|
|
264
311
|
});
|
|
265
312
|
|
|
313
|
+
test('should preserve the tuple shape under a generic parameter', () => {
|
|
314
|
+
const blankMiddle = <const T extends readonly number[]>(
|
|
315
|
+
xs: T,
|
|
316
|
+
): Readonly<{ [K in keyof T]: number }> => toRangeFilled(xs, 0, [1, 2]);
|
|
317
|
+
|
|
318
|
+
const result = blankMiddle([1, 2, 3] as const);
|
|
319
|
+
|
|
320
|
+
expectType<typeof result, FixedLengthTuple<3, number>>('=');
|
|
321
|
+
|
|
322
|
+
assert.deepStrictEqual<FixedLengthTuple<3, number>>(result, [1, 0, 3]);
|
|
323
|
+
});
|
|
324
|
+
|
|
266
325
|
test('should clamp range to array bounds', () => {
|
|
267
326
|
const arr = [1, 2, 3] as readonly number[];
|
|
268
327
|
|
|
@@ -305,7 +364,7 @@ describe('Arr modifications', () => {
|
|
|
305
364
|
describe(set, () => {
|
|
306
365
|
const result = set([1, 2, 3], 1, 4);
|
|
307
366
|
|
|
308
|
-
expectType<typeof result, readonly [1
|
|
367
|
+
expectType<typeof result, readonly [1, 4, 3]>('=');
|
|
309
368
|
|
|
310
369
|
test('case 1', () => {
|
|
311
370
|
assert.deepStrictEqual(result, [1, 4, 3]);
|
|
@@ -316,9 +375,7 @@ describe('Arr modifications', () => {
|
|
|
316
375
|
|
|
317
376
|
const withString = set(nums, 1, 'two');
|
|
318
377
|
|
|
319
|
-
expectType<typeof withString, readonly [1
|
|
320
|
-
'=',
|
|
321
|
-
);
|
|
378
|
+
expectType<typeof withString, readonly [1, 'two', 3]>('=');
|
|
322
379
|
|
|
323
380
|
assert.deepStrictEqual(withString, [1, 'two', 3]);
|
|
324
381
|
});
|
|
@@ -348,3 +405,85 @@ describe('Arr modifications', () => {
|
|
|
348
405
|
});
|
|
349
406
|
});
|
|
350
407
|
});
|
|
408
|
+
|
|
409
|
+
/* `Arr.set` / `Arr.toUpdated` across the input shapes the positional
|
|
410
|
+
`ConstrainedList.SetAt` annotation has to cope with; the suite previously
|
|
411
|
+
covered only the plain-tuple case. Written as an unexported-by-value function
|
|
412
|
+
so the assertions get real overload resolution — it is never called, and the
|
|
413
|
+
branded shapes have no runtime representation to build. */
|
|
414
|
+
export const setTypeChecks = (
|
|
415
|
+
shapeTuple: readonly [1, 2, 3],
|
|
416
|
+
shapeArray: readonly number[],
|
|
417
|
+
shapeMutableArray: readonly number[],
|
|
418
|
+
shapeBranded: MinLengthArray<3, string>,
|
|
419
|
+
shapeBounded: BoundedLengthArray<2, 5, string>,
|
|
420
|
+
shapeFixed: FixedLengthArray<3, string>,
|
|
421
|
+
shapeBrandedTuple: MinLengthArray<3, number> & readonly [1, 2, 3, 4, 5],
|
|
422
|
+
unionIndex: 0 | 2,
|
|
423
|
+
widenedIndex: SizeType.ArgArr,
|
|
424
|
+
): void => {
|
|
425
|
+
/* Plain tuple — the point of the positional form: only the touched position
|
|
426
|
+
changes, the rest keep their literal types. */
|
|
427
|
+
const _onTuple = set(shapeTuple, 1, 'x');
|
|
428
|
+
|
|
429
|
+
expectType<typeof _onTuple, readonly [1, 'x', 3]>('=');
|
|
430
|
+
|
|
431
|
+
// Plain array: no positions to pin, so the element type widens.
|
|
432
|
+
const _onArray = set(shapeArray, widenedIndex, 'x');
|
|
433
|
+
|
|
434
|
+
expectType<typeof _onArray, readonly (number | 'x')[]>('=');
|
|
435
|
+
|
|
436
|
+
// A mutable array is not brand-carrying, and behaves like the readonly one.
|
|
437
|
+
const _onMutable = set(shapeMutableArray, widenedIndex, 'x');
|
|
438
|
+
|
|
439
|
+
expectType<typeof _onMutable, readonly (number | 'x')[]>('=');
|
|
440
|
+
|
|
441
|
+
/* Branded with no exact tuple: the length constraint survives, and with no
|
|
442
|
+
positions to pin the element type absorbs the new value. */
|
|
443
|
+
const _onBranded = set(shapeBranded, 1, 'x');
|
|
444
|
+
|
|
445
|
+
expectType<typeof _onBranded, MinLengthArray<3, string>>('<=');
|
|
446
|
+
|
|
447
|
+
const _onBounded = set(shapeBounded, 1, 'x');
|
|
448
|
+
|
|
449
|
+
expectType<typeof _onBounded, BoundedLengthArray<2, 5, string>>('<=');
|
|
450
|
+
|
|
451
|
+
// Fixed length: brand *and* exact positions both survive.
|
|
452
|
+
const _onFixed = set(shapeFixed, 1, 'x');
|
|
453
|
+
|
|
454
|
+
expectType<
|
|
455
|
+
typeof _onFixed,
|
|
456
|
+
FixedLengthArray<3, string> & readonly [string, 'x', string]
|
|
457
|
+
>('~=');
|
|
458
|
+
|
|
459
|
+
/* Brand intersected with an exact tuple — the shape `Arr.isMinLengthArray`
|
|
460
|
+
produces. All three survive: the brand, the exact length, and the
|
|
461
|
+
positions, the last as a structural conjunct beside the widened one. */
|
|
462
|
+
const _onBrandedTuple = set(shapeBrandedTuple, 1, 'x');
|
|
463
|
+
|
|
464
|
+
expectType<(typeof _onBrandedTuple)[0], 1>('=');
|
|
465
|
+
|
|
466
|
+
expectType<(typeof _onBrandedTuple)[1], 'x'>('=');
|
|
467
|
+
|
|
468
|
+
expectType<(typeof _onBrandedTuple)[4], 5>('=');
|
|
469
|
+
|
|
470
|
+
expectType<(typeof _onBrandedTuple)['length'], 5>('=');
|
|
471
|
+
|
|
472
|
+
/* A union index must NOT be replaced positionally. The call sets one of the
|
|
473
|
+
positions and never both, so `readonly ['x', 2, 'x']` — what the upstream
|
|
474
|
+
`List.SetAt` used to answer for a union index — is a type that no possible
|
|
475
|
+
result satisfies. ts-type-forge 9.1.1 widens the candidate positions
|
|
476
|
+
instead, and only those: index 1 is not among `0 | 2`, so it stays `2`. */
|
|
477
|
+
const _onUnionIndex = set(shapeTuple, unionIndex, 'x');
|
|
478
|
+
|
|
479
|
+
expectType<typeof _onUnionIndex, readonly [1 | 'x', 2, 3 | 'x']>('=');
|
|
480
|
+
|
|
481
|
+
// `toUpdated` follows `set`, with the updater's return as the value.
|
|
482
|
+
const _updatedTuple = toUpdated(shapeTuple, 1, (x) => x + 1);
|
|
483
|
+
|
|
484
|
+
expectType<typeof _updatedTuple, readonly [1, number, 3]>('=');
|
|
485
|
+
|
|
486
|
+
const _updatedUnion = toUpdated(shapeTuple, unionIndex, (x) => x + 1);
|
|
487
|
+
|
|
488
|
+
expectType<typeof _updatedUnion, readonly [number, 2, number]>('=');
|
|
489
|
+
};
|
|
@@ -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 {
|
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
import {
|
|
2
|
+
type BoundedLengthArray,
|
|
3
|
+
type FixedLengthArray,
|
|
4
|
+
type FixedLengthTuple,
|
|
5
|
+
type MaxLengthArray,
|
|
6
|
+
type MaxLengthOf,
|
|
7
|
+
type MinLengthArray,
|
|
8
|
+
type MinLengthOf,
|
|
9
|
+
} from 'ts-type-forge';
|
|
10
|
+
import { expectType } from '../../expect-type.mjs';
|
|
11
|
+
import {
|
|
12
|
+
asBoundedLengthArray,
|
|
13
|
+
asFixedLengthArray,
|
|
14
|
+
asMaxLengthArray,
|
|
15
|
+
asMinLengthArray,
|
|
16
|
+
} from './array-utils-length-bounded-array-cast.mjs';
|
|
17
|
+
import { toFilled, toRangeFilled } from './array-utils-modification.mjs';
|
|
18
|
+
import { map, toReversed, toSorted } from './array-utils-transformation.mjs';
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Invariant-based coverage for the array -> array transforms.
|
|
22
|
+
*
|
|
23
|
+
* Rather than pinning an expected type for every (brand x shape x context)
|
|
24
|
+
* combination — which is a large table whose expected side is easy to derive
|
|
25
|
+
* from the implementation and therefore prove nothing — this asserts the small
|
|
26
|
+
* set of properties every shape-preserving transform must satisfy, across a
|
|
27
|
+
* fixed list of sample inputs that covers the axes.
|
|
28
|
+
*
|
|
29
|
+
* The properties are:
|
|
30
|
+
*
|
|
31
|
+
* 1. **Length** — the result reports the same `length` as the input.
|
|
32
|
+
* 2. **Bounds** — a length brand on the input survives onto the result.
|
|
33
|
+
* 3. **Generic context** — from a caller that is itself generic over the
|
|
34
|
+
* array, the result is assignable to the caller's own homomorphic mapping.
|
|
35
|
+
*
|
|
36
|
+
* Property 3 is the one with no coverage before this file, and is what the
|
|
37
|
+
* `Arr.map` regression fixed in this PR violated: a conditional return type a
|
|
38
|
+
* generic `Ar` cannot decide stays deferred, and its brand-carrying branch is
|
|
39
|
+
* then not assignable to the caller's mapping.
|
|
40
|
+
*/
|
|
41
|
+
|
|
42
|
+
// The sample inputs. Between them they cover the axes: empty / singleton /
|
|
43
|
+
// fixed tuple / non-empty tuple / unbounded array on the shape axis, and
|
|
44
|
+
// unbranded / lower bound / upper bound / both / exact on the brand axis.
|
|
45
|
+
const emptyTuple: readonly [] = [] as const;
|
|
46
|
+
|
|
47
|
+
const singleton: readonly [1] = [1] as const;
|
|
48
|
+
|
|
49
|
+
const fixedTuple: readonly [1, 2, 3] = [1, 2, 3] as const;
|
|
50
|
+
|
|
51
|
+
const nonEmptyTuple: readonly [1, ...number[]] = [1, 2, 3] as const;
|
|
52
|
+
|
|
53
|
+
const plainArray: readonly number[] = [1, 2, 3] as const;
|
|
54
|
+
|
|
55
|
+
const lowerBounded: MinLengthArray<2, number> = asMinLengthArray(2, [1, 2, 3]);
|
|
56
|
+
|
|
57
|
+
const upperBounded: MaxLengthArray<5, number> = asMaxLengthArray(5, [1, 2, 3]);
|
|
58
|
+
|
|
59
|
+
const bounded: BoundedLengthArray<2, 5, number> = asBoundedLengthArray(
|
|
60
|
+
2,
|
|
61
|
+
5,
|
|
62
|
+
[1, 2, 3],
|
|
63
|
+
);
|
|
64
|
+
|
|
65
|
+
const exactLength: FixedLengthArray<3, number> = asFixedLengthArray(
|
|
66
|
+
3,
|
|
67
|
+
[1, 2, 3],
|
|
68
|
+
);
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Property 3 — generic context.
|
|
72
|
+
*
|
|
73
|
+
* Each of these is an assertion by construction: the annotated return type is
|
|
74
|
+
* the caller's own homomorphic mapping, so the declaration only compiles while
|
|
75
|
+
* the transform reports something assignable to it. There is nothing to run.
|
|
76
|
+
*
|
|
77
|
+
* Only the transforms that satisfy this today are listed. `toSorted`,
|
|
78
|
+
* `toSortedBy`, `toReversed`, `toUpdated`, `tail`, `butLast` and `zip` do not
|
|
79
|
+
* yet — see the PR description for why each one resists the same fix. Add them
|
|
80
|
+
* here as they are fixed; that is what makes this list the coverage record.
|
|
81
|
+
*/
|
|
82
|
+
|
|
83
|
+
const mapInGenericContext = <const T extends readonly number[]>(
|
|
84
|
+
xs: T,
|
|
85
|
+
): Readonly<{ [K in keyof T]: string }> => map(xs, String);
|
|
86
|
+
|
|
87
|
+
const mapCurriedInGenericContext = <const T extends readonly number[]>(
|
|
88
|
+
xs: T,
|
|
89
|
+
): Readonly<{ [K in keyof T]: string }> => map(String)(xs);
|
|
90
|
+
|
|
91
|
+
const toFilledInGenericContext = <const T extends readonly number[]>(
|
|
92
|
+
xs: T,
|
|
93
|
+
): Readonly<{ [K in keyof T]: 0 }> => toFilled(xs, 0);
|
|
94
|
+
|
|
95
|
+
const toFilledCurriedInGenericContext = <const T extends readonly number[]>(
|
|
96
|
+
xs: T,
|
|
97
|
+
): Readonly<{ [K in keyof T]: 0 }> => toFilled(0)(xs);
|
|
98
|
+
|
|
99
|
+
const toRangeFilledInGenericContext = <const T extends readonly number[]>(
|
|
100
|
+
xs: T,
|
|
101
|
+
): Readonly<{ [K in keyof T]: number }> => toRangeFilled(xs, 0, [0, 1]);
|
|
102
|
+
|
|
103
|
+
describe('Arr shape invariants', () => {
|
|
104
|
+
// The type-level assertions below are checked by `tsc`; this pins the same
|
|
105
|
+
// property at runtime, so a transform that silently drops or adds an element
|
|
106
|
+
// fails here even where the type says otherwise.
|
|
107
|
+
test('every shape-preserving transform keeps the length at runtime', () => {
|
|
108
|
+
const input = [3, 1, 2] as const;
|
|
109
|
+
|
|
110
|
+
assert.deepStrictEqual<FixedLengthTuple<5, 3>>(
|
|
111
|
+
[
|
|
112
|
+
map(input, String).length,
|
|
113
|
+
toFilled(input, 0).length,
|
|
114
|
+
toRangeFilled(input, 0, [1, 2]).length,
|
|
115
|
+
toSorted(input).length,
|
|
116
|
+
toReversed(input).length,
|
|
117
|
+
] as const,
|
|
118
|
+
[3, 3, 3, 3, 3] as const,
|
|
119
|
+
);
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
test('the generic-context callers agree with their annotations', () => {
|
|
123
|
+
const input = [3, 1, 2] as const;
|
|
124
|
+
|
|
125
|
+
assert.deepStrictEqual<FixedLengthTuple<3, string>>(
|
|
126
|
+
mapInGenericContext(input),
|
|
127
|
+
['3', '1', '2'],
|
|
128
|
+
);
|
|
129
|
+
|
|
130
|
+
assert.deepStrictEqual<FixedLengthTuple<3, string>>(
|
|
131
|
+
mapCurriedInGenericContext(input),
|
|
132
|
+
['3', '1', '2'],
|
|
133
|
+
);
|
|
134
|
+
|
|
135
|
+
assert.deepStrictEqual<FixedLengthTuple<3, 0>>(
|
|
136
|
+
toFilledInGenericContext(input),
|
|
137
|
+
[0, 0, 0],
|
|
138
|
+
);
|
|
139
|
+
|
|
140
|
+
assert.deepStrictEqual<FixedLengthTuple<3, 0>>(
|
|
141
|
+
toFilledCurriedInGenericContext(input),
|
|
142
|
+
[0, 0, 0],
|
|
143
|
+
);
|
|
144
|
+
|
|
145
|
+
assert.deepStrictEqual<FixedLengthTuple<3, number>>(
|
|
146
|
+
toRangeFilledInGenericContext(input),
|
|
147
|
+
[0, 1, 2],
|
|
148
|
+
);
|
|
149
|
+
});
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
// `map` — property 1 (length) across every unbranded sample.
|
|
153
|
+
{
|
|
154
|
+
const _a = map(emptyTuple, String);
|
|
155
|
+
|
|
156
|
+
const _b = map(singleton, String);
|
|
157
|
+
|
|
158
|
+
const _c = map(fixedTuple, String);
|
|
159
|
+
|
|
160
|
+
const _d = map(nonEmptyTuple, String);
|
|
161
|
+
|
|
162
|
+
const _e = map(plainArray, String);
|
|
163
|
+
|
|
164
|
+
expectType<(typeof _a)['length'], (typeof emptyTuple)['length']>('=');
|
|
165
|
+
|
|
166
|
+
expectType<(typeof _b)['length'], (typeof singleton)['length']>('=');
|
|
167
|
+
|
|
168
|
+
expectType<(typeof _c)['length'], (typeof fixedTuple)['length']>('=');
|
|
169
|
+
|
|
170
|
+
expectType<(typeof _d)['length'], (typeof nonEmptyTuple)['length']>('=');
|
|
171
|
+
|
|
172
|
+
expectType<(typeof _e)['length'], (typeof plainArray)['length']>('=');
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// `map` — property 2 (bounds) across every branded sample.
|
|
176
|
+
{
|
|
177
|
+
const _a = map(lowerBounded, String);
|
|
178
|
+
|
|
179
|
+
const _b = map(upperBounded, String);
|
|
180
|
+
|
|
181
|
+
const _c = map(bounded, String);
|
|
182
|
+
|
|
183
|
+
const _d = map(exactLength, String);
|
|
184
|
+
|
|
185
|
+
expectType<MinLengthOf<typeof _a>, MinLengthOf<typeof lowerBounded>>('=');
|
|
186
|
+
|
|
187
|
+
expectType<MaxLengthOf<typeof _b>, MaxLengthOf<typeof upperBounded>>('=');
|
|
188
|
+
|
|
189
|
+
expectType<MinLengthOf<typeof _c>, MinLengthOf<typeof bounded>>('=');
|
|
190
|
+
|
|
191
|
+
expectType<MaxLengthOf<typeof _c>, MaxLengthOf<typeof bounded>>('=');
|
|
192
|
+
|
|
193
|
+
expectType<MinLengthOf<typeof _d>, MinLengthOf<typeof exactLength>>('=');
|
|
194
|
+
|
|
195
|
+
expectType<MaxLengthOf<typeof _d>, MaxLengthOf<typeof exactLength>>('=');
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// `toFilled` — properties 1 and 2.
|
|
199
|
+
{
|
|
200
|
+
const _a = toFilled(fixedTuple, 0);
|
|
201
|
+
|
|
202
|
+
const _b = toFilled(nonEmptyTuple, 0);
|
|
203
|
+
|
|
204
|
+
const _c = toFilled(plainArray, 0);
|
|
205
|
+
|
|
206
|
+
const _d = toFilled(lowerBounded, 0);
|
|
207
|
+
|
|
208
|
+
const _e = toFilled(bounded, 0);
|
|
209
|
+
|
|
210
|
+
expectType<(typeof _a)['length'], (typeof fixedTuple)['length']>('=');
|
|
211
|
+
|
|
212
|
+
expectType<(typeof _b)['length'], (typeof nonEmptyTuple)['length']>('=');
|
|
213
|
+
|
|
214
|
+
expectType<(typeof _c)['length'], (typeof plainArray)['length']>('=');
|
|
215
|
+
|
|
216
|
+
expectType<MinLengthOf<typeof _d>, MinLengthOf<typeof lowerBounded>>('=');
|
|
217
|
+
|
|
218
|
+
expectType<MinLengthOf<typeof _e>, MinLengthOf<typeof bounded>>('=');
|
|
219
|
+
|
|
220
|
+
expectType<MaxLengthOf<typeof _e>, MaxLengthOf<typeof bounded>>('=');
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// `toRangeFilled` — properties 1 and 2.
|
|
224
|
+
{
|
|
225
|
+
const _a = toRangeFilled(fixedTuple, 0, [1, 2]);
|
|
226
|
+
|
|
227
|
+
const _b = toRangeFilled(plainArray, 0, [0, 1]);
|
|
228
|
+
|
|
229
|
+
const _c = toRangeFilled(lowerBounded, 0, [0, 1]);
|
|
230
|
+
|
|
231
|
+
expectType<(typeof _a)['length'], (typeof fixedTuple)['length']>('=');
|
|
232
|
+
|
|
233
|
+
expectType<(typeof _b)['length'], (typeof plainArray)['length']>('=');
|
|
234
|
+
|
|
235
|
+
expectType<MinLengthOf<typeof _c>, MinLengthOf<typeof lowerBounded>>('=');
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// `toSorted` — properties 1 and 2. Sorting neither adds nor drops elements, so
|
|
239
|
+
// it is held to the same contract even though it is not fixed for property 3.
|
|
240
|
+
{
|
|
241
|
+
const _a = toSorted(fixedTuple);
|
|
242
|
+
|
|
243
|
+
const _b = toSorted(nonEmptyTuple);
|
|
244
|
+
|
|
245
|
+
const _c = toSorted(plainArray);
|
|
246
|
+
|
|
247
|
+
const _d = toSorted(lowerBounded);
|
|
248
|
+
|
|
249
|
+
const _e = toSorted(bounded);
|
|
250
|
+
|
|
251
|
+
expectType<(typeof _a)['length'], (typeof fixedTuple)['length']>('=');
|
|
252
|
+
|
|
253
|
+
expectType<(typeof _b)['length'], (typeof nonEmptyTuple)['length']>('=');
|
|
254
|
+
|
|
255
|
+
expectType<(typeof _c)['length'], (typeof plainArray)['length']>('=');
|
|
256
|
+
|
|
257
|
+
expectType<MinLengthOf<typeof _d>, MinLengthOf<typeof lowerBounded>>('=');
|
|
258
|
+
|
|
259
|
+
expectType<MinLengthOf<typeof _e>, MinLengthOf<typeof bounded>>('=');
|
|
260
|
+
|
|
261
|
+
expectType<MaxLengthOf<typeof _e>, MaxLengthOf<typeof bounded>>('=');
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// `toReversed` — properties 1 and 2. Reversal permutes, so the length and the
|
|
265
|
+
// bounds are exactly what must not move.
|
|
266
|
+
{
|
|
267
|
+
const _a = toReversed(fixedTuple);
|
|
268
|
+
|
|
269
|
+
const _b = toReversed(plainArray);
|
|
270
|
+
|
|
271
|
+
const _c = toReversed(lowerBounded);
|
|
272
|
+
|
|
273
|
+
const _d = toReversed(bounded);
|
|
274
|
+
|
|
275
|
+
expectType<(typeof _a)['length'], (typeof fixedTuple)['length']>('=');
|
|
276
|
+
|
|
277
|
+
expectType<(typeof _b)['length'], (typeof plainArray)['length']>('=');
|
|
278
|
+
|
|
279
|
+
expectType<MinLengthOf<typeof _c>, MinLengthOf<typeof lowerBounded>>('=');
|
|
280
|
+
|
|
281
|
+
expectType<MinLengthOf<typeof _d>, MinLengthOf<typeof bounded>>('=');
|
|
282
|
+
|
|
283
|
+
expectType<MaxLengthOf<typeof _d>, MaxLengthOf<typeof bounded>>('=');
|
|
284
|
+
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type FixedLengthTuple } from 'ts-type-forge';
|
|
1
2
|
import { Num } from '../../number/index.mjs';
|
|
2
3
|
import { type ArgArrayIndexWithNegative, type SizeType } from '../../types.mjs';
|
|
3
4
|
|
|
@@ -36,7 +37,7 @@ export function sliceClamped<E>(
|
|
|
36
37
|
SizeType.ArgArrWithNegative,
|
|
37
38
|
SizeType.ArgArrWithNegative,
|
|
38
39
|
]
|
|
39
|
-
|
|
|
40
|
+
| FixedLengthTuple<2, SizeType.ArgArrWithNegative>
|
|
40
41
|
): readonly E[] | ((array: readonly E[]) => readonly E[]) {
|
|
41
42
|
switch (args.length) {
|
|
42
43
|
case 3:
|