Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
3 changes: 3 additions & 0 deletions index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,9 @@ 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 {Overloads} from './source/overloads.d.ts';
export type {OverloadParameters} from './source/overload-parameters.d.ts';
export type {OverloadReturnType} from './source/overload-return-type.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
10 changes: 8 additions & 2 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -159,8 +159,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 describes a single key-value pair produced when calling a collection’s `entries` method.
- [`Entries`](source/entries.d.ts) - Create a type that describes the key-value pairs produced when calling a collection’s `entries` method.
- [`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 @@ -248,6 +246,14 @@ Click the type names for complete docs.

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

### Function

- [`Overloads`](source/overloads.d.ts) - Extract all overload signatures of the given function type as a tuple, preserving declaration order.
- [`OverloadParameters`](source/overload-parameters.d.ts) - Extract the parameter types of all overloads as a union.
- [`OverloadReturnType`](source/overload-return-type.d.ts) - Extract the return types of all overloads as a union.
- [`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
61 changes: 61 additions & 0 deletions source/internal/function.d.ts
Comment thread
ikeyan marked this conversation as resolved.
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import type {IsEqual} from '../is-equal.d.ts';
import type {IsUnknown} from '../is-unknown.d.ts';
import type {UnknownArray} from '../unknown-array.d.ts';

/**
Obtain the parameters of a function type in a tuple.

This works even when the parameters type is a readonly array.
*/
export type FunctionParameters<T extends (...args: any) => any> = T extends (...args: infer P extends UnknownArray) => any ? P : never;

/*
Internal documentation: TypeScript's overload enumeration behavior
========================================================================

Understanding how TypeScript enumerates overloads from an intersection type is essential for understanding (and correctly using) `CollectOverloads`.

## Overload enumeration

Given an intersection of function types (e.g. `F1 & F2 & F3`), TypeScript builds an overload list by scanning left to right and **deduplicating**:

- Two overloads with the same (This, Parameters, Return) are considered duplicates. The first one wins; later ones are dropped.
- HOWEVER, if one or both of the overloads has **implicit `this`** (i.e. no `this` annotation), the comparison ignores `This` and only checks (Parameters, Return). This means an implicit-`this` overload and an explicit-`this` overload with the same params/return are considered duplicates, and whichever appears first wins.

Example (F1 = implicit this, F1wT<T> = explicit this: T, same params/return):

F1 & F1wT<1> & F1wT<2>
→ Enumerated as: [F1]
F1wT<1> has same (P,R) as F1 (implicit) → duplicate, dropped.
F1wT<2> likewise dropped.

F1wT<1> & F1 & F1wT<2>
→ Enumerated as: [F1wT<1>, F1wT<2>]
F1 has same (P,R) as F1wT<1>, and F1 is implicit → duplicate, dropped.
F1wT<2> vs F1wT<1>: both explicit, This differs → NOT duplicate, kept.

## Pattern matching: `X extends (this: T, ...args: P) => R`

TypeScript enumerates the overloads of X as above, replaces implicit `this` with `this: unknown`, then matches the **rightmost** overload satisfying the constraint on the right-hand side.
*/

declare const unique: unique symbol;
type Unique = typeof unique;

/**
Detect whether an overload with the given (This, Parameters, Return) has an explicit `this` annotation in the original function type.

Intersects `(this: Unique, ...args: Parameters) => Return` onto the function type from the right. Per TypeScript's deduplication rules (see "Overload enumeration" above), implicit `this` absorbs the sentinel (same (P,R), one implicit -- first-wins), so `ThisParameterType` stays `unknown`. Explicit `this: unknown` does not absorb it (both explicit, different `This` -- not duplicate), so the sentinel becomes the last overload and `ThisParameterType` returns `Unique`.

Limitation: This does not work for generic overloads. There is no way to extract a generic overload while preserving its type parameters, so this type receives the already-instantiated (concrete) signature and cannot recover the original `this` annotation. As a result, generic overloads always appear with explicit `this: unknown` in the `Overloads` output.
*/
export type HasExplicitThis<
FunctionType extends (...args: any) => any,
This,
Parameters_ extends UnknownArray,
Return,
> = IsUnknown<This> extends true
? IsEqual<ThisParameterType<FunctionType & ((this: Unique, ...args: Parameters_) => Return)>, Unique>
: true;

export {};
1 change: 1 addition & 0 deletions source/internal/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,6 @@ export type * from './string.d.ts';
export type * from './tuple.d.ts';
export type * from './type.d.ts';
export type * from './enforce-optional.d.ts';
export type * from './function.d.ts';

export {};
26 changes: 26 additions & 0 deletions source/overload-parameters.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import type {FunctionParameters} from './internal/index.d.ts';
import type {Overloads} from './overloads.d.ts';

/**
Extract the parameter types of all overloads as a union.

This is the overload-aware counterpart to the built-in `Parameters` utility type, which only extracts from the last overload.

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

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

type AllParameters = OverloadParameters<typeof request>;
//=> [url: string] | [url: string, options: {json: true}]
```

Known limitations are the same as {@link Overloads}.

@category Function
*/
export type OverloadParameters<FunctionType extends (...args: any) => any> = FunctionParameters<Overloads<FunctionType>[number]>;

export {};
25 changes: 25 additions & 0 deletions source/overload-return-type.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import type {Overloads} from './overloads.d.ts';

/**
Extract the return types of all overloads as a union.

This is the overload-aware counterpart to the built-in `ReturnType` utility type, which only extracts from the last overload.

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

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

type AllReturnTypes = OverloadReturnType<typeof request>;
//=> Promise<string> | Promise<unknown>
```

Known limitations are the same as {@link Overloads}.

@category Function
*/
export type OverloadReturnType<FunctionType extends (...args: any) => any> = ReturnType<Overloads<FunctionType>[number]>;

export {};
99 changes: 99 additions & 0 deletions source/overloads.d.ts
Comment thread
ikeyan marked this conversation as resolved.
Comment thread
ikeyan marked this conversation as resolved.
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
import type {HasExplicitThis} from './internal/index.d.ts';
import type {IsAny} from './is-any.d.ts';
import type {IsEqual} from './is-equal.d.ts';
import type {Sum} from './sum.d.ts';
import type {UnknownArray} from './unknown-array.d.ts';
import type {IntRange} from './int-range.d.ts';

declare const unique: unique symbol;
type Unique = typeof unique;
type MaxOverloadPatterns = 4;
type OverloadIndex = IntRange<0, MaxOverloadPatterns>;

/**
Extract all overload signatures of the given function type as a tuple, preserving declaration order.

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, 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 overloads:
- Type parameters are lost -- they are replaced by their upper bound (e.g. `<T>` becomes `unknown`, `<T extends string>` becomes `string`).
- When there are 4 or more generic overloads, extraction stops at the 4th-from-last generic overload and any overloads before it are omitted.
- Implicit `this` (no annotation) is indistinguishable from explicit `this: unknown`, so the output always includes `this: unknown`.
- TypeScript deduplicates overloads that share the same parameters and return type. When one has implicit `this` (no annotation) and another has explicit `this`, they are treated as duplicates, and whichever appears first in the intersection suppresses the other. See `source/internal/function.d.ts` for details on TypeScript's overload enumeration behavior.

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

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

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

// To get a union instead of a tuple, index with [number]:
type RequestOverloadsUnion = Overloads<typeof request>[number];
//=> ((url: string) => Promise<string>) | ((url: string, options: {
// json: true;
// }) => Promise<unknown>)
```

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

@category Function
*/
export type Overloads<FunctionType extends (...args: any) => any> = FunctionType extends unknown
? IsAny<FunctionType> extends true
? any
: _Overloads<(() => Unique) & FunctionType>
: never;

type _Overloads<
F extends (...args: any) => any,
N extends OverloadIndex = 0,
ResultOverloads extends Array<(...args: any) => any> = [],
> = NthLastOverload<F, N> extends infer ExtractedN extends (...args: any) => any
? IsEqual<ExtractedN, () => Unique> extends true
? ResultOverloads // Sentinel reached, return accumulated result.
: IsEqual<NthLastOverload<ExtractedN & F, N>, ExtractedN> extends true
// `NthLastOverload` can’t preserve generics.
// So, if the last overload contains generics the extracted signature replaces them with their constraints.
// This means intersecting the extracted signature with the original function won’t cause the last overload to move to the first.
// Therefore, in such cases we increment `N`.
? Sum<N, 1> extends infer NextN extends OverloadIndex
? _Overloads<F, NextN, [ExtractedN, ...ResultOverloads]>
: [ExtractedN, ...ResultOverloads]
: _Overloads<ExtractedN & F, N, [ExtractedN, ...ResultOverloads]>
: never;

/**
Extract the Nth-from-last (N < MaxOverloadPatterns) overload of a function type as a standalone function, correctly preserving implicit `this` (omitted) vs explicit `this` (kept).
*/
type NthLastOverload<F extends (...args: any) => any, N extends OverloadIndex> = F extends {
(this: infer T3, ...args: infer P3 extends UnknownArray): infer R3;
(this: infer T2, ...args: infer P2 extends UnknownArray): infer R2;
(this: infer T1, ...args: infer P1 extends UnknownArray): infer R1;
(this: infer T0, ...args: infer P0 extends UnknownArray): infer R0;
}
? ({
3: [T3, P3, R3];
2: [T2, P2, R2];
1: [T1, P1, R1];
0: [T0, P0, R0];
}[N] extends [infer T, infer P extends UnknownArray, infer R]
? HasExplicitThis<F, T, P, R> extends true
? (this: T, ...args: P) => R
: (...args: P) => R
: never)
: never;

export {};
86 changes: 86 additions & 0 deletions test-d/internal/function.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import {expectType} from 'tsd';
import type {FunctionParameters, HasExplicitThis} from '../../source/internal/index.d.ts';
import type {UnknownArray} from '../../source/unknown-array.d.ts';

// FunctionParameters
// ------------------

expectType<[foo: string, bar: number]>({} as FunctionParameters<(foo: string, bar: number) => void>);
expectType<[]>({} as FunctionParameters<() => void>);
expectType<any[]>({} as FunctionParameters<(...args: any[]) => void>);
expectType<readonly any[]>({} as FunctionParameters<(...args: readonly any[]) => void>);
expectType<[foo: string, ...bar: number[]]>({} as FunctionParameters<(foo: string, ...bar: number[]) => void>);
expectType<[foo: string, bar?: number | undefined]>({} as FunctionParameters<(foo: string, bar?: number) => void>);
expectType<UnknownArray>({} as FunctionParameters<any>);
expectType<never>({} as FunctionParameters<never>);

// HasExplicitThis
// ---------------

type ExplicitThisDate = (this: Date, foo: string) => void;
expectType<true>({} as HasExplicitThis<ExplicitThisDate, Date, [foo: string], void>);

type ExplicitThisUnknown = (this: unknown, foo: string) => void;
expectType<true>({} as HasExplicitThis<ExplicitThisUnknown, unknown, [foo: string], void>);

type ImplicitThis = (foo: string) => void;
expectType<false>({} as HasExplicitThis<ImplicitThis, unknown, [foo: string], void>);

type ExplicitThisUrl = (this: URL, foo: number, bar: boolean) => string;
expectType<true>({} as HasExplicitThis<ExplicitThisUrl, URL, [foo: number, bar: boolean], string>);

type ImplicitThisNoParameters = () => void;
expectType<false>({} as HasExplicitThis<ImplicitThisNoParameters, unknown, [], void>);

type ExplicitThisUnknownNoParameters = (this: unknown) => void;
expectType<true>({} as HasExplicitThis<ExplicitThisUnknownNoParameters, unknown, [], void>);

type OverloadedExplicitThis = {
(this: Date, foo: string): void;
(foo: number): string;
};
expectType<true>({} as HasExplicitThis<OverloadedExplicitThis, Date, [foo: string], void>);

type OverloadedImplicitThis = {
(foo: string): void;
(foo: number): string;
};
expectType<false>({} as HasExplicitThis<OverloadedImplicitThis, unknown, [foo: number], string>);

type OverloadedMixedThis = {
(this: Date, foo: string): void;
(foo: number): string;
};
expectType<true>({} as HasExplicitThis<OverloadedMixedThis, Date, [foo: string], void>);

type ImplicitThisRest = (...args: any[]) => void;
expectType<false>({} as HasExplicitThis<ImplicitThisRest, unknown, any[], void>);

type ExplicitThisRest = (this: string, ...args: any[]) => void;
expectType<true>({} as HasExplicitThis<ExplicitThisRest, string, any[], void>);

// Generic functions — the sentinel deduplication trick does not work with generic signatures
// because TS doesn't consider a generic instantiation a duplicate of the concrete sentinel.

declare function genericImplicit<Value>(input: Value): Value;
expectType<true>({} as HasExplicitThis<typeof genericImplicit, unknown, [input: unknown], unknown>);

declare function genericExplicitThis<Value>(this: Date, input: Value): Value;
expectType<true>({} as HasExplicitThis<typeof genericExplicitThis, Date, [input: unknown], unknown>);

declare function genericExplicitThisUnknown<Value>(this: unknown, input: Value): Value;
expectType<true>({} as HasExplicitThis<typeof genericExplicitThisUnknown, unknown, [input: unknown], unknown>);

declare function genericThisParameter<This>(this: This, input: string): This;
expectType<true>({} as HasExplicitThis<typeof genericThisParameter, unknown, [input: string], unknown>);

// Mixed generic + concrete overloads: concrete overload still deduplicates normally.
declare function genericOverloaded<Value>(input: Value): Value;
declare function genericOverloaded(input: string): string;
expectType<true>({} as HasExplicitThis<typeof genericOverloaded, unknown, [input: unknown], unknown>);
expectType<false>({} as HasExplicitThis<typeof genericOverloaded, unknown, [input: string], string>);

declare function genericOverloadedWithThis<Value>(this: Value, input: Value): Value;
declare function genericOverloadedWithThis(input: string): string;
expectType<true>({} as HasExplicitThis<typeof genericOverloadedWithThis, unknown, [input: unknown], unknown>);
expectType<false>({} as HasExplicitThis<typeof genericOverloadedWithThis, unknown, [input: string], string>);
12 changes: 12 additions & 0 deletions test-d/overload-parameters.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import {expectType} from 'tsd';
import type {OverloadParameters, UnknownArray} from '../index.d.ts';

// The details of overload enumeration are tested in overloads.ts, so only one case is tested here.
type Function1 = (foo: string, bar: number) => object;
type Function2 = (foo: bigint, ...bar: any[]) => void;

declare const overloadParameters: OverloadParameters<Function1 & Function2>;
expectType<[foo: string, bar: number] | [foo: bigint, ...bar: any[]]>(overloadParameters);

declare const anyOverloadParameters: OverloadParameters<any>;
expectType<UnknownArray>(anyOverloadParameters);
12 changes: 12 additions & 0 deletions test-d/overload-return-type.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import {expectType} from 'tsd';
import type {OverloadReturnType} from '../index.d.ts';

// The details of overload enumeration are tested in overloads.ts, so only one case is tested here.
type Function1 = (foo: string, bar: number) => object;
type Function2 = (foo: bigint, ...bar: any[]) => void;

declare const overloadReturnType: OverloadReturnType<Function1 & Function2>;
expectType<object | void>(overloadReturnType);

declare const anyOverloadReturnType: OverloadReturnType<any>;
expectType<any>(anyOverloadReturnType);
Loading