Skip to content

Commit bbce298

Browse files
authored
Add SomeExtend type (#1380)
1 parent d3302ce commit bbce298

File tree

6 files changed

+243
-7
lines changed

6 files changed

+243
-7
lines changed

index.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,7 @@ export type {And} from './source/and.d.ts';
158158
export type {Or} from './source/or.d.ts';
159159
export type {Xor} from './source/xor.d.ts';
160160
export type {AllExtend, AllExtendOptions} from './source/all-extend.d.ts';
161+
export type {SomeExtend, SomeExtendOptions} from './source/some-extend.d.ts';
161162
export type {NonEmptyTuple} from './source/non-empty-tuple.d.ts';
162163
export type {FindGlobalInstanceType, FindGlobalType} from './source/find-global-type.d.ts';
163164
export type {If} from './source/if.d.ts';

readme.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,7 @@ Click the type names for complete docs.
184184
- [`Or`](source/or.d.ts) - Returns a boolean for whether either of two given types is true.
185185
- [`Xor`](source/xor.d.ts) - Returns a boolean for whether only one of two given types is true.
186186
- [`AllExtend`](source/all-extend.d.ts) - Returns a boolean for whether every element in an array type extends another type.
187+
- [`SomeExtend`](source/some-extend.d.ts) - Returns a boolean for whether some element in an array type extends another type.
187188
- [`NonEmptyTuple`](source/non-empty-tuple.d.ts) - Matches any non-empty tuple.
188189
- [`NonEmptyString`](source/non-empty-string.d.ts) - Matches any non-empty string.
189190
- [`FindGlobalType`](source/find-global-type.d.ts) - Tries to find the type of a global with the given name.

source/all-extend.d.ts

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import type {If} from './if.d.ts';
21
import type {CollapseRestElement} from './internal/array.d.ts';
32
import type {ApplyDefaultOptions} from './internal/object.d.ts';
43
import type {IfNotAnyOrNever, Not} from './internal/type.d.ts';
@@ -104,17 +103,17 @@ type B = AllExtend<[1?, 2?, 3?], number | undefined>;
104103
export type AllExtend<TArray extends UnknownArray, Type, Options extends AllExtendOptions = {}> =
105104
_AllExtend<CollapseRestElement<TArray>, Type, ApplyDefaultOptions<AllExtendOptions, DefaultAllExtendOptions, Options>>;
106105

107-
type _AllExtend<TArray extends UnknownArray, Type, Options extends Required<AllExtendOptions>> = IfNotAnyOrNever<TArray, If<IsAny<Type>, true,
106+
type _AllExtend<TArray extends UnknownArray, Type, Options extends Required<AllExtendOptions>> = IfNotAnyOrNever<TArray,
108107
TArray extends readonly [infer First, ...infer Rest]
109108
? IsNever<First> extends true
110-
? Or<IsNever<Type>, Not<Options['strictNever']>> extends true
111-
// If target `Type` is also `never` OR `strictNever` is disabled, recurse further.
109+
? Or<Or<IsNever<Type>, IsAny<Type>>, Not<Options['strictNever']>> extends true
110+
// If target `Type` is also `never`, or is `any`, or `strictNever` is disabled, recurse further.
112111
? _AllExtend<Rest, Type, Options>
113112
: false
114113
: First extends Type
115114
? _AllExtend<Rest, Type, Options>
116115
: false
117-
: true
118-
>, false, false>;
116+
: true,
117+
false, false>;
119118

120119
export {};

source/some-extend.d.ts

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
import type {CollapseRestElement} from './internal/array.d.ts';
2+
import type {ApplyDefaultOptions} from './internal/object.d.ts';
3+
import type {IfNotAnyOrNever, Not} from './internal/type.d.ts';
4+
import type {IsAny} from './is-any.d.ts';
5+
import type {IsNever} from './is-never.d.ts';
6+
import type {Or} from './or.d.ts';
7+
import type {UnknownArray} from './unknown-array.d.ts';
8+
9+
/**
10+
@see {@link SomeExtend}
11+
*/
12+
export type SomeExtendOptions = {
13+
/**
14+
Consider `never` elements to match the target type only if the target type itself is `never` (or `any`).
15+
16+
- 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`).
17+
- When set to `false`, `never` is treated as a bottom type, and behaves as it normally would.
18+
19+
@default true
20+
21+
@example
22+
```
23+
import type {SomeExtend} from 'type-fest';
24+
25+
type A = SomeExtend<[1, 2, never], string, {strictNever: true}>;
26+
//=> false
27+
28+
type B = SomeExtend<[1, 2, never], string, {strictNever: false}>;
29+
//=> true
30+
31+
type C = SomeExtend<[1, never], never, {strictNever: true}>;
32+
//=> true
33+
34+
type D = SomeExtend<[1, never], never, {strictNever: false}>;
35+
//=> true
36+
37+
type E = SomeExtend<[never], any, {strictNever: true}>;
38+
//=> true
39+
40+
type F = SomeExtend<[never], any, {strictNever: false}>;
41+
//=> true
42+
```
43+
*/
44+
strictNever?: boolean;
45+
};
46+
47+
type DefaultSomeExtendOptions = {
48+
strictNever: true;
49+
};
50+
51+
/**
52+
Returns a boolean for whether some element in an array type extends another type.
53+
54+
@example
55+
```
56+
import type {SomeExtend} from 'type-fest';
57+
58+
type A = SomeExtend<['1', '2', 3], number>;
59+
//=> true
60+
61+
type B = SomeExtend<[1, 2, 3], string>;
62+
//=> false
63+
64+
type C = SomeExtend<[string, number | string], number>;
65+
//=> boolean
66+
67+
type D = SomeExtend<[true, boolean, true], false>;
68+
//=> boolean
69+
```
70+
71+
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.
72+
73+
```
74+
// @exactOptionalPropertyTypes: true
75+
import type {SomeExtend} from 'type-fest';
76+
77+
type A = SomeExtend<[1?, 2?, '3'?], string>;
78+
//=> true
79+
```
80+
81+
```
82+
// @exactOptionalPropertyTypes: false
83+
import type {SomeExtend} from 'type-fest';
84+
85+
type A = SomeExtend<[1?, 2?, '3'?], string>;
86+
//=> boolean
87+
88+
type B = SomeExtend<[1?, 2?, '3'?], string | undefined>;
89+
//=> true
90+
```
91+
92+
@see {@link SomeExtendOptions}
93+
94+
@category Utilities
95+
@category Array
96+
*/
97+
export type SomeExtend<TArray extends UnknownArray, Type, Options extends SomeExtendOptions = {}> =
98+
_SomeExtend<CollapseRestElement<TArray>, Type, ApplyDefaultOptions<SomeExtendOptions, DefaultSomeExtendOptions, Options>>;
99+
100+
type _SomeExtend<TArray extends UnknownArray, Type, Options extends Required<SomeExtendOptions>> = IfNotAnyOrNever<TArray,
101+
TArray extends readonly [infer First, ...infer Rest]
102+
? IsNever<First> extends true
103+
? Or<Or<IsNever<Type>, IsAny<Type>>, Not<Options['strictNever']>> extends true
104+
// If target `Type` is also `never`, or is `any`, or `strictNever` is disabled, return `true`.
105+
? true
106+
: _SomeExtend<Rest, Type, Options>
107+
: First extends Type
108+
? true
109+
: _SomeExtend<Rest, Type, Options>
110+
: false,
111+
false, false>;
112+
113+
export {};

test-d/all-extend.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ expectType<AllExtend<readonly [...ReadonlyArray<string | undefined>, string, str
4040

4141
// Optional elements
4242
// If `exactOptionalPropertyTypes` were disabled, the target type would need an additional `| undefined` for a successful match.
43-
// For example, `AllExtend<[1?, 2?], number>` would return `false`. To make it return `true`, the target type must be `number | undefined`.
43+
// For example, `AllExtend<[1?, 2?], number>` would return `boolean`. To make it return `true`, the target type must be `number | undefined`.
4444
expectType<AllExtend<[1?, 2?, 3?], number>>(true);
4545
expectType<AllExtend<[1?, (2 | undefined)?], number>>({} as boolean);
4646
expectType<AllExtend<[1?, 2?, 3?], number | undefined>>(true);

test-d/some-extend.ts

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
import {expectType} from 'tsd';
2+
import type {SomeExtend} from '../source/some-extend.d.ts';
3+
import type {UnknownArray} from '../source/unknown-array.d.ts';
4+
5+
expectType<SomeExtend<[], number>>(false);
6+
expectType<SomeExtend<[1, '2', '3'], number>>(true); // First element matches
7+
expectType<SomeExtend<['1', '2', 3], number>>(true); // Last element matches
8+
expectType<SomeExtend<['1', 2, '3'], number>>(true); // Mid element matches
9+
expectType<SomeExtend<[number, '1', 2, `${number}`], string>>(true); // Multiple elements match
10+
expectType<SomeExtend<[1, 2, 3], number>>(true); // All elements match
11+
// Trailing rest element
12+
expectType<SomeExtend<['2', 1, ...string[]], number>>(true); // Non-rest element matches
13+
expectType<SomeExtend<['2', '1', ...number[]], number>>(true); // Rest element matches
14+
// Leading rest element
15+
expectType<SomeExtend<[...Array<1 | -1>, number, string], string>>(true); // Non-rest element matches
16+
expectType<SomeExtend<[...Array<1 | -1>, string, string], number>>(true); // Rest element matches
17+
// Middle rest element
18+
expectType<SomeExtend<[string, number, ...string[], string, string], number>>(true); // Non-rest element before rest element matches
19+
expectType<SomeExtend<[string, string, ...string[], number, string], number>>(true); // Non-rest element after rest element matches
20+
expectType<SomeExtend<[string, ...number[], string, string], number>>(true); // Rest element matches
21+
22+
expectType<SomeExtend<['1', '2', '3'], number>>(false);
23+
expectType<SomeExtend<[1, 2, 3], string>>(false);
24+
expectType<SomeExtend<[1, 2, 3, ...string[]], bigint>>(false);
25+
expectType<SomeExtend<[...bigint[], true, false], number | string>>(false);
26+
expectType<SomeExtend<[1, 2, ...number[], '1', 3], true>>(false);
27+
expectType<SomeExtend<['1', '2', ...bigint[]], number>>(false);
28+
29+
// Union targets
30+
expectType<SomeExtend<['1', '2', 3], number | bigint>>(true);
31+
expectType<SomeExtend<[1, false, 0, true, 1], boolean>>(true);
32+
expectType<SomeExtend<[...bigint[], number, string], number | string>>(true);
33+
34+
// Union type elements
35+
expectType<SomeExtend<[number, string | number], string>>({} as boolean);
36+
expectType<SomeExtend<[false, false, false, boolean], true>>({} as boolean);
37+
expectType<SomeExtend<[true, true, boolean, true], false>>({} as boolean);
38+
expectType<SomeExtend<[1, 2n, number | bigint, 3n], string>>(false);
39+
expectType<SomeExtend<['1', '2', number | bigint, '3'], number | bigint>>(true);
40+
expectType<SomeExtend<[true, false, true, ...Array<string | undefined>], string | number>>({} as boolean);
41+
expectType<SomeExtend<['foo', ...Array<number | string>, 'bar'], bigint | undefined>>(false);
42+
43+
// Readonly arrays
44+
expectType<SomeExtend<readonly [], number>>(false);
45+
expectType<SomeExtend<readonly ['1', 2, '3'], number>>(true);
46+
expectType<SomeExtend<readonly ['1', '2', '3'], number>>(false);
47+
expectType<SomeExtend<readonly ['^', ...number[], '$'], number>>(true);
48+
49+
// Optional elements
50+
// If `exactOptionalPropertyTypes` were disabled, the target type would need an additional `| undefined` for a successful match.
51+
// For example, `SomeExtend<['1'?, 2?], number>` would return `boolean`. To make it return `true`, the target type must be `number | undefined`.
52+
expectType<SomeExtend<['1'?, '2'?, 3?], number>>(true);
53+
expectType<SomeExtend<['1'?, (2 | undefined)?], number>>({} as boolean);
54+
expectType<SomeExtend<[1?, '2'?, 3?], string | undefined>>(true);
55+
expectType<SomeExtend<[1, 2?, 3?, ...string[]], string>>(true);
56+
expectType<SomeExtend<['1', '2'?, '3'?, ...Array<number | undefined>], number>>({} as boolean);
57+
expectType<SomeExtend<[1?, 2?, 3?, ...boolean[]], string>>(false);
58+
59+
// Labelled tuples
60+
expectType<SomeExtend<[x: string, y: number], string>>(true);
61+
expectType<SomeExtend<[x?: number, y?: number], string>>(false);
62+
expectType<SomeExtend<[x?: string, y?: string, ...rest: number[]], number>>(true);
63+
expectType<SomeExtend<[...rest: number[], z: string], string>>(true);
64+
expectType<SomeExtend<[...rest: number[], z: string], bigint>>(false);
65+
66+
// Non-tuple arrays
67+
expectType<SomeExtend<string[], string>>(true);
68+
expectType<SomeExtend<Array<string | number>, number>>({} as boolean);
69+
expectType<SomeExtend<ReadonlyArray<string | undefined>, string>>({} as boolean);
70+
expectType<SomeExtend<[...readonly boolean[]], string>>(false);
71+
72+
// Unions
73+
expectType<SomeExtend<['1', '2', 3] | ['4', 5, '6'], number>>(true); // Both `true`
74+
expectType<SomeExtend<[1, 2, 3] | ['4', '5', '6'], bigint>>(false); // Both `false`
75+
expectType<SomeExtend<['1', '2', '3'] | ['1', '2', 3], number>>({} as boolean); // One `true`, one `false`
76+
expectType<SomeExtend<[true, false] | [false, boolean], true>>({} as boolean); // One `true`, one `boolean`
77+
expectType<SomeExtend<[false, false] | [boolean, false], true>>({} as boolean); // One `false`, one `boolean`
78+
79+
expectType<SomeExtend<[string, string, ...number[]] | [number, string, ...number[]], number>>(true);
80+
expectType<SomeExtend<readonly [(number | bigint)?, ...string[]] | [0, 'a', 'b'] | [...ReadonlyArray<string | number>, 1], true>>(false);
81+
expectType<SomeExtend<number[] | [...ReadonlyArray<string | number>, number], string>>({} as boolean);
82+
expectType<SomeExtend<readonly number[] | [...rest: string[], l1: string, l2: string] | [(number | string)?, string?, ...string[]], number>>(
83+
{} as boolean,
84+
);
85+
86+
// Boundary cases
87+
expectType<SomeExtend<[], any>>(false);
88+
expectType<SomeExtend<[], never>>(false);
89+
// `never` target
90+
expectType<SomeExtend<[number, string, boolean], never>>(false);
91+
expectType<SomeExtend<[number, string, never], never>>(true);
92+
expectType<SomeExtend<[number, string, any], never>>({} as boolean);
93+
// `any` target
94+
expectType<SomeExtend<[number], any>>(true);
95+
expectType<SomeExtend<[never], any>>(true);
96+
expectType<SomeExtend<[any], any>>(true);
97+
// `never` and `any` elements
98+
expectType<SomeExtend<[1, 2, never], string>>(false);
99+
expectType<SomeExtend<[1, 2, any], string>>({} as boolean);
100+
// `never[]` and `any[]` elements
101+
expectType<SomeExtend<[1, 2, ...any[]], string>>({} as boolean);
102+
expectType<SomeExtend<[1, 2, ...any[], 3, 4], string>>({} as boolean);
103+
expectType<SomeExtend<[...any[], 3, 4], string>>({} as boolean);
104+
expectType<SomeExtend<any[], number>>({} as boolean);
105+
expectType<SomeExtend<[1, 2, ...never[]], string>>(false);
106+
expectType<SomeExtend<[1, 2, ...never[], 3, 4], string>>(false);
107+
expectType<SomeExtend<[...never[], 1, 2], string>>(false);
108+
expectType<SomeExtend<never[], number>>(false);
109+
110+
// === strictNever: false ===
111+
type NonStrictNeverSomeExtend<TArray extends UnknownArray, Type> = SomeExtend<TArray, Type, {strictNever: false}>;
112+
113+
// `never` elements
114+
expectType<NonStrictNeverSomeExtend<[1, 2, never], string>>(true);
115+
// `never[]` elements
116+
expectType<NonStrictNeverSomeExtend<[1, 2, ...never[]], string>>(true);
117+
expectType<NonStrictNeverSomeExtend<[1, 2, ...never[], 3, 4], string>>(true);
118+
expectType<NonStrictNeverSomeExtend<[...never[], 1, 2], string>>(true);
119+
expectType<NonStrictNeverSomeExtend<never[], number>>(true);
120+
121+
expectType<SomeExtend<any, never>>(false);
122+
expectType<SomeExtend<never, any>>(false);

0 commit comments

Comments
 (0)