diff --git a/index.d.ts b/index.d.ts index 0514a0815..bf584c5e9 100644 --- a/index.d.ts +++ b/index.d.ts @@ -70,6 +70,9 @@ export type {Entry} from './source/entry.d.ts'; export type {Entries} from './source/entries.d.ts'; export type {SetReturnType} from './source/set-return-type.d.ts'; export type {SetParameterType} from './source/set-parameter-type.d.ts'; +export type {Overloads} from './source/overloads.d.ts'; +export type {OverloadParameters} from './source/overload-parameters.d.ts'; +export type {OverloadReturnType} from './source/overload-return-type.d.ts'; export type {Asyncify} from './source/asyncify.d.ts'; export type {Simplify} from './source/simplify.d.ts'; export type {SimplifyDeep} from './source/simplify-deep.d.ts'; diff --git a/readme.md b/readme.md index a21b4fadd..3884b9eb1 100644 --- a/readme.md +++ b/readme.md @@ -159,8 +159,6 @@ Click the type names for complete docs. - [`IterableElement`](source/iterable-element.d.ts) - Get the element type of an `Iterable`/`AsyncIterable`. For example, `Array`, `Set`, `Map`, generator, stream, etc. - [`Entry`](source/entry.d.ts) - Create a type that describes a single key-value pair produced when calling a collection’s `entries` method. - [`Entries`](source/entries.d.ts) - Create a type that describes the key-value pairs produced when calling a collection’s `entries` method. -- [`SetReturnType`](source/set-return-type.d.ts) - Create a function type with a return type of your choice and the same parameters as the given function type. -- [`SetParameterType`](source/set-parameter-type.d.ts) - Create a function that replaces some parameters with the given parameters. - [`Simplify`](source/simplify.d.ts) - Useful to flatten the type output to improve type hints shown in editors. And also to transform an interface into a type to aide with assignability. - [`SimplifyDeep`](source/simplify-deep.d.ts) - Deeply simplifies an object type. - [`Get`](source/get.d.ts) - Get a deeply-nested property from an object using a key path, like [Lodash's `.get()`](https://lodash.com/docs/latest#get) function. @@ -248,6 +246,14 @@ Click the type names for complete docs. - [`StructuredCloneable`](source/structured-cloneable.d.ts) - Matches a value that can be losslessly cloned using `structuredClone`. +### Function + +- [`Overloads`](source/overloads.d.ts) - Extract all overload signatures of the given function type as a tuple, preserving declaration order. +- [`OverloadParameters`](source/overload-parameters.d.ts) - Extract the parameter types of all overloads as a union. +- [`OverloadReturnType`](source/overload-return-type.d.ts) - Extract the return types of all overloads as a union. +- [`SetReturnType`](source/set-return-type.d.ts) - Create a function type with a return type of your choice and the same parameters as the given function type. +- [`SetParameterType`](source/set-parameter-type.d.ts) - Create a function that replaces some parameters with the given parameters. + ### Async - [`Promisable`](source/promisable.d.ts) - Create a type that represents either the value or the value wrapped in `PromiseLike`. diff --git a/source/internal/function.d.ts b/source/internal/function.d.ts new file mode 100644 index 000000000..fc795c088 --- /dev/null +++ b/source/internal/function.d.ts @@ -0,0 +1,61 @@ +import type {IsEqual} from '../is-equal.d.ts'; +import type {IsUnknown} from '../is-unknown.d.ts'; +import type {UnknownArray} from '../unknown-array.d.ts'; + +/** +Obtain the parameters of a function type in a tuple. + +This works even when the parameters type is a readonly array. +*/ +export type FunctionParameters any> = T extends (...args: infer P extends UnknownArray) => any ? P : never; + +/* +Internal documentation: TypeScript's overload enumeration behavior +======================================================================== + +Understanding how TypeScript enumerates overloads from an intersection type is essential for understanding (and correctly using) `CollectOverloads`. + +## Overload enumeration + +Given an intersection of function types (e.g. `F1 & F2 & F3`), TypeScript builds an overload list by scanning left to right and **deduplicating**: + +- Two overloads with the same (This, Parameters, Return) are considered duplicates. The first one wins; later ones are dropped. +- HOWEVER, if one or both of the overloads has **implicit `this`** (i.e. no `this` annotation), the comparison ignores `This` and only checks (Parameters, Return). This means an implicit-`this` overload and an explicit-`this` overload with the same params/return are considered duplicates, and whichever appears first wins. + +Example (F1 = implicit this, F1wT = explicit this: T, same params/return): + + F1 & F1wT<1> & F1wT<2> + → Enumerated as: [F1] + F1wT<1> has same (P,R) as F1 (implicit) → duplicate, dropped. + F1wT<2> likewise dropped. + + F1wT<1> & F1 & F1wT<2> + → Enumerated as: [F1wT<1>, F1wT<2>] + F1 has same (P,R) as F1wT<1>, and F1 is implicit → duplicate, dropped. + F1wT<2> vs F1wT<1>: both explicit, This differs → NOT duplicate, kept. + +## Pattern matching: `X extends (this: T, ...args: P) => R` + +TypeScript enumerates the overloads of X as above, replaces implicit `this` with `this: unknown`, then matches the **rightmost** overload satisfying the constraint on the right-hand side. +*/ + +declare const unique: unique symbol; +type Unique = typeof unique; + +/** +Detect whether an overload with the given (This, Parameters, Return) has an explicit `this` annotation in the original function type. + +Intersects `(this: Unique, ...args: Parameters) => Return` onto the function type from the right. Per TypeScript's deduplication rules (see "Overload enumeration" above), implicit `this` absorbs the sentinel (same (P,R), one implicit -- first-wins), so `ThisParameterType` stays `unknown`. Explicit `this: unknown` does not absorb it (both explicit, different `This` -- not duplicate), so the sentinel becomes the last overload and `ThisParameterType` returns `Unique`. + +Limitation: This does not work for generic overloads. There is no way to extract a generic overload while preserving its type parameters, so this type receives the already-instantiated (concrete) signature and cannot recover the original `this` annotation. As a result, generic overloads always appear with explicit `this: unknown` in the `Overloads` output. +*/ +export type HasExplicitThis< + FunctionType extends (...args: any) => any, + This, + Parameters_ extends UnknownArray, + Return, +> = IsUnknown extends true + ? IsEqual Return)>, Unique> + : true; + +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 {}; diff --git a/source/overload-parameters.d.ts b/source/overload-parameters.d.ts new file mode 100644 index 000000000..96f8616bf --- /dev/null +++ b/source/overload-parameters.d.ts @@ -0,0 +1,26 @@ +import type {FunctionParameters} from './internal/index.d.ts'; +import type {Overloads} from './overloads.d.ts'; + +/** +Extract the parameter types of all overloads as a union. + +This is the overload-aware counterpart to the built-in `Parameters` utility type, which only extracts from the last overload. + +@example +``` +import type {OverloadParameters} from 'type-fest'; + +declare function request(url: string): Promise; +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> = FunctionParameters[number]>; + +export {}; diff --git a/source/overload-return-type.d.ts b/source/overload-return-type.d.ts new file mode 100644 index 000000000..b3aa0fc0a --- /dev/null +++ b/source/overload-return-type.d.ts @@ -0,0 +1,25 @@ +import type {Overloads} from './overloads.d.ts'; + +/** +Extract the return types of all overloads as a union. + +This is the overload-aware counterpart to the built-in `ReturnType` utility type, which only extracts from the last overload. + +@example +``` +import type {OverloadReturnType} from 'type-fest'; + +declare function request(url: string): Promise; +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/overloads.d.ts b/source/overloads.d.ts new file mode 100644 index 000000000..71a7f2047 --- /dev/null +++ b/source/overloads.d.ts @@ -0,0 +1,99 @@ +import type {HasExplicitThis} from './internal/index.d.ts'; +import type {IsAny} from './is-any.d.ts'; +import type {IsEqual} from './is-equal.d.ts'; +import type {Sum} from './sum.d.ts'; +import type {UnknownArray} from './unknown-array.d.ts'; +import type {IntRange} from './int-range.d.ts'; + +declare const unique: unique symbol; +type Unique = typeof unique; +type MaxOverloadPatterns = 4; +type OverloadIndex = IntRange<0, MaxOverloadPatterns>; + +/** +Extract all overload signatures of the given function type as a tuple, preserving declaration order. + +TypeScript's built-in utility types like `Parameters` and `ReturnType` only work with the last overload signature, [by design](https://github.com/microsoft/TypeScript/issues/32164). This type extracts all overload signatures, allowing you to work with each overload individually. + +Use-cases: +- Extract parameter types from specific overloads using `Extract` and `Parameters` +- Analyze all possible function signatures in type-level code +- Extract event handler signatures from framework APIs + +Known limitations: +- Generic overloads: + - Type parameters are lost -- they are replaced by their upper bound (e.g. `` 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 +``` +import type {Overloads} from 'type-fest'; + +declare function request(url: string): Promise; +declare function request(url: string, options: {json: true}): Promise; + +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) +``` + +@see https://github.com/microsoft/TypeScript/issues/14107 +@see https://github.com/microsoft/TypeScript/issues/32164 + +@category Function +*/ +export type Overloads any> = FunctionType extends unknown + ? IsAny extends true + ? any + : _Overloads<(() => Unique) & FunctionType> + : never; + +type _Overloads< + F extends (...args: any) => any, + N extends OverloadIndex = 0, + ResultOverloads extends Array<(...args: any) => any> = [], +> = NthLastOverload 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; + +/** +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; + +export {}; 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); diff --git a/test-d/overload-parameters.ts b/test-d/overload-parameters.ts new file mode 100644 index 000000000..c9d1b6d9f --- /dev/null +++ b/test-d/overload-parameters.ts @@ -0,0 +1,12 @@ +import {expectType} from 'tsd'; +import type {OverloadParameters, UnknownArray} from '../index.d.ts'; + +// The details of overload enumeration are tested in overloads.ts, so only one case is tested here. +type Function1 = (foo: string, bar: number) => object; +type Function2 = (foo: bigint, ...bar: any[]) => void; + +declare const overloadParameters: OverloadParameters; +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 new file mode 100644 index 000000000..d687a8ca7 --- /dev/null +++ b/test-d/overload-return-type.ts @@ -0,0 +1,12 @@ +import {expectType} from 'tsd'; +import type {OverloadReturnType} from '../index.d.ts'; + +// The details of overload enumeration are tested in overloads.ts, so only one case is tested here. +type Function1 = (foo: string, bar: number) => object; +type Function2 = (foo: bigint, ...bar: any[]) => void; + +declare const overloadReturnType: OverloadReturnType; +expectType(overloadReturnType); + +declare const anyOverloadReturnType: OverloadReturnType; +expectType(anyOverloadReturnType); diff --git a/test-d/overloads.ts b/test-d/overloads.ts new file mode 100644 index 000000000..538ceda9d --- /dev/null +++ b/test-d/overloads.ts @@ -0,0 +1,266 @@ +import {expectType} from 'tsd'; +import type {IsEqual, Overloads, UnknownArray} from '../index.d.ts'; + +/* +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 any> = + Function_ extends (...arguments_: infer Parameters_ extends UnknownArray) => infer Return + ? [ThisParameterType<((this: IsEqualStrictNothing, ...arguments_: Parameters_) => Return) & Function_>, Parameters_, Return] + : never; +type IsEqualStrict< + 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: Overloads; +expectType<[Function1]>(normalFunction); + +// Two overloads via intersection +declare const twoOverloads: Overloads; +expectType<[Function1, Function2]>(twoOverloads); + +// Two overloads via interface syntax +declare const twoOverloadsInterface: Overloads<{ + (foo: string, bar: number): object; + (foo: bigint, ...bar: any[]): void; +}>; +expectType<[Function1, Function2]>(twoOverloadsInterface); + +// Identical overloads collapse +declare const twoIdenticalOverloads: Overloads<{ + (foo: string, bar: number): object; + (foo: string, bar: number): object; +}>; +expectType<[Function1]>(twoIdenticalOverloads); + +type Function3 = (foo: string, bar: number, baz?: boolean) => object; + +// Two overloads with assignable but distinct signatures +declare const twoOverloadsWithAssignableSignature: Overloads; +expectType<[Function1, Function3]>(twoOverloadsWithAssignableSignature); + +// Three overloads - declaration order preserved +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: Overloads; +expectType<[Function4]>(normalFunctionWithOnlyRestWritableParameter); + +declare const normalFunctionWithOnlyRestReadonlyParameter: Overloads; +expectType<[Function5]>(normalFunctionWithOnlyRestReadonlyParameter); + +// The compiler ignores subsequent identical-up-to-readonly overloads +declare const twoOverloadsWithDifferentRestParameterReadonliness: Overloads; +expectType<[Function4]>(twoOverloadsWithDifferentRestParameterReadonliness); + +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: Overloads; +expectType<[Function6]>(normalFunctionWithNormalAndRestWritableParameter); + +// Readonly rest parameter cannot be represented with tuples +declare const normalFunctionWithNormalAndRestReadonlyParameter: Overloads; +expectType<[(foo: string, ...bar: any[]) => void]>(normalFunctionWithNormalAndRestReadonlyParameter); + +type Function8 = () => never; + +declare const normalFunctionNoParameters: Overloads; +expectType<[Function8]>(normalFunctionNoParameters); + +declare const twoOverloadsWithNoAndPresentParameters: Overloads; +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 - order preserved +declare const manyOverloads: Overloads< + Function1 + & Function2 + & Function3 + & Function4 + & Function5 + & Function6 + & Function7 + & Function8 + & Function9 + & Function10 + & Function11 + & Function12 +>; +expectType<[ + Function1, + Function2, + Function3, + Function4, + Function6, + Function8, + Function9, + Function10, + Function11, + Function12, +]>(manyOverloads); + +declare const anyOverload: Overloads; +expectType(anyOverload); + +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: 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: Overloads; +// Verify `this` is preserved +expectType<[ThisOverload1, ThisOverload2]>(thisOverloads); + +// Same parameters, different return types +type SameParametersDifferentReturn1 = (foo: string) => string; +type SameParametersDifferentReturn2 = (foo: string) => number; + +declare const sameParametersDifferentReturn: 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: Overloads; +expectType<[(input: unknown) => unknown, (input: string) => string]>(genericOverloadResult); + +// Interface-style overload +type InterfaceOverload = { + (input: string): 1; + (input: number): 2; +}; + +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: Overloads; +expectType<[SameParametersDifferentThis1, SameParametersDifferentThis2]>(sameParametersDifferentThis); +declare const sameParametersDifferentThis2: Overloads; +expectType<[SameParametersDifferentThis2, SameParametersDifferentThis1]>(sameParametersDifferentThis2); + +// Duplicate overloads in interface are collapsed +declare const duplicateOverloads: Overloads<{ + (foo: string, bar: number): object; + (): string; + (): string; +}>; +expectType<[Function1, () => string]>(duplicateOverloads); + +// 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: 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. + +type Function1WithThis = (this: This, foo: string, bar: number) => object; +type Function2WithThis = (this: This, foo: bigint, ...bar: any[]) => void; + +// Single overload with explicit `this: unknown` +declare const singleOverloadWithExplicitThisUnknown: Overloads>; +expectType]>>(true); + +// Single overload with implicit `this` +expectType>(true); + +// Mixed explicit `this: unknown` and implicit `this` overloads +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, 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 +declare const implicitThenExplicitThis1: Overloads>; +expectType>(true); +// When the explicit `this` overload comes first, the implicit `this` overload may be absorbed +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 +declare const implicitThenExplicitThisUnknown: Overloads>; +expectType>(true); +declare const explicitThisUnknownThenImplicit: Overloads & Function1>; +expectType]>>(true);