Skip to content

Commit 449bcea

Browse files
committed
- Enhanced Simplify type to improve flattening and support of function/classes.
- Added `ExtractCallSignature` type to extract call signatures from function types. - Moved `HasMultipleCallSignatures` type to `internal/function.d.ts`. - Updated `SetOptional`, `SetReadonly`, and `SetRequired` types to utilize `ExtractCallSignature`. - Updated tests to reflect changes in type behavior and ensure correctness.
1 parent 3c26560 commit 449bcea

15 files changed

+156
-143
lines changed

source/internal/function.d.ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import type {IsUnknown} from '../is-unknown.d.ts';
2+
3+
/**
4+
Test if the given function has multiple call signatures.
5+
6+
Needed to handle the case of a single call signature with properties.
7+
8+
Multiple call signatures cannot currently be supported due to a TypeScript limitation.
9+
@see https://github.com/microsoft/TypeScript/issues/29732
10+
*/
11+
export type HasMultipleCallSignatures<T extends (...arguments_: any[]) => unknown> =
12+
T extends {(...arguments_: infer A): unknown; (...arguments_: infer B): unknown}
13+
? B extends A
14+
? A extends B
15+
? false
16+
: true
17+
: true
18+
: false;
19+
20+
/**
21+
Extract the call signature of an object type.
22+
*/
23+
export type ExtractCallSignature<Type> =
24+
Type extends (this: infer This, ...args: infer Parameters) => infer Return
25+
? IsUnknown<This> extends true // TODO: replace with `FunctionWithMaybeThisParameter`
26+
? (...args: Parameters) => Return
27+
: (this: This, ...args: Parameters) => Return
28+
: unknown;
29+
30+
export {};

source/internal/index.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
export type * from './array.d.ts';
22
export type * from './characters.d.ts';
3+
export type * from './function.d.ts';
34
export type * from './keys.d.ts';
45
export type * from './numeric.d.ts';
56
export type * from './object.d.ts';

source/internal/type.d.ts

Lines changed: 0 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -22,23 +22,6 @@ export type IsBothExtends<BaseType, FirstType, SecondType> = FirstType extends B
2222
: false
2323
: false;
2424

25-
/**
26-
Test if the given function has multiple call signatures.
27-
28-
Needed to handle the case of a single call signature with properties.
29-
30-
Multiple call signatures cannot currently be supported due to a TypeScript limitation.
31-
@see https://github.com/microsoft/TypeScript/issues/29732
32-
*/
33-
export type HasMultipleCallSignatures<T extends (...arguments_: any[]) => unknown> =
34-
T extends {(...arguments_: infer A): unknown; (...arguments_: infer B): unknown}
35-
? B extends A
36-
? A extends B
37-
? false
38-
: true
39-
: true
40-
: false;
41-
4225
/**
4326
Returns a boolean for whether the given `boolean` is not `false`.
4427
*/

source/partial-deep.d.ts

Lines changed: 31 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import type {ApplyDefaultOptions, BuiltIns, HasMultipleCallSignatures} from './internal/index.d.ts';
2+
import type {Simplify} from './simplify.d.ts';
23
import type {IsNever} from './is-never.d.ts';
34

45
/**
@@ -97,33 +98,34 @@ partialShape.dimensions = [15]; // OK
9798
export type PartialDeep<T, Options extends PartialDeepOptions = {}> =
9899
_PartialDeep<T, ApplyDefaultOptions<PartialDeepOptions, DefaultPartialDeepOptions, Options>>;
99100

100-
type _PartialDeep<T, Options extends Required<PartialDeepOptions>> = T extends BuiltIns | ((new (...arguments_: any[]) => unknown))
101-
? T
102-
: T extends Map<infer KeyType, infer ValueType>
103-
? PartialMapDeep<KeyType, ValueType, Options>
104-
: T extends Set<infer ItemType>
105-
? PartialSetDeep<ItemType, Options>
106-
: T extends ReadonlyMap<infer KeyType, infer ValueType>
107-
? PartialReadonlyMapDeep<KeyType, ValueType, Options>
108-
: T extends ReadonlySet<infer ItemType>
109-
? PartialReadonlySetDeep<ItemType, Options>
110-
: T extends (...arguments_: any[]) => unknown
111-
? IsNever<keyof T> extends true
112-
? T // For functions with no properties
113-
: HasMultipleCallSignatures<T> extends true
114-
? T
115-
: ((...arguments_: Parameters<T>) => ReturnType<T>) & PartialObjectDeep<T, Options>
116-
: T extends object
117-
? T extends ReadonlyArray<infer ItemType> // Test for arrays/tuples, per https://github.com/microsoft/TypeScript/issues/35156
118-
? Options['recurseIntoArrays'] extends true
119-
? ItemType[] extends T // Test for arrays (non-tuples) specifically
120-
? readonly ItemType[] extends T // Differentiate readonly and mutable arrays
121-
? ReadonlyArray<_PartialDeep<Options['allowUndefinedInNonTupleArrays'] extends false ? ItemType : ItemType | undefined, Options>>
122-
: Array<_PartialDeep<Options['allowUndefinedInNonTupleArrays'] extends false ? ItemType : ItemType | undefined, Options>>
123-
: PartialObjectDeep<T, Options> // Tuples behave properly
124-
: T // If they don't opt into array testing, just use the original type
125-
: PartialObjectDeep<T, Options>
126-
: unknown;
101+
type _PartialDeep<T, Options extends Required<PartialDeepOptions>> =
102+
T extends BuiltIns | ((new (...arguments_: any[]) => unknown))
103+
? T
104+
: T extends Map<infer KeyType, infer ValueType>
105+
? PartialMapDeep<KeyType, ValueType, Options>
106+
: T extends Set<infer ItemType>
107+
? PartialSetDeep<ItemType, Options>
108+
: T extends ReadonlyMap<infer KeyType, infer ValueType>
109+
? PartialReadonlyMapDeep<KeyType, ValueType, Options>
110+
: T extends ReadonlySet<infer ItemType>
111+
? PartialReadonlySetDeep<ItemType, Options>
112+
: T extends (...arguments_: any[]) => unknown
113+
? IsNever<keyof T> extends true
114+
? T // For functions with no properties
115+
: HasMultipleCallSignatures<T> extends true
116+
? T
117+
: ((...arguments_: Parameters<T>) => ReturnType<T>) & PartialObjectDeep<T, Options>
118+
: T extends object
119+
? T extends ReadonlyArray<infer ItemType> // Test for arrays/tuples, per https://github.com/microsoft/TypeScript/issues/35156
120+
? Options['recurseIntoArrays'] extends true
121+
? ItemType[] extends T // Test for arrays (non-tuples) specifically
122+
? readonly ItemType[] extends T // Differentiate readonly and mutable arrays
123+
? ReadonlyArray<_PartialDeep<Options['allowUndefinedInNonTupleArrays'] extends false ? ItemType : ItemType | undefined, Options>>
124+
: Array<_PartialDeep<Options['allowUndefinedInNonTupleArrays'] extends false ? ItemType : ItemType | undefined, Options>>
125+
: PartialObjectDeep<T, Options> // Tuples behave properly
126+
: T // If they don't opt into array testing, just use the original type
127+
: PartialObjectDeep<T, Options>
128+
: unknown;
127129

128130
/**
129131
Same as `PartialDeep`, but accepts only `Map`s and as inputs. Internal helper for `PartialDeep`.
@@ -148,8 +150,8 @@ type PartialReadonlySetDeep<T, Options extends Required<PartialDeepOptions>> = {
148150
/**
149151
Same as `PartialDeep`, but accepts only `object`s as inputs. Internal helper for `PartialDeep`.
150152
*/
151-
type PartialObjectDeep<ObjectType extends object, Options extends Required<PartialDeepOptions>> = {
153+
type PartialObjectDeep<ObjectType extends object, Options extends Required<PartialDeepOptions>> = Simplify<{
152154
[KeyType in keyof ObjectType]?: _PartialDeep<ObjectType[KeyType], Options>
153-
};
155+
}>;
154156

155157
export {};

source/required-deep.d.ts

Lines changed: 28 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import type {BuiltIns, HasMultipleCallSignatures} from './internal/index.d.ts';
2+
import type {Simplify} from './simplify.d.ts';
23
import type {IsNever} from './is-never.d.ts';
34

45
/**
@@ -42,34 +43,35 @@ Note that types containing overloaded functions are not made deeply required due
4243
@category Set
4344
@category Map
4445
*/
45-
export type RequiredDeep<T> = T extends BuiltIns
46-
? T
47-
: T extends Map<infer KeyType, infer ValueType>
48-
? Map<RequiredDeep<KeyType>, RequiredDeep<ValueType>>
49-
: T extends Set<infer ItemType>
50-
? Set<RequiredDeep<ItemType>>
51-
: T extends ReadonlyMap<infer KeyType, infer ValueType>
52-
? ReadonlyMap<RequiredDeep<KeyType>, RequiredDeep<ValueType>>
53-
: T extends ReadonlySet<infer ItemType>
54-
? ReadonlySet<RequiredDeep<ItemType>>
55-
: T extends WeakMap<infer KeyType, infer ValueType>
56-
? WeakMap<RequiredDeep<KeyType>, RequiredDeep<ValueType>>
57-
: T extends WeakSet<infer ItemType>
58-
? WeakSet<RequiredDeep<ItemType>>
59-
: T extends Promise<infer ValueType>
60-
? Promise<RequiredDeep<ValueType>>
61-
: T extends (...arguments_: any[]) => unknown
62-
? IsNever<keyof T> extends true
63-
? T
64-
: HasMultipleCallSignatures<T> extends true
46+
export type RequiredDeep<T> =
47+
T extends BuiltIns
48+
? T
49+
: T extends Map<infer KeyType, infer ValueType>
50+
? Map<RequiredDeep<KeyType>, RequiredDeep<ValueType>>
51+
: T extends Set<infer ItemType>
52+
? Set<RequiredDeep<ItemType>>
53+
: T extends ReadonlyMap<infer KeyType, infer ValueType>
54+
? ReadonlyMap<RequiredDeep<KeyType>, RequiredDeep<ValueType>>
55+
: T extends ReadonlySet<infer ItemType>
56+
? ReadonlySet<RequiredDeep<ItemType>>
57+
: T extends WeakMap<infer KeyType, infer ValueType>
58+
? WeakMap<RequiredDeep<KeyType>, RequiredDeep<ValueType>>
59+
: T extends WeakSet<infer ItemType>
60+
? WeakSet<RequiredDeep<ItemType>>
61+
: T extends Promise<infer ValueType>
62+
? Promise<RequiredDeep<ValueType>>
63+
: T extends (...arguments_: any[]) => unknown
64+
? IsNever<keyof T> extends true
6565
? T
66-
: ((...arguments_: Parameters<T>) => ReturnType<T>) & RequiredObjectDeep<T>
67-
: T extends object
68-
? RequiredObjectDeep<T>
69-
: unknown;
66+
: HasMultipleCallSignatures<T> extends true
67+
? T
68+
: ((...arguments_: Parameters<T>) => ReturnType<T>) & RequiredObjectDeep<T>
69+
: T extends object
70+
? RequiredObjectDeep<T>
71+
: unknown;
7072

71-
type RequiredObjectDeep<ObjectType extends object> = {
73+
type RequiredObjectDeep<ObjectType extends object> = Simplify<{
7274
[KeyType in keyof ObjectType]-?: RequiredDeep<ObjectType[KeyType]>
73-
};
75+
}>;
7476

7577
export {};

source/set-optional.d.ts

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
import type {Except} from './except.d.ts';
2-
import type {HomomorphicPick} from './internal/index.d.ts';
1+
import type {ExtractCallSignature, HomomorphicPick} from './internal/index.d.ts';
32
import type {Simplify} from './simplify.d.ts';
3+
import type {Except} from './except.d.ts';
44

55
/**
66
Create a type that makes the given keys optional. The remaining keys are kept as is. The sister of the `SetRequired` type.
@@ -28,19 +28,16 @@ type SomeOptional = SetOptional<Foo, 'b' | 'c'>;
2828
@category Object
2929
*/
3030
export type SetOptional<BaseType, Keys extends keyof BaseType> =
31-
(BaseType extends (...arguments_: never) => any
32-
? (...arguments_: Parameters<BaseType>) => ReturnType<BaseType>
33-
: unknown)
34-
& _SetOptional<BaseType, Keys>;
31+
Simplify<ExtractCallSignature<BaseType> & _SetOptional<BaseType, Keys>>;
3532

3633
type _SetOptional<BaseType, Keys extends keyof BaseType> =
3734
BaseType extends unknown // To distribute `BaseType` when it's a union type.
38-
? Simplify<
39-
// Pick just the keys that are readonly from the base type.
35+
? (
36+
// Pick just the keys that are readonly from the base type.
4037
Except<BaseType, Keys> &
41-
// Pick the keys that should be mutable from the base type and make them mutable.
38+
// Pick the keys that should be mutable from the base type and make them mutable.
4239
Partial<HomomorphicPick<BaseType, Keys>>
43-
>
40+
)
4441
: never;
4542

4643
export {};

source/set-readonly.d.ts

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
import type {Except} from './except.d.ts';
2-
import type {HomomorphicPick} from './internal/index.d.ts';
1+
import type {ExtractCallSignature, HomomorphicPick} from './internal/index.d.ts';
32
import type {Simplify} from './simplify.d.ts';
3+
import type {Except} from './except.d.ts';
44

55
/**
66
Create a type that makes the given keys readonly. The remaining keys are kept as is.
@@ -28,17 +28,16 @@ type SomeReadonly = SetReadonly<Foo, 'b' | 'c'>;
2828
@category Object
2929
*/
3030
export type SetReadonly<BaseType, Keys extends keyof BaseType> =
31-
(BaseType extends (...arguments_: never) => any
32-
? (...arguments_: Parameters<BaseType>) => ReturnType<BaseType>
33-
: unknown)
34-
& _SetReadonly<BaseType, Keys>;
31+
Simplify<ExtractCallSignature<BaseType> & _SetReadonly<BaseType, Keys>>;
3532

3633
export type _SetReadonly<BaseType, Keys extends keyof BaseType> =
3734
BaseType extends unknown // To distribute `BaseType` when it's a union type.
38-
? Simplify<
35+
? (
36+
// Pick just the keys that are writable from the base type.
3937
Except<BaseType, Keys> &
38+
// Pick the keys that should be readonly from the base type and make them readonly.
4039
Readonly<HomomorphicPick<BaseType, Keys>>
41-
>
40+
)
4241
: never;
4342

4443
export {};

source/set-required.d.ts

Lines changed: 10 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
1+
import type {ExtractCallSignature, HomomorphicPick, IsArrayReadonly} from './internal/index.d.ts';
2+
import type {IsOptionalKeyOf} from './is-optional-key-of.d.ts';
3+
import type {UnknownArray} from './unknown-array.d.ts';
4+
import type {Simplify} from './simplify.d.ts';
15
import type {Except} from './except.d.ts';
26
import type {If} from './if.d.ts';
3-
import type {HomomorphicPick, IsArrayReadonly} from './internal/index.d.ts';
4-
import type {OptionalKeysOf} from './optional-keys-of.d.ts';
5-
import type {Simplify} from './simplify.d.ts';
6-
import type {UnknownArray} from './unknown-array.d.ts';
77

88
/**
99
Create a type that makes the given keys required. The remaining keys are kept as is. The sister of the `SetOptional` type.
@@ -35,22 +35,19 @@ type ArrayExample = SetRequired<[number?, number?, number?], 0 | 1>;
3535
@category Object
3636
*/
3737
export type SetRequired<BaseType, Keys extends keyof BaseType> =
38-
(BaseType extends (...arguments_: never) => any
39-
? (...arguments_: Parameters<BaseType>) => ReturnType<BaseType>
40-
: unknown)
41-
& _SetRequired<BaseType, Keys>;
38+
Simplify<ExtractCallSignature<BaseType> & _SetRequired<BaseType, Keys>>;
4239

4340
type _SetRequired<BaseType, Keys extends keyof BaseType> =
4441
BaseType extends UnknownArray
4542
? SetArrayRequired<BaseType, Keys> extends infer ResultantArray
4643
? If<IsArrayReadonly<BaseType>, Readonly<ResultantArray>, ResultantArray>
4744
: never
48-
: Simplify<
49-
// Pick just the keys that are optional from the base type.
45+
: (
46+
// Pick just the keys that are optional from the base type.
5047
Except<BaseType, Keys> &
51-
// Pick the keys that should be required from the base type and make them required.
48+
// Pick the keys that should be required from the base type and make them required.
5249
Required<HomomorphicPick<BaseType, Keys>>
53-
>;
50+
);
5451

5552
/**
5653
Remove the optional modifier from the specified keys in an array.
@@ -66,7 +63,7 @@ type SetArrayRequired<
6663
// `TArray` contains no non-rest elements preceding the rest element (e.g., `[...string[]]` or `[...string[], string]`).
6764
? [...Accumulator, ...TArray]
6865
: TArray extends readonly [(infer First)?, ...infer Rest]
69-
? '0' extends OptionalKeysOf<TArray> // If the first element of `TArray` is optional
66+
? IsOptionalKeyOf<TArray, 0> extends true // If the first element of `TArray` is optional
7067
? `${Counter['length']}` extends `${Keys & (string | number)}` // If the current index needs to be required
7168
? SetArrayRequired<Rest, Keys, [...Counter, any], [...Accumulator, First]>
7269
// If the current element is optional, but it doesn't need to be required,

source/simplify.d.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import type {ExtractCallSignature} from './internal/function.d.ts';
2+
13
/**
24
Useful to flatten the type output to improve type hints shown in editors. And also to transform an interface into a type to aide with assignability.
35
@@ -55,6 +57,11 @@ fn(someInterface as Simplify<SomeInterface>); // Good: transform an `interface`
5557
@see {@link SimplifyDeep}
5658
@category Object
5759
*/
58-
export type Simplify<T> = {[KeyType in keyof T]: T[KeyType]} & {};
60+
export type Simplify<Type> =
61+
Type extends unknown
62+
? ExtractCallSignature<Type> & _Simplify<Type>
63+
: never;
64+
65+
type _Simplify<T> = {[K in keyof T]: T[K]} & {};
5966

6067
export {};

test-d/partial-deep.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -114,24 +114,24 @@ expectType<readonly ['foo'] | undefined>(partialDeepNoRecurseIntoArraysBar.reado
114114
type FunctionWithProperties = {(a1: string, a2: number): boolean; p1: string; readonly p2: number};
115115
declare const functionWithProperties: PartialDeep<FunctionWithProperties>;
116116
expectType<boolean>(functionWithProperties('foo', 1));
117-
expectType<{p1?: string; readonly p2?: number}>({} as Simplify<typeof functionWithProperties>); // `Simplify` removes the call signature from `typeof functionWithProperties`
117+
expectType<{p1?: string; readonly p2?: number}>(functionWithProperties);
118118

119119
type FunctionWithProperties2 = {(a1: boolean, ...a2: string[]): number; p1: {p2?: string; p3: {readonly p4?: boolean}}};
120120
declare const functionWithProperties2: PartialDeep<FunctionWithProperties2>;
121121
expectType<number>(functionWithProperties2(true, 'foo', 'bar'));
122-
expectType<{p1?: {p2?: string; p3?: {readonly p4?: boolean}}}>({} as Simplify<typeof functionWithProperties2>);
122+
expectType<((a1: boolean, ...a2: string[]) => number) & {p1?: {p2?: string; p3?: {readonly p4?: boolean}}}>(functionWithProperties2);
123123

124124
type FunctionWithProperties3 = {(): void; p1: {p2?: string; p3: [{p4: number}, string]}};
125125
declare const functionWithProperties3: PartialDeep<FunctionWithProperties3, {recurseIntoArrays: true}>;
126126
expectType<void>(functionWithProperties3());
127-
expectType<{p1?: {p2?: string; p3?: [{p4?: number}?, string?]}}>({} as Simplify<typeof functionWithProperties3>);
127+
expectType<(() => void) & {p1?: {p2?: string; p3?: [{p4?: number}?, string?]}}>(functionWithProperties3);
128128

129-
expectType<{p1?: string[]}>({} as Simplify<PartialDeep<{(): void; p1: string[]}, {allowUndefinedInNonTupleArrays: false}>>);
130-
expectType<{p1?: string[]}>({} as Simplify<PartialDeep<{(): void; p1: string[]}, {allowUndefinedInNonTupleArrays: true}>>);
129+
expectType<(() => void) & {p1?: string[]}>({} as PartialDeep<{(): void; p1: string[]}, {allowUndefinedInNonTupleArrays: false}>);
130+
expectType<(() => void) & {p1?: string[]}>({} as PartialDeep<{(): void; p1: string[]}, {allowUndefinedInNonTupleArrays: true}>);
131131

132132
// Properties within functions containing multiple call signatures are not made partial due to TS limitations, refer https://github.com/microsoft/TypeScript/issues/29732
133133
type FunctionWithProperties4 = {(a1: number): string; (a1: string, a2: number): number; p1: string};
134134
declare const functionWithProperties4: PartialDeep<FunctionWithProperties4>;
135135
expectType<string>(functionWithProperties4(1));
136136
expectType<number>(functionWithProperties4('foo', 1));
137-
expectNotType<{p1?: string}>({} as Simplify<typeof functionWithProperties4>);
137+
expectNotType<((a1: number) => string) & ((a1: string, a2: number) => number) & {p1?: string}>(functionWithProperties4);

0 commit comments

Comments
 (0)