Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,7 @@ export type {And} from './source/and.d.ts';
export type {Or} from './source/or.d.ts';
export type {Xor} from './source/xor.d.ts';
export type {AllExtend, AllExtendOptions} from './source/all-extend.d.ts';
export type {SomeExtend, SomeExtendOptions} from './source/some-extend.d.ts';
export type {NonEmptyTuple} from './source/non-empty-tuple.d.ts';
export type {FindGlobalInstanceType, FindGlobalType} from './source/find-global-type.d.ts';
export type {If} from './source/if.d.ts';
Expand Down
1 change: 1 addition & 0 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,7 @@ Click the type names for complete docs.
- [`Or`](source/or.d.ts) - Returns a boolean for whether either of two given types is true.
- [`Xor`](source/xor.d.ts) - Returns a boolean for whether only one of two given types is true.
- [`AllExtend`](source/all-extend.d.ts) - Returns a boolean for whether every element in an array type extends another type.
- [`SomeExtend`](source/some-extend.d.ts) - Returns a boolean for whether some element in an array type extends another type.
- [`NonEmptyTuple`](source/non-empty-tuple.d.ts) - Matches any non-empty tuple.
- [`NonEmptyString`](source/non-empty-string.d.ts) - Matches any non-empty string.
- [`FindGlobalType`](source/find-global-type.d.ts) - Tries to find the type of a global with the given name.
Expand Down
11 changes: 5 additions & 6 deletions source/all-extend.d.ts
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For SomeExtend, we can't directly return true if the target is any because SomeExtend<[], any> should return false. So, adjusted AllExtend's implementation as well so that it stays aligned with the implementation of SomeExtend.

Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import type {If} from './if.d.ts';
import type {CollapseRestElement} from './internal/array.d.ts';
import type {ApplyDefaultOptions} from './internal/object.d.ts';
import type {IfNotAnyOrNever, Not} from './internal/type.d.ts';
Expand Down Expand Up @@ -104,17 +103,17 @@ type B = AllExtend<[1?, 2?, 3?], number | undefined>;
export type AllExtend<TArray extends UnknownArray, Type, Options extends AllExtendOptions = {}> =
_AllExtend<CollapseRestElement<TArray>, Type, ApplyDefaultOptions<AllExtendOptions, DefaultAllExtendOptions, Options>>;

type _AllExtend<TArray extends UnknownArray, Type, Options extends Required<AllExtendOptions>> = IfNotAnyOrNever<TArray, If<IsAny<Type>, true,
type _AllExtend<TArray extends UnknownArray, Type, Options extends Required<AllExtendOptions>> = IfNotAnyOrNever<TArray,
TArray extends readonly [infer First, ...infer Rest]
? IsNever<First> extends true
? Or<IsNever<Type>, Not<Options['strictNever']>> extends true
// If target `Type` is also `never` OR `strictNever` is disabled, recurse further.
? Or<Or<IsNever<Type>, IsAny<Type>>, Not<Options['strictNever']>> extends true
// If target `Type` is also `never`, or is `any`, or `strictNever` is disabled, recurse further.
? _AllExtend<Rest, Type, Options>
: false
: First extends Type
? _AllExtend<Rest, Type, Options>
: false
: true
>, false, false>;
: true,
false, false>;

export {};
113 changes: 113 additions & 0 deletions source/some-extend.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
import type {CollapseRestElement} from './internal/array.d.ts';
import type {ApplyDefaultOptions} from './internal/object.d.ts';
import type {IfNotAnyOrNever, Not} from './internal/type.d.ts';
import type {IsAny} from './is-any.d.ts';
import type {IsNever} from './is-never.d.ts';
import type {Or} from './or.d.ts';
import type {UnknownArray} from './unknown-array.d.ts';

/**
@see {@link SomeExtend}
*/
export type SomeExtendOptions = {
/**
Consider `never` elements to match the target type only if the target type itself is `never` (or `any`).

- When set to `true` (default), `never` is _not_ treated as a bottom type, instead, it is treated as a type that matches only itself (or `any`).
- When set to `false`, `never` is treated as a bottom type, and behaves as it normally would.

@default true

@example
```
import type {SomeExtend} from 'type-fest';

type A = SomeExtend<[1, 2, never], string, {strictNever: true}>;
//=> false

type B = SomeExtend<[1, 2, never], string, {strictNever: false}>;
//=> true

type C = SomeExtend<[1, never], never, {strictNever: true}>;
//=> true

type D = SomeExtend<[1, never], never, {strictNever: false}>;
//=> true

type E = SomeExtend<[never], any, {strictNever: true}>;
//=> true

type F = SomeExtend<[never], any, {strictNever: false}>;
//=> true
```
*/
strictNever?: boolean;
};

type DefaultSomeExtendOptions = {
strictNever: true;
};

/**
Returns a boolean for whether some element in an array type extends another type.

@example
```
import type {SomeExtend} from 'type-fest';

type A = SomeExtend<['1', '2', 3], number>;
//=> true

type B = SomeExtend<[1, 2, 3], string>;
//=> false

type C = SomeExtend<[string, number | string], number>;
//=> boolean

type D = SomeExtend<[true, boolean, true], false>;
//=> boolean
```

Note: Behaviour of optional elements depend on the `exactOptionalPropertyTypes` compiler option. When the option is disabled, the target type must include `undefined` for a successful match.

```
// @exactOptionalPropertyTypes: true
import type {SomeExtend} from 'type-fest';

type A = SomeExtend<[1?, 2?, '3'?], string>;
//=> true
```

```
// @exactOptionalPropertyTypes: false
import type {SomeExtend} from 'type-fest';

type A = SomeExtend<[1?, 2?, '3'?], string>;
//=> boolean

type B = SomeExtend<[1?, 2?, '3'?], string | undefined>;
//=> true
```

@see {@link SomeExtendOptions}

@category Utilities
@category Array
*/
export type SomeExtend<TArray extends UnknownArray, Type, Options extends SomeExtendOptions = {}> =
_SomeExtend<CollapseRestElement<TArray>, Type, ApplyDefaultOptions<SomeExtendOptions, DefaultSomeExtendOptions, Options>>;

type _SomeExtend<TArray extends UnknownArray, Type, Options extends Required<SomeExtendOptions>> = IfNotAnyOrNever<TArray,
TArray extends readonly [infer First, ...infer Rest]
? IsNever<First> extends true
? Or<Or<IsNever<Type>, IsAny<Type>>, Not<Options['strictNever']>> extends true
// If target `Type` is also `never`, or is `any`, or `strictNever` is disabled, return `true`.
? true
: _SomeExtend<Rest, Type, Options>
: First extends Type
? true
: _SomeExtend<Rest, Type, Options>
: false,
false, false>;

export {};
2 changes: 1 addition & 1 deletion test-d/all-extend.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ expectType<AllExtend<readonly [...ReadonlyArray<string | undefined>, string, str

// Optional elements
// If `exactOptionalPropertyTypes` were disabled, the target type would need an additional `| undefined` for a successful match.
// For example, `AllExtend<[1?, 2?], number>` would return `false`. To make it return `true`, the target type must be `number | undefined`.
// For example, `AllExtend<[1?, 2?], number>` would return `boolean`. To make it return `true`, the target type must be `number | undefined`.
Copy link
Copy Markdown
Collaborator Author

@som-sm som-sm Mar 14, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed typo.

expectType<AllExtend<[1?, 2?, 3?], number>>(true);
expectType<AllExtend<[1?, (2 | undefined)?], number>>({} as boolean);
expectType<AllExtend<[1?, 2?, 3?], number | undefined>>(true);
Expand Down
122 changes: 122 additions & 0 deletions test-d/some-extend.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
import {expectType} from 'tsd';
import type {SomeExtend} from '../source/some-extend.d.ts';
import type {UnknownArray} from '../source/unknown-array.d.ts';

expectType<SomeExtend<[], number>>(false);
expectType<SomeExtend<[1, '2', '3'], number>>(true); // First element matches
expectType<SomeExtend<['1', '2', 3], number>>(true); // Last element matches
expectType<SomeExtend<['1', 2, '3'], number>>(true); // Mid element matches
expectType<SomeExtend<[number, '1', 2, `${number}`], string>>(true); // Multiple elements match
expectType<SomeExtend<[1, 2, 3], number>>(true); // All elements match
// Trailing rest element
expectType<SomeExtend<['2', 1, ...string[]], number>>(true); // Non-rest element matches
expectType<SomeExtend<['2', '1', ...number[]], number>>(true); // Rest element matches
// Leading rest element
expectType<SomeExtend<[...Array<1 | -1>, number, string], string>>(true); // Non-rest element matches
expectType<SomeExtend<[...Array<1 | -1>, string, string], number>>(true); // Rest element matches
// Middle rest element
expectType<SomeExtend<[string, number, ...string[], string, string], number>>(true); // Non-rest element before rest element matches
expectType<SomeExtend<[string, string, ...string[], number, string], number>>(true); // Non-rest element after rest element matches
expectType<SomeExtend<[string, ...number[], string, string], number>>(true); // Rest element matches

expectType<SomeExtend<['1', '2', '3'], number>>(false);
expectType<SomeExtend<[1, 2, 3], string>>(false);
expectType<SomeExtend<[1, 2, 3, ...string[]], bigint>>(false);
expectType<SomeExtend<[...bigint[], true, false], number | string>>(false);
expectType<SomeExtend<[1, 2, ...number[], '1', 3], true>>(false);
expectType<SomeExtend<['1', '2', ...bigint[]], number>>(false);

// Union targets
expectType<SomeExtend<['1', '2', 3], number | bigint>>(true);
expectType<SomeExtend<[1, false, 0, true, 1], boolean>>(true);
expectType<SomeExtend<[...bigint[], number, string], number | string>>(true);

// Union type elements
expectType<SomeExtend<[number, string | number], string>>({} as boolean);
expectType<SomeExtend<[false, false, false, boolean], true>>({} as boolean);
expectType<SomeExtend<[true, true, boolean, true], false>>({} as boolean);
expectType<SomeExtend<[1, 2n, number | bigint, 3n], string>>(false);
expectType<SomeExtend<['1', '2', number | bigint, '3'], number | bigint>>(true);
expectType<SomeExtend<[true, false, true, ...Array<string | undefined>], string | number>>({} as boolean);
expectType<SomeExtend<['foo', ...Array<number | string>, 'bar'], bigint | undefined>>(false);

// Readonly arrays
expectType<SomeExtend<readonly [], number>>(false);
expectType<SomeExtend<readonly ['1', 2, '3'], number>>(true);
expectType<SomeExtend<readonly ['1', '2', '3'], number>>(false);
expectType<SomeExtend<readonly ['^', ...number[], '$'], number>>(true);

// Optional elements
// If `exactOptionalPropertyTypes` were disabled, the target type would need an additional `| undefined` for a successful match.
// For example, `SomeExtend<['1'?, 2?], number>` would return `boolean`. To make it return `true`, the target type must be `number | undefined`.
expectType<SomeExtend<['1'?, '2'?, 3?], number>>(true);
expectType<SomeExtend<['1'?, (2 | undefined)?], number>>({} as boolean);
expectType<SomeExtend<[1?, '2'?, 3?], string | undefined>>(true);
expectType<SomeExtend<[1, 2?, 3?, ...string[]], string>>(true);
expectType<SomeExtend<['1', '2'?, '3'?, ...Array<number | undefined>], number>>({} as boolean);
expectType<SomeExtend<[1?, 2?, 3?, ...boolean[]], string>>(false);

// Labelled tuples
expectType<SomeExtend<[x: string, y: number], string>>(true);
expectType<SomeExtend<[x?: number, y?: number], string>>(false);
expectType<SomeExtend<[x?: string, y?: string, ...rest: number[]], number>>(true);
expectType<SomeExtend<[...rest: number[], z: string], string>>(true);
expectType<SomeExtend<[...rest: number[], z: string], bigint>>(false);

// Non-tuple arrays
expectType<SomeExtend<string[], string>>(true);
expectType<SomeExtend<Array<string | number>, number>>({} as boolean);
expectType<SomeExtend<ReadonlyArray<string | undefined>, string>>({} as boolean);
expectType<SomeExtend<[...readonly boolean[]], string>>(false);

// Unions
expectType<SomeExtend<['1', '2', 3] | ['4', 5, '6'], number>>(true); // Both `true`
expectType<SomeExtend<[1, 2, 3] | ['4', '5', '6'], bigint>>(false); // Both `false`
expectType<SomeExtend<['1', '2', '3'] | ['1', '2', 3], number>>({} as boolean); // One `true`, one `false`
expectType<SomeExtend<[true, false] | [false, boolean], true>>({} as boolean); // One `true`, one `boolean`
expectType<SomeExtend<[false, false] | [boolean, false], true>>({} as boolean); // One `false`, one `boolean`

expectType<SomeExtend<[string, string, ...number[]] | [number, string, ...number[]], number>>(true);
expectType<SomeExtend<readonly [(number | bigint)?, ...string[]] | [0, 'a', 'b'] | [...ReadonlyArray<string | number>, 1], true>>(false);
expectType<SomeExtend<number[] | [...ReadonlyArray<string | number>, number], string>>({} as boolean);
expectType<SomeExtend<readonly number[] | [...rest: string[], l1: string, l2: string] | [(number | string)?, string?, ...string[]], number>>(
{} as boolean,
);

// Boundary cases
expectType<SomeExtend<[], any>>(false);
expectType<SomeExtend<[], never>>(false);
// `never` target
expectType<SomeExtend<[number, string, boolean], never>>(false);
expectType<SomeExtend<[number, string, never], never>>(true);
expectType<SomeExtend<[number, string, any], never>>({} as boolean);
// `any` target
expectType<SomeExtend<[number], any>>(true);
expectType<SomeExtend<[never], any>>(true);
expectType<SomeExtend<[any], any>>(true);
// `never` and `any` elements
expectType<SomeExtend<[1, 2, never], string>>(false);
expectType<SomeExtend<[1, 2, any], string>>({} as boolean);
// `never[]` and `any[]` elements
expectType<SomeExtend<[1, 2, ...any[]], string>>({} as boolean);
expectType<SomeExtend<[1, 2, ...any[], 3, 4], string>>({} as boolean);
expectType<SomeExtend<[...any[], 3, 4], string>>({} as boolean);
expectType<SomeExtend<any[], number>>({} as boolean);
expectType<SomeExtend<[1, 2, ...never[]], string>>(false);
expectType<SomeExtend<[1, 2, ...never[], 3, 4], string>>(false);
expectType<SomeExtend<[...never[], 1, 2], string>>(false);
expectType<SomeExtend<never[], number>>(false);

// === strictNever: false ===
type NonStrictNeverSomeExtend<TArray extends UnknownArray, Type> = SomeExtend<TArray, Type, {strictNever: false}>;

// `never` elements
expectType<NonStrictNeverSomeExtend<[1, 2, never], string>>(true);
// `never[]` elements
expectType<NonStrictNeverSomeExtend<[1, 2, ...never[]], string>>(true);
expectType<NonStrictNeverSomeExtend<[1, 2, ...never[], 3, 4], string>>(true);
expectType<NonStrictNeverSomeExtend<[...never[], 1, 2], string>>(true);
expectType<NonStrictNeverSomeExtend<never[], number>>(true);

expectType<SomeExtend<any, never>>(false);
expectType<SomeExtend<never, any>>(false);
Loading