-
-
Notifications
You must be signed in to change notification settings - Fork 714
Add Overloads, OverloadParameters, OverloadReturnType
#1399
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
ikeyan
wants to merge
27
commits into
sindresorhus:main
Choose a base branch
from
ikeyan:feat/function-overloads-v2
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+599
−2
Open
Changes from 15 commits
Commits
Show all changes
27 commits
Select commit
Hold shift + click to select a range
6b8d049
Add `FunctionOverloads` type
ikeyan 4020509
Add `IsEqualStrict` to distinguish implicit `this` from `this: unknown`
ikeyan 19912c9
Convert `type _Test*` assertions to `expectType` style
ikeyan 508c9f8
Add `Function` category to readme
ikeyan e519f33
Refactor internals: use tuple representation and mapped types
ikeyan f6ba682
Rewrite `CollectOverloads` with `HasExplicitThis`/`LastOverload`
ikeyan 732302e
Move internals to `source/internal/function.d.ts`, fix docs
ikeyan 271492f
Rename to `Overloads`, add `OverloadParameters`/`OverloadReturnType`,…
ikeyan a896a7b
Update function.d.ts
sindresorhus a3e267b
Clean up comment style and formatting
ikeyan e2b8ed3
Replace mdash with ASCII punctuation
ikeyan 2deba74
`Overloads`: Extract overloads preceding generic overloads via N-adva…
ikeyan 9d00440
Merge branch 'main' into feat/function-overloads-v2
ikeyan 1396b74
Update `Overloads` description in readme
ikeyan 5488b7d
Merge `CollectOverloadsLoop` into `CollectOverloads`
ikeyan d6c1d02
Move sentinel check from `NthLastOverload` to `CollectOverloads`
ikeyan d7beb1c
Split `Overloads`, `OverloadParameters`, and `OverloadReturnType` int…
ikeyan 3cc06b8
Move `NthLastOverload` and `CollectOverloads` into overloads.d.ts
ikeyan 6cc8866
Rename `Parameters` -> `FunctionParameters`
ikeyan f4d5a74
Put type parameters in different lines
ikeyan 667aa60
Add tests for internal/function.d.ts
ikeyan 2d2dbd4
Let `Overloads<any>` be any
ikeyan 93b1a40
Add limitation comment
ikeyan 398317f
Merge commit 'a5491644b32160f804dd10d0b44dad461037f4c1' into feat/fun…
ikeyan 950c792
Update readme.md
ikeyan 64c3373
Merge remote-tracking branch 'upstream/main' into feat/function-overl…
claude 6282d78
refactor: cleanup `overloads.d.ts`
som-sm File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,92 @@ | ||
| import type {CollectOverloads} from './internal/index.d.ts'; | ||
| import type {IsAny} from './is-any.d.ts'; | ||
|
|
||
| /** | ||
| 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 type parameters are lost -- they are replaced by their upper bound (e.g. `<T>` becomes `unknown`, `<T extends string>` becomes `string`). Functions with fewer than 4 generic overloads are fully extracted; when there are 4 or more generic overloads, extraction stops at the 4th-from-last generic overload and any overloads before it are omitted. | ||
| - 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 | ||
| ? [(...arguments_: any[]) => any] | ||
| : CollectOverloads<FunctionType> | ||
| : never; | ||
|
|
||
| /** | ||
| 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> = Parameters<Overloads<FunctionType>[number]>; | ||
|
|
||
| /** | ||
| 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]>; | ||
|
ikeyan marked this conversation as resolved.
Outdated
|
||
|
|
||
| export {}; | ||
|
ikeyan marked this conversation as resolved.
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,112 @@ | ||
| import type {IntRange} from '../int-range.d.ts'; | ||
| import type {IsEqual} from '../is-equal.d.ts'; | ||
| import type {IsUnknown} from '../is-unknown.d.ts'; | ||
| import type {Sum} from '../sum.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 Parameters<T extends (...args: any) => any> = T extends (...args: infer P extends UnknownArray) => any ? P : never; | ||
|
ikeyan marked this conversation as resolved.
Outdated
|
||
|
|
||
| /* | ||
| 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`. | ||
| */ | ||
| type HasExplicitThis< | ||
| FunctionType extends (...args: any) => any, | ||
| This, Parameters_ extends UnknownArray, Return, | ||
|
ikeyan marked this conversation as resolved.
Outdated
|
||
| > = IsUnknown<This> extends true | ||
| ? IsEqual<ThisParameterType<FunctionType & ((this: Unique, ...args: Parameters_) => Return)>, Unique> | ||
| : true; | ||
|
|
||
| type MaxOverloadPatterns = 4; | ||
| type OverloadIndex = IntRange<0, MaxOverloadPatterns>; | ||
| /** | ||
|
ikeyan marked this conversation as resolved.
Outdated
|
||
| 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). | ||
|
|
||
| F can contain a `() => Unique` sentinel (prepended by `CollectOverloads`). | ||
| */ | ||
| 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] | ||
| ? IsEqual<R, Unique> extends true | ||
| ? undefined // No overload at this position | ||
|
ikeyan marked this conversation as resolved.
Outdated
|
||
| : HasExplicitThis<F, T, P, R> extends true | ||
| ? (this: T, ...args: P) => R | ||
| : (...args: P) => R | ||
| : never) | ||
| : never; | ||
|
|
||
| /** | ||
| Extracts TypeScript's enumerated overload list into a tuple (see "Overload enumeration" above). | ||
|
|
||
| `AllOverloads` defaults to `(() => Unique) & F`, prepending a `() => Unique` sentinel that marks the boundary of the original overloads. Each iteration extracts `NthLastOverload<AllOverloads, N>` and then checks whether intersecting the extracted overload onto `AllOverloads` changes what position N sees: | ||
|
|
||
| - **Effect observed** (the two differ): the intersection advanced the view. Output `ExtractedN`, intersect it onto `AllOverloads`, and continue with the same N. | ||
| - **No effect** (they are equal): the intersection would not advance the view (e.g. aliasing generic overloads that infer to the same concrete signature). Output `ExtractedN` without intersecting, and advance N to the next position. | ||
| - **Sentinel hit** (`NthLastOverload` returns `undefined`): no original overload at this depth. Return the accumulated result. | ||
|
|
||
| The loop terminates when either N exceeds `OverloadIndex` or the sentinel is reached. | ||
|
|
||
| @see https://github.com/microsoft/TypeScript/issues/32164#issuecomment-1146737709 | ||
| */ | ||
| export type CollectOverloads< | ||
|
ikeyan marked this conversation as resolved.
Outdated
|
||
| F extends (...args: any) => any, | ||
| AllOverloads extends (...args: any) => any = (() => Unique) & F, | ||
| N extends OverloadIndex = 0, | ||
| ResultOverloads extends Array<(...args: any) => any> = [], | ||
| > = NthLastOverload<AllOverloads, N> extends infer ExtractedN extends (...args: any) => any | ||
| ? IsEqual<NthLastOverload<ExtractedN & AllOverloads, N>, ExtractedN> extends true | ||
| ? Sum<N, 1> extends infer NextN extends OverloadIndex | ||
| ? CollectOverloads<F, AllOverloads, NextN, [ExtractedN, ...ResultOverloads]> | ||
| : [ExtractedN, ...ResultOverloads] | ||
| : CollectOverloads<F, ExtractedN & AllOverloads, N, [ExtractedN, ...ResultOverloads]> | ||
| : ResultOverloads; | ||
|
|
||
| export {}; | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.