diff --git a/index.d.ts b/index.d.ts index 8a9926d8d..8b3de0782 100644 --- a/index.d.ts +++ b/index.d.ts @@ -144,6 +144,7 @@ export type {ArraySlice} from './source/array-slice.d.ts'; export type {ArraySplice} from './source/array-splice.d.ts'; export type {ArrayTail} from './source/array-tail.d.ts'; export type {ArrayElement} from './source/array-element.d.ts'; +export type {ArrayExclude} from './source/array-exclude.d.ts'; export type {SetFieldType, SetFieldTypeOptions} from './source/set-field-type.d.ts'; export type {Paths, PathsOptions} from './source/paths.d.ts'; export type {AllUnionFields} from './source/all-union-fields.d.ts'; diff --git a/source/array-exclude.d.ts b/source/array-exclude.d.ts new file mode 100644 index 000000000..de2bfb76b --- /dev/null +++ b/source/array-exclude.d.ts @@ -0,0 +1,71 @@ +import type {And} from './and.d.ts'; +import type {If} from './if.d.ts'; +import type {IsArrayReadonly} from './internal/array.d.ts'; +import type {IsExactOptionalPropertyTypesEnabled, Not} from './internal/type.d.ts'; +import type {IsNever} from './is-never.d.ts'; +import type {IsOptionalKeyOf} from './is-optional-key-of.d.ts'; +import type {IsTuple} from './is-tuple.d.ts'; +import type {UnknownArray} from './unknown-array.d.ts'; + +/** +Exclude elements of the specified type from an array type. +*/ +export type ArrayExclude = + TArray extends UnknownArray // For distributing `TArray` + ? If< + IsTuple, + TupleExclude, + Array> + > extends infer Result + ? If, Readonly, Result> + : never + : never; // Should never happen + +type TupleExclude< + TTuple extends UnknownArray, + TExclude, + ForwardAccumulator extends UnknownArray = [], + BackwardAccumulator extends UnknownArray = [], +> = + TTuple extends UnknownArray // For distributing `TArray` + ? keyof TTuple & `${number}` extends never + // Enters this branch, if `TArray` is empty (e.g., `[]`), + // or `TArray` contains no non-rest elements preceding the rest element (e.g., `[...string[]]` or `[...string[], string]`). + ? TTuple extends readonly [...infer Rest, infer Last] + ? TupleExclude< + Rest, + TExclude, + ForwardAccumulator, + Exclude extends infer ExcludedLast + ? If, BackwardAccumulator, [ExcludedLast, ...BackwardAccumulator]> + : never + > + : Exclude extends infer ExcludedRestElement + ? If< + IsNever, + [...ForwardAccumulator, ...BackwardAccumulator], + [...ForwardAccumulator, ...ExcludedRestElement[], ...BackwardAccumulator] + > + : never // Should never happen + : TTuple extends readonly [(infer First)?, ...infer Rest] + ? TupleExclude< + Rest, + TExclude, + IsOptionalKeyOf extends infer IsFirstOptional extends boolean + ? Exclude< + First | (If>, undefined, never>), // Add `| undefined` for optional elements, if `exactOptionalPropertyTypes` is disabled. + TExclude + > extends infer ExcludedFirst + ? If< + IsNever, + ForwardAccumulator, + [...ForwardAccumulator, ...(If)] + > + : never // Should never happen + : never, // Should never happen + BackwardAccumulator + > + : never // Should never happen, since `[(infer First)?, ...infer Rest]` is a top-type for arrays. + : never; // Should never happen + +export {}; diff --git a/test-d/array-exclude.ts b/test-d/array-exclude.ts new file mode 100644 index 000000000..35b568f39 --- /dev/null +++ b/test-d/array-exclude.ts @@ -0,0 +1,71 @@ +import {expectType} from 'tsd'; +import type {ArrayExclude} from '../index.d.ts'; + +// All required elements +expectType<[1, 3, 4]>({} as ArrayExclude<[1, 2, 3, 4], 2>); + +// All optional elements +expectType<[1?, 3?, 4?]>({} as ArrayExclude<[1?, 2?, 3?, 4?], 2>); + +// Mixed required and optional elements +expectType<[1, 3?, 4?]>({} as ArrayExclude<[1, 2, 3?, 4?], 2>); +expectType<[1, 3?, 4?]>({} as ArrayExclude<[1, 2?, 3?, 4?], 2>); + +// Explicit undefined in optional elements +expectType<[1, undefined?, 3?]>({} as ArrayExclude<[1, (2 | undefined)?, 3?], 2>); + +// Union elements +expectType<[1, 3, 4 | 5]>({} as ArrayExclude<[1, 2 | 3, 4 | 5], 2>); + +// Excluding unions +expectType<[1, 3, 5]>({} as ArrayExclude<[1, 2 | 3, 4 | 5], 2 | 4>); + +// Required and rest element +// Trailing rest element +expectType<[2, ...string[]]>({} as ArrayExclude<[1, 2, ...string[]], 1>); +expectType({} as ArrayExclude<[1, 2, ...string[]], 1 | 2>); +// Leading rest element +expectType<[...string[], 2]>({} as ArrayExclude<[...string[], 1, 2], 1>); +expectType({} as ArrayExclude<[...string[], 1, 2], 1 | 2>); +// Rest element in the middle +expectType<[1, ...string[], 3]>({} as ArrayExclude<[1, 2, ...string[], 3, 4], 2 | 4>); +expectType({} as ArrayExclude<[1, 2, ...string[], 3, 4], 1 | 2 | 3 | 4>); + +// Optional and rest element +expectType<[1?, ...string[]]>({} as ArrayExclude<[1?, 2?, ...string[]], 2>); +expectType({} as ArrayExclude<[1?, 2?, ...string[]], 1 | 2>); + +// Required, optional, and rest element +expectType<[1, 4?, ...string[]]>({} as ArrayExclude<[1, 2, 3?, 4?, ...string[]], 2 | 3>); + +// Excluding rest element +// Trailing rest element +expectType<[1, 2]>({} as ArrayExclude<[1, 2, ...string[]], string>); +expectType<[1?, 2?]>({} as ArrayExclude<[1?, 2?, ...string[]], string>); +expectType<[1, 2, 3?, 4?]>({} as ArrayExclude<[1, 2, 3?, 4?, ...string[]], string>); +expectType<[1, 2, ...number[]]>({} as ArrayExclude<[1, 2, ...Array], string>); +expectType<[1?, 2?, ...number[]]>({} as ArrayExclude<[1?, 2?, ...Array], string>); +expectType<[1, 2, 3?, 4?, ...number[]]>({} as ArrayExclude<[1, 2, 3?, 4?, ...Array], string>); +// Leading rest element +expectType<[1, 2]>({} as ArrayExclude<[...string[], 1, 2], string>); +expectType<[...number[], 1, 2]>({} as ArrayExclude<[...Array, 1, 2], string>); +// Rest element in the middle +expectType<[1, 2, 3]>({} as ArrayExclude<[1, ...string[], 2, 3], string>); +expectType<[1, ...number[], 2, 3]>({} as ArrayExclude<[1, ...Array, 2, 3], string>); + +// Excluding both rest element and non-rest elements +expectType<[1, 3?, 5?, ...number[]]>({} as ArrayExclude<[1, 2, 3?, (4 | 5)?, ...Array], 2 | 4 | string>); + +// All elements excluded +expectType<[]>({} as ArrayExclude<[1, 2, 3], 1 | 2 | 3>); +expectType<[]>({} as ArrayExclude<[string, number], string | number>); +expectType<[]>({} as ArrayExclude<[1, 2?, 3?, ...string[]], any>); +expectType<[]>({} as ArrayExclude<[1, 2?, 3?, ...string[]], unknown>); + +// No elements excluded +expectType<[1, 2, 3]>({} as ArrayExclude<[1, 2, 3], 4>); +expectType<[1, 2?, 3?, ...string[]]>({} as ArrayExclude<[1, 2?, 3?, ...string[]], never>); + +// Non-tuple arrays +expectType({} as ArrayExclude, number>); +expectType({} as ArrayExclude, string | number>);