From 6b8d04963c8c2651da1b5c317b7f2adcd5a84308 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=B1=A0=E4=B8=8B=20=E5=85=8B=E5=BD=A6?= Date: Wed, 8 Apr 2026 18:17:43 +0900 Subject: [PATCH 01/24] Add `FunctionOverloads` type MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Create a union of all overload signatures of a given function type. TypeScript's built-in `Parameters` and `ReturnType` only work with the last overload — this type extracts all of them. The implementation correctly distinguishes between implicit `this` and explicit `this: unknown` overloads via a two-pass approach. Resolves #868 Co-Authored-By: Claude Opus 4.6 (1M context) --- index.d.ts | 1 + readme.md | 1 + source/function-overloads.d.ts | 127 ++++++++++++++++++ test-d/function-overloads.ts | 227 +++++++++++++++++++++++++++++++++ 4 files changed, 356 insertions(+) create mode 100644 source/function-overloads.d.ts create mode 100644 test-d/function-overloads.ts diff --git a/index.d.ts b/index.d.ts index ccdeca915..77cfb0df9 100644 --- a/index.d.ts +++ b/index.d.ts @@ -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'; diff --git a/readme.md b/readme.md index dfe63a3cb..ec7a553cd 100644 --- a/readme.md +++ b/readme.md @@ -160,6 +160,7 @@ Click the type names for complete docs. - [`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. +- [`FunctionOverloads`](source/function-overloads.d.ts) - Create a union of all overload signatures of the given function type. - [`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. diff --git a/source/function-overloads.d.ts b/source/function-overloads.d.ts new file mode 100644 index 000000000..4fdb4e69b --- /dev/null +++ b/source/function-overloads.d.ts @@ -0,0 +1,127 @@ +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; +declare function request(url: string, options: {json: true}): Promise; + +type RequestOverloads = FunctionOverloads; +//=> ((url: string) => Promise) | ((url: string, options: { +// json: true; +// }) => Promise) + +// You can also get all parameters and return types using built-in `Parameters` and `ReturnType` utilities: + +type RequestParameters = Parameters; +//=> [url: string] | [url: string, options: {json: true}] + +type RequestReturnType = ReturnType; +//=> Promise | Promise +``` + +@see https://github.com/microsoft/TypeScript/issues/14107 +@see https://github.com/microsoft/TypeScript/issues/32164 + +@category Function +*/ +export type FunctionOverloads = FunctionType extends unknown + ? IsAny extends true + ? (...arguments_: readonly unknown[]) => unknown + : DistinguishUnknownThisOverloads + : never; + +declare const nothing: unique symbol; +type Nothing = typeof nothing; +type AnyOverload = [This: unknown, Parameters: UnknownArray, Return: unknown]; + +type MatchesAnyOverload = + true extends ( + TargetOverloads extends unknown ? IsEqual : never + ) + ? true + : false; + +/** +Iterates over all overload signatures from bottom to top, collecting each as a `[This, Parameters, Return]` tuple union. + +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 = never, + ResultFunctionType = AllOverloads, +> = + IsEqual extends true + ? [ResultOverloads, 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, + ResultOverloads | [This, Parameters_, Return], + IsUnknown extends true + ? ((this: Nothing, ...arguments_: Parameters_) => Return) & ResultFunctionType + : ResultFunctionType + > + : never; + +/** +Finds overloads that explicitly declare `this: unknown` (as opposed to having no `this` annotation, which TypeScript also infers as `unknown`). + +The second pass (running `CollectOverloads` on the secondary function type where implicit-`this` was replaced with `Nothing`) lets us distinguish the two cases. +*/ +type ExtractExplicitUnknownThisOverloads = Overloads extends AnyOverload + ? IsUnknown extends true + ? MatchesAnyOverload extends true + ? Overloads + : never + : never + : never; + +type IsImplicitThisOverload = [ + IsUnknown, + MatchesAnyOverload, +] extends [true, false] + ? true + : false; + +/** +Reconstructs each overload as a proper function type, omitting the `this` parameter for overloads that did not explicitly declare one. +*/ +type DistinguishUnknownThisOverloads< + FunctionType, + Overloads = CollectOverloads[0], + SecondPassOverloads = CollectOverloads[1]>[0], + ExplicitUnknownThisOverloads = ExtractExplicitUnknownThisOverloads, +> = Overloads extends [infer This, infer Parameters_ extends UnknownArray, infer Return] + ? IsImplicitThisOverload extends true + ? (...arguments_: Parameters_) => Return + : (this: This, ...arguments_: Parameters_) => Return + : never; + +export {}; diff --git a/test-d/function-overloads.ts b/test-d/function-overloads.ts new file mode 100644 index 000000000..4ca398dc6 --- /dev/null +++ b/test-d/function-overloads.ts @@ -0,0 +1,227 @@ +import {expectType} from 'tsd'; +import type {FunctionOverloads, IsEqual} from '../index.d.ts'; + +type Function1 = (foo: string, bar: number) => object; +type Function2 = (foo: bigint, ...bar: any[]) => void; + +// Single function (no overload) +declare const normalFunction: FunctionOverloads; +expectType(normalFunction); + +// Two overloads via intersection +declare const twoOverloads: FunctionOverloads; +expectType(twoOverloads); + +// Two overloads via interface syntax +declare const twoOverloadsInterface: FunctionOverloads<{ + (foo: string, bar: number): object; + (foo: bigint, ...bar: any[]): void; +}>; +expectType(twoOverloadsInterface); + +// Identical overloads collapse +declare const twoIdenticalOverloads: FunctionOverloads<{ + (foo: string, bar: number): object; + (foo: string, bar: number): object; +}>; +expectType(twoIdenticalOverloads); + +type Function3 = (foo: string, bar: number, baz?: boolean) => object; + +// Two overloads with assignable but distinct signatures +declare const twoOverloadsWithAssignableSignature: FunctionOverloads; +expectType(twoOverloadsWithAssignableSignature); + +// Three overloads +declare const threeOverloads: FunctionOverloads; +expectType(threeOverloads); + +type Function4 = (...foo: any[]) => void; +type Function5 = (...foo: readonly any[]) => void; + +// Rest parameter overloads +declare const normalFunctionWithOnlyRestWritableParameter: FunctionOverloads; +expectType(normalFunctionWithOnlyRestWritableParameter); + +declare const normalFunctionWithOnlyRestReadonlyParameter: FunctionOverloads; +expectType(normalFunctionWithOnlyRestReadonlyParameter); + +// The compiler ignores subsequent identical-up-to-readonly overloads +declare const twoOverloadsWithDifferentRestParameterReadonliness: FunctionOverloads; +expectType(twoOverloadsWithDifferentRestParameterReadonliness); + +declare const twoOverloadsWithDifferentRestParameterReadonlinessReversed: FunctionOverloads; +expectType(twoOverloadsWithDifferentRestParameterReadonlinessReversed); + +type Function6 = (foo: string, ...bar: any[]) => void; +type Function7 = (foo: string, ...bar: readonly any[]) => void; + +declare const normalFunctionWithNormalAndRestWritableParameter: FunctionOverloads; +expectType(normalFunctionWithNormalAndRestWritableParameter); + +// Readonly rest parameter cannot be represented with tuples +declare const normalFunctionWithNormalAndRestReadonlyParameter: FunctionOverloads; +expectType<(foo: string, ...bar: any[]) => void>(normalFunctionWithNormalAndRestReadonlyParameter); + +type Function8 = () => never; + +declare const normalFunctionNoParameters: FunctionOverloads; +expectType(normalFunctionNoParameters); + +declare const twoOverloadsWithNoAndPresentParameters: FunctionOverloads; +expectType(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(noOverloads); + +// Edge case: `any` returns a generic function signature +declare const anyOverload: FunctionOverloads; +expectType<(...arguments_: readonly unknown[]) => unknown>(anyOverload); + +// Edge case: `never` returns never +declare const neverOverload: FunctionOverloads; +expectType(neverOverload); + +// Edge case: `unknown` returns never +declare const unknownOverload: FunctionOverloads; +expectType(unknownOverload); + +// `declare function` overloads +declare function declaredOverload(input: string): {kind: 'string'}; +declare function declaredOverload(input: number, flag: boolean): {kind: 'number'}; + +declare const declaredOverloadResult: FunctionOverloads; +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; +// Verify `this` is preserved +expectType(thisOverloads); + +// Same parameters, different return types +type SameParametersDifferentReturn1 = (foo: string) => string; +type SameParametersDifferentReturn2 = (foo: string) => number; + +declare const sameParametersDifferentReturn: FunctionOverloads; +expectType(sameParametersDifferentReturn); + +// Generic overloads — generic parameters become `unknown` +declare function genericOverload(input: T): T; +declare function genericOverload(input: string): string; + +declare const genericOverloadResult: FunctionOverloads; +expectType<((input: unknown) => unknown) | ((input: string) => string)>(genericOverloadResult); + +// Interface-style overload +type InterfaceOverload = { + (input: string): 1; + (input: number): 2; +}; + +declare const interfaceOverload: FunctionOverloads; +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; +expectType(sameParametersDifferentThis); + +// Duplicate overloads in interface are collapsed +declare const duplicateOverloads: FunctionOverloads<{ + (foo: string, bar: number): object; + (): string; + (): string; +}>; +expectType string)>(duplicateOverloads); + +// Generic overload at intersection level stops iteration — only the last inferred overload +declare const genericIntersectionOverload: FunctionOverloads<((this: string) => string) & ((this: T, argument: T) => T)>; +expectType<(this: unknown, argument: unknown) => unknown>(genericIntersectionOverload); + +// Verify that explicit `this: unknown` is preserved while implicit `this` is omitted +type AssertTrue = T; + +// Single overload with explicit `this: unknown` +type _TestExplicitUnknownThis = AssertTrue void>, + (this: unknown) => void +>>; + +// Single overload with implicit `this` +type _TestImplicitThis = AssertTrue void>, + () => void +>>; + +// Mixed explicit `this: unknown` and implicit `this` overloads +type _TestMixedThis = AssertTrue void) & ((event: 'implicit') => void)>, + ((this: unknown, event: 'explicit') => void) | ((event: 'implicit') => void) +>>; + +// Multiple explicit and implicit `this` overloads +type _TestMultipleMixedThis = AssertTrue, + | ((this: unknown, event: 'explicit') => void) + | ((this: unknown, event: 'explicit2') => void) + | ((event: 'implicit') => void) + | ((event: 'implicit2') => void) +>>; + +// When implicit `this` and explicit `this: unknown` overloads share same params/return, implicit may be lost +type Function1WithThis = (this: This, foo: string, bar: number) => object; + +type _TestImplicitThisLimitation = [ + AssertTrue>, Function1 | Function1WithThis<1>>>, + // When the explicit `this` overload comes first, the implicit `this` overload may be absorbed + AssertTrue & Function1>, Function1WithThis<1>>>, + // With `this: unknown` specifically, implicit `this` is always absorbed + AssertTrue>, Function1WithThis>>, + AssertTrue & Function1>, Function1WithThis>>, +]; From 402050953ca2deedf9430ce66ab955ceb6ad022b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=B1=A0=E4=B8=8B=20=E5=85=8B=E5=BD=A6?= Date: Wed, 8 Apr 2026 19:58:22 +0900 Subject: [PATCH 02/24] Add `IsEqualStrict` to distinguish implicit `this` from `this: unknown` Neither tsd's `expectType` nor type-fest's `IsEqual` can distinguish `() => void` from `(this: unknown) => void`. Add a test-local `IsEqualStrict` helper that extracts the `this` parameter via a sentinel type, and use it in tests that verify this distinction. Co-Authored-By: Claude Opus 4.6 (1M context) --- test-d/function-overloads.ts | 38 ++++++++++++++++++++++++++---------- 1 file changed, 28 insertions(+), 10 deletions(-) diff --git a/test-d/function-overloads.ts b/test-d/function-overloads.ts index 4ca398dc6..86d760838 100644 --- a/test-d/function-overloads.ts +++ b/test-d/function-overloads.ts @@ -1,5 +1,22 @@ import {expectType} from 'tsd'; -import type {FunctionOverloads, IsEqual} from '../index.d.ts'; +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 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], [Function2_, FuncToTuple]>; type Function1 = (foo: string, bar: number) => object; type Function2 = (foo: bigint, ...bar: any[]) => void; @@ -179,29 +196,30 @@ expectType string)>(duplicateOverloads); declare const genericIntersectionOverload: FunctionOverloads<((this: string) => string) & ((this: T, argument: T) => T)>; expectType<(this: unknown, argument: unknown) => unknown>(genericIntersectionOverload); -// Verify that explicit `this: unknown` is preserved while implicit `this` is omitted +// 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 AssertTrue = T; // Single overload with explicit `this: unknown` -type _TestExplicitUnknownThis = AssertTrue void>, (this: unknown) => void >>; // Single overload with implicit `this` -type _TestImplicitThis = AssertTrue void>, () => void >>; // Mixed explicit `this: unknown` and implicit `this` overloads -type _TestMixedThis = AssertTrue void) & ((event: 'implicit') => void)>, ((this: unknown, event: 'explicit') => void) | ((event: 'implicit') => void) >>; // Multiple explicit and implicit `this` overloads -type _TestMultipleMixedThis = AssertTrue = (this: This, foo: string, bar: number) => object; type _TestImplicitThisLimitation = [ - AssertTrue>, Function1 | Function1WithThis<1>>>, + AssertTrue>, Function1 | Function1WithThis<1>>>, // When the explicit `this` overload comes first, the implicit `this` overload may be absorbed - AssertTrue & Function1>, Function1WithThis<1>>>, + AssertTrue & Function1>, Function1WithThis<1>>>, // With `this: unknown` specifically, implicit `this` is always absorbed - AssertTrue>, Function1WithThis>>, - AssertTrue & Function1>, Function1WithThis>>, + AssertTrue>, Function1WithThis>>, + AssertTrue & Function1>, Function1WithThis>>, ]; From 19912c96e7c3d720bbef65e5124c9ff8dea19227 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=B1=A0=E4=B8=8B=20=E5=85=8B=E5=BD=A6?= Date: Wed, 8 Apr 2026 22:37:29 +0900 Subject: [PATCH 03/24] Convert `type _Test*` assertions to `expectType` style Co-Authored-By: Claude Opus 4.6 (1M context) --- test-d/function-overloads.ts | 64 +++++++++++++++++------------------- 1 file changed, 31 insertions(+), 33 deletions(-) diff --git a/test-d/function-overloads.ts b/test-d/function-overloads.ts index 86d760838..7a9b5a885 100644 --- a/test-d/function-overloads.ts +++ b/test-d/function-overloads.ts @@ -198,48 +198,46 @@ expectType<(this: unknown, argument: unknown) => unknown>(genericIntersectionOve // 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 AssertTrue = T; + +type Function1WithThis = (this: This, foo: string, bar: number) => object; +type Function2WithThis = (this: This, foo: bigint, ...bar: any[]) => void; // Single overload with explicit `this: unknown` -type _TestExplicitUnknownThis = AssertTrue void>, - (this: unknown) => void ->>; +expectType>, + Function1WithThis +>>(true); // Single overload with implicit `this` -type _TestImplicitThis = AssertTrue void>, - () => void ->>; +expectType, + Function1 +>>(true); // Mixed explicit `this: unknown` and implicit `this` overloads -type _TestMixedThis = AssertTrue void) & ((event: 'implicit') => void)>, - ((this: unknown, event: 'explicit') => void) | ((event: 'implicit') => void) ->>; +expectType & Function2>, + Function1WithThis | Function2 +>>(true); // Multiple explicit and implicit `this` overloads -type _TestMultipleMixedThis = AssertTrue, - | ((this: unknown, event: 'explicit') => void) - | ((this: unknown, event: 'explicit2') => void) - | ((event: 'implicit') => void) - | ((event: 'implicit2') => void) ->>; + | Function1WithThis + | Function2WithThis + | Function3 + | Function4 +>>(true); // When implicit `this` and explicit `this: unknown` overloads share same params/return, implicit may be lost -type Function1WithThis = (this: This, foo: string, bar: number) => object; - -type _TestImplicitThisLimitation = [ - AssertTrue>, Function1 | Function1WithThis<1>>>, - // When the explicit `this` overload comes first, the implicit `this` overload may be absorbed - AssertTrue & Function1>, Function1WithThis<1>>>, - // With `this: unknown` specifically, implicit `this` is always absorbed - AssertTrue>, Function1WithThis>>, - AssertTrue & Function1>, Function1WithThis>>, -]; +expectType>, Function1 | Function1WithThis<1>>>(true); +// When the explicit `this` overload comes first, the implicit `this` overload may be absorbed +expectType & Function1>, Function1WithThis<1>>>(true); +// With `this: unknown` specifically, implicit `this` is always absorbed +expectType>, Function1WithThis>>(true); +expectType & Function1>, Function1WithThis>>(true); From 508c9f8837a55406235e0a0d6ace6d37fdbfe4d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=B1=A0=E4=B8=8B=20=E5=85=8B=E5=BD=A6?= Date: Wed, 8 Apr 2026 22:46:27 +0900 Subject: [PATCH 04/24] Add `Function` category to readme Move `SetReturnType`, `SetParameterType`, and `FunctionOverloads` from Utilities into a new Function section, matching their `@category` tags. Co-Authored-By: Claude Opus 4.6 (1M context) --- readme.md | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/readme.md b/readme.md index ec7a553cd..645e4ecb2 100644 --- a/readme.md +++ b/readme.md @@ -158,9 +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. -- [`FunctionOverloads`](source/function-overloads.d.ts) - Create a union of all overload signatures of the given function type. - [`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. @@ -247,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`. From e519f337f02f621928db133d4661b092dde8f609 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=B1=A0=E4=B8=8B=20=E5=85=8B=E5=BD=A6?= Date: Thu, 9 Apr 2026 13:54:10 +0900 Subject: [PATCH 05/24] Refactor internals: use tuple representation and mapped types - CollectOverloads now builds a tuple instead of a union, preserving declaration order. Duplicates from the termination lag are removed by dropping the first element at return. - Replace recursive tuple iterations (MatchesAnyOverload, ExtractExplicitUnknownThisOverloads, IsImplicitThisOverload, OverloadsTupleToUnion) with a single mapped type (OverloadsToFunctions). - DistinguishUnknownThisOverloads now returns a tuple of function types; FunctionOverloads indexes with [number] to produce the union. Co-Authored-By: Claude Opus 4.6 (1M context) --- source/function-overloads.d.ts | 62 ++++++++++++++-------------------- 1 file changed, 26 insertions(+), 36 deletions(-) diff --git a/source/function-overloads.d.ts b/source/function-overloads.d.ts index 4fdb4e69b..0eef97793 100644 --- a/source/function-overloads.d.ts +++ b/source/function-overloads.d.ts @@ -46,22 +46,17 @@ type RequestReturnType = ReturnType; export type FunctionOverloads = FunctionType extends unknown ? IsAny extends true ? (...arguments_: readonly unknown[]) => unknown - : DistinguishUnknownThisOverloads + : DistinguishUnknownThisOverloads[number] : never; declare const nothing: unique symbol; type Nothing = typeof nothing; type AnyOverload = [This: unknown, Parameters: UnknownArray, Return: unknown]; -type MatchesAnyOverload = - true extends ( - TargetOverloads extends unknown ? IsEqual : never - ) - ? true - : false; - /** -Iterates over all overload signatures from bottom to top, collecting each as a `[This, Parameters, Return]` tuple union. +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`. @@ -71,11 +66,11 @@ type CollectOverloads< AllOverloads, CheckedOverloads = unknown, PreviousCheckedOverloads = never, - ResultOverloads extends AnyOverload = never, + ResultOverloads extends AnyOverload[] = [], ResultFunctionType = AllOverloads, > = IsEqual extends true - ? [ResultOverloads, ResultFunctionType] + ? [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" @@ -83,7 +78,7 @@ type CollectOverloads< ((this: This, ...arguments_: Parameters_) => Return) & AllOverloads, ((this: This, ...arguments_: Parameters_) => Return) & CheckedOverloads, CheckedOverloads, - ResultOverloads | [This, Parameters_, Return], + [[This, Parameters_, Return], ...ResultOverloads], IsUnknown extends true ? ((this: Nothing, ...arguments_: Parameters_) => Return) & ResultFunctionType : ResultFunctionType @@ -91,37 +86,32 @@ type CollectOverloads< : never; /** -Finds overloads that explicitly declare `this: unknown` (as opposed to having no `this` annotation, which TypeScript also infers as `unknown`). +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. -The second pass (running `CollectOverloads` on the secondary function type where implicit-`this` was replaced with `Nothing`) lets us distinguish the two cases. +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 ExtractExplicitUnknownThisOverloads = Overloads extends AnyOverload - ? IsUnknown extends true - ? MatchesAnyOverload extends true - ? Overloads - : never +type OverloadsToFunctions< + Overloads extends AnyOverload[], + SecondPassOverloads extends AnyOverload[], +> = { + [K in keyof Overloads]: Overloads[K] extends infer Overload extends AnyOverload + ? IsUnknown extends true + ? true extends { + [J in keyof SecondPassOverloads]: IsEqual + }[number] + ? (this: Overload[0], ...arguments_: Overload[1]) => Overload[2] + : (...arguments_: Overload[1]) => Overload[2] + : (this: Overload[0], ...arguments_: Overload[1]) => Overload[2] : never - : never; - -type IsImplicitThisOverload = [ - IsUnknown, - MatchesAnyOverload, -] extends [true, false] - ? true - : false; +}; /** -Reconstructs each overload as a proper function type, omitting the `this` parameter for overloads that did not explicitly declare one. +Orchestrates the two-pass approach: collects overloads, then maps them to proper function types as a tuple. */ type DistinguishUnknownThisOverloads< FunctionType, - Overloads = CollectOverloads[0], - SecondPassOverloads = CollectOverloads[1]>[0], - ExplicitUnknownThisOverloads = ExtractExplicitUnknownThisOverloads, -> = Overloads extends [infer This, infer Parameters_ extends UnknownArray, infer Return] - ? IsImplicitThisOverload extends true - ? (...arguments_: Parameters_) => Return - : (this: This, ...arguments_: Parameters_) => Return - : never; + Overloads extends AnyOverload[] = CollectOverloads[0], + SecondPassOverloads extends AnyOverload[] = CollectOverloads[1]>[0], +> = OverloadsToFunctions; export {}; From f6ba6823dd6be2053b76cee3d89cb0cdf283e4fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=B1=A0=E4=B8=8B=20=E5=85=8B=E5=BD=A6?= Date: Fri, 10 Apr 2026 19:43:23 +0900 Subject: [PATCH 06/24] Rewrite `CollectOverloads` with `HasExplicitThis`/`LastOverload` MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace the two-pass approach with a simpler design: - `HasExplicitThis`: detects implicit vs explicit `this: unknown` by intersecting a `(this: Nothing, ...)` sentinel from the right — implicit `this` absorbs it (TS deduplication), explicit `this: unknown` doesn't. - `LastOverload`: extracts the last overload as a standalone function with correct `this` handling. - `CollectOverloads`: uses `LastOverload` directly, no intermediate `[This, Params, Return]` tuples or secondary function type. Also: - Export `OverloadsToTuple` (tuple counterpart to `FunctionOverloads`) - Fix `Parameters` extraction for `readonly` arrays - Add detailed internal documentation of TypeScript's overload enumeration behavior (deduplication rules, implicit `this` semantics) - Update tests to target `OverloadsToTuple` with tuple expectations Co-Authored-By: Claude Opus 4.6 (1M context) --- index.d.ts | 2 +- readme.md | 1 + source/function-overloads.d.ts | 197 ++++++++++++++++++++++++--------- test-d/function-overloads.ts | 197 +++++++++++++++++---------------- 4 files changed, 248 insertions(+), 149 deletions(-) diff --git a/index.d.ts b/index.d.ts index 77cfb0df9..c55e54aae 100644 --- a/index.d.ts +++ b/index.d.ts @@ -69,7 +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 {FunctionOverloads, OverloadsToTuple} 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'; diff --git a/readme.md b/readme.md index 645e4ecb2..87ac0bc93 100644 --- a/readme.md +++ b/readme.md @@ -247,6 +247,7 @@ Click the type names for complete docs. ### Function - [`FunctionOverloads`](source/function-overloads.d.ts) - Create a union of all overload signatures of the given function type. +- [`OverloadsToTuple`](source/function-overloads.d.ts) - Extract all overload signatures of the given function type as a tuple, preserving declaration order. - [`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. diff --git a/source/function-overloads.d.ts b/source/function-overloads.d.ts index 0eef97793..1b0af2c18 100644 --- a/source/function-overloads.d.ts +++ b/source/function-overloads.d.ts @@ -14,8 +14,8 @@ Use-cases: - 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. +- Generic type parameters are lost and inferred as `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 — whichever appears first in the intersection suppresses the other. See the internal documentation comment in this file for details. @example ``` @@ -43,75 +43,166 @@ type RequestReturnType = ReturnType; @category Function */ -export type FunctionOverloads = FunctionType extends unknown - ? IsAny extends true - ? (...arguments_: readonly unknown[]) => unknown - : DistinguishUnknownThisOverloads[number] - : never; +export type FunctionOverloads any> = OverloadsToTuple[number]; + +// ======================================================================== +// 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 — whichever appears first wins. +// +// Example (F1 = implicit this, F1wT = 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. +// +// ## Detecting implicit `this` vs explicit `this: unknown` +// +// Both implicit `this` and explicit `this: unknown` give `ThisParameterType` +// = `unknown`. To distinguish them, we intersect a sentinel signature +// `(this: Nothing, ...args) => ...` from the **right**: +// +// - If the original has implicit `this`: TypeScript's deduplication treats +// them as the same (implicit → compare by (P,R) only). The original wins +// (first-wins rule), so the Nothing signature is absorbed. The result's +// ThisParameterType remains `unknown` (not Nothing). +// +// - If the original has explicit `this: unknown`: Both are explicit, so +// TypeScript compares (This, P, R). `unknown ≠ Nothing`, so they are NOT +// duplicates — both survive. The rightmost is the Nothing signature, so +// ThisParameterType returns `Nothing`. +// +// This is what `HasExplicitThis` implements below. +// +// ## What CollectOverloads returns +// +// `CollectOverloads` extracts TypeScript's **enumerated** overload list (as +// described above), not necessarily all **declared** overloads. In particular, +// when an implicit-`this` overload and an explicit-`this` overload share the +// same params/return, whichever appears first in the intersection suppresses +// the other. This is a fundamental property of TypeScript's overload +// deduplication and cannot be worked around. +// +// @see https://github.com/microsoft/TypeScript/issues/32164#issuecomment-1146737709 +// ======================================================================== 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. + * Obtain the parameters of a function type in a tuple + * This works even when the parameters type is a readonly array + */ +type Parameters any> = T extends (...args: infer P extends UnknownArray) => any ? P : never; + +/** +Detect whether a function type has an explicit `this` annotation. + +Both implicit `this` and explicit `this: unknown` give `ThisParameterType` = `unknown`. +To tell them apart, we intersect a `(this: Nothing, ...)` signature from the right. +If the original `this` was implicit, the Nothing signature is absorbed by deduplication +and ThisParameterType remains `unknown`. If it was explicit `this: unknown`, the Nothing +signature survives as a separate overload, and ThisParameterType returns `Nothing`. +*/ +type HasExplicitThis any> = + IsUnknown> extends true + ? IsEqual) => ReturnType)>, Nothing> extends true + ? true + : false + : true; + +/** +Extract the last overload of a function type as a standalone function, +correctly preserving implicit `this` (omitted) vs explicit `this` (kept). +*/ +type LastOverload any> = + HasExplicitThis extends true + ? (this: ThisParameterType, ...args: Parameters) => ReturnType + : (...args: Parameters) => ReturnType; -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. +/** +Iterates over overload signatures right to left, collecting each into a tuple. -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`. +Uses the intersection trick: intersecting the just-extracted signature onto the left +of the function type makes TypeScript's pattern matching skip it on the next iteration, +effectively advancing through all enumerated overloads. -@see https://github.com/microsoft/TypeScript/issues/32164#issuecomment-1146737709 +The termination condition (`CheckedOverloads === PreviousCheckedOverloads`) lags by one +iteration, so the last extracted overload is always a duplicate. We compensate by dropping +the first element of the result tuple at termination. */ type CollectOverloads< - AllOverloads, + AllOverloads extends (...args: any) => any, CheckedOverloads = unknown, PreviousCheckedOverloads = never, - ResultOverloads extends AnyOverload[] = [], - ResultFunctionType = AllOverloads, + ResultOverloads extends Array<(...args: any) => any> = [], > = IsEqual 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 extends true - ? ((this: Nothing, ...arguments_: Parameters_) => Return) & ResultFunctionType - : ResultFunctionType - > - : never; + ? ResultOverloads extends [(...args: any) => any, ...infer Rest extends Array<(...args: any) => any>] ? Rest : [] + : 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. + LastOverload & AllOverloads, + LastOverload & CheckedOverloads, + CheckedOverloads, + [LastOverload, ...ResultOverloads] + > +; /** -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. +Extract all overload signatures of the given function type as a tuple, preserving declaration order. -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 extends true - ? true extends { - [J in keyof SecondPassOverloads]: IsEqual - }[number] - ? (this: Overload[0], ...arguments_: Overload[1]) => Overload[2] - : (...arguments_: Overload[1]) => Overload[2] - : (this: Overload[0], ...arguments_: Overload[1]) => Overload[2] - : never -}; +This is the tuple counterpart to {@link FunctionOverloads}, which returns a union. Use this when overload order matters. -/** -Orchestrates the two-pass approach: collects overloads, then maps them to proper function types as a tuple. +@example +``` +import type {OverloadsToTuple} from 'type-fest'; + +declare function request(url: string): Promise; +declare function request(url: string, options: {json: true}): Promise; + +type RequestOverloads = OverloadsToTuple; +//=> [(url: string) => Promise, (url: string, options: { +// json: true; +// }) => Promise] +``` + +@see https://github.com/microsoft/TypeScript/issues/14107 +@see https://github.com/microsoft/TypeScript/issues/32164 + +@category Function */ -type DistinguishUnknownThisOverloads< - FunctionType, - Overloads extends AnyOverload[] = CollectOverloads[0], - SecondPassOverloads extends AnyOverload[] = CollectOverloads[1]>[0], -> = OverloadsToFunctions; +export type OverloadsToTuple any> = FunctionType extends unknown + ? IsAny extends true + ? [(...arguments_: any[]) => any] + : CollectOverloads + : never; export {}; diff --git a/test-d/function-overloads.ts b/test-d/function-overloads.ts index 7a9b5a885..054b8439c 100644 --- a/test-d/function-overloads.ts +++ b/test-d/function-overloads.ts @@ -1,10 +1,11 @@ import {expectType} from 'tsd'; -import type {FunctionOverloads, IsEqual, UnknownArray} from '../index.d.ts'; +import type {FunctionOverloads, IsEqual, OverloadsToTuple, 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. +// It accepts tuples of functions and compares them element-wise. declare const isEqualStrictNothing: unique symbol; type IsEqualStrictNothing = typeof isEqualStrictNothing; type FuncToTuple unknown> = @@ -14,87 +15,90 @@ type FuncToTuple unknown> = : never : never; type IsEqualStrict< - Function1_ extends (...arguments_: never) => unknown, - Function2_ extends (...arguments_: never) => unknown, -> = IsEqual<[Function1_, FuncToTuple], [Function2_, FuncToTuple]>; + FunctionTuple1 extends ReadonlyArray<(...arguments_: never) => unknown>, + FunctionTuple2 extends ReadonlyArray<(...arguments_: never) => unknown>, +> = IsEqual< + [FunctionTuple1, {[K in keyof FunctionTuple1]: FuncToTuple}], + [FunctionTuple2, {[K in keyof FunctionTuple2]: FuncToTuple}] +>; type Function1 = (foo: string, bar: number) => object; type Function2 = (foo: bigint, ...bar: any[]) => void; // Single function (no overload) -declare const normalFunction: FunctionOverloads; -expectType(normalFunction); +declare const normalFunction: OverloadsToTuple; +expectType<[Function1]>(normalFunction); // Two overloads via intersection -declare const twoOverloads: FunctionOverloads; -expectType(twoOverloads); +declare const twoOverloads: OverloadsToTuple; +expectType<[Function1, Function2]>(twoOverloads); // Two overloads via interface syntax -declare const twoOverloadsInterface: FunctionOverloads<{ +declare const twoOverloadsInterface: OverloadsToTuple<{ (foo: string, bar: number): object; (foo: bigint, ...bar: any[]): void; }>; -expectType(twoOverloadsInterface); +expectType<[Function1, Function2]>(twoOverloadsInterface); // Identical overloads collapse -declare const twoIdenticalOverloads: FunctionOverloads<{ +declare const twoIdenticalOverloads: OverloadsToTuple<{ (foo: string, bar: number): object; (foo: string, bar: number): object; }>; -expectType(twoIdenticalOverloads); +expectType<[Function1]>(twoIdenticalOverloads); type Function3 = (foo: string, bar: number, baz?: boolean) => object; // Two overloads with assignable but distinct signatures -declare const twoOverloadsWithAssignableSignature: FunctionOverloads; -expectType(twoOverloadsWithAssignableSignature); +declare const twoOverloadsWithAssignableSignature: OverloadsToTuple; +expectType<[Function1, Function3]>(twoOverloadsWithAssignableSignature); -// Three overloads -declare const threeOverloads: FunctionOverloads; -expectType(threeOverloads); +// Three overloads — declaration order preserved +declare const threeOverloads: OverloadsToTuple; +expectType<[Function1, Function2, Function3]>(threeOverloads); type Function4 = (...foo: any[]) => void; type Function5 = (...foo: readonly any[]) => void; // Rest parameter overloads -declare const normalFunctionWithOnlyRestWritableParameter: FunctionOverloads; -expectType(normalFunctionWithOnlyRestWritableParameter); +declare const normalFunctionWithOnlyRestWritableParameter: OverloadsToTuple; +expectType<[Function4]>(normalFunctionWithOnlyRestWritableParameter); -declare const normalFunctionWithOnlyRestReadonlyParameter: FunctionOverloads; -expectType(normalFunctionWithOnlyRestReadonlyParameter); +declare const normalFunctionWithOnlyRestReadonlyParameter: OverloadsToTuple; +expectType<[Function5]>(normalFunctionWithOnlyRestReadonlyParameter); // The compiler ignores subsequent identical-up-to-readonly overloads -declare const twoOverloadsWithDifferentRestParameterReadonliness: FunctionOverloads; -expectType(twoOverloadsWithDifferentRestParameterReadonliness); +declare const twoOverloadsWithDifferentRestParameterReadonliness: OverloadsToTuple; +expectType<[Function4]>(twoOverloadsWithDifferentRestParameterReadonliness); -declare const twoOverloadsWithDifferentRestParameterReadonlinessReversed: FunctionOverloads; -expectType(twoOverloadsWithDifferentRestParameterReadonlinessReversed); +declare const twoOverloadsWithDifferentRestParameterReadonlinessReversed: OverloadsToTuple; +expectType<[Function5]>(twoOverloadsWithDifferentRestParameterReadonlinessReversed); type Function6 = (foo: string, ...bar: any[]) => void; type Function7 = (foo: string, ...bar: readonly any[]) => void; -declare const normalFunctionWithNormalAndRestWritableParameter: FunctionOverloads; -expectType(normalFunctionWithNormalAndRestWritableParameter); +declare const normalFunctionWithNormalAndRestWritableParameter: OverloadsToTuple; +expectType<[Function6]>(normalFunctionWithNormalAndRestWritableParameter); // Readonly rest parameter cannot be represented with tuples -declare const normalFunctionWithNormalAndRestReadonlyParameter: FunctionOverloads; -expectType<(foo: string, ...bar: any[]) => void>(normalFunctionWithNormalAndRestReadonlyParameter); +declare const normalFunctionWithNormalAndRestReadonlyParameter: OverloadsToTuple; +expectType<[(foo: string, ...bar: any[]) => void]>(normalFunctionWithNormalAndRestReadonlyParameter); type Function8 = () => never; -declare const normalFunctionNoParameters: FunctionOverloads; -expectType(normalFunctionNoParameters); +declare const normalFunctionNoParameters: OverloadsToTuple; +expectType<[Function8]>(normalFunctionNoParameters); -declare const twoOverloadsWithNoAndPresentParameters: FunctionOverloads; -expectType(twoOverloadsWithNoAndPresentParameters); +declare const twoOverloadsWithNoAndPresentParameters: OverloadsToTuple; +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< +// Many overloads — order preserved +declare const manyOverloads: OverloadsToTuple< Function1 & Function2 & Function3 @@ -108,65 +112,55 @@ declare const manyOverloads: FunctionOverloads< & 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(noOverloads); - -// Edge case: `any` returns a generic function signature -declare const anyOverload: FunctionOverloads; -expectType<(...arguments_: readonly unknown[]) => unknown>(anyOverload); - -// Edge case: `never` returns never -declare const neverOverload: FunctionOverloads; +expectType<[ + Function1, + Function2, + Function3, + Function4, + Function6, + Function8, + Function9, + Function10, + Function11, + Function12, +]>(manyOverloads); + +// Edge case: `any` returns a single generic function signature +declare const anyOverload: OverloadsToTuple; +expectType<[(...arguments_: any[]) => any]>(anyOverload); + +// Edge case: `never` returns never (distributes over union) +declare const neverOverload: OverloadsToTuple; expectType(neverOverload); -// Edge case: `unknown` returns never -declare const unknownOverload: FunctionOverloads; -expectType(unknownOverload); - // `declare function` overloads declare function declaredOverload(input: string): {kind: 'string'}; declare function declaredOverload(input: number, flag: boolean): {kind: 'number'}; -declare const declaredOverloadResult: FunctionOverloads; -expectType<((input: string) => {kind: 'string'}) | ((input: number, flag: boolean) => {kind: 'number'})>(declaredOverloadResult); +declare const declaredOverloadResult: OverloadsToTuple; +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; +declare const thisOverloads: OverloadsToTuple; // Verify `this` is preserved -expectType(thisOverloads); +expectType<[ThisOverload1, ThisOverload2]>(thisOverloads); // Same parameters, different return types type SameParametersDifferentReturn1 = (foo: string) => string; type SameParametersDifferentReturn2 = (foo: string) => number; -declare const sameParametersDifferentReturn: FunctionOverloads; -expectType(sameParametersDifferentReturn); +declare const sameParametersDifferentReturn: OverloadsToTuple; +expectType<[SameParametersDifferentReturn1, SameParametersDifferentReturn2]>(sameParametersDifferentReturn); // Generic overloads — generic parameters become `unknown` declare function genericOverload(input: T): T; declare function genericOverload(input: string): string; -declare const genericOverloadResult: FunctionOverloads; -expectType<((input: unknown) => unknown) | ((input: string) => string)>(genericOverloadResult); +declare const genericOverloadResult: OverloadsToTuple; +expectType<[(input: unknown) => unknown, (input: string) => string]>(genericOverloadResult); // Interface-style overload type InterfaceOverload = { @@ -174,27 +168,29 @@ type InterfaceOverload = { (input: number): 2; }; -declare const interfaceOverload: FunctionOverloads; -expectType<((input: string) => 1) | ((input: number) => 2)>(interfaceOverload); +declare const interfaceOverload: OverloadsToTuple; +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; -expectType(sameParametersDifferentThis); +declare const sameParametersDifferentThis: OverloadsToTuple; +expectType<[SameParametersDifferentThis1, SameParametersDifferentThis2]>(sameParametersDifferentThis); +declare const sameParametersDifferentThis2: OverloadsToTuple; +expectType<[SameParametersDifferentThis2, SameParametersDifferentThis1]>(sameParametersDifferentThis2); // Duplicate overloads in interface are collapsed -declare const duplicateOverloads: FunctionOverloads<{ +declare const duplicateOverloads: OverloadsToTuple<{ (foo: string, bar: number): object; (): string; (): string; }>; -expectType string)>(duplicateOverloads); +expectType<[Function1, () => string]>(duplicateOverloads); // Generic overload at intersection level stops iteration — only the last inferred overload -declare const genericIntersectionOverload: FunctionOverloads<((this: string) => string) & ((this: T, argument: T) => T)>; -expectType<(this: unknown, argument: unknown) => unknown>(genericIntersectionOverload); +declare const genericIntersectionOverload: OverloadsToTuple<((this: string) => string) & ((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. @@ -204,40 +200,51 @@ type Function2WithThis = (this: This, foo: bigint, ...bar: any[]) => void; // Single overload with explicit `this: unknown` expectType>, - Function1WithThis + OverloadsToTuple>, + [Function1WithThis] >>(true); // Single overload with implicit `this` expectType, - Function1 + OverloadsToTuple, + [Function1] >>(true); // Mixed explicit `this: unknown` and implicit `this` overloads expectType & Function2>, - Function1WithThis | Function2 + OverloadsToTuple & Function2>, + [Function1WithThis, Function2] >>(true); // Multiple explicit and implicit `this` overloads expectType, - | Function1WithThis - | Function2WithThis - | Function3 - | Function4 + [ + Function1WithThis, + Function2WithThis, + Function3, + Function4, + ] >>(true); // When implicit `this` and explicit `this: unknown` overloads share same params/return, implicit may be lost -expectType>, Function1 | Function1WithThis<1>>>(true); +expectType>, [Function1]>>(true); // When the explicit `this` overload comes first, the implicit `this` overload may be absorbed -expectType & Function1>, Function1WithThis<1>>>(true); +expectType & Function1>, [Function1WithThis<1>]>>(true); +expectType & Function1 & Function1WithThis<2>>, [Function1WithThis<1>, Function1WithThis<2>]>>(true); // With `this: unknown` specifically, implicit `this` is always absorbed -expectType>, Function1WithThis>>(true); -expectType & Function1>, Function1WithThis>>(true); +expectType>, [Function1]>>(true); +expectType & Function1>, [Function1WithThis]>>(true); + +// === FunctionOverloads (union counterpart — basic coverage) === + +declare const functionOverloadsSingle: FunctionOverloads; +expectType(functionOverloadsSingle); + +declare const functionOverloadsTwo: FunctionOverloads; +expectType(functionOverloadsTwo); From 732302e0b937832e3d301aebb08ac5bd893af61a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=B1=A0=E4=B8=8B=20=E5=85=8B=E5=BD=A6?= Date: Sat, 11 Apr 2026 09:36:25 +0900 Subject: [PATCH 07/24] Move internals to `source/internal/function.d.ts`, fix docs - Move `HasExplicitThis`, `LastOverload`, `CollectOverloads`, and internal `Parameters` to `source/internal/function.d.ts` - Deduplicate JSDoc: TS overload enumeration behavior documented once in the internal file; `HasExplicitThis` and `CollectOverloads` refer to it instead of repeating - Fix stale "in this file" reference in `FunctionOverloads` JSDoc - Add "Known limitations are the same as FunctionOverloads" to `OverloadsToTuple` JSDoc Co-Authored-By: Claude Opus 4.6 (1M context) --- source/function-overloads.d.ts | 139 +-------------------------------- source/internal/function.d.ts | 111 ++++++++++++++++++++++++++ source/internal/index.d.ts | 1 + 3 files changed, 116 insertions(+), 135 deletions(-) create mode 100644 source/internal/function.d.ts diff --git a/source/function-overloads.d.ts b/source/function-overloads.d.ts index 1b0af2c18..e302e9089 100644 --- a/source/function-overloads.d.ts +++ b/source/function-overloads.d.ts @@ -1,7 +1,5 @@ +import type {CollectOverloads} from './internal/index.d.ts'; 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. @@ -15,7 +13,7 @@ Use-cases: Known limitations: - Generic type parameters are lost and inferred as `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 — whichever appears first in the intersection suppresses the other. See the internal documentation comment in this file for details. +- 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 — whichever appears first in the intersection suppresses the other. See `source/internal/function.d.ts` for details on TypeScript's overload enumeration behavior. @example ``` @@ -45,142 +43,13 @@ type RequestReturnType = ReturnType; */ export type FunctionOverloads any> = OverloadsToTuple[number]; -// ======================================================================== -// 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 — whichever appears first wins. -// -// Example (F1 = implicit this, F1wT = 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. -// -// ## Detecting implicit `this` vs explicit `this: unknown` -// -// Both implicit `this` and explicit `this: unknown` give `ThisParameterType` -// = `unknown`. To distinguish them, we intersect a sentinel signature -// `(this: Nothing, ...args) => ...` from the **right**: -// -// - If the original has implicit `this`: TypeScript's deduplication treats -// them as the same (implicit → compare by (P,R) only). The original wins -// (first-wins rule), so the Nothing signature is absorbed. The result's -// ThisParameterType remains `unknown` (not Nothing). -// -// - If the original has explicit `this: unknown`: Both are explicit, so -// TypeScript compares (This, P, R). `unknown ≠ Nothing`, so they are NOT -// duplicates — both survive. The rightmost is the Nothing signature, so -// ThisParameterType returns `Nothing`. -// -// This is what `HasExplicitThis` implements below. -// -// ## What CollectOverloads returns -// -// `CollectOverloads` extracts TypeScript's **enumerated** overload list (as -// described above), not necessarily all **declared** overloads. In particular, -// when an implicit-`this` overload and an explicit-`this` overload share the -// same params/return, whichever appears first in the intersection suppresses -// the other. This is a fundamental property of TypeScript's overload -// deduplication and cannot be worked around. -// -// @see https://github.com/microsoft/TypeScript/issues/32164#issuecomment-1146737709 -// ======================================================================== - -declare const nothing: unique symbol; -type Nothing = typeof nothing; - -/** - * Obtain the parameters of a function type in a tuple - * This works even when the parameters type is a readonly array - */ -type Parameters any> = T extends (...args: infer P extends UnknownArray) => any ? P : never; - -/** -Detect whether a function type has an explicit `this` annotation. - -Both implicit `this` and explicit `this: unknown` give `ThisParameterType` = `unknown`. -To tell them apart, we intersect a `(this: Nothing, ...)` signature from the right. -If the original `this` was implicit, the Nothing signature is absorbed by deduplication -and ThisParameterType remains `unknown`. If it was explicit `this: unknown`, the Nothing -signature survives as a separate overload, and ThisParameterType returns `Nothing`. -*/ -type HasExplicitThis any> = - IsUnknown> extends true - ? IsEqual) => ReturnType)>, Nothing> extends true - ? true - : false - : true; - -/** -Extract the last overload of a function type as a standalone function, -correctly preserving implicit `this` (omitted) vs explicit `this` (kept). -*/ -type LastOverload any> = - HasExplicitThis extends true - ? (this: ThisParameterType, ...args: Parameters) => ReturnType - : (...args: Parameters) => ReturnType; - -/** -Iterates over overload signatures right to left, collecting each into a tuple. - -Uses the intersection trick: intersecting the just-extracted signature onto the left -of the function type makes TypeScript's pattern matching skip it on the next iteration, -effectively advancing through all enumerated overloads. - -The termination condition (`CheckedOverloads === PreviousCheckedOverloads`) lags by one -iteration, so the last extracted overload is always a duplicate. We compensate by dropping -the first element of the result tuple at termination. -*/ -type CollectOverloads< - AllOverloads extends (...args: any) => any, - CheckedOverloads = unknown, - PreviousCheckedOverloads = never, - ResultOverloads extends Array<(...args: any) => any> = [], -> = - IsEqual extends true - ? ResultOverloads extends [(...args: any) => any, ...infer Rest extends Array<(...args: any) => any>] ? Rest : [] - : 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. - LastOverload & AllOverloads, - LastOverload & CheckedOverloads, - CheckedOverloads, - [LastOverload, ...ResultOverloads] - > -; - /** Extract all overload signatures of the given function type as a tuple, preserving declaration order. This is the tuple counterpart to {@link FunctionOverloads}, which returns a union. Use this when overload order matters. +Known limitations are the same as {@link FunctionOverloads}. + @example ``` import type {OverloadsToTuple} from 'type-fest'; diff --git a/source/internal/function.d.ts b/source/internal/function.d.ts new file mode 100644 index 000000000..226ea1c51 --- /dev/null +++ b/source/internal/function.d.ts @@ -0,0 +1,111 @@ +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'; + +/** + * Obtain the parameters of a function type in a tuple + * This works even when the parameters type is a readonly array + */ +export type Parameters 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 — whichever appears first wins. +// +// Example (F1 = implicit this, F1wT = 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 the last overload of a function type has an explicit `this` annotation. + +Intersects `(this: Unique, ...)` from the right. Per TypeScript's deduplication rules +(see "Overload enumeration" above), implicit `this` absorbs the sentinel (same (P,R) → +duplicate, first-wins), so `ThisParameterType` stays `unknown`. Explicit `this: unknown` +does not absorb it (both explicit, different `This` → not duplicate), so +`ThisParameterType` returns `Unique`. +*/ +type HasExplicitThis any> = + IsUnknown> extends true + ? IsEqual) => ReturnType)>, Unique> extends true + ? true + : false + : true; + +/** +Extract the last overload of a function type as a standalone function, +correctly preserving implicit `this` (omitted) vs explicit `this` (kept). +*/ +type LastOverload any> = + HasExplicitThis extends true + ? (this: ThisParameterType, ...args: Parameters) => ReturnType + : (...args: Parameters) => ReturnType; + +/** +Extracts TypeScript's enumerated overload list into a tuple (see "Overload enumeration" above). + +Uses the intersection trick: intersecting `LastOverload` onto the left +makes TypeScript's pattern matching skip it on the next iteration, effectively advancing +through all overloads right to left. `LastOverload` preserves implicit-vs-explicit `this` +via `HasExplicitThis`. + +The termination condition (`CheckedOverloads === PreviousCheckedOverloads`) lags by one +iteration, so the last extracted overload is always a duplicate. We compensate by dropping +the first element of the result tuple at termination. + +@see https://github.com/microsoft/TypeScript/issues/32164#issuecomment-1146737709 +*/ +export type CollectOverloads< + AllOverloads extends (...args: any) => any, + CheckedOverloads = unknown, + PreviousCheckedOverloads = never, + ResultOverloads extends Array<(...args: any) => any> = [], +> = + IsEqual extends true + ? ResultOverloads extends [(...args: any) => any, ...infer Rest extends Array<(...args: any) => any>] ? Rest : [] + : 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. + LastOverload & AllOverloads, + LastOverload & CheckedOverloads, + CheckedOverloads, + [LastOverload, ...ResultOverloads] + > +; + +export {}; diff --git a/source/internal/index.d.ts b/source/internal/index.d.ts index 864db2d97..192e3743d 100644 --- a/source/internal/index.d.ts +++ b/source/internal/index.d.ts @@ -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 {}; From 271492f2c8cfde39f5d3d155a9ae74d6ad4cf8ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=B1=A0=E4=B8=8B=20=E5=85=8B=E5=BD=A6?= Date: Sun, 12 Apr 2026 08:22:58 +0900 Subject: [PATCH 08/24] Rename to `Overloads`, add `OverloadParameters`/`OverloadReturnType`, drop `OverloadsToUnion` MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Rename `OverloadsToTuple` → `Overloads` (tuple is the natural representation) - Remove `OverloadsToUnion` (redundant — `Overloads[number]` is equivalent) - Add `OverloadParameters` and `OverloadReturnType` as overload-aware counterparts to the built-in `Parameters` and `ReturnType` Co-Authored-By: Claude Opus 4.6 (1M context) --- index.d.ts | 2 +- readme.md | 5 +- source/function-overloads.d.ts | 71 ++++++++++++++++------------ source/internal/function.d.ts | 3 +- test-d/function-overloads.ts | 84 +++++++++++++++++----------------- 5 files changed, 91 insertions(+), 74 deletions(-) diff --git a/index.d.ts b/index.d.ts index c55e54aae..372ee3218 100644 --- a/index.d.ts +++ b/index.d.ts @@ -69,7 +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, OverloadsToTuple} from './source/function-overloads.d.ts'; +export type {Overloads, OverloadParameters, OverloadReturnType} 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'; diff --git a/readme.md b/readme.md index 87ac0bc93..639405aee 100644 --- a/readme.md +++ b/readme.md @@ -246,8 +246,9 @@ Click the type names for complete docs. ### Function -- [`FunctionOverloads`](source/function-overloads.d.ts) - Create a union of all overload signatures of the given function type. -- [`OverloadsToTuple`](source/function-overloads.d.ts) - Extract all overload signatures of the given function type as a tuple, preserving declaration order. +- [`Overloads`](source/function-overloads.d.ts) - Extract all overload signatures of the given function type as a tuple. +- [`OverloadParameters`](source/function-overloads.d.ts) - Extract the parameter types of all overloads as a union. +- [`OverloadReturnType`](source/function-overloads.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. diff --git a/source/function-overloads.d.ts b/source/function-overloads.d.ts index e302e9089..75cd37114 100644 --- a/source/function-overloads.d.ts +++ b/source/function-overloads.d.ts @@ -2,9 +2,9 @@ import type {CollectOverloads} from './internal/index.d.ts'; import type {IsAny} from './is-any.d.ts'; /** -Create a union of all overload signatures of the given function type. +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 as a union, allowing you to work with each overload individually. +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` @@ -17,23 +17,21 @@ Known limitations: @example ``` -import type {FunctionOverloads} from 'type-fest'; +import type {Overloads} from 'type-fest'; declare function request(url: string): Promise; declare function request(url: string, options: {json: true}): Promise; -type RequestOverloads = FunctionOverloads; +type RequestOverloads = Overloads; +//=> [(url: string) => Promise, (url: string, options: { +// json: true; +// }) => Promise] + +// To get a union instead of a tuple, index with [number]: +type RequestOverloadsUnion = Overloads[number]; //=> ((url: string) => Promise) | ((url: string, options: { // json: true; // }) => Promise) - -// You can also get all parameters and return types using built-in `Parameters` and `ReturnType` utilities: - -type RequestParameters = Parameters; -//=> [url: string] | [url: string, options: {json: true}] - -type RequestReturnType = ReturnType; -//=> Promise | Promise ``` @see https://github.com/microsoft/TypeScript/issues/14107 @@ -41,37 +39,54 @@ type RequestReturnType = ReturnType; @category Function */ -export type FunctionOverloads any> = OverloadsToTuple[number]; +export type Overloads any> = FunctionType extends unknown + ? IsAny extends true + ? [(...arguments_: any[]) => any] + : CollectOverloads + : never; /** -Extract all overload signatures of the given function type as a tuple, preserving declaration order. +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; +declare function request(url: string, options: {json: true}): Promise; + +type AllParameters = OverloadParameters; +//=> [url: string] | [url: string, options: {json: true}] +``` -This is the tuple counterpart to {@link FunctionOverloads}, which returns a union. Use this when overload order matters. +Known limitations are the same as {@link Overloads}. + +@category Function +*/ +export type OverloadParameters any> = Parameters[number]>; + +/** +Extract the return types of all overloads as a union. -Known limitations are the same as {@link FunctionOverloads}. +This is the overload-aware counterpart to the built-in `ReturnType` utility type, which only extracts from the last overload. @example ``` -import type {OverloadsToTuple} from 'type-fest'; +import type {OverloadReturnType} from 'type-fest'; declare function request(url: string): Promise; declare function request(url: string, options: {json: true}): Promise; -type RequestOverloads = OverloadsToTuple; -//=> [(url: string) => Promise, (url: string, options: { -// json: true; -// }) => Promise] +type AllReturnTypes = OverloadReturnType; +//=> Promise | Promise ``` -@see https://github.com/microsoft/TypeScript/issues/14107 -@see https://github.com/microsoft/TypeScript/issues/32164 +Known limitations are the same as {@link Overloads}. @category Function */ -export type OverloadsToTuple any> = FunctionType extends unknown - ? IsAny extends true - ? [(...arguments_: any[]) => any] - : CollectOverloads - : never; +export type OverloadReturnType any> = ReturnType[number]>; export {}; diff --git a/source/internal/function.d.ts b/source/internal/function.d.ts index 226ea1c51..98aab2c34 100644 --- a/source/internal/function.d.ts +++ b/source/internal/function.d.ts @@ -105,7 +105,6 @@ export type CollectOverloads< LastOverload & CheckedOverloads, CheckedOverloads, [LastOverload, ...ResultOverloads] - > -; + >; export {}; diff --git a/test-d/function-overloads.ts b/test-d/function-overloads.ts index 054b8439c..3c0bf6f1d 100644 --- a/test-d/function-overloads.ts +++ b/test-d/function-overloads.ts @@ -1,5 +1,5 @@ import {expectType} from 'tsd'; -import type {FunctionOverloads, IsEqual, OverloadsToTuple, UnknownArray} from '../index.d.ts'; +import type {IsEqual, Overloads, OverloadParameters, OverloadReturnType, 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 @@ -26,22 +26,22 @@ type Function1 = (foo: string, bar: number) => object; type Function2 = (foo: bigint, ...bar: any[]) => void; // Single function (no overload) -declare const normalFunction: OverloadsToTuple; +declare const normalFunction: Overloads; expectType<[Function1]>(normalFunction); // Two overloads via intersection -declare const twoOverloads: OverloadsToTuple; +declare const twoOverloads: Overloads; expectType<[Function1, Function2]>(twoOverloads); // Two overloads via interface syntax -declare const twoOverloadsInterface: OverloadsToTuple<{ +declare const twoOverloadsInterface: Overloads<{ (foo: string, bar: number): object; (foo: bigint, ...bar: any[]): void; }>; expectType<[Function1, Function2]>(twoOverloadsInterface); // Identical overloads collapse -declare const twoIdenticalOverloads: OverloadsToTuple<{ +declare const twoIdenticalOverloads: Overloads<{ (foo: string, bar: number): object; (foo: string, bar: number): object; }>; @@ -50,46 +50,46 @@ expectType<[Function1]>(twoIdenticalOverloads); type Function3 = (foo: string, bar: number, baz?: boolean) => object; // Two overloads with assignable but distinct signatures -declare const twoOverloadsWithAssignableSignature: OverloadsToTuple; +declare const twoOverloadsWithAssignableSignature: Overloads; expectType<[Function1, Function3]>(twoOverloadsWithAssignableSignature); // Three overloads — declaration order preserved -declare const threeOverloads: OverloadsToTuple; +declare const threeOverloads: Overloads; expectType<[Function1, Function2, Function3]>(threeOverloads); type Function4 = (...foo: any[]) => void; type Function5 = (...foo: readonly any[]) => void; // Rest parameter overloads -declare const normalFunctionWithOnlyRestWritableParameter: OverloadsToTuple; +declare const normalFunctionWithOnlyRestWritableParameter: Overloads; expectType<[Function4]>(normalFunctionWithOnlyRestWritableParameter); -declare const normalFunctionWithOnlyRestReadonlyParameter: OverloadsToTuple; +declare const normalFunctionWithOnlyRestReadonlyParameter: Overloads; expectType<[Function5]>(normalFunctionWithOnlyRestReadonlyParameter); // The compiler ignores subsequent identical-up-to-readonly overloads -declare const twoOverloadsWithDifferentRestParameterReadonliness: OverloadsToTuple; +declare const twoOverloadsWithDifferentRestParameterReadonliness: Overloads; expectType<[Function4]>(twoOverloadsWithDifferentRestParameterReadonliness); -declare const twoOverloadsWithDifferentRestParameterReadonlinessReversed: OverloadsToTuple; +declare const twoOverloadsWithDifferentRestParameterReadonlinessReversed: Overloads; expectType<[Function5]>(twoOverloadsWithDifferentRestParameterReadonlinessReversed); type Function6 = (foo: string, ...bar: any[]) => void; type Function7 = (foo: string, ...bar: readonly any[]) => void; -declare const normalFunctionWithNormalAndRestWritableParameter: OverloadsToTuple; +declare const normalFunctionWithNormalAndRestWritableParameter: Overloads; expectType<[Function6]>(normalFunctionWithNormalAndRestWritableParameter); // Readonly rest parameter cannot be represented with tuples -declare const normalFunctionWithNormalAndRestReadonlyParameter: OverloadsToTuple; +declare const normalFunctionWithNormalAndRestReadonlyParameter: Overloads; expectType<[(foo: string, ...bar: any[]) => void]>(normalFunctionWithNormalAndRestReadonlyParameter); type Function8 = () => never; -declare const normalFunctionNoParameters: OverloadsToTuple; +declare const normalFunctionNoParameters: Overloads; expectType<[Function8]>(normalFunctionNoParameters); -declare const twoOverloadsWithNoAndPresentParameters: OverloadsToTuple; +declare const twoOverloadsWithNoAndPresentParameters: Overloads; expectType<[Function8, Function6]>(twoOverloadsWithNoAndPresentParameters); type Function9 = (event: 'event9', argument: string) => void; @@ -98,7 +98,7 @@ type Function11 = (event: 'event11', argument: boolean) => never; type Function12 = (event: 'event12', argument: bigint) => object; // Many overloads — order preserved -declare const manyOverloads: OverloadsToTuple< +declare const manyOverloads: Overloads< Function1 & Function2 & Function3 @@ -126,25 +126,25 @@ expectType<[ ]>(manyOverloads); // Edge case: `any` returns a single generic function signature -declare const anyOverload: OverloadsToTuple; +declare const anyOverload: Overloads; expectType<[(...arguments_: any[]) => any]>(anyOverload); // Edge case: `never` returns never (distributes over union) -declare const neverOverload: OverloadsToTuple; +declare const neverOverload: Overloads; expectType(neverOverload); // `declare function` overloads declare function declaredOverload(input: string): {kind: 'string'}; declare function declaredOverload(input: number, flag: boolean): {kind: 'number'}; -declare const declaredOverloadResult: OverloadsToTuple; +declare const declaredOverloadResult: Overloads; 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: OverloadsToTuple; +declare const thisOverloads: Overloads; // Verify `this` is preserved expectType<[ThisOverload1, ThisOverload2]>(thisOverloads); @@ -152,14 +152,14 @@ expectType<[ThisOverload1, ThisOverload2]>(thisOverloads); type SameParametersDifferentReturn1 = (foo: string) => string; type SameParametersDifferentReturn2 = (foo: string) => number; -declare const sameParametersDifferentReturn: OverloadsToTuple; +declare const sameParametersDifferentReturn: Overloads; expectType<[SameParametersDifferentReturn1, SameParametersDifferentReturn2]>(sameParametersDifferentReturn); // Generic overloads — generic parameters become `unknown` declare function genericOverload(input: T): T; declare function genericOverload(input: string): string; -declare const genericOverloadResult: OverloadsToTuple; +declare const genericOverloadResult: Overloads; expectType<[(input: unknown) => unknown, (input: string) => string]>(genericOverloadResult); // Interface-style overload @@ -168,20 +168,20 @@ type InterfaceOverload = { (input: number): 2; }; -declare const interfaceOverload: OverloadsToTuple; +declare const interfaceOverload: Overloads; 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: OverloadsToTuple; +declare const sameParametersDifferentThis: Overloads; expectType<[SameParametersDifferentThis1, SameParametersDifferentThis2]>(sameParametersDifferentThis); -declare const sameParametersDifferentThis2: OverloadsToTuple; +declare const sameParametersDifferentThis2: Overloads; expectType<[SameParametersDifferentThis2, SameParametersDifferentThis1]>(sameParametersDifferentThis2); // Duplicate overloads in interface are collapsed -declare const duplicateOverloads: OverloadsToTuple<{ +declare const duplicateOverloads: Overloads<{ (foo: string, bar: number): object; (): string; (): string; @@ -189,7 +189,7 @@ declare const duplicateOverloads: OverloadsToTuple<{ expectType<[Function1, () => string]>(duplicateOverloads); // Generic overload at intersection level stops iteration — only the last inferred overload -declare const genericIntersectionOverload: OverloadsToTuple<((this: string) => string) & ((this: T, argument: T) => T)>; +declare const genericIntersectionOverload: Overloads<((this: string) => string) & ((this: T, argument: T) => T)>; expectType<[(this: unknown, argument: unknown) => unknown]>(genericIntersectionOverload); // Verify that explicit `this: unknown` is preserved while implicit `this` is omitted. @@ -200,25 +200,25 @@ type Function2WithThis = (this: This, foo: bigint, ...bar: any[]) => void; // Single overload with explicit `this: unknown` expectType>, + Overloads>, [Function1WithThis] >>(true); // Single overload with implicit `this` expectType, + Overloads, [Function1] >>(true); // Mixed explicit `this: unknown` and implicit `this` overloads expectType & Function2>, + Overloads & Function2>, [Function1WithThis, Function2] >>(true); // Multiple explicit and implicit `this` overloads expectType>(true); // When implicit `this` and explicit `this: unknown` overloads share same params/return, implicit may be lost -expectType>, [Function1]>>(true); +expectType>, [Function1]>>(true); // When the explicit `this` overload comes first, the implicit `this` overload may be absorbed -expectType & Function1>, [Function1WithThis<1>]>>(true); -expectType & Function1 & Function1WithThis<2>>, [Function1WithThis<1>, Function1WithThis<2>]>>(true); +expectType & Function1>, [Function1WithThis<1>]>>(true); +expectType & Function1 & Function1WithThis<2>>, [Function1WithThis<1>, Function1WithThis<2>]>>(true); // With `this: unknown` specifically, implicit `this` is always absorbed -expectType>, [Function1]>>(true); -expectType & Function1>, [Function1WithThis]>>(true); +expectType>, [Function1]>>(true); +expectType & Function1>, [Function1WithThis]>>(true); -// === FunctionOverloads (union counterpart — basic coverage) === +// === OverloadParameters / OverloadReturnType === -declare const functionOverloadsSingle: FunctionOverloads; -expectType(functionOverloadsSingle); +declare const overloadParameters: OverloadParameters; +expectType<[foo: string, bar: number] | [foo: bigint, ...bar: any[]]>(overloadParameters); -declare const functionOverloadsTwo: FunctionOverloads; -expectType(functionOverloadsTwo); +// eslint-disable-next-line @typescript-eslint/no-redundant-type-constituents +declare const overloadReturnType: OverloadReturnType; +// eslint-disable-next-line @typescript-eslint/no-redundant-type-constituents +expectType(overloadReturnType); From a896a7bda123bdea8bae28b5102134f9d8fdae79 Mon Sep 17 00:00:00 2001 From: Sindre Sorhus Date: Sun, 12 Apr 2026 14:56:21 +0700 Subject: [PATCH 09/24] Update function.d.ts --- source/internal/function.d.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/source/internal/function.d.ts b/source/internal/function.d.ts index 98aab2c34..6dd5fd910 100644 --- a/source/internal/function.d.ts +++ b/source/internal/function.d.ts @@ -4,9 +4,10 @@ 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 - */ +Obtain the parameters of a function type in a tuple. + +This works even when the parameters type is a readonly array. +*/ export type Parameters any> = T extends (...args: infer P extends UnknownArray) => any ? P : never; // ======================================================================== From a3e267b3cb2ead5cd4452d9539e23dcacc5a35e2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=B1=A0=E4=B8=8B=20=E5=85=8B=E5=BD=A6?= Date: Sun, 12 Apr 2026 17:43:00 +0900 Subject: [PATCH 10/24] Clean up comment style and formatting Co-Authored-By: Claude Opus 4.6 (1M context) --- source/internal/function.d.ts | 86 ++++++++++++++--------------------- test-d/function-overloads.ts | 16 +++++-- 2 files changed, 44 insertions(+), 58 deletions(-) diff --git a/source/internal/function.d.ts b/source/internal/function.d.ts index 6dd5fd910..2ce94e0bb 100644 --- a/source/internal/function.d.ts +++ b/source/internal/function.d.ts @@ -1,4 +1,3 @@ -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'; @@ -10,44 +9,35 @@ This works even when the parameters type is a readonly array. */ export type Parameters 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 — whichever appears first wins. -// -// Example (F1 = implicit this, F1wT = 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. -// ======================================================================== +/* +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 — whichever appears first wins. + +Example (F1 = implicit this, F1wT = 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; @@ -55,11 +45,7 @@ type Unique = typeof unique; /** Detect whether the last overload of a function type has an explicit `this` annotation. -Intersects `(this: Unique, ...)` from the right. Per TypeScript's deduplication rules -(see "Overload enumeration" above), implicit `this` absorbs the sentinel (same (P,R) → -duplicate, first-wins), so `ThisParameterType` stays `unknown`. Explicit `this: unknown` -does not absorb it (both explicit, different `This` → not duplicate), so -`ThisParameterType` returns `Unique`. +Intersects `(this: Unique, ...)` from the right. Per TypeScript's deduplication rules (see "Overload enumeration" above), implicit `this` absorbs the sentinel (same (P,R) → duplicate, first-wins), so `ThisParameterType` stays `unknown`. Explicit `this: unknown` does not absorb it (both explicit, different `This` → not duplicate), so `ThisParameterType` returns `Unique`. */ type HasExplicitThis any> = IsUnknown> extends true @@ -69,8 +55,7 @@ type HasExplicitThis any> = : true; /** -Extract the last overload of a function type as a standalone function, -correctly preserving implicit `this` (omitted) vs explicit `this` (kept). +Extract the last overload of a function type as a standalone function, correctly preserving implicit `this` (omitted) vs explicit `this` (kept). */ type LastOverload any> = HasExplicitThis extends true @@ -80,14 +65,9 @@ type LastOverload any> = /** Extracts TypeScript's enumerated overload list into a tuple (see "Overload enumeration" above). -Uses the intersection trick: intersecting `LastOverload` onto the left -makes TypeScript's pattern matching skip it on the next iteration, effectively advancing -through all overloads right to left. `LastOverload` preserves implicit-vs-explicit `this` -via `HasExplicitThis`. +Uses the intersection trick: intersecting `LastOverload` onto the left makes TypeScript's pattern matching skip it on the next iteration, effectively advancing through all overloads right to left. `LastOverload` preserves implicit-vs-explicit `this` via `HasExplicitThis`. -The termination condition (`CheckedOverloads === PreviousCheckedOverloads`) lags by one -iteration, so the last extracted overload is always a duplicate. We compensate by dropping -the first element of the result tuple at termination. +The termination condition (`CheckedOverloads === PreviousCheckedOverloads`) lags by one iteration, so the last extracted overload is always a duplicate. We compensate by dropping the first element of the result tuple at termination. @see https://github.com/microsoft/TypeScript/issues/32164#issuecomment-1146737709 */ diff --git a/test-d/function-overloads.ts b/test-d/function-overloads.ts index 3c0bf6f1d..ec8b4c9a3 100644 --- a/test-d/function-overloads.ts +++ b/test-d/function-overloads.ts @@ -1,11 +1,17 @@ import {expectType} from 'tsd'; import type {IsEqual, Overloads, OverloadParameters, OverloadReturnType, 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. -// It accepts tuples of functions and compares them element-wise. +/* +Neither `expectType` nor `IsEqual` can distinguish implicit `this` from explicit `this: unknown`: + +```ts +expectType<() => void>(x as (this: unknown) => void); // no error +type _ = IsEqual<() => void, (this: unknown) => void>; // => true +``` + +`IsEqualStrict` resolves this by also comparing the extracted `this` parameter via a sentinel type. +It accepts tuples of functions and compares them element-wise. +*/ declare const isEqualStrictNothing: unique symbol; type IsEqualStrictNothing = typeof isEqualStrictNothing; type FuncToTuple unknown> = From e2b8ed3db68941ed3013f7c532fbab2410477816 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=B1=A0=E4=B8=8B=20=E5=85=8B=E5=BD=A6?= Date: Sun, 12 Apr 2026 19:56:28 +0900 Subject: [PATCH 11/24] Replace mdash with ASCII punctuation Co-Authored-By: Claude Opus 4.6 (1M context) --- source/function-overloads.d.ts | 4 ++-- source/internal/function.d.ts | 2 +- test-d/function-overloads.ts | 8 ++++---- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/source/function-overloads.d.ts b/source/function-overloads.d.ts index 75cd37114..5d00d1c54 100644 --- a/source/function-overloads.d.ts +++ b/source/function-overloads.d.ts @@ -12,8 +12,8 @@ Use-cases: - Extract event handler signatures from framework APIs Known limitations: -- Generic type parameters are lost and inferred as `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 — whichever appears first in the intersection suppresses the other. See `source/internal/function.d.ts` for details on TypeScript's overload enumeration behavior. +- Generic type parameters are lost and inferred as `unknown`. Additionally, overloads preceding a generic overload are not extracted; the generic overload terminates the iteration. +- 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 ``` diff --git a/source/internal/function.d.ts b/source/internal/function.d.ts index 2ce94e0bb..2100f8ad5 100644 --- a/source/internal/function.d.ts +++ b/source/internal/function.d.ts @@ -20,7 +20,7 @@ Understanding how TypeScript enumerates overloads from an intersection type is e 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 — whichever appears first wins. +- 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 = explicit this: T, same params/return): diff --git a/test-d/function-overloads.ts b/test-d/function-overloads.ts index ec8b4c9a3..c026a5534 100644 --- a/test-d/function-overloads.ts +++ b/test-d/function-overloads.ts @@ -59,7 +59,7 @@ type Function3 = (foo: string, bar: number, baz?: boolean) => object; declare const twoOverloadsWithAssignableSignature: Overloads; expectType<[Function1, Function3]>(twoOverloadsWithAssignableSignature); -// Three overloads — declaration order preserved +// Three overloads - declaration order preserved declare const threeOverloads: Overloads; expectType<[Function1, Function2, Function3]>(threeOverloads); @@ -103,7 +103,7 @@ type Function10 = (event: 'event10', argument: number) => string; type Function11 = (event: 'event11', argument: boolean) => never; type Function12 = (event: 'event12', argument: bigint) => object; -// Many overloads — order preserved +// Many overloads - order preserved declare const manyOverloads: Overloads< Function1 & Function2 @@ -161,7 +161,7 @@ type SameParametersDifferentReturn2 = (foo: string) => number; declare const sameParametersDifferentReturn: Overloads; expectType<[SameParametersDifferentReturn1, SameParametersDifferentReturn2]>(sameParametersDifferentReturn); -// Generic overloads — generic parameters become `unknown` +// Generic overloads - generic parameters become `unknown` declare function genericOverload(input: T): T; declare function genericOverload(input: string): string; @@ -194,7 +194,7 @@ declare const duplicateOverloads: Overloads<{ }>; expectType<[Function1, () => string]>(duplicateOverloads); -// Generic overload at intersection level stops iteration — only the last inferred overload +// Generic overload at intersection level stops iteration - only the last inferred overload declare const genericIntersectionOverload: Overloads<((this: string) => string) & ((this: T, argument: T) => T)>; expectType<[(this: unknown, argument: unknown) => unknown]>(genericIntersectionOverload); From 2deba748c5618914669b78d0d36f2916c33fda1e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=B1=A0=E4=B8=8B=20=E5=85=8B=E5=BD=A6?= Date: Wed, 15 Apr 2026 03:19:03 +0900 Subject: [PATCH 12/24] `Overloads`: Extract overloads preceding generic overloads via N-advancement Rewrite `CollectOverloads` internals to handle generic overloads that previously terminated the iteration, losing all preceding overloads. - Replace `LastOverload` with `NthLastOverload`: uses a multi-signature pattern (MaxOverloadPatterns=4 slots) to extract the Nth-from-last overload, with `HasExplicitThis` generalized to check any position. - Replace convergence-based loop with effect-observation loop: each iteration checks whether intersecting ExtractedN onto AllOverloads changes the view at position N. If it does (effect), intersect and continue. If not (no effect, e.g. aliasing generic overloads), output without intersecting and advance N. - Add `() => Unique` sentinel boundary: `CollectOverloads` prepends the sentinel before the original overloads so `NthLastOverload` can detect when all original overloads have been exhausted. - Add `FuncToTupleForCompare` for this-aware function comparison, reused by test `IsEqualStrict`. - Update Known limitations: generic type parameters are replaced by their upper bound; functions with <4 generic overloads are fully extracted. Co-Authored-By: Claude Opus 4.6 (1M context) --- source/function-overloads.d.ts | 2 +- source/internal/function.d.ts | 88 +++++++++++++++++++++----------- test-d/function-overloads.ts | 92 ++++++++++++++++++++-------------- 3 files changed, 115 insertions(+), 67 deletions(-) diff --git a/source/function-overloads.d.ts b/source/function-overloads.d.ts index 5d00d1c54..967f6ad29 100644 --- a/source/function-overloads.d.ts +++ b/source/function-overloads.d.ts @@ -12,7 +12,7 @@ Use-cases: - Extract event handler signatures from framework APIs Known limitations: -- Generic type parameters are lost and inferred as `unknown`. Additionally, overloads preceding a generic overload are not extracted; the generic overload terminates the iteration. +- Generic type parameters are lost -- they are replaced by their upper bound (e.g. `` becomes `unknown`, `` 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 diff --git a/source/internal/function.d.ts b/source/internal/function.d.ts index 2100f8ad5..b124f9add 100644 --- a/source/internal/function.d.ts +++ b/source/internal/function.d.ts @@ -1,5 +1,7 @@ +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'; /** @@ -43,49 +45,77 @@ declare const unique: unique symbol; type Unique = typeof unique; /** -Detect whether the last overload of a function type has an explicit `this` annotation. +Detect whether an overload with the given (This, Parameters, Return) has an explicit `this` annotation in the original function type. -Intersects `(this: Unique, ...)` from the right. Per TypeScript's deduplication rules (see "Overload enumeration" above), implicit `this` absorbs the sentinel (same (P,R) → duplicate, first-wins), so `ThisParameterType` stays `unknown`. Explicit `this: unknown` does not absorb it (both explicit, different `This` → not duplicate), so `ThisParameterType` returns `Unique`. +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 any> = - IsUnknown> extends true - ? IsEqual) => ReturnType)>, Unique> extends true - ? true - : false - : true; - +type HasExplicitThis< + FunctionType extends (...args: any) => any, + This, Parameters_ extends UnknownArray, Return, +> = IsUnknown extends true + ? IsEqual Return)>, Unique> + : true; + +type MaxOverloadPatterns = 4; +type OverloadIndex = IntRange<0, MaxOverloadPatterns>; /** -Extract the last overload of a function type as a standalone function, correctly preserving implicit `this` (omitted) vs explicit `this` (kept). +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 LastOverload any> = - HasExplicitThis extends true - ? (this: ThisParameterType, ...args: Parameters) => ReturnType - : (...args: Parameters) => ReturnType; +type NthLastOverload 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 extends true + ? undefined // No overload at this position + : HasExplicitThis 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). -Uses the intersection trick: intersecting `LastOverload` onto the left makes TypeScript's pattern matching skip it on the next iteration, effectively advancing through all overloads right to left. `LastOverload` preserves implicit-vs-explicit `this` via `HasExplicitThis`. - -The termination condition (`CheckedOverloads === PreviousCheckedOverloads`) lags by one iteration, so the last extracted overload is always a duplicate. We compensate by dropping the first element of the result tuple at termination. +Prepends a `() => Unique` sentinel before the original overloads to mark the boundary, then delegates to `CollectOverloadsLoop`. @see https://github.com/microsoft/TypeScript/issues/32164#issuecomment-1146737709 */ export type CollectOverloads< AllOverloads extends (...args: any) => any, - CheckedOverloads = unknown, - PreviousCheckedOverloads = never, +> = CollectOverloadsLoop<(() => Unique) & AllOverloads>; + +/** +Recursive core of `CollectOverloads`. + +Each iteration extracts `NthLastOverload` 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. +*/ +type CollectOverloadsLoop< + AllOverloads extends (...args: any) => any, + N extends OverloadIndex = 0, ResultOverloads extends Array<(...args: any) => any> = [], > = - IsEqual extends true - ? ResultOverloads extends [(...args: any) => any, ...infer Rest extends Array<(...args: any) => any>] ? Rest : [] - : 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. - LastOverload & AllOverloads, - LastOverload & CheckedOverloads, - CheckedOverloads, - [LastOverload, ...ResultOverloads] - >; + NthLastOverload extends infer ExtractedN extends (...args: any) => any + ? IsEqual, ExtractedN> extends true + ? Sum extends infer NextN extends OverloadIndex + ? CollectOverloadsLoop + : [ExtractedN, ...ResultOverloads] + : CollectOverloadsLoop + : ResultOverloads; export {}; diff --git a/test-d/function-overloads.ts b/test-d/function-overloads.ts index c026a5534..0f13e59b2 100644 --- a/test-d/function-overloads.ts +++ b/test-d/function-overloads.ts @@ -14,11 +14,9 @@ It accepts tuples of functions and compares them element-wise. */ declare const isEqualStrictNothing: unique symbol; type IsEqualStrictNothing = typeof isEqualStrictNothing; -type FuncToTuple unknown> = +type FuncToTuple any> = 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 + ? [ThisParameterType<((this: IsEqualStrictNothing, ...arguments_: Parameters_) => Return) & Function_>, Parameters_, Return] : never; type IsEqualStrict< FunctionTuple1 extends ReadonlyArray<(...arguments_: never) => unknown>, @@ -194,9 +192,9 @@ declare const duplicateOverloads: Overloads<{ }>; expectType<[Function1, () => string]>(duplicateOverloads); -// Generic overload at intersection level stops iteration - only the last inferred overload +// Generic overload at intersection level - overloads preceding the generic are now extracted via N-advancement declare const genericIntersectionOverload: Overloads<((this: string) => string) & ((this: T, argument: T) => T)>; -expectType<[(this: unknown, argument: unknown) => unknown]>(genericIntersectionOverload); +expectType<[(this: string) => string, (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. @@ -205,54 +203,74 @@ type Function1WithThis = (this: This, foo: string, bar: number) => object; type Function2WithThis = (this: This, foo: bigint, ...bar: any[]) => void; // Single overload with explicit `this: unknown` -expectType>, - [Function1WithThis] ->>(true); +declare const singleOverloadWithExplicitThisUnknown: Overloads>; +expectType]>>(true); // Single overload with implicit `this` -expectType, - [Function1] ->>(true); +expectType>(true); // Mixed explicit `this: unknown` and implicit `this` overloads -expectType & Function2>, - [Function1WithThis, Function2] ->>(true); +declare const mixedExplicitImplicitThis: Overloads & Function2>; +expectType, Function2]>>(true); // Multiple explicit and implicit `this` overloads +declare const multipleExplicitImplicitThis: Overloads<{ + (this: unknown, foo: string, bar: number): object; + (this: unknown, foo: bigint, ...bar: any[]): void; + (foo: string, bar: number, baz?: boolean): object; + (...foo: any[]): void; +}>; expectType, - [ - Function1WithThis, - Function2WithThis, - Function3, - Function4, - ] + typeof multipleExplicitImplicitThis, + [Function1WithThis, Function2WithThis, Function3, Function4] >>(true); +declare const aliasingGenericOverloads1: Overloads<{ + (foo: string, bar: number): object; + (this: unknown, l: unknown, r: unknown): [unknown, unknown]; + // (l: T, r: U): [U, T]; + (l: T, r: U): [T, U]; + (l: T, r: T): [T, T]; + (this: unknown, foo: bigint, ...bar: any[]): void; +}>; +expectType [unknown, unknown], + (this: unknown, l: unknown, r: unknown) => [unknown, unknown], + (this: unknown, l: unknown, r: unknown) => [unknown, unknown], + Function2WithThis, +]>>(true); +declare const aliasingGenericOverloads2: Overloads<{ + (foo: string, bar: number): object; + (l: T, r: U): [T, U]; + (l: T, r: T): [T, T]; + (this: unknown, foo: bigint, ...bar: any[]): void; +}>; +expectType [unknown, unknown], + (this: unknown, l: unknown, r: unknown) => [unknown, unknown], + Function2WithThis, +]>>(true); + // When implicit `this` and explicit `this: unknown` overloads share same params/return, implicit may be lost -expectType>, [Function1]>>(true); +declare const implicitThenExplicitThis1: Overloads>; +expectType>(true); // When the explicit `this` overload comes first, the implicit `this` overload may be absorbed -expectType & Function1>, [Function1WithThis<1>]>>(true); -expectType & Function1 & Function1WithThis<2>>, [Function1WithThis<1>, Function1WithThis<2>]>>(true); +declare const explicitThenImplicitThis1: Overloads & Function1>; +expectType]>>(true); +declare const explicitImplicitExplicitThis: Overloads & Function1 & Function1WithThis<2>>; +expectType, Function1WithThis<2>]>>(true); // With `this: unknown` specifically, implicit `this` is always absorbed -expectType>, [Function1]>>(true); -expectType & Function1>, [Function1WithThis]>>(true); +declare const implicitThenExplicitThisUnknown: Overloads>; +expectType>(true); +declare const explicitThisUnknownThenImplicit: Overloads & Function1>; +expectType]>>(true); // === OverloadParameters / OverloadReturnType === declare const overloadParameters: OverloadParameters; expectType<[foo: string, bar: number] | [foo: bigint, ...bar: any[]]>(overloadParameters); -// eslint-disable-next-line @typescript-eslint/no-redundant-type-constituents declare const overloadReturnType: OverloadReturnType; -// eslint-disable-next-line @typescript-eslint/no-redundant-type-constituents expectType(overloadReturnType); From 1396b747b5a8aedc9ee777e3661a7058e7ba7d6b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=B1=A0=E4=B8=8B=20=E5=85=8B=E5=BD=A6?= Date: Wed, 15 Apr 2026 08:20:02 +0900 Subject: [PATCH 13/24] Update `Overloads` description in readme Co-Authored-By: Claude Opus 4.6 (1M context) --- readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/readme.md b/readme.md index e9fe582fe..6ce9c2b67 100644 --- a/readme.md +++ b/readme.md @@ -247,7 +247,7 @@ Click the type names for complete docs. ### Function -- [`Overloads`](source/function-overloads.d.ts) - Extract all overload signatures of the given function type as a tuple. +- [`Overloads`](source/function-overloads.d.ts) - Extract all overload signatures of the given function type as a tuple, preserving declaration order. - [`OverloadParameters`](source/function-overloads.d.ts) - Extract the parameter types of all overloads as a union. - [`OverloadReturnType`](source/function-overloads.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. From 5488b7d6cc5ecbc538e8fc78355869cf65f8b57b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=B1=A0=E4=B8=8B=20=E5=85=8B=E5=BD=A6?= Date: Wed, 15 Apr 2026 10:35:08 +0900 Subject: [PATCH 14/24] Merge `CollectOverloadsLoop` into `CollectOverloads` Use a default type parameter (`AllOverloads = (() => Unique) & F`) to prepend the sentinel, eliminating the separate wrapper type. Update doc to reflect the unified signature. Co-Authored-By: Claude Opus 4.6 (1M context) --- source/internal/function.d.ts | 35 +++++++++++++---------------------- 1 file changed, 13 insertions(+), 22 deletions(-) diff --git a/source/internal/function.d.ts b/source/internal/function.d.ts index b124f9add..4c7d03f01 100644 --- a/source/internal/function.d.ts +++ b/source/internal/function.d.ts @@ -86,36 +86,27 @@ type NthLastOverload any, N extends OverloadIndex> = /** Extracts TypeScript's enumerated overload list into a tuple (see "Overload enumeration" above). -Prepends a `() => Unique` sentinel before the original overloads to mark the boundary, then delegates to `CollectOverloadsLoop`. - -@see https://github.com/microsoft/TypeScript/issues/32164#issuecomment-1146737709 -*/ -export type CollectOverloads< - AllOverloads extends (...args: any) => any, -> = CollectOverloadsLoop<(() => Unique) & AllOverloads>; - -/** -Recursive core of `CollectOverloads`. - -Each iteration extracts `NthLastOverload` and then checks whether intersecting the extracted overload onto `AllOverloads` changes what position N sees: +`AllOverloads` defaults to `(() => Unique) & F`, prepending a `() => Unique` sentinel that marks the boundary of the original overloads. Each iteration extracts `NthLastOverload` 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 */ -type CollectOverloadsLoop< - AllOverloads extends (...args: any) => any, +export type CollectOverloads< + F extends (...args: any) => any, + AllOverloads extends (...args: any) => any = (() => Unique) & F, N extends OverloadIndex = 0, ResultOverloads extends Array<(...args: any) => any> = [], -> = - NthLastOverload extends infer ExtractedN extends (...args: any) => any - ? IsEqual, ExtractedN> extends true - ? Sum extends infer NextN extends OverloadIndex - ? CollectOverloadsLoop - : [ExtractedN, ...ResultOverloads] - : CollectOverloadsLoop - : ResultOverloads; +> = NthLastOverload extends infer ExtractedN extends (...args: any) => any + ? IsEqual, ExtractedN> extends true + ? Sum extends infer NextN extends OverloadIndex + ? CollectOverloads + : [ExtractedN, ...ResultOverloads] + : CollectOverloads + : ResultOverloads; export {}; From d6c1d0277f7dd880ae228481b31b088a4e676d1c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=B1=A0=E4=B8=8B=20=E5=85=8B=E5=BD=A6?= Date: Tue, 21 Apr 2026 16:20:54 +0900 Subject: [PATCH 15/24] Move sentinel check from `NthLastOverload` to `CollectOverloads` --- source/internal/function.d.ts | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/source/internal/function.d.ts b/source/internal/function.d.ts index 4c7d03f01..f6c7913bd 100644 --- a/source/internal/function.d.ts +++ b/source/internal/function.d.ts @@ -60,8 +60,6 @@ type MaxOverloadPatterns = 4; type OverloadIndex = IntRange<0, MaxOverloadPatterns>; /** 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 any, N extends OverloadIndex> = F extends { (this: infer T3, ...args: infer P3 extends UnknownArray): infer R3; @@ -75,9 +73,7 @@ type NthLastOverload any, N extends OverloadIndex> = 1: [T1, P1, R1]; 0: [T0, P0, R0]; }[N] extends [infer T, infer P extends UnknownArray, infer R] - ? IsEqual extends true - ? undefined // No overload at this position - : HasExplicitThis extends true + ? HasExplicitThis extends true ? (this: T, ...args: P) => R : (...args: P) => R : never) @@ -102,11 +98,13 @@ export type CollectOverloads< N extends OverloadIndex = 0, ResultOverloads extends Array<(...args: any) => any> = [], > = NthLastOverload extends infer ExtractedN extends (...args: any) => any - ? IsEqual, ExtractedN> extends true - ? Sum extends infer NextN extends OverloadIndex - ? CollectOverloads - : [ExtractedN, ...ResultOverloads] - : CollectOverloads - : ResultOverloads; + ? IsEqual Unique> extends true + ? ResultOverloads + : IsEqual, ExtractedN> extends true + ? Sum extends infer NextN extends OverloadIndex + ? CollectOverloads + : [ExtractedN, ...ResultOverloads] + : CollectOverloads + : never; export {}; From d7beb1c81edbefda65e9d2ca9fb3c3a216f881f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=B1=A0=E4=B8=8B=20=E5=85=8B=E5=BD=A6?= Date: Tue, 21 Apr 2026 16:36:59 +0900 Subject: [PATCH 16/24] Split `Overloads`, `OverloadParameters`, and `OverloadReturnType` into separate files --- index.d.ts | 4 +- source/overload-parameters.d.ts | 25 ++++++++++ source/overload-return-type.d.ts | 25 ++++++++++ ...function-overloads.d.ts => overloads.d.ts} | 46 +------------------ test-d/overload-parameters.ts | 9 ++++ test-d/overload-return-type.ts | 9 ++++ .../{function-overloads.ts => overloads.ts} | 10 +--- 7 files changed, 74 insertions(+), 54 deletions(-) create mode 100644 source/overload-parameters.d.ts create mode 100644 source/overload-return-type.d.ts rename source/{function-overloads.d.ts => overloads.d.ts} (60%) create mode 100644 test-d/overload-parameters.ts create mode 100644 test-d/overload-return-type.ts rename test-d/{function-overloads.ts => overloads.ts} (96%) diff --git a/index.d.ts b/index.d.ts index 617629252..b98fdc89c 100644 --- a/index.d.ts +++ b/index.d.ts @@ -70,7 +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, OverloadParameters, OverloadReturnType} from './source/function-overloads.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'; diff --git a/source/overload-parameters.d.ts b/source/overload-parameters.d.ts new file mode 100644 index 000000000..fd875ac59 --- /dev/null +++ b/source/overload-parameters.d.ts @@ -0,0 +1,25 @@ +import type {Overloads} from "./overloads.js"; + +/** +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; +declare function request(url: string, options: {json: true}): Promise; + +type AllParameters = OverloadParameters; +//=> [url: string] | [url: string, options: {json: true}] +``` + +Known limitations are the same as {@link Overloads}. + +@category Function +*/ +export type OverloadParameters any> = Parameters[number]>; + +export {}; diff --git a/source/overload-return-type.d.ts b/source/overload-return-type.d.ts new file mode 100644 index 000000000..915753d7a --- /dev/null +++ b/source/overload-return-type.d.ts @@ -0,0 +1,25 @@ +import type {Overloads} from "./overloads.js"; + +/** +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; +declare function request(url: string, options: {json: true}): Promise; + +type AllReturnTypes = OverloadReturnType; +//=> Promise | Promise +``` + +Known limitations are the same as {@link Overloads}. + +@category Function +*/ +export type OverloadReturnType any> = ReturnType[number]>; + +export {}; diff --git a/source/function-overloads.d.ts b/source/overloads.d.ts similarity index 60% rename from source/function-overloads.d.ts rename to source/overloads.d.ts index 967f6ad29..9d5fb5fd6 100644 --- a/source/function-overloads.d.ts +++ b/source/overloads.d.ts @@ -1,5 +1,5 @@ -import type {CollectOverloads} from './internal/index.d.ts'; -import type {IsAny} from './is-any.d.ts'; +import type {CollectOverloads} from './internal/index.js'; +import type {IsAny} from './is-any.js'; /** Extract all overload signatures of the given function type as a tuple, preserving declaration order. @@ -45,48 +45,6 @@ export type Overloads any> = FunctionType : CollectOverloads : 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; -declare function request(url: string, options: {json: true}): Promise; - -type AllParameters = OverloadParameters; -//=> [url: string] | [url: string, options: {json: true}] -``` - -Known limitations are the same as {@link Overloads}. - -@category Function -*/ -export type OverloadParameters any> = Parameters[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; -declare function request(url: string, options: {json: true}): Promise; - -type AllReturnTypes = OverloadReturnType; -//=> Promise | Promise -``` - -Known limitations are the same as {@link Overloads}. - -@category Function -*/ -export type OverloadReturnType any> = ReturnType[number]>; export {}; diff --git a/test-d/overload-parameters.ts b/test-d/overload-parameters.ts new file mode 100644 index 000000000..558ad6347 --- /dev/null +++ b/test-d/overload-parameters.ts @@ -0,0 +1,9 @@ +import {expectType} from 'tsd'; +import type {OverloadParameters} from '../index.js'; + +// 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; +expectType<[foo: string, bar: number] | [foo: bigint, ...bar: any[]]>(overloadParameters); diff --git a/test-d/overload-return-type.ts b/test-d/overload-return-type.ts new file mode 100644 index 000000000..ea184711e --- /dev/null +++ b/test-d/overload-return-type.ts @@ -0,0 +1,9 @@ +import {expectType} from 'tsd'; +import type {OverloadReturnType} from '../index.js'; + +// 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; +expectType(overloadReturnType); diff --git a/test-d/function-overloads.ts b/test-d/overloads.ts similarity index 96% rename from test-d/function-overloads.ts rename to test-d/overloads.ts index 0f13e59b2..88ce8b9a2 100644 --- a/test-d/function-overloads.ts +++ b/test-d/overloads.ts @@ -1,5 +1,5 @@ import {expectType} from 'tsd'; -import type {IsEqual, Overloads, OverloadParameters, OverloadReturnType, UnknownArray} from '../index.d.ts'; +import type {IsEqual, Overloads, OverloadReturnType, UnknownArray} from '../index.js'; /* Neither `expectType` nor `IsEqual` can distinguish implicit `this` from explicit `this: unknown`: @@ -266,11 +266,3 @@ declare const implicitThenExplicitThisUnknown: Overloads>(true); declare const explicitThisUnknownThenImplicit: Overloads & Function1>; expectType]>>(true); - -// === OverloadParameters / OverloadReturnType === - -declare const overloadParameters: OverloadParameters; -expectType<[foo: string, bar: number] | [foo: bigint, ...bar: any[]]>(overloadParameters); - -declare const overloadReturnType: OverloadReturnType; -expectType(overloadReturnType); From 3cc06b84caf99daefe91d4ba2413ce511679e0e2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=B1=A0=E4=B8=8B=20=E5=85=8B=E5=BD=A6?= Date: Tue, 21 Apr 2026 16:52:45 +0900 Subject: [PATCH 17/24] Move `NthLastOverload` and `CollectOverloads` into overloads.d.ts --- source/internal/function.d.ts | 55 +---------------------------- source/overload-parameters.d.ts | 2 +- source/overload-return-type.d.ts | 2 +- source/overloads.d.ts | 60 ++++++++++++++++++++++++++++++-- test-d/overload-parameters.ts | 2 +- test-d/overload-return-type.ts | 2 +- test-d/overloads.ts | 2 +- 7 files changed, 64 insertions(+), 61 deletions(-) diff --git a/source/internal/function.d.ts b/source/internal/function.d.ts index f6c7913bd..696224c55 100644 --- a/source/internal/function.d.ts +++ b/source/internal/function.d.ts @@ -1,7 +1,5 @@ -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'; /** @@ -49,62 +47,11 @@ Detect whether an overload with the given (This, Parameters, Return) has an expl 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< +export type HasExplicitThis< FunctionType extends (...args: any) => any, This, Parameters_ extends UnknownArray, Return, > = IsUnknown extends true ? IsEqual Return)>, Unique> : true; -type MaxOverloadPatterns = 4; -type OverloadIndex = IntRange<0, MaxOverloadPatterns>; -/** -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 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 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` 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< - F extends (...args: any) => any, - AllOverloads extends (...args: any) => any = (() => Unique) & F, - N extends OverloadIndex = 0, - ResultOverloads extends Array<(...args: any) => any> = [], -> = NthLastOverload extends infer ExtractedN extends (...args: any) => any - ? IsEqual Unique> extends true - ? ResultOverloads - : IsEqual, ExtractedN> extends true - ? Sum extends infer NextN extends OverloadIndex - ? CollectOverloads - : [ExtractedN, ...ResultOverloads] - : CollectOverloads - : never; - export {}; diff --git a/source/overload-parameters.d.ts b/source/overload-parameters.d.ts index fd875ac59..70cbdab27 100644 --- a/source/overload-parameters.d.ts +++ b/source/overload-parameters.d.ts @@ -1,4 +1,4 @@ -import type {Overloads} from "./overloads.js"; +import type {Overloads} from './overloads.d.ts'; /** Extract the parameter types of all overloads as a union. diff --git a/source/overload-return-type.d.ts b/source/overload-return-type.d.ts index 915753d7a..b3aa0fc0a 100644 --- a/source/overload-return-type.d.ts +++ b/source/overload-return-type.d.ts @@ -1,4 +1,4 @@ -import type {Overloads} from "./overloads.js"; +import type {Overloads} from './overloads.d.ts'; /** Extract the return types of all overloads as a union. diff --git a/source/overloads.d.ts b/source/overloads.d.ts index 9d5fb5fd6..b8dafda92 100644 --- a/source/overloads.d.ts +++ b/source/overloads.d.ts @@ -1,5 +1,9 @@ -import type {CollectOverloads} from './internal/index.js'; -import type {IsAny} from './is-any.js'; +import type {HasExplicitThis} from './internal/index.d.ts'; +import type {IntRange} from './int-range.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'; /** Extract all overload signatures of the given function type as a tuple, preserving declaration order. @@ -45,6 +49,58 @@ export type Overloads any> = FunctionType : CollectOverloads : never; +type MaxOverloadPatterns = 4; +type OverloadIndex = IntRange<0, MaxOverloadPatterns>; +/** +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 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 extends true + ? (this: T, ...args: P) => R + : (...args: P) => R + : never) + : never; + +declare const unique: unique symbol; +type Unique = typeof unique; + +/** +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` 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 +*/ +type CollectOverloads< + F extends (...args: any) => any, + AllOverloads extends (...args: any) => any = (() => Unique) & F, + N extends OverloadIndex = 0, + ResultOverloads extends Array<(...args: any) => any> = [], +> = NthLastOverload extends infer ExtractedN extends (...args: any) => any + ? IsEqual Unique> extends true + ? ResultOverloads + : IsEqual, ExtractedN> extends true + ? Sum extends infer NextN extends OverloadIndex + ? CollectOverloads + : [ExtractedN, ...ResultOverloads] + : CollectOverloads + : never; export {}; diff --git a/test-d/overload-parameters.ts b/test-d/overload-parameters.ts index 558ad6347..e56d8bf40 100644 --- a/test-d/overload-parameters.ts +++ b/test-d/overload-parameters.ts @@ -1,5 +1,5 @@ import {expectType} from 'tsd'; -import type {OverloadParameters} from '../index.js'; +import type {OverloadParameters} 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; diff --git a/test-d/overload-return-type.ts b/test-d/overload-return-type.ts index ea184711e..55a613d7a 100644 --- a/test-d/overload-return-type.ts +++ b/test-d/overload-return-type.ts @@ -1,5 +1,5 @@ import {expectType} from 'tsd'; -import type {OverloadReturnType} from '../index.js'; +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; diff --git a/test-d/overloads.ts b/test-d/overloads.ts index 88ce8b9a2..f334f1d46 100644 --- a/test-d/overloads.ts +++ b/test-d/overloads.ts @@ -1,5 +1,5 @@ import {expectType} from 'tsd'; -import type {IsEqual, Overloads, OverloadReturnType, UnknownArray} from '../index.js'; +import type {IsEqual, Overloads, UnknownArray} from '../index.d.ts'; /* Neither `expectType` nor `IsEqual` can distinguish implicit `this` from explicit `this: unknown`: From 6cc88668c9216dc12d4253f0c1c966c3fab80af6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=B1=A0=E4=B8=8B=20=E5=85=8B=E5=BD=A6?= Date: Tue, 21 Apr 2026 17:06:23 +0900 Subject: [PATCH 18/24] Rename `Parameters` -> `FunctionParameters` --- source/internal/function.d.ts | 2 +- source/overload-parameters.d.ts | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/source/internal/function.d.ts b/source/internal/function.d.ts index 696224c55..f865cb771 100644 --- a/source/internal/function.d.ts +++ b/source/internal/function.d.ts @@ -7,7 +7,7 @@ Obtain the parameters of a function type in a tuple. This works even when the parameters type is a readonly array. */ -export type Parameters any> = T extends (...args: infer P extends UnknownArray) => any ? P : never; +export type FunctionParameters any> = T extends (...args: infer P extends UnknownArray) => any ? P : never; /* Internal documentation: TypeScript's overload enumeration behavior diff --git a/source/overload-parameters.d.ts b/source/overload-parameters.d.ts index 70cbdab27..96f8616bf 100644 --- a/source/overload-parameters.d.ts +++ b/source/overload-parameters.d.ts @@ -1,3 +1,4 @@ +import type {FunctionParameters} from './internal/index.d.ts'; import type {Overloads} from './overloads.d.ts'; /** @@ -20,6 +21,6 @@ Known limitations are the same as {@link Overloads}. @category Function */ -export type OverloadParameters any> = Parameters[number]>; +export type OverloadParameters any> = FunctionParameters[number]>; export {}; From f4d5a744700e8c39a3f5a6d3b67b675decfb00fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=B1=A0=E4=B8=8B=20=E5=85=8B=E5=BD=A6?= Date: Tue, 21 Apr 2026 17:08:03 +0900 Subject: [PATCH 19/24] Put type parameters in different lines --- source/internal/function.d.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/source/internal/function.d.ts b/source/internal/function.d.ts index f865cb771..2c6714620 100644 --- a/source/internal/function.d.ts +++ b/source/internal/function.d.ts @@ -49,7 +49,9 @@ Intersects `(this: Unique, ...args: Parameters) => Return` onto the function typ */ export type HasExplicitThis< FunctionType extends (...args: any) => any, - This, Parameters_ extends UnknownArray, Return, + This, + Parameters_ extends UnknownArray, + Return, > = IsUnknown extends true ? IsEqual Return)>, Unique> : true; From 667aa60b3074b4270d96629589023b669f45d9ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=B1=A0=E4=B8=8B=20=E5=85=8B=E5=BD=A6?= Date: Tue, 21 Apr 2026 18:22:27 +0900 Subject: [PATCH 20/24] Add tests for internal/function.d.ts --- test-d/internal/function.ts | 86 +++++++++++++++++++++++++++++++++++++ 1 file changed, 86 insertions(+) create mode 100644 test-d/internal/function.ts diff --git a/test-d/internal/function.ts b/test-d/internal/function.ts new file mode 100644 index 000000000..d56a50211 --- /dev/null +++ b/test-d/internal/function.ts @@ -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({} as FunctionParameters<(...args: any[]) => void>); +expectType({} 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({} as FunctionParameters); +expectType({} as FunctionParameters); + +// HasExplicitThis +// --------------- + +type ExplicitThisDate = (this: Date, foo: string) => void; +expectType({} as HasExplicitThis); + +type ExplicitThisUnknown = (this: unknown, foo: string) => void; +expectType({} as HasExplicitThis); + +type ImplicitThis = (foo: string) => void; +expectType({} as HasExplicitThis); + +type ExplicitThisUrl = (this: URL, foo: number, bar: boolean) => string; +expectType({} as HasExplicitThis); + +type ImplicitThisNoParameters = () => void; +expectType({} as HasExplicitThis); + +type ExplicitThisUnknownNoParameters = (this: unknown) => void; +expectType({} as HasExplicitThis); + +type OverloadedExplicitThis = { + (this: Date, foo: string): void; + (foo: number): string; +}; +expectType({} as HasExplicitThis); + +type OverloadedImplicitThis = { + (foo: string): void; + (foo: number): string; +}; +expectType({} as HasExplicitThis); + +type OverloadedMixedThis = { + (this: Date, foo: string): void; + (foo: number): string; +}; +expectType({} as HasExplicitThis); + +type ImplicitThisRest = (...args: any[]) => void; +expectType({} as HasExplicitThis); + +type ExplicitThisRest = (this: string, ...args: any[]) => void; +expectType({} as HasExplicitThis); + +// 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(input: Value): Value; +expectType({} as HasExplicitThis); + +declare function genericExplicitThis(this: Date, input: Value): Value; +expectType({} as HasExplicitThis); + +declare function genericExplicitThisUnknown(this: unknown, input: Value): Value; +expectType({} as HasExplicitThis); + +declare function genericThisParameter(this: This, input: string): This; +expectType({} as HasExplicitThis); + +// Mixed generic + concrete overloads: concrete overload still deduplicates normally. +declare function genericOverloaded(input: Value): Value; +declare function genericOverloaded(input: string): string; +expectType({} as HasExplicitThis); +expectType({} as HasExplicitThis); + +declare function genericOverloadedWithThis(this: Value, input: Value): Value; +declare function genericOverloadedWithThis(input: string): string; +expectType({} as HasExplicitThis); +expectType({} as HasExplicitThis); From 2d2dbd4b11319e850df35dc9a15d339aef0c4d4b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=B1=A0=E4=B8=8B=20=E5=85=8B=E5=BD=A6?= Date: Tue, 21 Apr 2026 18:32:50 +0900 Subject: [PATCH 21/24] Let `Overloads` be any --- source/overloads.d.ts | 2 +- test-d/overload-parameters.ts | 5 ++++- test-d/overload-return-type.ts | 3 +++ test-d/overloads.ts | 4 +--- 4 files changed, 9 insertions(+), 5 deletions(-) diff --git a/source/overloads.d.ts b/source/overloads.d.ts index b8dafda92..09c886cf0 100644 --- a/source/overloads.d.ts +++ b/source/overloads.d.ts @@ -45,7 +45,7 @@ type RequestOverloadsUnion = Overloads[number]; */ export type Overloads any> = FunctionType extends unknown ? IsAny extends true - ? [(...arguments_: any[]) => any] + ? any : CollectOverloads : never; diff --git a/test-d/overload-parameters.ts b/test-d/overload-parameters.ts index e56d8bf40..c9d1b6d9f 100644 --- a/test-d/overload-parameters.ts +++ b/test-d/overload-parameters.ts @@ -1,5 +1,5 @@ import {expectType} from 'tsd'; -import type {OverloadParameters} from '../index.d.ts'; +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; @@ -7,3 +7,6 @@ type Function2 = (foo: bigint, ...bar: any[]) => void; declare const overloadParameters: OverloadParameters; expectType<[foo: string, bar: number] | [foo: bigint, ...bar: any[]]>(overloadParameters); + +declare const anyOverloadParameters: OverloadParameters; +expectType(anyOverloadParameters); diff --git a/test-d/overload-return-type.ts b/test-d/overload-return-type.ts index 55a613d7a..d687a8ca7 100644 --- a/test-d/overload-return-type.ts +++ b/test-d/overload-return-type.ts @@ -7,3 +7,6 @@ type Function2 = (foo: bigint, ...bar: any[]) => void; declare const overloadReturnType: OverloadReturnType; expectType(overloadReturnType); + +declare const anyOverloadReturnType: OverloadReturnType; +expectType(anyOverloadReturnType); diff --git a/test-d/overloads.ts b/test-d/overloads.ts index f334f1d46..538ceda9d 100644 --- a/test-d/overloads.ts +++ b/test-d/overloads.ts @@ -129,11 +129,9 @@ expectType<[ Function12, ]>(manyOverloads); -// Edge case: `any` returns a single generic function signature declare const anyOverload: Overloads; -expectType<[(...arguments_: any[]) => any]>(anyOverload); +expectType(anyOverload); -// Edge case: `never` returns never (distributes over union) declare const neverOverload: Overloads; expectType(neverOverload); From 93b1a4073777c40cb93558a603d6945a824d82a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=B1=A0=E4=B8=8B=20=E5=85=8B=E5=BD=A6?= Date: Tue, 21 Apr 2026 18:48:37 +0900 Subject: [PATCH 22/24] Add limitation comment --- source/internal/function.d.ts | 2 ++ source/overloads.d.ts | 5 ++++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/source/internal/function.d.ts b/source/internal/function.d.ts index 2c6714620..fc795c088 100644 --- a/source/internal/function.d.ts +++ b/source/internal/function.d.ts @@ -46,6 +46,8 @@ 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, diff --git a/source/overloads.d.ts b/source/overloads.d.ts index 09c886cf0..00d0b441b 100644 --- a/source/overloads.d.ts +++ b/source/overloads.d.ts @@ -16,7 +16,10 @@ Use-cases: - Extract event handler signatures from framework APIs Known limitations: -- Generic type parameters are lost -- they are replaced by their upper bound (e.g. `` becomes `unknown`, `` 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. +- Generic overloads: + - Type parameters are lost -- they are replaced by their upper bound (e.g. `` becomes `unknown`, `` 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 From 950c792c8fc26da135ed99baf71f508327dc23db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=B1=A0=E4=B8=8B=20=E5=85=8B=E5=BD=A6?= Date: Tue, 21 Apr 2026 19:20:17 +0900 Subject: [PATCH 23/24] Update readme.md --- readme.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/readme.md b/readme.md index 8271f5de7..909259f76 100644 --- a/readme.md +++ b/readme.md @@ -248,9 +248,9 @@ Click the type names for complete docs. ### Function -- [`Overloads`](source/function-overloads.d.ts) - Extract all overload signatures of the given function type as a tuple, preserving declaration order. -- [`OverloadParameters`](source/function-overloads.d.ts) - Extract the parameter types of all overloads as a union. -- [`OverloadReturnType`](source/function-overloads.d.ts) - Extract the return types of all overloads as a union. +- [`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. From 6282d78dfc68e275389f4332405326d067bc31c7 Mon Sep 17 00:00:00 2001 From: Som Shekhar Mukherjee <49264891+som-sm@users.noreply.github.com> Date: Sun, 7 Jun 2026 18:35:09 +0530 Subject: [PATCH 24/24] refactor: cleanup `overloads.d.ts` --- source/overloads.d.ts | 60 ++++++++++++++++++------------------------- 1 file changed, 25 insertions(+), 35 deletions(-) diff --git a/source/overloads.d.ts b/source/overloads.d.ts index 00d0b441b..71a7f2047 100644 --- a/source/overloads.d.ts +++ b/source/overloads.d.ts @@ -1,9 +1,14 @@ import type {HasExplicitThis} from './internal/index.d.ts'; -import type {IntRange} from './int-range.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. @@ -49,11 +54,27 @@ type RequestOverloadsUnion = Overloads[number]; export type Overloads any> = FunctionType extends unknown ? IsAny extends true ? any - : CollectOverloads + : _Overloads<(() => Unique) & FunctionType> + : never; + +type _Overloads< + F extends (...args: any) => any, + N extends OverloadIndex = 0, + ResultOverloads extends Array<(...args: any) => any> = [], +> = NthLastOverload extends infer ExtractedN extends (...args: any) => any + ? IsEqual Unique> extends true + ? ResultOverloads // Sentinel reached, return accumulated result. + : IsEqual, 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 extends infer NextN extends OverloadIndex + ? _Overloads + : [ExtractedN, ...ResultOverloads] + : _Overloads : never; -type MaxOverloadPatterns = 4; -type OverloadIndex = IntRange<0, MaxOverloadPatterns>; /** 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). */ @@ -75,35 +96,4 @@ type NthLastOverload any, N extends OverloadIndex> = : never) : never; -declare const unique: unique symbol; -type Unique = typeof unique; - -/** -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` 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 -*/ -type CollectOverloads< - F extends (...args: any) => any, - AllOverloads extends (...args: any) => any = (() => Unique) & F, - N extends OverloadIndex = 0, - ResultOverloads extends Array<(...args: any) => any> = [], -> = NthLastOverload extends infer ExtractedN extends (...args: any) => any - ? IsEqual Unique> extends true - ? ResultOverloads - : IsEqual, ExtractedN> extends true - ? Sum extends infer NextN extends OverloadIndex - ? CollectOverloads - : [ExtractedN, ...ResultOverloads] - : CollectOverloads - : never; - export {};