ArrayTail: Fix behaviour with non-tuple arrays (#1175)

This commit is contained in:
Som Shekhar Mukherjee 2025-06-09 04:51:49 +05:30 committed by GitHub
parent b34b1d865b
commit f3aabd8a5d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 40 additions and 27 deletions

View File

@ -1,5 +1,5 @@
import type {If} from './if.d.ts';
import type {IsArrayReadonly} from './internal/index.d.ts';
import type {IfNotAnyOrNever, IsArrayReadonly} from './internal/index.d.ts';
import type {UnknownArray} from './unknown-array.d.ts';
/**
@ -24,15 +24,18 @@ add3(4);
@category Array
*/
export type ArrayTail<TArray extends UnknownArray> =
export type ArrayTail<TArray extends UnknownArray> = IfNotAnyOrNever<TArray,
TArray extends UnknownArray // For distributing `TArray`
? _ArrayTail<TArray> extends infer Result
? If<IsArrayReadonly<TArray>, Readonly<Result>, Result>
: never // Should never happen
: never; // Should never happen
: never
>;
type _ArrayTail<TArray extends UnknownArray> = TArray extends readonly [unknown?, ...infer Tail]
? keyof TArray & `${number}` extends never
? []
? TArray extends readonly []
? []
: TArray // Happens when `TArray` is a non-tuple array (e.g., `string[]`) or has a leading rest element (e.g., `[...string[], number]`)
: Tail
: [];

View File

@ -8,12 +8,21 @@ import type {
UnknownArrayOrTuple,
} from './internal/index.d.ts';
import type {NonEmptyTuple} from './non-empty-tuple.d.ts';
import type {ArrayTail} from './array-tail.d.ts';
import type {ArrayTail as _ArrayTail} from './array-tail.d.ts';
import type {UnknownRecord} from './unknown-record.d.ts';
import type {EnforceOptional} from './enforce-optional.d.ts';
import type {SimplifyDeep} from './simplify-deep.d.ts';
import type {UnknownArray} from './unknown-array.d.ts';
type Writable<TArray extends UnknownArray> = {-readonly [Key in keyof TArray]: TArray[Key]}; // TODO: Remove this
// Using the default `ArrayTail` type causes issues, refer https://github.com/sindresorhus/type-fest/pull/1175/files#r2134694728.
type ArrayTail<TArray extends UnknownArray> = TArray extends unknown // For distributing `TArray`
? keyof TArray & `${number}` extends never
? []
: Writable<_ArrayTail<TArray>>
: never; // Should never happen
type SimplifyDeepExcludeArray<T> = SimplifyDeep<T, UnknownArray>;
/**
@ -86,7 +95,7 @@ type OmitRestTypeHelper<
Tail extends UnknownArrayOrTuple,
Type extends UnknownArrayOrTuple,
Result extends UnknownArrayOrTuple = [],
> = Tail extends readonly []
> = Tail extends []
? Result
: OmitRestType<Tail, [...Result, FirstArrayElement<Type>]>;

View File

@ -11,22 +11,14 @@ expectType<readonly []>(getArrayTail([] as const));
expectType<readonly []>(getArrayTail(['a'] as const));
expectType<readonly ['b', 'c']>(getArrayTail(['a', 'b', 'c'] as const));
// Optional elements tests
expectType<readonly [undefined, 'c']>(getArrayTail(['a', undefined, 'c'] as const));
// Mixed optional/required
type MixedArray = [string, undefined?, number?];
expectType<[undefined?, number?]>(getArrayTail(['hello'] as MixedArray));
// Optional numbers
expectType<readonly [undefined, 3]>(getArrayTail([1, undefined, 3] as const));
// Complex mixed case
type ComplexArray = [string, boolean, number?, string?];
expectType<[boolean, number?, string?]>(getArrayTail(['test', false] as ComplexArray));
// Optional elements
expectType<readonly [undefined, 'c'?]>(getArrayTail(['a', undefined, 'c'] as readonly ['a', undefined, 'c'?]));
expectType<[undefined?, number?]>(getArrayTail(['hello'] as [string, undefined?, number?]));
expectType<readonly [undefined?, 3?]>(getArrayTail([1, undefined, 3] as readonly [1, undefined?, 3?]));
expectType<[boolean, number?, string?]>(getArrayTail(['test', false] as [string, boolean, number?, string?]));
// All optional elements
expectType<['b'?]>([] as ArrayTail<['a'?, 'b'?]>);
expectType<['b'?]>({} as ArrayTail<['a'?, 'b'?]>);
expectType<readonly [number?]>({} as ArrayTail<readonly [string?, number?]>);
// Rest element
@ -36,14 +28,23 @@ expectType<readonly [number, boolean?, ...string[]]>({} as ArrayTail<readonly [s
// expectType<readonly [...string[], string, number]>({} as ArrayTail<readonly [...string[], string, number]>); // Rest & Required
expectType<readonly [number, ...string[], boolean, bigint]>({} as ArrayTail<readonly [string, number, ...string[], boolean, bigint]>); // Required, Rest & Required
// Labelled tuples
expectType<[y: string]>({} as ArrayTail<[x: number, y: string]>);
expectType<[bar: string, ...rest: boolean[]]>({} as ArrayTail<[foo: number, bar: string, ...rest: boolean[]]>);
expectType<[...rest: boolean[], foo: number, bar: string]>({} as ArrayTail<[...rest: boolean[], foo: number, bar: string]>);
// Union of tuples
expectType<[] | ['b']>([] as ArrayTail<[] | ['a', 'b']>);
expectType<readonly ['y'?] | ['b', ...string[]] | readonly []>([] as ArrayTail<readonly ['x'?, 'y'?] | ['a', 'b', ...string[]] | readonly string[]>);
expectType<[] | ['b']>({} as ArrayTail<[] | ['a', 'b']>);
expectType<readonly ['y'?] | ['b', ...string[]] | readonly string[]>({} as ArrayTail<readonly ['x'?, 'y'?] | ['a', 'b', ...string[]] | readonly string[]>);
expectType<[number] | readonly [boolean, string?]>({} as ArrayTail<[string, number] | readonly [number, boolean, string?]>);
expectType<readonly [number] | readonly []>({} as ArrayTail<readonly [string, number] | readonly string[]>);
expectType<readonly [number] | readonly string[]>({} as ArrayTail<readonly [string, number] | readonly string[]>);
// Non tuple arrays
expectType<[]>({} as ArrayTail<string[]>);
expectType<readonly []>({} as ArrayTail<readonly string[]>);
expectType<[]>({} as ArrayTail<never[]>);
expectType<[]>({} as ArrayTail<any[]>);
expectType<string[]>({} as ArrayTail<string[]>);
expectType<readonly string[]>({} as ArrayTail<readonly string[]>);
expectType<never[]>({} as ArrayTail<never[]>);
expectType<any[]>({} as ArrayTail<any[]>);
// Boundary cases
expectType<never>({} as ArrayTail<never>);
expectType<any>({} as ArrayTail<any>);