Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
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
Next Next commit
feat: add FunctionOverloads type
  • Loading branch information
andreww2012 committed Oct 9, 2025
commit 61ef2cc502d47c66ee45b67f8daf314ab548f263
1 change: 1 addition & 0 deletions index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ export type {MultidimensionalReadonlyArray} from './source/multidimensional-read
export type {IterableElement} from './source/iterable-element.d.ts';
export type {Entry} from './source/entry.d.ts';
export type {Entries} from './source/entries.d.ts';
export type {FunctionOverloads} from './source/function-overloads.d.ts';
export type {SetReturnType} from './source/set-return-type.d.ts';
export type {SetParameterType} from './source/set-parameter-type.d.ts';
export type {Asyncify} from './source/asyncify.d.ts';
Expand Down
8 changes: 6 additions & 2 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -145,8 +145,6 @@ Click the type names for complete docs.
- [`IterableElement`](source/iterable-element.d.ts) - Get the element type of an `Iterable`/`AsyncIterable`. For example, `Array`, `Set`, `Map`, generator, stream, etc.
- [`Entry`](source/entry.d.ts) - Create a type that represents the type of an entry of a collection.
- [`Entries`](source/entries.d.ts) - Create a type that represents the type of the entries of a collection.
- [`SetReturnType`](source/set-return-type.d.ts) - Create a function type with a return type of your choice and the same parameters as the given function type.
- [`SetParameterType`](source/set-parameter-type.d.ts) - Create a function that replaces some parameters with the given parameters.
- [`Simplify`](source/simplify.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.
- [`SimplifyDeep`](source/simplify-deep.d.ts) - Deeply simplifies an object type.
- [`Get`](source/get.d.ts) - Get a deeply-nested property from an object using a key path, like [Lodash's `.get()`](https://lodash.com/docs/latest#get) function.
Expand Down Expand Up @@ -233,6 +231,12 @@ Click the type names for complete docs.
- [`AsyncReturnType`](source/async-return-type.d.ts) - Unwrap the return type of a function that returns a `Promise`.
- [`Asyncify`](source/asyncify.d.ts) - Create an async version of the given function type.

### Function

- [`FunctionOverloads`](source/function-overloads.d.ts) - Create a union of all the function's overloads.
- [`SetReturnType`](source/set-return-type.d.ts) - Create a function type with a return type of your choice and the same parameters as the given function type.
- [`SetParameterType`](source/set-parameter-type.d.ts) - Create a function that replaces some parameters with the given parameters.

### String

- [`Trim`](source/trim.d.ts) - Remove leading and trailing spaces from a string.
Expand Down
79 changes: 79 additions & 0 deletions source/function-overloads.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import type {FunctionWithMaybeThisParameter} from './internal/function.d.ts';
import type {IsEqual} from './is-equal.d.ts';

/**
Create a union of all the function's overloads.
@example
```ts
import type {FunctionOverloads} from 'type-fest';
function request(url: string): Promise<string>;
function request(url: string, options: {json: true}): Promise<unknown>;
function request(url: string, options?: {json?: boolean}) {
// ...
}
type RequestFunctionType = FunctionOverloads<typeof request>;
//=> ((url: string) => Promise<string>) | ((url: string, options: {json: true}) => Promise<unknown>)
```
This type can be used to extract event types of a Vue component:
@example
```ts
// Given a component `HelloWorld` defines its events as follows:
defineEmits<{
submit: [formData: FormData];
cancel: [];
}>();
// Extract the type of `submit` event like this:
import type {ArrayTail, FunctionOverloads} from 'type-fest';
import HelloWorld from './HelloWorld.vue';
type SubmitEventType = ArrayTail<Parameters<Extract<FunctionOverloads<InstanceType<typeof HelloWorld>['$emit']>, (event: 'submit', ...args: any[]) => void>>>;
//=> [formData: FormData]
```
@category Function
*/
export type FunctionOverloads<T> = FunctionOverloadsInternal<T>;

type FunctionOverloadsInternal<
AllOverloads,
CheckedOverloads = {},
MustStopIfParametersAreEqual extends boolean = true,
LastParameters = never,
> = AllOverloads extends (
this: infer ThisType,
...args: infer ParametersType extends readonly unknown[]
) => infer ReturnType
? // This simultaneously checks if the last and the current parameters are equal and `MustStopIfParametersAreEqual` flag is true
IsEqual<
[LastParameters, true],
[
ParametersType,
[MustStopIfParametersAreEqual] extends [true] ? true : false, // Prevents distributivity
]
> extends true
? never
:
| FunctionOverloadsInternal<
// Normally, in `(FunctionType extends (...args: infer P) => infer R)`, compiler infers
// `P` and `R` from the last function overload.
// This trick (intersecting one of the function signatures with the full signature)
// makes compiler infer a last overload that do not equal one of the concatenated ones.
// Thus, we're ending up iterating over all the overloads from bottom to top.
// Credits: https://github.com/microsoft/TypeScript/issues/14107#issuecomment-1146738780
CheckedOverloads & AllOverloads,
CheckedOverloads & ((this: ThisType, ...args: ParametersType) => ReturnType),
MustStopIfParametersAreEqual extends true ? false : true,
ParametersType
>
| FunctionWithMaybeThisParameter<ThisType, ParametersType, ReturnType>
: never;

export {};
18 changes: 18 additions & 0 deletions source/internal/function.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import type {IsUnknown} from '../is-unknown.d.ts';

/**
Constructs a function type with an optional `this` parameter.
If `this` parameter is not needed, pass `unknown`.
*/
export type FunctionWithMaybeThisParameter<
ThisParameter,
Parameters_ extends readonly unknown[],
TypeToReturn,
> =
// If a function does not specify the `this` parameter, it will be inferred to `unknown`
IsUnknown<ThisParameter> extends true
? (...args: Parameters_) => TypeToReturn
: (this: ThisParameter, ...args: Parameters_) => TypeToReturn;

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
20 changes: 10 additions & 10 deletions source/set-parameter-type.d.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import type {IsUnknown} from './is-unknown.d.ts';
import type {StaticPartOfArray, VariablePartOfArray} from './internal/index.d.ts';
import type {FunctionWithMaybeThisParameter, StaticPartOfArray, VariablePartOfArray} from './internal/index.d.ts';
import type {UnknownArray} from './unknown-array.d.ts';

/**
Expand Down Expand Up @@ -104,16 +103,17 @@ type HandleLog2 = SetParameterType<HandleMessage, {2: string}>;

@category Function
*/
export type SetParameterType<Function_ extends (...arguments_: any[]) => unknown, P extends Record<number, unknown>> =
export type SetParameterType<
Function_ extends (...arguments_: any[]) => unknown,
P extends Record<number, unknown>,
> =
// Just using `Parameters<Fn>` isn't ideal because it doesn't handle the `this` fake parameter.
Function_ extends (this: infer ThisArgument, ...arguments_: infer Arguments) => unknown
? (
// If a function did not specify the `this` fake parameter, it will be inferred to `unknown`.
// We want to detect this situation just to display a friendlier type upon hovering on an IntelliSense-powered IDE.
IsUnknown<ThisArgument> extends true
? (...arguments_: MergeObjectToArray<Arguments, P>) => ReturnType<Function_>
: (this: ThisArgument, ...arguments_: MergeObjectToArray<Arguments, P>) => ReturnType<Function_>
)
? FunctionWithMaybeThisParameter<
ThisArgument,
MergeObjectToArray<Arguments, P>,
ReturnType<Function_>
>
: Function_; // This part should be unreachable

export {};
14 changes: 5 additions & 9 deletions source/set-return-type.d.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type {IsUnknown} from './is-unknown.d.ts';
import type {FunctionWithMaybeThisParameter} from './internal/index.d.ts';

/**
Create a function type with a return type of your choice and the same parameters as the given function type.
Expand All @@ -19,13 +19,9 @@ type MyWrappedFunction = SetReturnType<MyFunctionThatCanThrow, SomeOtherType | u
*/
export type SetReturnType<Function_ extends (...arguments_: any[]) => any, TypeToReturn> =
// Just using `Parameters<Fn>` isn't ideal because it doesn't handle the `this` fake parameter.
Function_ extends (this: infer ThisArgument, ...arguments_: infer Arguments) => any ? (
// If a function did not specify the `this` fake parameter, it will be inferred to `unknown`.
// We want to detect this situation just to display a friendlier type upon hovering on an IntelliSense-powered IDE.
IsUnknown<ThisArgument> extends true ? (...arguments_: Arguments) => TypeToReturn : (this: ThisArgument, ...arguments_: Arguments) => TypeToReturn
) : (
// This part should be unreachable, but we make it meaningful just in case…
(...arguments_: Parameters<Function_>) => TypeToReturn
);
Function_ extends (this: infer ThisArgument, ...arguments_: infer Arguments) => any
? FunctionWithMaybeThisParameter<ThisArgument, Arguments, TypeToReturn>
: // This part should be unreachable, but we make it meaningful just in case…
(...arguments_: Parameters<Function_>) => TypeToReturn;

export {};
100 changes: 100 additions & 0 deletions test-d/function-overloads.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
import {expectType} from 'tsd';
import type {FunctionOverloads} from '../source/function-overloads.d.ts';

type Function1 = (foo: string, bar: number) => object;
type Function2 = (foo: bigint, ...bar: any[]) => void;

declare const normalFunction: FunctionOverloads<Function1>;
expectType<Function1>(normalFunction);

// Note: function overload is equivalent to intersecting its signatures

declare const twoOverloads: FunctionOverloads<Function1 & Function2>;
expectType<Function1 | Function2>(twoOverloads);

declare const twoIdenticalOverloads: FunctionOverloads<Function1>;
expectType<Function1>(twoIdenticalOverloads);

type Function3 = (foo: string, bar: number, baz?: boolean) => object;

declare const twoOverloadWithAssignableSignature: FunctionOverloads<Function1 & Function3>;
expectType<Function1 | Function3>(twoOverloadWithAssignableSignature);

declare const threeOverloads: FunctionOverloads<Function1 & Function2 & Function3>;
expectType<Function1 | Function2 | Function3>(threeOverloads);

type Function4 = (...foo: any[]) => void;
type Function5 = (...foo: readonly any[]) => void;

declare const normalFunctionWithOnlyRestWritableParameter: FunctionOverloads<Function4>;
expectType<Function4>(normalFunctionWithOnlyRestWritableParameter);

declare const normalFunctionWithOnlyRestReadonlyParameter: FunctionOverloads<Function5>;
expectType<Function5>(normalFunctionWithOnlyRestReadonlyParameter);

declare const twoOverloadsWithDifferentRestParameterReadonliness: FunctionOverloads<
Function4 & Function5
>;
// Expected: it seems like the compiler ignores subsequent identical up to `readonly` modifier overloads
expectType<Function4>(twoOverloadsWithDifferentRestParameterReadonliness);

declare const twoOverloadsWithDifferentRestParameterReadonlinessReversed: FunctionOverloads<
Function5 & Function4
>;
// Expected: it seems like the compiler ignores subsequent identical up to `readonly` modifier overloads
expectType<Function5>(twoOverloadsWithDifferentRestParameterReadonlinessReversed);

type Function6 = (foo: string, ...bar: any[]) => void;
type Function7 = (foo: string, ...bar: readonly any[]) => void;

declare const normalFunctionWithNormalAndRestWritableParameter: FunctionOverloads<Function6>;
expectType<Function6>(normalFunctionWithNormalAndRestWritableParameter);

declare const normalFunctionWithNormalAndRestReadonlyParameter: FunctionOverloads<Function7>;
// Expected: readonly rest parameter cannot be represented with tuples
expectType<(foo: string, ...bar: any[]) => void>(normalFunctionWithNormalAndRestReadonlyParameter);

type Function8 = () => never;

declare const normalFunctionNoParameters: FunctionOverloads<Function8>;
expectType<Function8>(normalFunctionNoParameters);

declare const twoOverloadsWithNoAndPresentParameters: FunctionOverloads<Function8 & Function6>;
expectType<Function8 | Function6>(twoOverloadsWithNoAndPresentParameters);

type Function9 = (event: 'event9', arg: string) => void;
type Function10 = (event: 'event10', arg: number) => string;
type Function11 = (event: 'event11', arg: boolean) => never;
type Function12 = (event: 'event12', arg: bigint) => object;

declare const manyOverloads: FunctionOverloads<
Function1 &
Function2 &
Function3 &
Function4 &
Function5 &
Function6 &
Function7 &
Function8 &
Function9 &
Function10 &
Function11 &
Function12
>;
expectType<
| Function1
| Function2
| Function3
| Function4
| Function5
| Function6
| Function7
| Function8
| Function9
| Function10
| Function11
| Function12
>(manyOverloads);

declare const noOveloads: FunctionOverloads<{}>;
expectType<never>(noOveloads);