Skip to content

Commit f3aabd8

Browse files
authored
ArrayTail: Fix behaviour with non-tuple arrays (#1175)
1 parent b34b1d8 commit f3aabd8

File tree

3 files changed

+40
-27
lines changed

3 files changed

+40
-27
lines changed

source/array-tail.d.ts

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import type {If} from './if.d.ts';
2-
import type {IsArrayReadonly} from './internal/index.d.ts';
2+
import type {IfNotAnyOrNever, IsArrayReadonly} from './internal/index.d.ts';
33
import type {UnknownArray} from './unknown-array.d.ts';
44

55
/**
@@ -24,15 +24,18 @@ add3(4);
2424
2525
@category Array
2626
*/
27-
export type ArrayTail<TArray extends UnknownArray> =
27+
export type ArrayTail<TArray extends UnknownArray> = IfNotAnyOrNever<TArray,
2828
TArray extends UnknownArray // For distributing `TArray`
2929
? _ArrayTail<TArray> extends infer Result
3030
? If<IsArrayReadonly<TArray>, Readonly<Result>, Result>
3131
: never // Should never happen
32-
: never; // Should never happen
32+
: never
33+
>;
3334

3435
type _ArrayTail<TArray extends UnknownArray> = TArray extends readonly [unknown?, ...infer Tail]
3536
? keyof TArray & `${number}` extends never
36-
? []
37+
? TArray extends readonly []
38+
? []
39+
: TArray // Happens when `TArray` is a non-tuple array (e.g., `string[]`) or has a leading rest element (e.g., `[...string[], number]`)
3740
: Tail
3841
: [];

source/merge-deep.d.ts

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,21 @@ import type {
88
UnknownArrayOrTuple,
99
} from './internal/index.d.ts';
1010
import type {NonEmptyTuple} from './non-empty-tuple.d.ts';
11-
import type {ArrayTail} from './array-tail.d.ts';
11+
import type {ArrayTail as _ArrayTail} from './array-tail.d.ts';
1212
import type {UnknownRecord} from './unknown-record.d.ts';
1313
import type {EnforceOptional} from './enforce-optional.d.ts';
1414
import type {SimplifyDeep} from './simplify-deep.d.ts';
1515
import type {UnknownArray} from './unknown-array.d.ts';
1616

17+
type Writable<TArray extends UnknownArray> = {-readonly [Key in keyof TArray]: TArray[Key]}; // TODO: Remove this
18+
19+
// Using the default `ArrayTail` type causes issues, refer https://github.com/sindresorhus/type-fest/pull/1175/files#r2134694728.
20+
type ArrayTail<TArray extends UnknownArray> = TArray extends unknown // For distributing `TArray`
21+
? keyof TArray & `${number}` extends never
22+
? []
23+
: Writable<_ArrayTail<TArray>>
24+
: never; // Should never happen
25+
1726
type SimplifyDeepExcludeArray<T> = SimplifyDeep<T, UnknownArray>;
1827

1928
/**
@@ -86,7 +95,7 @@ type OmitRestTypeHelper<
8695
Tail extends UnknownArrayOrTuple,
8796
Type extends UnknownArrayOrTuple,
8897
Result extends UnknownArrayOrTuple = [],
89-
> = Tail extends readonly []
98+
> = Tail extends []
9099
? Result
91100
: OmitRestType<Tail, [...Result, FirstArrayElement<Type>]>;
92101

test-d/array-tail.ts

Lines changed: 22 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -11,22 +11,14 @@ expectType<readonly []>(getArrayTail([] as const));
1111
expectType<readonly []>(getArrayTail(['a'] as const));
1212
expectType<readonly ['b', 'c']>(getArrayTail(['a', 'b', 'c'] as const));
1313

14-
// Optional elements tests
15-
expectType<readonly [undefined, 'c']>(getArrayTail(['a', undefined, 'c'] as const));
16-
17-
// Mixed optional/required
18-
type MixedArray = [string, undefined?, number?];
19-
expectType<[undefined?, number?]>(getArrayTail(['hello'] as MixedArray));
20-
21-
// Optional numbers
22-
expectType<readonly [undefined, 3]>(getArrayTail([1, undefined, 3] as const));
23-
24-
// Complex mixed case
25-
type ComplexArray = [string, boolean, number?, string?];
26-
expectType<[boolean, number?, string?]>(getArrayTail(['test', false] as ComplexArray));
14+
// Optional elements
15+
expectType<readonly [undefined, 'c'?]>(getArrayTail(['a', undefined, 'c'] as readonly ['a', undefined, 'c'?]));
16+
expectType<[undefined?, number?]>(getArrayTail(['hello'] as [string, undefined?, number?]));
17+
expectType<readonly [undefined?, 3?]>(getArrayTail([1, undefined, 3] as readonly [1, undefined?, 3?]));
18+
expectType<[boolean, number?, string?]>(getArrayTail(['test', false] as [string, boolean, number?, string?]));
2719

2820
// All optional elements
29-
expectType<['b'?]>([] as ArrayTail<['a'?, 'b'?]>);
21+
expectType<['b'?]>({} as ArrayTail<['a'?, 'b'?]>);
3022
expectType<readonly [number?]>({} as ArrayTail<readonly [string?, number?]>);
3123

3224
// Rest element
@@ -36,14 +28,23 @@ expectType<readonly [number, boolean?, ...string[]]>({} as ArrayTail<readonly [s
3628
// expectType<readonly [...string[], string, number]>({} as ArrayTail<readonly [...string[], string, number]>); // Rest & Required
3729
expectType<readonly [number, ...string[], boolean, bigint]>({} as ArrayTail<readonly [string, number, ...string[], boolean, bigint]>); // Required, Rest & Required
3830

31+
// Labelled tuples
32+
expectType<[y: string]>({} as ArrayTail<[x: number, y: string]>);
33+
expectType<[bar: string, ...rest: boolean[]]>({} as ArrayTail<[foo: number, bar: string, ...rest: boolean[]]>);
34+
expectType<[...rest: boolean[], foo: number, bar: string]>({} as ArrayTail<[...rest: boolean[], foo: number, bar: string]>);
35+
3936
// Union of tuples
40-
expectType<[] | ['b']>([] as ArrayTail<[] | ['a', 'b']>);
41-
expectType<readonly ['y'?] | ['b', ...string[]] | readonly []>([] as ArrayTail<readonly ['x'?, 'y'?] | ['a', 'b', ...string[]] | readonly string[]>);
37+
expectType<[] | ['b']>({} as ArrayTail<[] | ['a', 'b']>);
38+
expectType<readonly ['y'?] | ['b', ...string[]] | readonly string[]>({} as ArrayTail<readonly ['x'?, 'y'?] | ['a', 'b', ...string[]] | readonly string[]>);
4239
expectType<[number] | readonly [boolean, string?]>({} as ArrayTail<[string, number] | readonly [number, boolean, string?]>);
43-
expectType<readonly [number] | readonly []>({} as ArrayTail<readonly [string, number] | readonly string[]>);
40+
expectType<readonly [number] | readonly string[]>({} as ArrayTail<readonly [string, number] | readonly string[]>);
4441

4542
// Non tuple arrays
46-
expectType<[]>({} as ArrayTail<string[]>);
47-
expectType<readonly []>({} as ArrayTail<readonly string[]>);
48-
expectType<[]>({} as ArrayTail<never[]>);
49-
expectType<[]>({} as ArrayTail<any[]>);
43+
expectType<string[]>({} as ArrayTail<string[]>);
44+
expectType<readonly string[]>({} as ArrayTail<readonly string[]>);
45+
expectType<never[]>({} as ArrayTail<never[]>);
46+
expectType<any[]>({} as ArrayTail<any[]>);
47+
48+
// Boundary cases
49+
expectType<never>({} as ArrayTail<never>);
50+
expectType<any>({} as ArrayTail<any>);

0 commit comments

Comments
 (0)