Skip to content

Commit 133258b

Browse files
authored
ArraySlice / StringSlice: Fix behavior with unions (#1291)
1 parent c300548 commit 133258b

File tree

4 files changed

+102
-7
lines changed

4 files changed

+102
-7
lines changed

source/array-slice.d.ts

Lines changed: 26 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import type {Not, TupleMin} from './internal/index.d.ts';
77
import type {IsEqual} from './is-equal.d.ts';
88
import type {And} from './and.d.ts';
99
import type {ArraySplice} from './array-splice.d.ts';
10+
import type {IsNever} from './is-never.d.ts';
1011

1112
/**
1213
Returns an array slice of a given range, just like `Array#slice()`.
@@ -60,13 +61,33 @@ export type ArraySlice<
6061
Start extends number = never,
6162
End extends number = never,
6263
> = Array_ extends unknown // To distributive type
63-
? And<IsEqual<Start, never>, IsEqual<End, never>> extends true
64-
? Array_
65-
: number extends Array_['length']
66-
? VariableLengthArraySliceHelper<Array_, Start, End>
67-
: ArraySliceHelper<Array_, IsEqual<Start, never> extends true ? 0 : Start, IsEqual<End, never> extends true ? Array_['length'] : End>
64+
? IsNever<Start> extends true
65+
? IsNever<End> extends true
66+
? _ArraySlice<Array_, Start, End>
67+
: End extends unknown // To distribute `End`
68+
? _ArraySlice<Array_, Start, End>
69+
: never // Never happens
70+
: IsNever<End> extends true
71+
? Start extends unknown // To distribute `Start`
72+
? _ArraySlice<Array_, Start, End>
73+
: never // Never happens
74+
: Start extends unknown // To distribute `Start`
75+
? End extends unknown // To distribute `End`
76+
? _ArraySlice<Array_, Start, End>
77+
: never // Never happens
78+
: never // Never happens
6879
: never; // Never happens
6980

81+
type _ArraySlice<
82+
Array_ extends readonly unknown[],
83+
Start extends number = 0,
84+
End extends number = Array_['length'],
85+
> = And<IsEqual<Start, never>, IsEqual<End, never>> extends true
86+
? Array_
87+
: number extends Array_['length']
88+
? VariableLengthArraySliceHelper<Array_, Start, End>
89+
: ArraySliceHelper<Array_, IsEqual<Start, never> extends true ? 0 : Start, IsEqual<End, never> extends true ? Array_['length'] : End>;
90+
7091
type VariableLengthArraySliceHelper<
7192
Array_ extends readonly unknown[],
7293
Start extends number,

source/string-slice.d.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,8 @@ StringSlice<'abcde', -2, -1>;
2828
*/
2929
export type StringSlice<
3030
S extends string,
31-
Start extends number = 0,
32-
End extends number = StringToArray<S>['length'],
31+
Start extends number = never,
32+
End extends number = never,
3333
> = string extends S
3434
? string
3535
: ArraySlice<StringToArray<S>, Start, End> extends infer R extends readonly string[]

test-d/array-slice.ts

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,3 +38,40 @@ expectType<ArraySlice<[1, 2, 3, ...string[], 4, 5], 0>>([1, 2, 3, ...(null! as s
3838
expectType<ArraySlice<[1, 2, 3, ...string[], 4, 5], 1>>([2, 3, ...(null! as string[]), 4, 5]);
3939
expectType<ArraySlice<[1, 2, 3, ...string[], 4, 5], 3>>([...(null! as string[]), 4, 5]);
4040
expectType<ArraySlice<[1, 2, 3, ...string[], 4, 5], 10>>([...(null! as string[]), 4, 5]);
41+
42+
// Unions
43+
// Array is union
44+
expectType<ArraySlice<[0, 1, 2] | ['a', 'b', 'c', 'd'], 0>>({} as [0, 1, 2] | ['a', 'b', 'c', 'd']); // Positive start, no end
45+
expectType<ArraySlice<[0, 1, 2] | ['a', 'b', 'c', 'd'], -2>>({} as [1, 2] | ['c', 'd']); // Negative start, no end
46+
expectType<ArraySlice<[0, 1, 2] | ['a', 'b', 'c', 'd'], 0, 2>>({} as [0, 1] | ['a', 'b']); // Positive start, positive end
47+
expectType<ArraySlice<[0, 1, 2] | ['a', 'b', 'c', 'd'], -2, -1>>({} as [1] | ['c']); // Negative start, negative end
48+
expectType<ArraySlice<[0, 1, 2] | ['a', 'b', 'c', 'd'], -3, 2>>({} as [0, 1] | ['b']); // Negative start, positive end
49+
expectType<ArraySlice<[0, 1, 2] | ['a', 'b', 'c', 'd'], 1, -1>>({} as [1] | ['b', 'c']); // Positive start, negative end
50+
51+
// Start is union
52+
expectType<ArraySlice<[0, 1, 2, 3], 1 | -2>>({} as [1, 2, 3] | [2, 3]); // Positive/Negative start, no end
53+
expectType<ArraySlice<[0, 1, 2, 3], 2 | -3, 3>>({} as [2] | [1, 2]); // Positive/Negative start, positive end
54+
expectType<ArraySlice<[0, 1, 2, 3], 0 | -2, -1>>({} as [2] | [0, 1, 2]); // Positive/Negative start, negative end
55+
56+
// End is union
57+
expectType<ArraySlice<[0, 1, 2, 3], 0, 1 | -2>>({} as [0] | [0, 1]); // Positive start, positive/negative end
58+
expectType<ArraySlice<[0, 1, 2, 3], -2, 2 | -1>>({} as [] | [2]); // Negative start, positive/negative end
59+
60+
// Array and start are unions
61+
expectType<ArraySlice<[0, 1, 2] | ['a', 'b', 'c', 'd'], 1 | -1>>({} as [1, 2] | [2] | ['b', 'c', 'd'] | ['d']); // Positive/Negative start, no end
62+
expectType<ArraySlice<[0, 1, 2] | ['a', 'b', 'c', 'd'], 1 | -2, 2>>({} as [1] | ['b'] | []); // Positive/Negative start, positive end
63+
expectType<ArraySlice<[0, 1, 2] | ['a', 'b', 'c', 'd'], 0 | -2, -1>>({} as [0, 1] | [1] | ['a', 'b', 'c'] | ['c']); // Positive/Negative start, negative end
64+
65+
// Array and end are unions
66+
expectType<ArraySlice<[0, 1, 2] | ['a', 'b', 'c', 'd'], 2, 3 | -1>>({} as [2] | [] | ['c']); // Positive start, positive/negative end
67+
expectType<ArraySlice<[0, 1, 2] | ['a', 'b', 'c', 'd'], -3, 3 | -2>>({} as [0, 1, 2] | [0] | ['b', 'c'] | ['b']); // Negative start, positive/negative end
68+
69+
// Start and end are unions
70+
expectType<ArraySlice<[0, 1, 2, 3], -5 | 0 | 1, -2 | 0 | 3>>( // Positive/Negative start, positive/negative end
71+
{} as [0, 1] | [0, 1, 2] | [] | [1] | [1, 2],
72+
);
73+
74+
// Array, start and end are unions
75+
expectType<ArraySlice<[0, 1, 2] | ['a', 'b', 'c', 'd'], 1 | -4, 4 | -1>>( // Positive/Negative start, positive/negative end
76+
{} as [1] | [1, 2] | [0, 1] | [0, 1, 2] | ['a', 'b', 'c', 'd'] | ['a', 'b', 'c'] | ['b', 'c'] | ['b', 'c', 'd'],
77+
);

test-d/string-slice.ts

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,3 +15,40 @@ expectType<StringSlice<'abcde', 100, 1>>('');
1515
expectType<StringSlice<string>>(null! as string);
1616
expectType<StringSlice<string, 1>>(null! as string);
1717
expectType<StringSlice<string, 1, 2>>(null! as string);
18+
19+
// Unions
20+
// String is union
21+
expectType<StringSlice<'012' | 'abcd', 0>>({} as '012' | 'abcd'); // Positive start, no end
22+
expectType<StringSlice<'012' | 'abcd', -2>>({} as '12' | 'cd'); // Negative start, no end
23+
expectType<StringSlice<'012' | 'abcd', 0, 2>>({} as '01' | 'ab'); // Positive start, positive end
24+
expectType<StringSlice<'012' | 'abcd', -2, -1>>({} as '1' | 'c'); // Negative start, negative end
25+
expectType<StringSlice<'012' | 'abcd', -3, 2>>({} as '01' | 'b'); // Negative start, positive end
26+
expectType<StringSlice<'012' | 'abcd', 1, -1>>({} as '1' | 'bc'); // Positive start, negative end
27+
28+
// Start is union
29+
expectType<StringSlice<'0123', 1 | -2>>({} as '123' | '23'); // Positive/Negative start, no end
30+
expectType<StringSlice<'0123', 2 | -3, 3>>({} as '2' | '12'); // Positive/Negative start, positive end
31+
expectType<StringSlice<'0123', 0 | -2, -1>>({} as '2' | '012'); // Positive/Negative start, negative end
32+
33+
// End is union
34+
expectType<StringSlice<'0123', 0, 1 | -2>>({} as '0' | '01'); // Positive start, positive/negative end
35+
expectType<StringSlice<'0123', -2, 2 | -1>>({} as '' | '2'); // Negative start, positive/negative end
36+
37+
// Array and start are unions
38+
expectType<StringSlice<'012' | 'abcd', 1 | -1>>({} as '12' | '2' | 'bcd' | 'd'); // Positive/Negative start, no end
39+
expectType<StringSlice<'012' | 'abcd', 1 | -2, 2>>({} as '1' | 'b' | ''); // Positive/Negative start, positive end
40+
expectType<StringSlice<'012' | 'abcd', 0 | -2, -1>>({} as '01' | '1' | 'abc' | 'c'); // Positive/Negative start, negative end
41+
42+
// Array and end are unions
43+
expectType<StringSlice<'012' | 'abcd', 2, 3 | -1>>({} as '2' | '' | 'c'); // Positive start, positive/negative end
44+
expectType<StringSlice<'012' | 'abcd', -3, 3 | -2>>({} as '012' | '0' | 'bc' | 'b'); // Negative start, positive/negative end
45+
46+
// Start and end are unions
47+
expectType<StringSlice<'0123', -5 | 0 | 1, -2 | 0 | 3>>( // Positive/Negative start, positive/negative end
48+
{} as '01' | '012' | '' | '1' | '12',
49+
);
50+
51+
// Array, start and end are unions
52+
expectType<StringSlice<'012' | 'abcd', 1 | -4, 4 | -1>>( // Positive/Negative start, positive/negative end
53+
{} as '1' | '12' | '01' | '012' | 'abcd' | 'abc' | 'bc' | 'bcd',
54+
);

0 commit comments

Comments
 (0)