Skip to content
Open
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
30 changes: 30 additions & 0 deletions source/internal/function.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import type {IsUnknown} from '../is-unknown.d.ts';

/**
Test if the given function has multiple call signatures.

Needed to handle the case of a single call signature with properties.

Multiple call signatures cannot currently be supported due to a TypeScript limitation.
@see https://github.com/microsoft/TypeScript/issues/29732
*/
export type HasMultipleCallSignatures<T extends (...arguments_: any[]) => unknown> =
T extends {(...arguments_: infer A): unknown; (...arguments_: infer B): unknown}
? B extends A
? A extends B
? false
: true
: true
: false;

/**
Extract the call signature of an object type.
*/
export type ExtractCallSignature<Type> =
Type extends (this: infer This, ...args: infer Parameters) => infer Return
? IsUnknown<This> extends true // TODO: replace with `FunctionWithMaybeThisParameter`
? (...args: Parameters) => Return
: (this: This, ...args: Parameters) => Return
: unknown;

export {};
1 change: 1 addition & 0 deletions source/internal/index.d.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
export type * from './array.d.ts';
export type * from './characters.d.ts';
export type * from './function.d.ts';
export type * from './keys.d.ts';
export type * from './numeric.d.ts';
export type * from './object.d.ts';
Expand Down
17 changes: 0 additions & 17 deletions source/internal/type.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,23 +22,6 @@ export type IsBothExtends<BaseType, FirstType, SecondType> = FirstType extends B
: false
: false;

/**
Test if the given function has multiple call signatures.

Needed to handle the case of a single call signature with properties.

Multiple call signatures cannot currently be supported due to a TypeScript limitation.
@see https://github.com/microsoft/TypeScript/issues/29732
*/
export type HasMultipleCallSignatures<T extends (...arguments_: any[]) => unknown> =
T extends {(...arguments_: infer A): unknown; (...arguments_: infer B): unknown}
? B extends A
? A extends B
? false
: true
: true
: false;

/**
Returns a boolean for whether the given `boolean` is not `false`.
*/
Expand Down
5 changes: 3 additions & 2 deletions source/partial-deep.d.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type {ApplyDefaultOptions, BuiltIns, HasMultipleCallSignatures} from './internal/index.d.ts';
import type {Simplify} from './simplify.d.ts';
import type {IsNever} from './is-never.d.ts';

/**
Expand Down Expand Up @@ -148,8 +149,8 @@ type PartialReadonlySetDeep<T, Options extends Required<PartialDeepOptions>> = {
/**
Same as `PartialDeep`, but accepts only `object`s as inputs. Internal helper for `PartialDeep`.
*/
type PartialObjectDeep<ObjectType extends object, Options extends Required<PartialDeepOptions>> = {
type PartialObjectDeep<ObjectType extends object, Options extends Required<PartialDeepOptions>> = Simplify<{
[KeyType in keyof ObjectType]?: _PartialDeep<ObjectType[KeyType], Options>
};
}>;

export {};
5 changes: 3 additions & 2 deletions source/required-deep.d.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type {BuiltIns, HasMultipleCallSignatures} from './internal/index.d.ts';
import type {Simplify} from './simplify.d.ts';
import type {IsNever} from './is-never.d.ts';

/**
Expand Down Expand Up @@ -68,8 +69,8 @@ export type RequiredDeep<T> = T extends BuiltIns
? RequiredObjectDeep<T>
: unknown;

type RequiredObjectDeep<ObjectType extends object> = {
type RequiredObjectDeep<ObjectType extends object> = Simplify<{
[KeyType in keyof ObjectType]-?: RequiredDeep<ObjectType[KeyType]>
};
}>;

export {};
17 changes: 7 additions & 10 deletions source/set-optional.d.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type {Except} from './except.d.ts';
import type {HomomorphicPick} from './internal/index.d.ts';
import type {ExtractCallSignature, HomomorphicPick} from './internal/index.d.ts';
import type {Simplify} from './simplify.d.ts';
import type {Except} from './except.d.ts';

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

type _SetOptional<BaseType, Keys extends keyof BaseType> =
BaseType extends unknown // To distribute `BaseType` when it's a union type.
? Simplify<
// Pick just the keys that are readonly from the base type.
? (
// Pick just the keys that are readonly from the base type.
Except<BaseType, Keys> &
// Pick the keys that should be mutable from the base type and make them mutable.
// Pick the keys that should be mutable from the base type and make them mutable.
Partial<HomomorphicPick<BaseType, Keys>>
>
)
: never;

export {};
15 changes: 7 additions & 8 deletions source/set-readonly.d.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type {Except} from './except.d.ts';
import type {HomomorphicPick} from './internal/index.d.ts';
import type {ExtractCallSignature, HomomorphicPick} from './internal/index.d.ts';
import type {Simplify} from './simplify.d.ts';
import type {Except} from './except.d.ts';

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

export type _SetReadonly<BaseType, Keys extends keyof BaseType> =
BaseType extends unknown // To distribute `BaseType` when it's a union type.
? Simplify<
? (
// Pick just the keys that are writable from the base type.
Except<BaseType, Keys> &
// Pick the keys that should be readonly from the base type and make them readonly.
Readonly<HomomorphicPick<BaseType, Keys>>
>
)
: never;

export {};
23 changes: 10 additions & 13 deletions source/set-required.d.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import type {ExtractCallSignature, HomomorphicPick, IsArrayReadonly} from './internal/index.d.ts';
import type {IsOptionalKeyOf} from './is-optional-key-of.d.ts';
import type {UnknownArray} from './unknown-array.d.ts';
import type {Simplify} from './simplify.d.ts';
import type {Except} from './except.d.ts';
import type {If} from './if.d.ts';
import type {HomomorphicPick, IsArrayReadonly} from './internal/index.d.ts';
import type {OptionalKeysOf} from './optional-keys-of.d.ts';
import type {Simplify} from './simplify.d.ts';
import type {UnknownArray} from './unknown-array.d.ts';

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

type _SetRequired<BaseType, Keys extends keyof BaseType> =
BaseType extends UnknownArray
? SetArrayRequired<BaseType, Keys> extends infer ResultantArray
? If<IsArrayReadonly<BaseType>, Readonly<ResultantArray>, ResultantArray>
: never
: Simplify<
// Pick just the keys that are optional from the base type.
: (
// Pick just the keys that are optional from the base type.
Except<BaseType, Keys> &
// Pick the keys that should be required from the base type and make them required.
// Pick the keys that should be required from the base type and make them required.
Required<HomomorphicPick<BaseType, Keys>>
>;
);

/**
Remove the optional modifier from the specified keys in an array.
Expand All @@ -66,7 +63,7 @@ type SetArrayRequired<
// `TArray` contains no non-rest elements preceding the rest element (e.g., `[...string[]]` or `[...string[], string]`).
? [...Accumulator, ...TArray]
: TArray extends readonly [(infer First)?, ...infer Rest]
? '0' extends OptionalKeysOf<TArray> // If the first element of `TArray` is optional
? IsOptionalKeyOf<TArray, 0> extends true // If the first element of `TArray` is optional
? `${Counter['length']}` extends `${Keys & (string | number)}` // If the current index needs to be required
? SetArrayRequired<Rest, Keys, [...Counter, any], [...Accumulator, First]>
// If the current element is optional, but it doesn't need to be required,
Expand Down
9 changes: 8 additions & 1 deletion source/simplify.d.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import type {ExtractCallSignature} from './internal/function.d.ts';

/**
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.

Expand Down Expand Up @@ -55,6 +57,11 @@ fn(someInterface as Simplify<SomeInterface>); // Good: transform an `interface`
@see {@link SimplifyDeep}
@category Object
*/
export type Simplify<T> = {[KeyType in keyof T]: T[KeyType]} & {};
export type Simplify<Type> =
Type extends unknown
? ExtractCallSignature<Type> & _Simplify<Type> // TODO: change to `(_Simplify<Type> extends {} & infer U ? U : never)`
: never;

type _Simplify<T> = {[K in keyof T]: T[K]} & {};

export {};
12 changes: 6 additions & 6 deletions test-d/partial-deep.ts
Original file line number Diff line number Diff line change
Expand Up @@ -114,24 +114,24 @@ expectType<readonly ['foo'] | undefined>(partialDeepNoRecurseIntoArraysBar.reado
type FunctionWithProperties = {(a1: string, a2: number): boolean; p1: string; readonly p2: number};
declare const functionWithProperties: PartialDeep<FunctionWithProperties>;
expectType<boolean>(functionWithProperties('foo', 1));
expectType<{p1?: string; readonly p2?: number}>({} as Simplify<typeof functionWithProperties>); // `Simplify` removes the call signature from `typeof functionWithProperties`
expectType<((a1: string, a2: number) => boolean) & {p1?: string; readonly p2?: number}>(functionWithProperties);

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

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

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

// Properties within functions containing multiple call signatures are not made partial due to TS limitations, refer https://github.com/microsoft/TypeScript/issues/29732
type FunctionWithProperties4 = {(a1: number): string; (a1: string, a2: number): number; p1: string};
declare const functionWithProperties4: PartialDeep<FunctionWithProperties4>;
expectType<string>(functionWithProperties4(1));
expectType<number>(functionWithProperties4('foo', 1));
expectNotType<{p1?: string}>({} as Simplify<typeof functionWithProperties4>);
expectNotType<((a1: number) => string) & ((a1: string, a2: number) => number) & {p1?: string}>(functionWithProperties4);
8 changes: 4 additions & 4 deletions test-d/required-deep.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,21 +65,21 @@ expectType<RequiredDeep<Promise<Promise<{a?: string; b?: [number, number?]}>>>>(
type FunctionWithProperties = {(a1: string, a2: number): boolean; p1?: string; readonly p2?: number};
declare const functionWithProperties: RequiredDeep<FunctionWithProperties>;
expectType<boolean>(functionWithProperties('foo', 1));
expectType<{p1: string; readonly p2: number}>({} as Simplify<typeof functionWithProperties>); // `Simplify` removes the call signature from `typeof functionWithProperties`
expectType<((a1: string, a2: number) => boolean) & {p1: string; readonly p2: number}>(functionWithProperties);

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

type FunctionWithProperties3 = {(): void; p1?: {p2?: string; p3: [{p4?: number}, string?]}};
declare const functionWithProperties3: RequiredDeep<FunctionWithProperties3>;
expectType<void>(functionWithProperties3());
expectType<{p1: {p2: string; p3: [{p4: number}, string]}}>({} as Simplify<typeof functionWithProperties3>);
expectType<(() => void) & {p1: {p2: string; p3: [{p4: number}, string]}}>(functionWithProperties3);

// Properties within functions containing multiple call signatures are not made required due to TS limitations, refer https://github.com/microsoft/TypeScript/issues/29732
type FunctionWithProperties4 = {(a1: number): string; (a1: string, a2: number): number; p1?: string};
declare const functionWithProperties4: RequiredDeep<FunctionWithProperties4>;
expectType<string>(functionWithProperties4(1));
expectType<number>(functionWithProperties4('foo', 1));
expectNotType<{p1: string}>({} as Simplify<typeof functionWithProperties4>);
expectNotType<((a1: number) => string) & ((a1: string, a2: number) => number) & {p1: string}>(functionWithProperties4);
6 changes: 3 additions & 3 deletions test-d/set-optional.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import {expectNotAssignable, expectType} from 'tsd';
import type {SetOptional, Simplify} from '../index.d.ts';
import type {SetOptional} from '../index.d.ts';

// Update one required and one optional to optional.
declare const variation1: SetOptional<{a: number; b?: string; c: boolean}, 'b' | 'c'>;
Expand Down Expand Up @@ -40,11 +40,11 @@ expectType<{[k: string]: unknown; a?: number; b?: string}>(variation9);
// Works with functions containing properties
declare const variation10: SetOptional<{(a1: string, a2: number): boolean; p1: string; readonly p2?: number}, 'p1'>;
expectType<boolean>(variation10('foo', 1));
expectType<{p1?: string; readonly p2?: number}>({} as Simplify<typeof variation10>); // `Simplify` removes the call signature from `typeof variation10`
expectType<((a1: string, a2: number) => boolean) & {p1?: string; readonly p2?: number}>(variation10);

declare const variation11: SetOptional<{(a1: boolean, ...a2: string[]): number; p1: string; readonly p2: number; p3: boolean}, 'p1' | 'p2'>;
expectType<number>(variation11(true, 'foo', 'bar', 'baz'));
expectType<{p1?: string; readonly p2?: number; p3: boolean}>({} as Simplify<typeof variation11>);
expectType<((a1: boolean, ...a2: string[]) => number) & {p1?: string; readonly p2?: number; p3: boolean}>(variation11);

// Functions without properties are returned as is
declare const variation12: SetOptional<(a: string) => number, never>;
Expand Down
6 changes: 3 additions & 3 deletions test-d/set-readonly.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import {expectNotAssignable, expectType} from 'tsd';
import type {SetReadonly, Simplify} from '../index.d.ts';
import type {SetReadonly} from '../index.d.ts';

// Update one readonly and one non readonly to readonly.
declare const variation1: SetReadonly<{a: number; readonly b: string; c: boolean}, 'b' | 'c'>;
Expand Down Expand Up @@ -40,11 +40,11 @@ expectType<{[k: string]: unknown; readonly a: number; readonly b: string}>(varia
// Works with functions containing properties
declare const variation10: SetReadonly<{(a1: string, a2: number): boolean; p1: string; readonly p2?: number}, 'p1'>;
expectType<boolean>(variation10('foo', 1));
expectType<{readonly p1: string; readonly p2?: number}>({} as Simplify<typeof variation10>); // Simplify removes the call signature from `typeof variation10`
expectType<((a1: string, a2: number) => boolean) & {readonly p1: string; readonly p2?: number}>(variation10);

declare const variation11: SetReadonly<{(a1: boolean, ...a2: string[]): number; p1: string; p2?: number; p3: boolean}, 'p1' | 'p2'>;
expectType<number>(variation11(true, 'foo', 'bar', 'baz'));
expectType<{readonly p1: string; readonly p2?: number; p3: boolean}>({} as Simplify<typeof variation11>);
expectType<((a1: boolean, ...a2: string[]) => number) & {readonly p1: string; readonly p2?: number; p3: boolean}>(variation11);

// Functions without properties are returned as is
declare const variation12: SetReadonly<(a: string) => number, never>;
Expand Down
6 changes: 3 additions & 3 deletions test-d/set-required.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import {expectNotAssignable, expectType} from 'tsd';
import type {SetRequired, Simplify} from '../index.d.ts';
import type {SetRequired} from '../index.d.ts';

// Update one required and one optional to required.
declare const variation1: SetRequired<{a?: number; b: string; c?: boolean}, 'b' | 'c'>;
Expand Down Expand Up @@ -44,11 +44,11 @@ expectType<{[k: string]: unknown; a: number; b: string}>(variation10);
// Works with functions containing properties
declare const variation11: SetRequired<{(a1: string, a2: number): boolean; p1?: string; readonly p2: number}, 'p1'>;
expectType<boolean>(variation11('foo', 1));
expectType<{p1: string; readonly p2: number}>({} as Simplify<typeof variation11>);
expectType<((a1: string, a2: number) => boolean) & {p1: string; readonly p2: number}>(variation11);

declare const variation12: SetRequired<{(a1: boolean, ...a2: string[]): number; p1?: string; readonly p2?: number; p3?: boolean}, 'p1' | 'p2'>;
expectType<number>(variation12(true, 'foo', 'bar', 'baz'));
expectType<{p1: string; readonly p2: number; p3?: boolean}>({} as Simplify<typeof variation12>);
expectType<((a1: boolean, ...a2: string[]) => number) & {p1: string; readonly p2: number; p3?: boolean}>(variation12);

// Functions without properties are returned as is
declare const variation13: SetRequired<(a: string) => number, never>;
Expand Down
35 changes: 15 additions & 20 deletions test-d/simplify.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,32 +44,27 @@ expectAssignable<Record<string, unknown>>(valueAsLiteral);
expectAssignable<Record<string, unknown>>(valueAsSimplifiedInterface);
expectNotAssignable<Record<string, unknown>>(valueAsInterface); // Index signature is missing in interface

// The following tests should be fixed once we have determined the cause of the bug reported in https://github.com/sindresorhus/type-fest/issues/436

type SomeFunction = (type: string) => string;
type SimplifiedFunction = Simplify<SomeFunction>; // Return '{}' expected 'SomeFunction'
type SimplifiedFunction = Simplify<SomeFunction>;

declare const someFunction: SimplifiedFunction;

expectNotAssignable<SomeFunction>(someFunction);

// // Should return the original type if it is not simplifiable, like a function.
// type SomeFunction = (type: string) => string;
// expectType<Simplify<SomeFunction>>((type: string) => type);
expectType<(SomeFunction & Record<never, never>)>(someFunction); // TODO: find a way to remove `& {}` without breaking other types.

// class SomeClass {
// id: string;
class SomeClass {
id: string;

// private readonly code: number;
private readonly code: number;

// constructor() {
// this.id = 'some-class';
// this.code = 42;
// }
constructor() {
this.id = 'some-class';
this.code = 42;
}

// someMethod() {
// return this.code;
// }
// }
someMethod() {
return this.code;
}
}

// expectType<Simplify<SomeClass>>(new SomeClass());
expectType<Simplify<SomeClass>>({} as {id: string; someMethod: () => number});
expectType<Simplify<typeof SomeClass>>({prototype: new SomeClass()});