From 56e83c14f85ce57dfdc46c8c028aa6c6b0fc8587 Mon Sep 17 00:00:00 2001 From: emiyaaaaa Date: Tue, 25 Mar 2025 10:39:27 +0800 Subject: [PATCH 1/7] Add ArrayFlat type --- index.d.ts | 1 + readme.md | 1 + source/array-flat.d.ts | 56 +++++++++++++++++++++++++++++++++++++++ test-d/array-flat.ts | 60 ++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 118 insertions(+) create mode 100644 source/array-flat.d.ts create mode 100644 test-d/array-flat.ts diff --git a/index.d.ts b/index.d.ts index c8c021f36..ad70c4179 100644 --- a/index.d.ts +++ b/index.d.ts @@ -129,6 +129,7 @@ export type {ArrayValues} from './source/array-values'; export type {ArraySlice} from './source/array-slice'; export type {ArraySplice} from './source/array-splice'; export type {ArrayTail} from './source/array-tail'; +export type {ArrayFlat} from './source/array-flat'; export type {SetFieldType} from './source/set-field-type'; export type {Paths} from './source/paths'; export type {AllUnionFields} from './source/all-union-fields'; diff --git a/readme.md b/readme.md index e74c9572e..ca68a29ff 100644 --- a/readme.md +++ b/readme.md @@ -285,6 +285,7 @@ type ShouldBeNever = IfAny<'not any', 'not never', 'never'>; - [`Includes`](source/includes.d.ts) - Returns a boolean for whether the given array includes the given item. - [`Join`](source/join.d.ts) - Join an array of strings and/or numbers using the given string as a delimiter. - [`ArraySlice`](source/array-slice.d.ts) - Returns an array slice of a given range, just like `Array#slice()`. +- [`ArrayFlat`](source/array-flat.d.ts) - Creates a new array type by flattening an array to a specified depth, just like `Array#flat()`. - [`LastArrayElement`](source/last-array-element.d.ts) - Extracts the type of the last element of an array. - [`FixedLengthArray`](source/fixed-length-array.d.ts) - Create a type that represents an array of the given type and length. - [`MultidimensionalArray`](source/multidimensional-array.d.ts) - Create a type that represents a multidimensional array of the given type and dimensions. diff --git a/source/array-flat.d.ts b/source/array-flat.d.ts new file mode 100644 index 000000000..760c2339b --- /dev/null +++ b/source/array-flat.d.ts @@ -0,0 +1,56 @@ +import type {IsAny} from './is-any'; +import type {IsNever} from './is-never'; +import type {Or} from './or'; +import type {Subtract} from './subtract'; +import type {UnknownArray} from './unknown-array'; + +/** +Creates a new array type by flattening an array to a specified depth. + +Use-case: Flatten an array type to a specified depth. + +Like [`Array#flat()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/flat) but for types. + +@example +``` +import type {ArrayFlat, PositiveInfinity} from 'type-fest'; + +type FlatArr0 = ArrayFlat<[[0, 1], [2, 3], [4, 5]]>; +//=> type FlatArr0 = [0, 1, 2, 3, 4, 5]; + +// Flatten to depth +type Arr1 = [[0, [1, [2, [3, [4, [5]]]]]]]; +type FlatArr1 = ArrayFlat; +//=> type FlatArr1 = [0, [1, [2, [3, [4, [5]]]]]]; + +type FlatArr2 = ArrayFlat; +//=> type FlatArr2 = [0, 1, 2, [3, [4, [5]]]]; + +// Flatten to depth Infinity +type FlatArr3 = ArrayFlat; +//=> type FlatArr3 = [0, 1, 2, 3, 4, 5]; +``` + +@category Array +*/ +export type ArrayFlat = InternalArrayFlat; + +// Internal implementation +type InternalArrayFlat = +T extends UnknownArray + ? T['length'] extends 0 + ? [...Result, ...T] + : Depth extends 0 + ? [...Result, ...T] + : T extends readonly [infer ArrayItem, ...infer Last] + ? ArrayItem extends UnknownArray + ? InternalArrayFlat>]> + : InternalArrayFlat + : T extends Array + ? Or, IsNever> extends true + ? [...Result, ...ArrayItem2[]] // Return never/any[] when input is never/any[] + : ArrayItem2 extends UnknownArray + ? InternalArrayFlat, Result> + : [...Result, ...ArrayItem2[]] + : [...Result, ...T] + : T; diff --git a/test-d/array-flat.ts b/test-d/array-flat.ts new file mode 100644 index 000000000..ef3342fc9 --- /dev/null +++ b/test-d/array-flat.ts @@ -0,0 +1,60 @@ +import {expectType} from 'tsd'; +import type {ArrayFlat, PositiveInfinity} from '../index'; + +// Basic flattening tests +expectType>([]); +expectType>([1, 2, 3]); +expectType>([1, 2, 3, 4]); +expectType>([1, 2, 3, 4]); +expectType>([1, 2, [3], 4]); + +// Test with explicit depth +// eslint-disable-next-line unicorn/prevent-abbreviations +type Arr = [[0, [1, [2, [3, [4, [5]]]]]]]; +expectType>(null! as Arr); +// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-arguments +expectType>([0, [1, [2, [3, [4, [5]]]]]]); +expectType>([0, 1, [2, [3, [4, [5]]]]]); +expectType>([0, 1, 2, [3, [4, [5]]]]); +expectType>([0, 1, 2, 3, [4, [5]]]); +expectType>([0, 1, 2, 3, 4, [5]]); +expectType>([0, 1, 2, 3, 4, 5]); +expectType>([0, 1, 2, 3, 4, 5]); +expectType>([0, 1, 2, 3, 4, 5]); + +// Test with Infinity depth +expectType>([1, 2, 3, 4]); +expectType>([1]); + +// Test with different element types +expectType>([null! as string, null! as number, null! as boolean]); +expectType>([null! as {a: number}, null! as {b: string}]); + +// Test with union types +expectType>([1, 2] as [1, 2] | [3, 4]); +expectType>([1, 2] as [1, 2] | [3, 4]); + +// Test with rest elements +expectType]>>([null! as number, null! as string]); +expectType]>>([1, 2, null! as string, null! as boolean]); +expectType], 4]>>([1, 2, ...(null! as Array<3>), 4]); + +// Test with mixed arrays and tuples +expectType>([1, ...(null! as number[]), 3]); +expectType>([null! as string, null! as number, ...(null! as boolean[])]); + +// Test with deeply nested structures +expectType>([1, 2, 3, 4, [5]]); + +// Test with readonly arrays +expectType>([1, 2, 3]); +expectType>([1, 2, 3, 4]); + +// Edge cases +expectType>>([]); +expectType>([]); +expectType>([]); +expectType>(null! as undefined[]); +expectType>(null! as any[]); +expectType>(null! as unknown[]); +expectType>(null! as never[]); From 904caec8ec490d9e8fe7ffabe8ffad9791337a88 Mon Sep 17 00:00:00 2001 From: emiyaaaaa Date: Thu, 3 Apr 2025 10:29:48 +0800 Subject: [PATCH 2/7] fix: support non-fixed array --- source/array-flat.d.ts | 77 ++++++++++++++++++++++++++++++++++-------- test-d/array-flat.ts | 53 +++++++++++++++++++++++++---- 2 files changed, 110 insertions(+), 20 deletions(-) diff --git a/source/array-flat.d.ts b/source/array-flat.d.ts index 760c2339b..f58b0ce16 100644 --- a/source/array-flat.d.ts +++ b/source/array-flat.d.ts @@ -4,6 +4,39 @@ import type {Or} from './or'; import type {Subtract} from './subtract'; import type {UnknownArray} from './unknown-array'; +/** + * Builds an array by repeating the given array items the specified number of times. + * + * @example + * ``` + * type FlatArr0 = BuildRepeatedArray<[number, string], 3>; + * //=> type FlatArr0 = [number, string, number, string, number, string]; + * ``` + */ +type BuildRepeatedArray = + N extends 0 + ? R + : BuildRepeatedArray, [...T, ...R]>; + +type ArrayFlatOptions = { + /** + * The number of times to repeat the array items when flattening an non-fixed length array. + * + * @example + * ``` + * type FlatArr0 = ArrayFlat, 1, { repeat: 3 }>; + * //=> type FlatArr0 = [number?, string?, number?, string?, number?, string?]; + * ``` + * + * @default 10 + */ + repeat: number; +}; + +type ArrayFlatDefaultOptions = { + repeat: 10; +}; + /** Creates a new array type by flattening an array to a specified depth. @@ -33,24 +66,40 @@ type FlatArr3 = ArrayFlat; @category Array */ -export type ArrayFlat = InternalArrayFlat; +export type ArrayFlat = +InternalArrayFlat; // Internal implementation -type InternalArrayFlat = +type InternalArrayFlat = T extends UnknownArray ? T['length'] extends 0 ? [...Result, ...T] : Depth extends 0 ? [...Result, ...T] - : T extends readonly [infer ArrayItem, ...infer Last] - ? ArrayItem extends UnknownArray - ? InternalArrayFlat>]> - : InternalArrayFlat - : T extends Array - ? Or, IsNever> extends true - ? [...Result, ...ArrayItem2[]] // Return never/any[] when input is never/any[] - : ArrayItem2 extends UnknownArray - ? InternalArrayFlat, Result> - : [...Result, ...ArrayItem2[]] - : [...Result, ...T] - : T; + : number extends T['length'] + ? [ + ...Result, + ...( + T[number] extends UnknownArray + ? BuildRepeatedArray, + Subtract, + Options + >, + Options['repeat'] + > + : T + ), + ] + : T extends readonly [infer ArrayItem, ...infer Last] + ? ArrayItem extends UnknownArray + ? InternalArrayFlat, Options>]> + : InternalArrayFlat + : T extends Array + ? Or, IsNever> extends true + ? [...Result, ...ArrayItem2[]] // Return never/any[] when input is never/any[] + : ArrayItem2 extends UnknownArray + ? InternalArrayFlat, Options, Result> + : [...Result, ...ArrayItem2[]] + : [...Result, ...T] + : []; diff --git a/test-d/array-flat.ts b/test-d/array-flat.ts index ef3342fc9..cb0868694 100644 --- a/test-d/array-flat.ts +++ b/test-d/array-flat.ts @@ -1,4 +1,4 @@ -import {expectType} from 'tsd'; +import {expectAssignable, expectType} from 'tsd'; import type {ArrayFlat, PositiveInfinity} from '../index'; // Basic flattening tests @@ -34,9 +34,7 @@ expectType>([null! as {a: number}, null! expectType>([1, 2] as [1, 2] | [3, 4]); expectType>([1, 2] as [1, 2] | [3, 4]); -// Test with rest elements -expectType]>>([null! as number, null! as string]); -expectType]>>([1, 2, null! as string, null! as boolean]); +expectType]>>(null! as [number?, string?, number?, string?, number?, string?, number?, string?, number?, string?, number?, string?, number?, string?, number?, string?, number?, string?, number?, string?]); expectType], 4]>>([1, 2, ...(null! as Array<3>), 4]); // Test with mixed arrays and tuples @@ -55,6 +53,49 @@ expectType>>([]); expectType>([]); expectType>([]); expectType>(null! as undefined[]); -expectType>(null! as any[]); expectType>(null! as unknown[]); -expectType>(null! as never[]); + +// Test specifically for non-tuple array handling (Array vs [T, T]) +type GenericNumberArrays = number[][]; +expectAssignable>([1, 2, 3, 4, 5]); + +// Test for deeply nested non-tuple arrays with specific depths +type DeepGenericArray = number[][][][]; +expectAssignable>([[1, 2], [3, 4]]); +expectAssignable>([1, 2, 3, 4]); + +// Test for mixed generic arrays and tuples +type MixedGenericAndTuple = Array<[number, string]>; +expectAssignable>([1, 'a', 2, 'b']); + +// Test for array with optional elements in nested structure +type NestedOptional = [number, Array<[string?, number?]>]; +expectAssignable>([1, 'a', 2, 'b', 3]); + +// Test for arrays with rest elements in nested structure +type NestedRest = [string, Array<[...number[]]>]; +expectAssignable>(['a', 1, 2, 3, 4, 5]); + +// Test for flattening arrays with union types in nested structures +type NestedUnion = Array>; +expectAssignable>(['a', 1, 'b', 2]); + +// Test for empty array in complex structure +type ComplexWithEmpty = [number, Array<[]>, string]; +expectAssignable>([1, 'string']); + +// Test for array with undefined/null elements +type ArrayWithNullish = [number, [undefined, null]]; +expectAssignable>([1, undefined, null]); + +// Test for array with mixed depth elements +type MixedDepthArray = [number, string[], [[boolean]]]; +expectAssignable>([1, 'a', 'b', [true]]); + +// Test for readonly nested arrays with different depths +type ReadonlyNestedComplex = readonly [number, ReadonlyArray]; +expectAssignable>([1, 'a', 'b', 'c']); + +// Test for recursive flattening with non-array elements +type RecursiveWithNonArray = [number, [string, {a: number}]]; +expectAssignable>([1, 'string', {a: 42}]); From 6ef28bac85fd8fe31b772d89e134cac7abc3dbea Mon Sep 17 00:00:00 2001 From: emiyaaaaa Date: Thu, 3 Apr 2025 10:39:21 +0800 Subject: [PATCH 3/7] fix: fix ts infinite error --- source/array-flat.d.ts | 16 ++++++++++------ type-fest@4.37.0 | 0 2 files changed, 10 insertions(+), 6 deletions(-) create mode 100644 type-fest@4.37.0 diff --git a/source/array-flat.d.ts b/source/array-flat.d.ts index f58b0ce16..04fe1f078 100644 --- a/source/array-flat.d.ts +++ b/source/array-flat.d.ts @@ -13,10 +13,10 @@ import type {UnknownArray} from './unknown-array'; * //=> type FlatArr0 = [number, string, number, string, number, string]; * ``` */ -type BuildRepeatedArray = +type BuildRepeatedArray = N extends 0 ? R - : BuildRepeatedArray, [...T, ...R]>; + : BuildRepeatedArray, [...R, ...CopyT]>; type ArrayFlatOptions = { /** @@ -77,20 +77,24 @@ T extends UnknownArray : Depth extends 0 ? [...Result, ...T] : number extends T['length'] + // Handle non-fixed length arrays ? [ ...Result, ...( T[number] extends UnknownArray - ? BuildRepeatedArray, Subtract, Options - >, - Options['repeat'] - > + > extends infer Item + ? Item extends UnknownArray + ? BuildRepeatedArray + : never // Never happens, just for fixed ts error TS2589: Type instantiation is excessively deep and possibly infinite. + : never // Never happens, just for fixed ts error TS2589: Type instantiation is excessively deep and possibly infinite. : T ), ] + // Handle fixed length arrays : T extends readonly [infer ArrayItem, ...infer Last] ? ArrayItem extends UnknownArray ? InternalArrayFlat, Options>]> diff --git a/type-fest@4.37.0 b/type-fest@4.37.0 new file mode 100644 index 000000000..e69de29bb From 9dab11862725c6ed04a17dfc7172a78eb43c3a4f Mon Sep 17 00:00:00 2001 From: emiyaaaaa Date: Mon, 7 Apr 2025 10:38:40 +0800 Subject: [PATCH 4/7] rm file --- type-fest@4.37.0 | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 type-fest@4.37.0 diff --git a/type-fest@4.37.0 b/type-fest@4.37.0 deleted file mode 100644 index e69de29bb..000000000 From 7d010974b744082571063de7ab964e8644ed9285 Mon Sep 17 00:00:00 2001 From: emiyaaaaa Date: Wed, 30 Apr 2025 17:20:55 +0800 Subject: [PATCH 5/7] enhance arrayflat for spread array --- source/array-flat.d.ts | 257 +++++++++++++++++++++++++++++-------- source/internal/array.d.ts | 70 ++++++++++ source/internal/type.d.ts | 5 + test-d/array-flat.ts | 55 +++++++- 4 files changed, 325 insertions(+), 62 deletions(-) diff --git a/source/array-flat.d.ts b/source/array-flat.d.ts index 04fe1f078..2b365acea 100644 --- a/source/array-flat.d.ts +++ b/source/array-flat.d.ts @@ -1,40 +1,41 @@ -import type {IsAny} from './is-any'; -import type {IsNever} from './is-never'; +import type {And} from './and'; +import type {ArrayLength, ExactOptionalPropertyTypesEnable, IsLeadingSpreadArray, IsTrailingSpreadArray, Not, OptionalPartOfArray, RequiredPartOfArray, StaticPartOfArray, StaticPartOfLeadingSpreadArray, VariablePartOfArray, VariablePartOfLeadingSpreadArray} from './internal'; +import type {IsEqual} from './is-equal'; import type {Or} from './or'; import type {Subtract} from './subtract'; import type {UnknownArray} from './unknown-array'; +declare const RepeatSymbol: unique symbol; + /** - * Builds an array by repeating the given array items the specified number of times. - * - * @example - * ``` - * type FlatArr0 = BuildRepeatedArray<[number, string], 3>; - * //=> type FlatArr0 = [number, string, number, string, number, string]; - * ``` - */ -type BuildRepeatedArray = - N extends 0 - ? R - : BuildRepeatedArray, [...R, ...CopyT]>; +Return true if the number is 0 +*/ +type IsZero = [T] extends [0] ? true : false; +/** + * Options for the `ArrayFlat` type. + */ type ArrayFlatOptions = { /** * The number of times to repeat the array items when flattening an non-fixed length array. * * @example * ``` - * type FlatArr0 = ArrayFlat, 1, { repeat: 3 }>; - * //=> type FlatArr0 = [number?, string?, number?, string?, number?, string?]; + * type FlatArr0 = ArrayFlat, 1, { maxRepeat: 3 }>; + * //=> type FlatArr0 = + * [] + * | [number, string] + * | [number, string, number, string] + * | [number, string, number, string, number, string]; * ``` * - * @default 10 + * @default 5 */ - repeat: number; + maxRepeat: number; }; -type ArrayFlatDefaultOptions = { - repeat: 10; +type DefaultArrayFlatOptions = { + maxRepeat: 5; }; /** @@ -66,44 +67,190 @@ type FlatArr3 = ArrayFlat; @category Array */ -export type ArrayFlat = -InternalArrayFlat; +export type ArrayFlat = +DoRepeatArrayItem, Options['maxRepeat']>; // Internal implementation -type InternalArrayFlat = +type InternalArrayFlat< + T, + Depth extends number = 1, + Options extends ArrayFlatOptions = DefaultArrayFlatOptions, + Result extends UnknownArray = [], +> = +T extends UnknownArray + ? Or>, IsZero> extends true + ? [...Result, ...T] + : number extends T['length'] + // Handle non-fixed length arrays + ? InternalNonFixedLengthArrayFlat + // Handle fixed length arrays + : InternalFixedLengthArrayFlat + : []; + +// Handle non-fixed length arrays +type InternalNonFixedLengthArrayFlat< + T, + Depth extends number = 1, + Options extends ArrayFlatOptions = DefaultArrayFlatOptions, + Result extends UnknownArray = [], +> = +T extends UnknownArray + ? Or>, IsZero> extends true + ? [...Result, ...T] + : IsTrailingSpreadArray extends true + // Handle trailing spread array + ? [StaticPartOfArray, VariablePartOfArray] extends [infer StaticPart, infer VariablePart] + ? InternalFixedLengthArrayFlat extends infer StaticPartResult extends UnknownArray + ? [StaticPartResult, InternalNonFixedLengthArrayFlat] extends + [infer Result1 extends UnknownArray, infer Result2 extends UnknownArray] + ? [...Result, ...Result1, ...Result2] + : never + : never + : never // Never happens + // Handle leading spread array + : IsLeadingSpreadArray extends true + ? [VariablePartOfLeadingSpreadArray, StaticPartOfLeadingSpreadArray] extends [infer VariablePart, infer StaticPart] + ? [InternalNonFixedLengthArrayFlat, InternalFixedLengthArrayFlat] extends + [infer Result1 extends UnknownArray, infer Result2 extends UnknownArray] + ? [...Result, ...Result1, ...Result2] + : never + : never // Never happens + // Handle non-spread and non-fixed-length array + : [ + T[number] extends UnknownArray + ? InternalArrayFlat, Options, Result> + : [T[number]], + ] extends [infer Item extends UnknownArray] + ? Item extends [{[RepeatSymbol]: unknown}] + ? Item + : [{[RepeatSymbol]: Item}] + : never // Never happens + : T; + +// Handle fixed length arrays +type InternalFixedLengthArrayFlat< + T, + Depth extends number = 1, + Options extends ArrayFlatOptions = DefaultArrayFlatOptions, + Result extends UnknownArray = [], +> = T extends UnknownArray - ? T['length'] extends 0 + ? Or>, IsZero> extends true ? [...Result, ...T] - : Depth extends 0 - ? [...Result, ...T] - : number extends T['length'] - // Handle non-fixed length arrays - ? [ - ...Result, - ...( - T[number] extends UnknownArray - ? InternalArrayFlat< - number extends T[number]['length'] ? T[number] : Partial, - Subtract, - Options - > extends infer Item - ? Item extends UnknownArray - ? BuildRepeatedArray + : T extends readonly [infer ArrayItem, ...infer Last] + ? [ArrayItem] extends [UnknownArray] + ? number extends ArrayLength + ? InternalNonFixedLengthArrayFlat extends infer Item extends UnknownArray + ? InternalFixedLengthArrayFlat + : never // Never happens, just for fixed ts error TS2589: Type instantiation is excessively deep and possibly infinite. + : [RequiredPartOfArray, OptionalPartOfArray] extends [infer RequiredPart, infer OptionalPart] + ? InternalArrayFlat< + Last, + Depth, + Options, + [InternalArrayFlat, Options>, (InternalArrayFlat, Options> | [])] extends + [infer Result1 extends UnknownArray, infer Result2 extends UnknownArray] + ? [...Result, ...Result1, ...Result2] + : never + > + : never // Never happens + : InternalInnerFixedLengthArrayFlat + : [...Result, ...T] + : []; + +// Handle fixed length arrays +type InternalInnerFixedLengthArrayFlat< + T, + Depth extends number = 1, + Options extends ArrayFlatOptions = DefaultArrayFlatOptions, + Result extends UnknownArray = [], +> = +[T] extends [UnknownArray] + ? Or>, IsZero> extends true + ? [...Result, ...T] + : [RequiredPartOfArray, OptionalPartOfArray] extends [infer RequiredPart, infer OptionalPart] + ? [InternalArrayFlat, (InternalArrayFlat | [])] extends + [infer Result1 extends UnknownArray, infer Result2 extends UnknownArray] + ? [...Result, ...Result1, ...Result2] + : never // Never happens, just for fixed ts error TS2589: Type instantiation is excessively deep and possibly infinite. + : never // Never happens + : []; + +/** + * Replaces items with the RepeatSymbol flag to the true result. + */ +type DoRepeatArrayItem = +T extends [infer _Item, ...infer Last] + ? [_Item] extends [{[RepeatSymbol]: infer Item extends UnknownArray}] + ? IsZero extends true + ? [...DoRepeatArrayItem] + : Item extends unknown + ? Item['length'] extends 1 + // If the item is a single element array, we can build [...Array], but if already has spread + // array before, we should build [...Array<'SomeSpreadArrayBefore'>, Item[number], Item[number], Item[number], ...] + ? [ + ...( + hasSpreadArray extends true + ? BuildRepeatedUnionArray extends infer Result extends UnknownArray + ? [...Result] : never // Never happens, just for fixed ts error TS2589: Type instantiation is excessively deep and possibly infinite. + : Array + ) + , ...DoRepeatArrayItem, + ] + // If the item is not a single element array, we only can build by repeating the item, like: + // ArrayFlat> => [1, 2, 1, 2, 1, 2, 1, 2, 1, 2, ...] + : [ + ...( + BuildRepeatedUnionArray> extends infer Result extends UnknownArray + ? [...Result] : never // Never happens, just for fixed ts error TS2589: Type instantiation is excessively deep and possibly infinite. - : T - ), - ] - // Handle fixed length arrays - : T extends readonly [infer ArrayItem, ...infer Last] - ? ArrayItem extends UnknownArray - ? InternalArrayFlat, Options>]> - : InternalArrayFlat - : T extends Array - ? Or, IsNever> extends true - ? [...Result, ...ArrayItem2[]] // Return never/any[] when input is never/any[] - : ArrayItem2 extends UnknownArray - ? InternalArrayFlat, Options, Result> - : [...Result, ...ArrayItem2[]] - : [...Result, ...T] - : []; + ) + , ...DoRepeatArrayItem, // eslint-disable-line @typescript-eslint/no-unnecessary-type-arguments + ] + : never // Never happens + : [_Item, ...DoRepeatArrayItem] + : T; + +/** + * Builds a union that lists all the possible combinations of the given array items and repeat times. + * + * @example + * ``` + * type A = BuildRepeatedUnionArray<[number, string?], 2, true>; + * //=> type A = + * [] + * | number[] + * | [number] + * | [number, string] + * | [number, number] + * | [number, string, number] + * | [number, number, string] + * | [number, string, number, string] + * ``` + */ +type BuildRepeatedUnionArray = +RepeatNumber extends 0 + ? R + : [RequiredPartOfArray, OptionalPartOfArray] extends [infer RequiredPart extends UnknownArray, infer OptionalPart extends UnknownArray] + ? ExactOptionalPropertyTypesEnable extends true + ? R + | [...RequiredPart] + | (And, CanSpread> extends true + ? [...Array] + : never) + | BuildRepeatedUnionArray< + T, + Subtract, + CanSpread, + [ + ...R, + ...( + ExactOptionalPropertyTypesEnable extends true + ? [...RequiredPart, ...([Exclude] | [])] + : T + ), + ] + > + : never + : never; diff --git a/source/internal/array.d.ts b/source/internal/array.d.ts index 6f72db229..055dfc547 100644 --- a/source/internal/array.d.ts +++ b/source/internal/array.d.ts @@ -30,6 +30,36 @@ It creates a type-safe way to access the element type of `unknown` type. */ export type ArrayElement = T extends readonly unknown[] ? T[0] : never; +/** + Returns the required part of the given array. + + @example + ``` + type A = [string, number, boolean?]; + type B = RequiredPartOfArray; + //=> [string, number] + ``` + */ +export type RequiredPartOfArray = + T extends readonly [infer U, ...infer V] + ? [U, ...RequiredPartOfArray] + : []; + +/** + Returns the optional part of the given array. + + @example + ``` + type A = [string, number, boolean?]; + type B = OptionalPartOfArray; + //=> [boolean?] + ``` + */ +export type OptionalPartOfArray = + T extends readonly [...RequiredPartOfArray, ...infer U] + ? U + : []; + /** Returns the static, fixed-length portion of the given array, excluding variable-length parts. @@ -66,6 +96,46 @@ export type VariablePartOfArray = : [] : never; // Should never happen +/** +Returns if the given array is a leading spread array. +*/ +export type IsLeadingSpreadArray = + T extends [...infer U, infer V] ? true : false; + +/** +Returns if the given array is a trailing spread array. +*/ +export type IsTrailingSpreadArray = + T extends [infer U, ...infer V] ? true : false; + +/** +Returns the static, fixed-length portion of the given leading spread array. +@example +``` +type A = [...string[], number, boolean]; +type B = StaticPartOfLeadingSpreadArray; +//=> [number, boolean] +``` +*/ +type StaticPartOfLeadingSpreadArray = + T extends [...infer U, infer V] + ? StaticPartOfLeadingSpreadArray + : Result; + +/** +Returns the variable, non-fixed-length portion of the given leading spread array. +@example +``` +type A = [...string[], number, boolean]; +type B = VariablePartOfLeadingSpreadArray; +//=> string[] +``` +*/ +export type VariablePartOfLeadingSpreadArray = + T extends [...infer U, ...StaticPartOfLeadingSpreadArray] + ? U + : never; + /** Set the given array to readonly if `IsReadonly` is `true`, otherwise set the given array to normal, then return the result. diff --git a/source/internal/type.d.ts b/source/internal/type.d.ts index 46c0343b4..d4d9ff586 100644 --- a/source/internal/type.d.ts +++ b/source/internal/type.d.ts @@ -111,3 +111,8 @@ type InternalIsUnion = ? boolean extends Result ? true : Result : never; // Should never happen + +/** +Return the value of exactOptionalPropertyTypes option in tsconfig +*/ +export type ExactOptionalPropertyTypesEnable = [(string | undefined)?] extends [string?] ? false : true; diff --git a/test-d/array-flat.ts b/test-d/array-flat.ts index cb0868694..99b6ca5e8 100644 --- a/test-d/array-flat.ts +++ b/test-d/array-flat.ts @@ -1,6 +1,8 @@ -import {expectAssignable, expectType} from 'tsd'; +import {expectAssignable, expectNotAssignable, expectType} from 'tsd'; import type {ArrayFlat, PositiveInfinity} from '../index'; +type DeepArrayFlat = ArrayFlat<[[[[[[T]]]]]], 10>; + // Basic flattening tests expectType>([]); expectType>([1, 2, 3]); @@ -26,20 +28,56 @@ expectType>([0, 1, 2, 3, 4, 5]); expectType>([1, 2, 3, 4]); expectType>([1]); +expectAssignable>( + [true, 'a', false], +); +expectAssignable>( + [true, 'a', 1, false], +); + // Test with different element types expectType>([null! as string, null! as number, null! as boolean]); +expectType>([null! as string, null! as number, null! as boolean]); + expectType>([null! as {a: number}, null! as {b: string}]); +expectType>([null! as {a: number}, null! as {b: string}]); // Test with union types -expectType>([1, 2] as [1, 2] | [3, 4]); -expectType>([1, 2] as [1, 2] | [3, 4]); +expectType>(null! as [1, 2] | [3, 4]); +expectType>(null! as [1, 2] | [3, 4]); + +expectType>(null! as [1, 2] | [3, 4]); +expectType>(null! as [1, 2] | [3, 4]); + +expectAssignable]>>([1, 3, 5]); +expectAssignable]>>([1, 3, 5]); -expectType]>>(null! as [number?, string?, number?, string?, number?, string?, number?, string?, number?, string?, number?, string?, number?, string?, number?, string?, number?, string?, number?, string?]); -expectType], 4]>>([1, 2, ...(null! as Array<3>), 4]); +expectType], 4]>>(null! as [1, 2, ...Array<3>, 4]); +expectType], 4]>>(null! as [1, 2, ...Array<3>, 4]); // Test with mixed arrays and tuples -expectType>([1, ...(null! as number[]), 3]); -expectType>([null! as string, null! as number, ...(null! as boolean[])]); +expectType>(null! as ['1', ...number[], '3']); +expectType>(null! as ['1', ...number[], '3']); +expectAssignable>([1, 2, 3, 'a', 'b', 'c']); +expectAssignable>([1, 2, 3, 'a', 'b', 'c']); +type MutiSpreadArray = [number, Array<'a'>, Array<'b'>, boolean]; +expectAssignable>([1, 'a', 'b', true]); +expectAssignable>([1, 'a', 'b', true]); +expectAssignable>([1, 'a', 'a', 'a', 'b', true]); +expectAssignable>([1, 'a', 'a', 'a', 'b', true]); + +type SpreadArray = ArrayFlat<[1, ...Array<[2, 3?]>, 4]>; +expectAssignable([1, 2, 3, 4]); +expectAssignable([1, 2, 2, 4]); +expectAssignable([1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 4]); +expectAssignable([1, 2, 3, 2, 4]); + +type SpreadArray2 = ArrayFlat<['start', ...Array<[2, 3, 4?]>, 'end']>; +expectAssignable(['start', 2, 3, 4, 'end']); +expectAssignable(['start', 2, 3, 4, 2, 3, 4, 'end']); +expectAssignable(['start', 2, 3, 4, 2, 3, 4, 2, 3, 4, 'end']); +expectAssignable(['start', 2, 3, 2, 3, 2, 3, 'end']); +expectAssignable(['start', 2, 3, 2, 3, 4, 'end']); // Test with deeply nested structures expectType>([1, 2, 3, 4, [5]]); @@ -71,6 +109,9 @@ expectAssignable>([1, 'a', 2, 'b']); // Test for array with optional elements in nested structure type NestedOptional = [number, Array<[string?, number?]>]; expectAssignable>([1, 'a', 2, 'b', 3]); +expectType>( + null! as [boolean, string, boolean] | [boolean, string, number | undefined, boolean], +); // Test for arrays with rest elements in nested structure type NestedRest = [string, Array<[...number[]]>]; From 2ea01566980ee54eb4f141d9b438f51b20c3abc8 Mon Sep 17 00:00:00 2001 From: emiyaaaaa Date: Wed, 30 Apr 2025 17:30:40 +0800 Subject: [PATCH 6/7] add more test --- test-d/array-flat.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/test-d/array-flat.ts b/test-d/array-flat.ts index 99b6ca5e8..e299d6ac2 100644 --- a/test-d/array-flat.ts +++ b/test-d/array-flat.ts @@ -3,6 +3,11 @@ import type {ArrayFlat, PositiveInfinity} from '../index'; type DeepArrayFlat = ArrayFlat<[[[[[[T]]]]]], 10>; +expectAssignable>([1, 2, 3, 'a', 'b', true]); +expectAssignable>([1, 2, 3, 'a', 'b', true]); +// TODO: fix this +// expectAssignable>(['a', true, 'b', 'c']); + // Basic flattening tests expectType>([]); expectType>([1, 2, 3]); From 6902b80c1fdbeafcbb348ddb3fa09257bc6f72ae Mon Sep 17 00:00:00 2001 From: emiyaaaaa Date: Wed, 30 Apr 2025 17:57:49 +0800 Subject: [PATCH 7/7] fix array item repeat --- source/array-flat.d.ts | 4 ++-- test-d/array-flat.ts | 3 +-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/source/array-flat.d.ts b/source/array-flat.d.ts index 2b365acea..cceb622f7 100644 --- a/source/array-flat.d.ts +++ b/source/array-flat.d.ts @@ -191,7 +191,7 @@ T extends [infer _Item, ...infer Last] ? [ ...( hasSpreadArray extends true - ? BuildRepeatedUnionArray extends infer Result extends UnknownArray + ? BuildRepeatedUnionArray> extends infer Result extends UnknownArray ? [...Result] : never // Never happens, just for fixed ts error TS2589: Type instantiation is excessively deep and possibly infinite. : Array @@ -247,7 +247,7 @@ RepeatNumber extends 0 ...R, ...( ExactOptionalPropertyTypesEnable extends true - ? [...RequiredPart, ...([Exclude] | [])] + ? [...RequiredPart, ...(IsZero> extends true ? [] : [Exclude] | [])] : T ), ] diff --git a/test-d/array-flat.ts b/test-d/array-flat.ts index e299d6ac2..d8d2bf415 100644 --- a/test-d/array-flat.ts +++ b/test-d/array-flat.ts @@ -5,8 +5,7 @@ type DeepArrayFlat = ArrayFlat<[[[[[[T]]]]]], 10>; expectAssignable>([1, 2, 3, 'a', 'b', true]); expectAssignable>([1, 2, 3, 'a', 'b', true]); -// TODO: fix this -// expectAssignable>(['a', true, 'b', 'c']); +expectAssignable>(['a', true, true, false, 'b']); // Basic flattening tests expectType>([]);