You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Parameter type interface for overloaded functions as union type #32164
Inferring parameters from a union of function types (which is how overloads are represented internally iirc) typically creates intersections instead of a unions, so this wouldn't do what you want. Case in point: UnionToIntersection:
Bruce Pascoe (@fatcerberus) overloads are represented as intersections (not unions) of function types... so, by the Power of Contravariance, could be interpreted as operating on unions of function parameters, as requested here.
That is, an overloaded function like { (x: string): number; (x: number): string } will definitely accept a string parameter and will definitely accept a number parameter. So you should be able to safely widen that to {(x: string | number): string | number}.
This issue is a duplicate of (or strongly related to) #14107.
Hmm, I just noticed that the "suggestion" template doesn't actually mention how to search for existing issues, nor does it have a "related issues" section like the "bug" template does. Maybe that can be changed?
Reacted by Bruce Pascoe, Yichuan Shen, yossarian and pqnet
Yeah, unions become intersections and vice versa under contravariance, that I knew. I just thought I remembered reading somewhere that overloads were represented internally as unions... huh. Intersections do make more sense though. Thanks for the correction!
Thank you for responses! Joe Calzaretta (@jcalz) I haven't turned off the github search for issues when submitting, but the keywords I searched didn't pop up, some of the specific issues clash with very popular and broad keywords, making finding them hard. I hadn't found that issue.
That issue is related, and I might've seen it before, and it's related in implementation, though slightly different in end result.
Edit: come to think of it, it is basically another aspect of the same issue, since if that worked, this would work by default, since if an overloaded function accepted an union type, then the inference from Parameters<> would also point to the intersection.
If Parameters<T>[0] returns string | number, then doCall(fn, 0, "") would incorrectly succeed. If Parameters<T>[0]> returns string & number, then doCall(fn, 0, 0) would incorrectly fail (and be a big breaking change). Notably, with conditional types and unions, really the only kind of functions that can't be typed with a single overload are exactly the ones that have this failure mode.
The current behavior at least makes it so that some calls are correctly accepted.
Reacted by Roberts Slisans, Shigma, 流浪大法师, Bowen, Paul Grau, Derek Ziemba and Alex Crișan
Reacted by Leandro Aguiar, Łukasz Moroz, Jarrod Davis, electrovir, BOCbMOU, Sander Altman, Jimmy Zhening Luo, Philipp Munin and Alan Slater
The case here though was infer over the entire parameter list which in the case above would yield [string,string] | [number,number]—which precisely describes the valid inputs to fn.
Reacted by Omar Diab, Nathan Hardy, Perry Healy, Tyler Murphy, Jason Park, Erin, Tomás Fox, Nicola, Yichuan Shen, Abhishek Datta and 35 more
If Parameters<T>[0] returns string | number, then doCall(fn, 0, "") would incorrectly succeed.
I don't see how this is incorrect based on the given declaration since (func: T, ...args: Parameters<T>) could be used to achieve a more correct typing, and this is how I plan to use it in my use case (which is similar to OP's). However, with an implementation attached, additional type constraints are introduced anyway:
Correct me if I'm wrong, but I think the case that Ryan Cavanaugh (@RyanCavanaugh) pointed out, can be solved by writing the doCall function using tuples likes this:
With this, if we assume Parameters<fn> gives [string, string] | [number, number], then doCall(fn, 0, "") would not succeed anymore, and only doCall(fn, 0, 1) or doCall(fn, "0", "") will succeed.
I'm trying to set up a list of potential event handlers as tuples, with code and handler, which should then be filtered: on("x", (pX, pT) => {}), on("y", (pZZ) => {}. Parameters<myQueue.on> only gives the tuple for the last handler, not all the possible combinations. I don't have the luxury to alter the type definitions, so a way to extract all the possible tuples in a union type would be nice.
Reacted by timmydoza, Mina Luke, Taisia Pitko and Annalisa Alden
I wanted to use this, because typing manually all types accepted by Buffer.from is tedious and error-prone - it can break from even patch-to-patch version of @types/node, like it just did for me.
// for some reason `Parameters<typeof Buffer.from>[0]` does not work well, resolving to just `string`typeUploadData=Parameters<typeofBuffer.from>[0];
It's not perfect but you can tease some info out of the compiler about overloads... up to an arbitrary fixed number of overloads, modulo some weird bugs with zero-arg function types (#28867)
The bulk "Close" action I applied to Design Limitation issues doesn't let you pick what kind of "Close" you're doing, so don't read too much into any issue's particular bit on that. I wish GitHub didn't make this distinction without a way to specify it in many UI actions!
The correct state for Design Limitation issues is closed since we don't have any plausible way of fixing them, and "Open" issues are for representing "work left to be done".
I figured out a recursive way of converting a function overload (function signature intersection) into a union of the individual signatures: Playground link
typeOverloadProps<TOverload>=Pick<TOverload,keyofTOverload>;typeOverloadUnionRecursive<TOverload,TPartialOverload=unknown>=TOverloadextends(
...args: infer TArgs)=> infer TReturn
? // Prevent infinite recursion by stopping recursion when TPartialOverload// has accumulated all of the TOverload signatures.TPartialOverloadextendsTOverload
? never
:
|OverloadUnionRecursive<TPartialOverload&TOverload,TPartialOverload&((...args: TArgs)=>TReturn)&OverloadProps<TOverload>>|((...args: TArgs)=>TReturn)
: never;typeOverloadUnion<TOverloadextends(...args: any[])=>any>=Exclude<OverloadUnionRecursive<// The "() => never" signature must be hoisted to the "front" of the// intersection, for two reasons: a) because recursion stops when it is// encountered, and b) it seems to prevent the collapse of subsequent// "compatible" signatures (eg. "() => void" into "(a?: 1) => void"),// which gives a direct conversion to a union.(()=>never)&TOverload>,TOverloadextends()=>never ? never : ()=>never>;// Inferring a union of parameter tuples or return types is now possible.typeOverloadParameters<Textends(...args: any[])=>any>=Parameters<OverloadUnion<T>>;typeOverloadReturnType<Textends(...args: any[])=>any>=ReturnType<OverloadUnion<T>>;
It also appears to work all the way back to TS version 4.1.5. There are a couple of edge cases outlined in the playground code, but they aren't deal breakers for me.
This is very neat, but it doesn't quite cover my use case here, as I'm getting Type instantiation is excessively deep and possibly infinite.(2589) for two different reasons:
over 8 overloads
overloads using generics
// ...typeHttpClientGetUpTo8OverloadsOptions=OverloadParameters<HttpClient['get']>[1];// Over 8 overloads results in typeHttpClientGetAllNonGenericOverloadOptions=OverloadParameters<HttpClientAllNonGenericOverloads['get']>[1];// Type instantiation is excessively deep and possibly infinite.(2589)// functions with generics result intypeHttpClientGetGenericOverloadsOptions=OverloadParameters<HttpClientGenericOverloads['get']>[1];// Type instantiation is excessively deep and possibly infinite.(2589)
For anyone having trouble with Chris Ackerman (@Shakeskeyboarde)'s solution because of infinite recursion depth with generics, here's a much dirtier alternative. It's not scalable and if you don't know the total amount of overloads, you may have to add an infinite amount of them... But it may work as a temporary workaround for this limitation for certain use cases.
typeFn<TArgsextendsunknown[],TReturnType>=(...args: TArgs)=>TReturnType;typeOverloads<TFunction>=TFunctionextends{(...args: infer Args): infer Ret;(...args: infer Args2): infer Ret2;(...args: infer Args3): infer Ret3;(...args: infer Args4): infer Ret4;(...args: infer Args5): infer Ret5;// add as many as necessary...} ? [Fn<Args,Ret>,Fn<Args2,Ret2>,Fn<Args3,Ret3>,Fn<Args4,Ret4>,Fn<Args5,Ret5>// keep up here too] : never;typeRes=Overloads<{(): void;(a?: 1): 1;<Textendsstring>(a: T,b: 2): T;// generic works(): never;}>;// If total amount of overloads of the function are less than the amount handled, the first overload will be added at the start multiple times but generally not a problem since intersections / unions will resolve same types.
Ryan Cavanaugh (@RyanCavanaugh) would you mind pointing me to an explanation of why this is a design limitation? I think #32164 (comment) describes what we're all after and the main concerns I saw raised (#32164 (comment)) seem to about a different feature.
I don't understand what people aren't understanding about the comment. factcerberus wrote
The case here though was infer over the entire parameter list which in the case above would yield [string,string] | [number,number] — which precisely describes the valid inputs to fn.
Okay, great, let's write a function that appears to usefully use that kind of functionality
declarefunctionoverload(s0: string,s1: string): void;declarefunctionoverload(n0: number,n1: number): void;// Hardcode the output since it doesn't work that waytypeParametersX<T>=[string,string]|[number,number];// Call a function with 2 argsdeclarefunctiondoCall<Textends(x: never,y: never)=>void>(func: T,arg0: ParametersX<T>[0],arg1: ParametersX<T>[1]): void;doCall(overload,"",42);
There's no error but we violated overload's contract by calling it with a string, number argument pair
Ryan Cavanaugh (@RyanCavanaugh) thank you! That limitation makes sense. But I think there's utility despite the limitation. I forgot that I commented on this before - here's something that always comes to mind for me: #32164 (comment)
I like to be able to write tests like that sometimes. I think it'd be useful to be able to do so for overloads, too.
That won't have the same semantics as overloads anyway, since overloads have ordering behavior but tuples don't
I'm not sure what this means for why the idea Joe Calzaretta (@jcalz) is describing isn't useful (see also: #32164 (comment)). The alternate doCall implementation seems useful.
In both of these cases, is there some potential danger we're overlooking?
Edit: had a though about your point of view on this. Is the idea to protect people by ensuring it remains impossible to define and use the version of doCall with the bad types by mistake?
Activity
fatcerberus commentedon Jun 28, 2019
Inferring parameters from a union of function types (which is how overloads are represented internally iirc) typically creates intersections instead of a unions, so this wouldn't do what you want. Case in point:
UnionToIntersection:jcalz commentedon Jun 28, 2019
Bruce Pascoe (@fatcerberus) overloads are represented as intersections (not unions) of function types... so, by the Power of Contravariance, could be interpreted as operating on unions of function parameters, as requested here.
That is, an overloaded function like
{ (x: string): number; (x: number): string }will definitely accept astringparameter and will definitely accept anumberparameter. So you should be able to safely widen that to{(x: string | number): string | number}.This issue is a duplicate of (or strongly related to) #14107.
Hmm, I just noticed that the "suggestion" template doesn't actually mention how to search for existing issues, nor does it have a "related issues" section like the "bug" template does. Maybe that can be changed?
fatcerberus commentedon Jun 28, 2019
Yeah, unions become intersections and vice versa under contravariance, that I knew. I just thought I remembered reading somewhere that overloads were represented internally as unions... huh. Intersections do make more sense though. Thanks for the correction!
rsxdalv commentedon Jun 28, 2019
Thank you for responses!
Joe Calzaretta (@jcalz) I haven't turned off the github search for issues when submitting, but the keywords I searched didn't pop up, some of the specific issues clash with very popular and broad keywords, making finding them hard. I hadn't found that issue.
That issue is related, and I might've seen it before, and it's related in implementation, though slightly different in end result.
Edit: come to think of it, it is basically another aspect of the same issue, since if that worked, this would work by default, since if an overloaded function accepted an union type, then the inference from Parameters<> would also point to the intersection.
RyanCavanaugh commentedon Jun 28, 2019
It's not really possible to make this in a way that's both useful and sound. Consider a function like
If
Parameters<T>[0]returnsstring | number, thendoCall(fn, 0, "")would incorrectly succeed. IfParameters<T>[0]>returnsstring & number, thendoCall(fn, 0, 0)would incorrectly fail (and be a big breaking change). Notably, with conditional types and unions, really the only kind of functions that can't be typed with a single overload are exactly the ones that have this failure mode.The current behavior at least makes it so that some calls are correctly accepted.
fatcerberus commentedon Jun 28, 2019
The case here though was
inferover the entire parameter list which in the case above would yield[string,string] | [number,number]—which precisely describes the valid inputs tofn.Tyler-Murphy commentedon Jul 24, 2019
Does the "Design Limitation" label still apply given the previous comment about getting the entire parameter list?
This would be helpful for writings tests for an overloaded function where a bunch of inputs and expected outputs are written and checked.
For example, I'd like to be able to do this:
eritbh commentedon Oct 23, 2019
I'm facing this issue as well. My use case is similar to the OP. Regarding Ryan Cavanaugh (@RyanCavanaugh)'s comment:
I don't see how this is incorrect based on the given declaration since
(func: T, ...args: Parameters<T>)could be used to achieve a more correct typing, and this is how I plan to use it in my use case (which is similar to OP's). However, with an implementation attached, additional type constraints are introduced anyway:Here, because of the
func(a0, a1)call, it should ideally be implied that[a0, a1]are compatible withParameters<typeof func>.Is there something I'm not getting about how the type system works that makes this impossible to implement?
abdatta commentedon Aug 9, 2020
Any updates with this? Is there any current workaround for achieving the events example provided by the OC?
abdatta commentedon Aug 10, 2020
Correct me if I'm wrong, but I think the case that Ryan Cavanaugh (@RyanCavanaugh) pointed out, can be solved by writing the doCall function using tuples likes this:
With this, if we assume
Parameters<fn>gives[string, string] | [number, number], thendoCall(fn, 0, "")would not succeed anymore, and onlydoCall(fn, 0, 1)ordoCall(fn, "0", "")will succeed.A simple playground attempt is here: Playground Link
tvedtorama commentedon Oct 2, 2020
I'm trying to set up a list of potential event handlers as tuples, with code and handler, which should then be filtered:
on("x", (pX, pT) => {}), on("y", (pZZ) => {}.Parameters<myQueue.on>only gives the tuple for the last handler, not all the possible combinations. I don't have the luxury to alter the type definitions, so a way to extract all the possible tuples in a union type would be nice.DEPS: Fix typing problem with overloaded function
falkenhawk commentedon Jan 21, 2021
I wanted to use this, because typing manually all types accepted by Buffer.from is tedious and error-prone - it can break from even patch-to-patch version of
@types/node, like it just did for me.jcalz commentedon Jan 21, 2021
It's not perfect but you can tease some info out of the compiler about overloads... up to an arbitrary fixed number of overloads, modulo some weird bugs with zero-arg function types (#28867)
Click to expand
Playground link
18 remaining items
rijenkii commentedon Nov 11, 2023
Another way of breaking Chris Ackerman (@Shakeskeyboarde) solution is using type predicates:
adrian-gierakowski commentedon Feb 24, 2024
Ryan Cavanaugh (@RyanCavanaugh) could you please link to PR which fixes this issue? Thanks!
rijenkii commentedon Feb 24, 2024
Agreed, latest nightly does not pass the example in the OP:
Playground
RyanCavanaugh commentedon Feb 25, 2024
The bulk "Close" action I applied to Design Limitation issues doesn't let you pick what kind of "Close" you're doing, so don't read too much into any issue's particular bit on that. I wish GitHub didn't make this distinction without a way to specify it in many UI actions!
The correct state for Design Limitation issues is closed since we don't have any plausible way of fixing them, and "Open" issues are for representing "work left to be done".
rijenkii commentedon Feb 25, 2024
Well that's disappointing, but thanks for clarification.
Tyler-Murphy commentedon Feb 27, 2024
Ryan Cavanaugh (@RyanCavanaugh) I may have missed it but I don't think this comment was ever addressed: #32164 (comment)
I'm still curious - what's the design limitation here?
188599 commentedon Oct 7, 2024
This is very neat, but it doesn't quite cover my use case here, as I'm getting
Type instantiation is excessively deep and possibly infinite.(2589)for two different reasons:Playground Link
I imagine you could modify
OverloadParametersto work with functions using generic types but I don't know about the maximum amount of overloads.codpro2005 commentedon Apr 11, 2025
For anyone having trouble with Chris Ackerman (@Shakeskeyboarde)'s solution because of infinite recursion depth with generics, here's a much dirtier alternative. It's not scalable and if you don't know the total amount of overloads, you may have to add an infinite amount of them... But it may work as a temporary workaround for this limitation for certain use cases.
Playground Link
Tyler-Murphy commentedon Jun 27, 2025
Ryan Cavanaugh (@RyanCavanaugh) would you mind pointing me to an explanation of why this is a design limitation? I think #32164 (comment) describes what we're all after and the main concerns I saw raised (#32164 (comment)) seem to about a different feature.
RyanCavanaugh commentedon Jun 28, 2025
I don't understand what people aren't understanding about the comment. factcerberus wrote
Okay, great, let's write a function that appears to usefully use that kind of functionality
There's no error but we violated
overload's contract by calling it with astring, numberargument pairjcalz commentedon Jun 28, 2025
Sure, but isn't it really supposed to be
(func: T, ...args: ParametersX<T>)instead?RyanCavanaugh commentedon Jun 28, 2025
That won't have the same semantics as overloads anyway, since overloads have ordering behavior but tuples don't
Tyler-Murphy commentedon Jul 1, 2025
Ryan Cavanaugh (@RyanCavanaugh) thank you! That limitation makes sense. But I think there's utility despite the limitation. I forgot that I commented on this before - here's something that always comes to mind for me: #32164 (comment)
I like to be able to write tests like that sometimes. I think it'd be useful to be able to do so for overloads, too.
I'm not sure what this means for why the idea Joe Calzaretta (@jcalz) is describing isn't useful (see also: #32164 (comment)). The alternate
doCallimplementation seems useful.In both of these cases, is there some potential danger we're overlooking?
Edit: had a though about your point of view on this. Is the idea to protect people by ensuring it remains impossible to define and use the version of
doCallwith the bad types by mistake?