Skip to content
Open
Show file tree
Hide file tree
Changes from 5 commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
6b8d049
Add `FunctionOverloads` type
ikeyan Apr 8, 2026
4020509
Add `IsEqualStrict` to distinguish implicit `this` from `this: unknown`
ikeyan Apr 8, 2026
19912c9
Convert `type _Test*` assertions to `expectType` style
ikeyan Apr 8, 2026
508c9f8
Add `Function` category to readme
ikeyan Apr 8, 2026
e519f33
Refactor internals: use tuple representation and mapped types
ikeyan Apr 9, 2026
f6ba682
Rewrite `CollectOverloads` with `HasExplicitThis`/`LastOverload`
ikeyan Apr 10, 2026
732302e
Move internals to `source/internal/function.d.ts`, fix docs
ikeyan Apr 11, 2026
271492f
Rename to `Overloads`, add `OverloadParameters`/`OverloadReturnType`,…
ikeyan Apr 11, 2026
a896a7b
Update function.d.ts
sindresorhus Apr 12, 2026
a3e267b
Clean up comment style and formatting
ikeyan Apr 12, 2026
e2b8ed3
Replace mdash with ASCII punctuation
ikeyan Apr 12, 2026
2deba74
`Overloads`: Extract overloads preceding generic overloads via N-adva…
ikeyan Apr 14, 2026
9d00440
Merge branch 'main' into feat/function-overloads-v2
ikeyan Apr 14, 2026
1396b74
Update `Overloads` description in readme
ikeyan Apr 14, 2026
5488b7d
Merge `CollectOverloadsLoop` into `CollectOverloads`
ikeyan Apr 15, 2026
d6c1d02
Move sentinel check from `NthLastOverload` to `CollectOverloads`
ikeyan Apr 21, 2026
d7beb1c
Split `Overloads`, `OverloadParameters`, and `OverloadReturnType` int…
ikeyan Apr 21, 2026
3cc06b8
Move `NthLastOverload` and `CollectOverloads` into overloads.d.ts
ikeyan Apr 21, 2026
6cc8866
Rename `Parameters` -> `FunctionParameters`
ikeyan Apr 21, 2026
f4d5a74
Put type parameters in different lines
ikeyan Apr 21, 2026
667aa60
Add tests for internal/function.d.ts
ikeyan Apr 21, 2026
2d2dbd4
Let `Overloads<any>` be any
ikeyan Apr 21, 2026
93b1a40
Add limitation comment
ikeyan Apr 21, 2026
398317f
Merge commit 'a5491644b32160f804dd10d0b44dad461037f4c1' into feat/fun…
ikeyan Apr 21, 2026
950c792
Update readme.md
ikeyan Apr 21, 2026
64c3373
Merge remote-tracking branch 'upstream/main' into feat/function-overl…
claude Apr 27, 2026
6282d78
refactor: cleanup `overloads.d.ts`
som-sm Jun 7, 2026
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 @@ -69,6 +69,7 @@ export type {Entry} from './source/entry.d.ts';
export type {Entries} from './source/entries.d.ts';
export type {SetReturnType} from './source/set-return-type.d.ts';
export type {SetParameterType} from './source/set-parameter-type.d.ts';
export type {FunctionOverloads} from './source/function-overloads.d.ts';
export type {Asyncify} from './source/asyncify.d.ts';
export type {Simplify} from './source/simplify.d.ts';
export type {SimplifyDeep} from './source/simplify-deep.d.ts';
Expand Down
8 changes: 6 additions & 2 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -158,8 +158,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 @@ -246,6 +244,12 @@ Click the type names for complete docs.

- [`StructuredCloneable`](source/structured-cloneable.d.ts) - Matches a value that can be losslessly cloned using `structuredClone`.

### Function

- [`FunctionOverloads`](source/function-overloads.d.ts) - Create a union of all overload signatures of the given function type.
- [`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.

### Async

- [`Promisable`](source/promisable.d.ts) - Create a type that represents either the value or the value wrapped in `PromiseLike`.
Expand Down
117 changes: 117 additions & 0 deletions source/function-overloads.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
import type {IsAny} from './is-any.d.ts';
import type {IsEqual} from './is-equal.d.ts';
import type {IsUnknown} from './is-unknown.d.ts';
import type {UnknownArray} from './unknown-array.d.ts';

/**
Create a union of all overload signatures of the given function type.

TypeScript's built-in utility types like `Parameters` and `ReturnType` only work with the last overload signature, [by design](https://github.com/microsoft/TypeScript/issues/32164). This type extracts all overload signatures as a union, allowing you to work with each overload individually.

Use-cases:
- Extract parameter types from specific overloads using `Extract` and `Parameters`
- Analyze all possible function signatures in type-level code
- Extract event handler signatures from framework APIs

Known limitations:
- Generic type parameters are lost and inferred as `unknown`
- When overloads share identical parameters but differ only in the `this` parameter, the implicit `this` (no `this` annotation) overload may be merged with an explicit `this: unknown` overload. See tests for detailed behavior.

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

declare function request(url: string): Promise<string>;
declare function request(url: string, options: {json: true}): Promise<unknown>;

type RequestOverloads = FunctionOverloads<typeof request>;
//=> ((url: string) => Promise<string>) | ((url: string, options: {
// json: true;
// }) => Promise<unknown>)

// You can also get all parameters and return types using built-in `Parameters` and `ReturnType` utilities:

type RequestParameters = Parameters<RequestOverloads>;
//=> [url: string] | [url: string, options: {json: true}]

type RequestReturnType = ReturnType<RequestOverloads>;
//=> Promise<string> | Promise<unknown>
```

@see https://github.com/microsoft/TypeScript/issues/14107
@see https://github.com/microsoft/TypeScript/issues/32164

@category Function
*/
export type FunctionOverloads<FunctionType> = FunctionType extends unknown
Comment thread
ikeyan marked this conversation as resolved.
Outdated
? IsAny<FunctionType> extends true
? (...arguments_: readonly unknown[]) => unknown
Comment thread
ikeyan marked this conversation as resolved.
Outdated
: DistinguishUnknownThisOverloads<FunctionType>[number]
: never;

declare const nothing: unique symbol;
type Nothing = typeof nothing;
type AnyOverload = [This: unknown, Parameters: UnknownArray, Return: unknown];

/**
Iterates over all overload signatures from bottom to top, collecting each as a `[This, Parameters, Return]` tuple in declaration order.

The termination condition (`CheckedOverloads === PreviousCheckedOverloads`) lags by one iteration, so the last extracted overload is always a duplicate. We drop it by removing the first element of the result tuple at termination.

It also builds up a "secondary" function type where implicit-`this` overloads have their `this` replaced with `Nothing`, enabling later disambiguation between implicit `this` and explicit `this: unknown`.

@see https://github.com/microsoft/TypeScript/issues/32164#issuecomment-1146737709
*/
type CollectOverloads<
AllOverloads,
CheckedOverloads = unknown,
PreviousCheckedOverloads = never,
ResultOverloads extends AnyOverload[] = [],
ResultFunctionType = AllOverloads,
> =
IsEqual<CheckedOverloads, PreviousCheckedOverloads> extends true
? [ResultOverloads extends [AnyOverload, ...infer Rest extends AnyOverload[]] ? Rest : [], ResultFunctionType]
: AllOverloads extends (this: infer This, ...arguments_: infer Parameters_ extends UnknownArray) => infer Return
? CollectOverloads<
// Intersecting one signature with the full type makes the compiler infer a different "last overload"
// each iteration, effectively iterating all overloads from bottom to top.
((this: This, ...arguments_: Parameters_) => Return) & AllOverloads,
((this: This, ...arguments_: Parameters_) => Return) & CheckedOverloads,
CheckedOverloads,
[[This, Parameters_, Return], ...ResultOverloads],
IsUnknown<This> extends true
? ((this: Nothing, ...arguments_: Parameters_) => Return) & ResultFunctionType
: ResultFunctionType
>
: never;

/**
Maps a tuple of `[This, Parameters, Return]` overloads into a tuple of function types, omitting the `this` parameter for overloads that did not explicitly declare one.

For each overload whose `this` is `unknown`, the second-pass tuple is consulted to determine whether the `this: unknown` was explicit (present in the second pass) or implicit (absent). Implicit-`this` overloads have their `this` parameter stripped.
*/
type OverloadsToFunctions<
Overloads extends AnyOverload[],
SecondPassOverloads extends AnyOverload[],
> = {
[K in keyof Overloads]: Overloads[K] extends infer Overload extends AnyOverload
? IsUnknown<Overload[0]> extends true
? true extends {
[J in keyof SecondPassOverloads]: IsEqual<Overload, SecondPassOverloads[J]>
}[number]
? (this: Overload[0], ...arguments_: Overload[1]) => Overload[2]
: (...arguments_: Overload[1]) => Overload[2]
: (this: Overload[0], ...arguments_: Overload[1]) => Overload[2]
: never
};

/**
Orchestrates the two-pass approach: collects overloads, then maps them to proper function types as a tuple.
*/
type DistinguishUnknownThisOverloads<
FunctionType,
Overloads extends AnyOverload[] = CollectOverloads<FunctionType>[0],
SecondPassOverloads extends AnyOverload[] = CollectOverloads<CollectOverloads<FunctionType>[1]>[0],
> = OverloadsToFunctions<Overloads, SecondPassOverloads>;

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

// Neither `expectType` nor `IsEqual` can distinguish implicit `this` from explicit `this: unknown`:
// expectType<() => void>(x as (this: unknown) => void); // no error
// IsEqual<() => void, (this: unknown) => void> // => true
// `IsEqualStrict` resolves this by also comparing the extracted `this` parameter via a sentinel type.
declare const isEqualStrictNothing: unique symbol;
type IsEqualStrictNothing = typeof isEqualStrictNothing;
type FuncToTuple<Function_ extends (...arguments_: never) => unknown> =
Function_ extends (...arguments_: infer Parameters_ extends UnknownArray) => infer Return
? ((this: IsEqualStrictNothing, ...arguments_: Parameters_) => Return) & Function_ extends (this: infer This, ...arguments_: never) => unknown
? [This, Parameters_, Return]
: never
: never;
type IsEqualStrict<
Function1_ extends (...arguments_: never) => unknown,
Function2_ extends (...arguments_: never) => unknown,
> = IsEqual<[Function1_, FuncToTuple<Function1_>], [Function2_, FuncToTuple<Function2_>]>;

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

// Single function (no overload)
declare const normalFunction: FunctionOverloads<Function1>;
expectType<Function1>(normalFunction);

// Two overloads via intersection
declare const twoOverloads: FunctionOverloads<Function1 & Function2>;
expectType<Function1 | Function2>(twoOverloads);

// Two overloads via interface syntax
declare const twoOverloadsInterface: FunctionOverloads<{
(foo: string, bar: number): object;
(foo: bigint, ...bar: any[]): void;
}>;
expectType<Function1 | Function2>(twoOverloadsInterface);

// Identical overloads collapse
declare const twoIdenticalOverloads: FunctionOverloads<{
(foo: string, bar: number): object;
(foo: string, bar: number): object;
}>;
expectType<Function1>(twoIdenticalOverloads);

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

// Two overloads with assignable but distinct signatures
declare const twoOverloadsWithAssignableSignature: FunctionOverloads<Function1 & Function3>;
expectType<Function1 | Function3>(twoOverloadsWithAssignableSignature);

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

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

// Rest parameter overloads
declare const normalFunctionWithOnlyRestWritableParameter: FunctionOverloads<Function4>;
expectType<Function4>(normalFunctionWithOnlyRestWritableParameter);

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

// The compiler ignores subsequent identical-up-to-readonly overloads
declare const twoOverloadsWithDifferentRestParameterReadonliness: FunctionOverloads<Function4 & Function5>;
expectType<Function4>(twoOverloadsWithDifferentRestParameterReadonliness);

declare const twoOverloadsWithDifferentRestParameterReadonlinessReversed: FunctionOverloads<Function5 & Function4>;
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);

// Readonly rest parameter cannot be represented with tuples
declare const normalFunctionWithNormalAndRestReadonlyParameter: FunctionOverloads<Function7>;
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', argument: string) => void;
type Function10 = (event: 'event10', argument: number) => string;
type Function11 = (event: 'event11', argument: boolean) => never;
type Function12 = (event: 'event12', argument: bigint) => object;

// Many overloads
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);

// Non-callable type returns never
declare const noOverloads: FunctionOverloads<{}>;
expectType<never>(noOverloads);

// Edge case: `any` returns a generic function signature
declare const anyOverload: FunctionOverloads<any>;
expectType<(...arguments_: readonly unknown[]) => unknown>(anyOverload);

// Edge case: `never` returns never
declare const neverOverload: FunctionOverloads<never>;
expectType<never>(neverOverload);

// Edge case: `unknown` returns never
declare const unknownOverload: FunctionOverloads<unknown>;
expectType<never>(unknownOverload);

// `declare function` overloads
declare function declaredOverload(input: string): {kind: 'string'};
declare function declaredOverload(input: number, flag: boolean): {kind: 'number'};

declare const declaredOverloadResult: FunctionOverloads<typeof declaredOverload>;
expectType<((input: string) => {kind: 'string'}) | ((input: number, flag: boolean) => {kind: 'number'})>(declaredOverloadResult);

// Overloads with explicit `this` parameters
type ThisOverload1 = (this: Date, foo: string) => void;
type ThisOverload2 = (this: URL, foo: number) => void;

declare const thisOverloads: FunctionOverloads<ThisOverload1 & ThisOverload2>;
// Verify `this` is preserved
expectType<ThisOverload1 | ThisOverload2>(thisOverloads);

// Same parameters, different return types
type SameParametersDifferentReturn1 = (foo: string) => string;
type SameParametersDifferentReturn2 = (foo: string) => number;

declare const sameParametersDifferentReturn: FunctionOverloads<SameParametersDifferentReturn1 & SameParametersDifferentReturn2>;
expectType<SameParametersDifferentReturn1 | SameParametersDifferentReturn2>(sameParametersDifferentReturn);

// Generic overloads — generic parameters become `unknown`
declare function genericOverload<T>(input: T): T;
declare function genericOverload(input: string): string;

declare const genericOverloadResult: FunctionOverloads<typeof genericOverload>;
expectType<((input: unknown) => unknown) | ((input: string) => string)>(genericOverloadResult);

// Interface-style overload
type InterfaceOverload = {
(input: string): 1;
(input: number): 2;
};

declare const interfaceOverload: FunctionOverloads<InterfaceOverload>;
expectType<((input: string) => 1) | ((input: number) => 2)>(interfaceOverload);

// Same parameters, different `this` types
type SameParametersDifferentThis1 = (this: Date, foo: string) => number;
type SameParametersDifferentThis2 = (this: URL, foo: string) => number;

declare const sameParametersDifferentThis: FunctionOverloads<SameParametersDifferentThis1 & SameParametersDifferentThis2>;
expectType<SameParametersDifferentThis1 | SameParametersDifferentThis2>(sameParametersDifferentThis);

// Duplicate overloads in interface are collapsed
declare const duplicateOverloads: FunctionOverloads<{
(foo: string, bar: number): object;
(): string;
(): string;
}>;
expectType<Function1 | (() => string)>(duplicateOverloads);

// Generic overload at intersection level stops iteration — only the last inferred overload
declare const genericIntersectionOverload: FunctionOverloads<((this: string) => string) & (<T>(this: T, argument: T) => T)>;
expectType<(this: unknown, argument: unknown) => unknown>(genericIntersectionOverload);

// Verify that explicit `this: unknown` is preserved while implicit `this` is omitted.
// `IsEqualStrict` is needed here because neither `expectType` nor `IsEqual` can distinguish the two.

type Function1WithThis<This> = (this: This, foo: string, bar: number) => object;
type Function2WithThis<This> = (this: This, foo: bigint, ...bar: any[]) => void;

// Single overload with explicit `this: unknown`
expectType<IsEqualStrict<
FunctionOverloads<Function1WithThis<unknown>>,
Function1WithThis<unknown>
>>(true);

// Single overload with implicit `this`
expectType<IsEqualStrict<
FunctionOverloads<Function1>,
Function1
>>(true);

// Mixed explicit `this: unknown` and implicit `this` overloads
expectType<IsEqualStrict<
FunctionOverloads<Function1WithThis<unknown> & Function2>,
Function1WithThis<unknown> | Function2
>>(true);

// Multiple explicit and implicit `this` overloads
expectType<IsEqualStrict<
FunctionOverloads<{
(this: unknown, foo: string, bar: number): object;
(this: unknown, foo: bigint, ...bar: any[]): void;
(foo: string, bar: number, baz?: boolean): object;
(...foo: any[]): void;
}>,
| Function1WithThis<unknown>
| Function2WithThis<unknown>
| Function3
| Function4
>>(true);

// When implicit `this` and explicit `this: unknown` overloads share same params/return, implicit may be lost
expectType<IsEqualStrict<FunctionOverloads<Function1 & Function1WithThis<1>>, Function1 | Function1WithThis<1>>>(true);
// When the explicit `this` overload comes first, the implicit `this` overload may be absorbed
expectType<IsEqualStrict<FunctionOverloads<Function1WithThis<1> & Function1>, Function1WithThis<1>>>(true);
// With `this: unknown` specifically, implicit `this` is always absorbed
expectType<IsEqualStrict<FunctionOverloads<Function1 & Function1WithThis<unknown>>, Function1WithThis<unknown>>>(true);
expectType<IsEqualStrict<FunctionOverloads<Function1WithThis<unknown> & Function1>, Function1WithThis<unknown>>>(true);