ArraySlice / StringSlice: Fix behavior with unions (#1291)

This commit is contained in:
Som Shekhar Mukherjee 2025-11-03 16:33:40 +05:30 committed by GitHub
parent c300548e0f
commit 133258b8eb
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 102 additions and 7 deletions

View File

@ -7,6 +7,7 @@ import type {Not, TupleMin} from './internal/index.d.ts';
import type {IsEqual} from './is-equal.d.ts';
import type {And} from './and.d.ts';
import type {ArraySplice} from './array-splice.d.ts';
import type {IsNever} from './is-never.d.ts';
/**
Returns an array slice of a given range, just like `Array#slice()`.
@ -60,13 +61,33 @@ export type ArraySlice<
Start extends number = never,
End extends number = never,
> = Array_ extends unknown // To distributive type
? And<IsEqual<Start, never>, IsEqual<End, never>> extends true
? Array_
: number extends Array_['length']
? VariableLengthArraySliceHelper<Array_, Start, End>
: ArraySliceHelper<Array_, IsEqual<Start, never> extends true ? 0 : Start, IsEqual<End, never> extends true ? Array_['length'] : End>
? IsNever<Start> extends true
? IsNever<End> extends true
? _ArraySlice<Array_, Start, End>
: End extends unknown // To distribute `End`
? _ArraySlice<Array_, Start, End>
: never // Never happens
: IsNever<End> extends true
? Start extends unknown // To distribute `Start`
? _ArraySlice<Array_, Start, End>
: never // Never happens
: Start extends unknown // To distribute `Start`
? End extends unknown // To distribute `End`
? _ArraySlice<Array_, Start, End>
: never // Never happens
: never // Never happens
: never; // Never happens
type _ArraySlice<
Array_ extends readonly unknown[],
Start extends number = 0,
End extends number = Array_['length'],
> = And<IsEqual<Start, never>, IsEqual<End, never>> extends true
? Array_
: number extends Array_['length']
? VariableLengthArraySliceHelper<Array_, Start, End>
: ArraySliceHelper<Array_, IsEqual<Start, never> extends true ? 0 : Start, IsEqual<End, never> extends true ? Array_['length'] : End>;
type VariableLengthArraySliceHelper<
Array_ extends readonly unknown[],
Start extends number,

View File

@ -28,8 +28,8 @@ StringSlice<'abcde', -2, -1>;
*/
export type StringSlice<
S extends string,
Start extends number = 0,
End extends number = StringToArray<S>['length'],
Start extends number = never,
End extends number = never,
> = string extends S
? string
: ArraySlice<StringToArray<S>, Start, End> extends infer R extends readonly string[]

View File

@ -38,3 +38,40 @@ expectType<ArraySlice<[1, 2, 3, ...string[], 4, 5], 0>>([1, 2, 3, ...(null! as s
expectType<ArraySlice<[1, 2, 3, ...string[], 4, 5], 1>>([2, 3, ...(null! as string[]), 4, 5]);
expectType<ArraySlice<[1, 2, 3, ...string[], 4, 5], 3>>([...(null! as string[]), 4, 5]);
expectType<ArraySlice<[1, 2, 3, ...string[], 4, 5], 10>>([...(null! as string[]), 4, 5]);
// Unions
// Array is union
expectType<ArraySlice<[0, 1, 2] | ['a', 'b', 'c', 'd'], 0>>({} as [0, 1, 2] | ['a', 'b', 'c', 'd']); // Positive start, no end
expectType<ArraySlice<[0, 1, 2] | ['a', 'b', 'c', 'd'], -2>>({} as [1, 2] | ['c', 'd']); // Negative start, no end
expectType<ArraySlice<[0, 1, 2] | ['a', 'b', 'c', 'd'], 0, 2>>({} as [0, 1] | ['a', 'b']); // Positive start, positive end
expectType<ArraySlice<[0, 1, 2] | ['a', 'b', 'c', 'd'], -2, -1>>({} as [1] | ['c']); // Negative start, negative end
expectType<ArraySlice<[0, 1, 2] | ['a', 'b', 'c', 'd'], -3, 2>>({} as [0, 1] | ['b']); // Negative start, positive end
expectType<ArraySlice<[0, 1, 2] | ['a', 'b', 'c', 'd'], 1, -1>>({} as [1] | ['b', 'c']); // Positive start, negative end
// Start is union
expectType<ArraySlice<[0, 1, 2, 3], 1 | -2>>({} as [1, 2, 3] | [2, 3]); // Positive/Negative start, no end
expectType<ArraySlice<[0, 1, 2, 3], 2 | -3, 3>>({} as [2] | [1, 2]); // Positive/Negative start, positive end
expectType<ArraySlice<[0, 1, 2, 3], 0 | -2, -1>>({} as [2] | [0, 1, 2]); // Positive/Negative start, negative end
// End is union
expectType<ArraySlice<[0, 1, 2, 3], 0, 1 | -2>>({} as [0] | [0, 1]); // Positive start, positive/negative end
expectType<ArraySlice<[0, 1, 2, 3], -2, 2 | -1>>({} as [] | [2]); // Negative start, positive/negative end
// Array and start are unions
expectType<ArraySlice<[0, 1, 2] | ['a', 'b', 'c', 'd'], 1 | -1>>({} as [1, 2] | [2] | ['b', 'c', 'd'] | ['d']); // Positive/Negative start, no end
expectType<ArraySlice<[0, 1, 2] | ['a', 'b', 'c', 'd'], 1 | -2, 2>>({} as [1] | ['b'] | []); // Positive/Negative start, positive end
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
// Array and end are unions
expectType<ArraySlice<[0, 1, 2] | ['a', 'b', 'c', 'd'], 2, 3 | -1>>({} as [2] | [] | ['c']); // Positive start, positive/negative end
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
// Start and end are unions
expectType<ArraySlice<[0, 1, 2, 3], -5 | 0 | 1, -2 | 0 | 3>>( // Positive/Negative start, positive/negative end
{} as [0, 1] | [0, 1, 2] | [] | [1] | [1, 2],
);
// Array, start and end are unions
expectType<ArraySlice<[0, 1, 2] | ['a', 'b', 'c', 'd'], 1 | -4, 4 | -1>>( // Positive/Negative start, positive/negative end
{} as [1] | [1, 2] | [0, 1] | [0, 1, 2] | ['a', 'b', 'c', 'd'] | ['a', 'b', 'c'] | ['b', 'c'] | ['b', 'c', 'd'],
);

View File

@ -15,3 +15,40 @@ expectType<StringSlice<'abcde', 100, 1>>('');
expectType<StringSlice<string>>(null! as string);
expectType<StringSlice<string, 1>>(null! as string);
expectType<StringSlice<string, 1, 2>>(null! as string);
// Unions
// String is union
expectType<StringSlice<'012' | 'abcd', 0>>({} as '012' | 'abcd'); // Positive start, no end
expectType<StringSlice<'012' | 'abcd', -2>>({} as '12' | 'cd'); // Negative start, no end
expectType<StringSlice<'012' | 'abcd', 0, 2>>({} as '01' | 'ab'); // Positive start, positive end
expectType<StringSlice<'012' | 'abcd', -2, -1>>({} as '1' | 'c'); // Negative start, negative end
expectType<StringSlice<'012' | 'abcd', -3, 2>>({} as '01' | 'b'); // Negative start, positive end
expectType<StringSlice<'012' | 'abcd', 1, -1>>({} as '1' | 'bc'); // Positive start, negative end
// Start is union
expectType<StringSlice<'0123', 1 | -2>>({} as '123' | '23'); // Positive/Negative start, no end
expectType<StringSlice<'0123', 2 | -3, 3>>({} as '2' | '12'); // Positive/Negative start, positive end
expectType<StringSlice<'0123', 0 | -2, -1>>({} as '2' | '012'); // Positive/Negative start, negative end
// End is union
expectType<StringSlice<'0123', 0, 1 | -2>>({} as '0' | '01'); // Positive start, positive/negative end
expectType<StringSlice<'0123', -2, 2 | -1>>({} as '' | '2'); // Negative start, positive/negative end
// Array and start are unions
expectType<StringSlice<'012' | 'abcd', 1 | -1>>({} as '12' | '2' | 'bcd' | 'd'); // Positive/Negative start, no end
expectType<StringSlice<'012' | 'abcd', 1 | -2, 2>>({} as '1' | 'b' | ''); // Positive/Negative start, positive end
expectType<StringSlice<'012' | 'abcd', 0 | -2, -1>>({} as '01' | '1' | 'abc' | 'c'); // Positive/Negative start, negative end
// Array and end are unions
expectType<StringSlice<'012' | 'abcd', 2, 3 | -1>>({} as '2' | '' | 'c'); // Positive start, positive/negative end
expectType<StringSlice<'012' | 'abcd', -3, 3 | -2>>({} as '012' | '0' | 'bc' | 'b'); // Negative start, positive/negative end
// Start and end are unions
expectType<StringSlice<'0123', -5 | 0 | 1, -2 | 0 | 3>>( // Positive/Negative start, positive/negative end
{} as '01' | '012' | '' | '1' | '12',
);
// Array, start and end are unions
expectType<StringSlice<'012' | 'abcd', 1 | -4, 4 | -1>>( // Positive/Negative start, positive/negative end
{} as '1' | '12' | '01' | '012' | 'abcd' | 'abc' | 'bc' | 'bcd',
);