-
Notifications
You must be signed in to change notification settings - Fork 12.8k
Overload call signature resolution resolves to first overload #59064
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. Weβll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Comments
You forgot to fill out the issue template.
This is working as intended and documented: https://www.typescriptlang.org/docs/handbook/2/conditional-types.html#inferring-within-conditional-types
Implementation signatures can't be part of interfaces because interfaces don't have implementations. Whether you should have a permissive "catch all" signature as part of your interface or not really depends on your use case. The only weird thing here is that |
Yes, I'm confusing the terms "implementation signature" and "catch all signature". It seems like catch-all signatures are a workaround inspired by implementation signatures. And they would largely not be needed if function overload distributed over union.
That's my understanding too. Maybe a duplicate of #39833? |
function foo(v: 1): void;
function foo(v: 2): void;
function foo(v: 1 | 2): void; // catch all overload, just another overload signature that's visible to the caller
function foo(v: 1 | 2): void { // implementation signature, not visible for caller
// implementation be here
}
No idea what you mean. This behavior is intentional.
Yes, that's the one. |
@MartinJohns By distribution over unions I think he means that given the overloads... declare function foo(v: 1): void;
declare function foo(v: 2): void; ...it should be legal to call |
Exactly. In this your example, it simplifies out to |
The combinatorial behavior Iβm talking isnβt about the return types, but how much work has to be done to typecheck the call sites. Basically if there are multiple arguments, each with a union type, youβd have to take each union apart and, for each member, check if it matches one of the overloads. Thatβs on average exponential in the number of arguments provided. |
Iβm still not seeing it. Could you give a concrete example? |
Suppose you have declare function foo(x: A, y: A): void;
declare function foo(x: A, y: B): void;
declare function foo(x: B, y: A): void;
declare function foo(x: B, y: B): void;
declare let x: A | B;
declare let y: A | B;
foo(x, y); This call should succeed according to the types. But to know that, you have to answer all of these in the affirmative:
and each of those individual checks has to check all the overloads for a match. Now increase the size of the unions and/or the number of parameters and this very quickly gets out of hand. |
|
It's not though - if all the overloads aren't there, it might be able to short-circuit once it finds a failing case, but in principle it still has to enumerate all the possible combinations of union members. The "40 questions" bit I described would still happen, even if the matching overloads aren't there.
Indeed, but IIRC there's already a (fairly low) limit to the size of the unions that can be expanded out during checking that way before it just fails. Something like 25 if my memory is right. |
Okay I think I understand. You're assuming the implementation will be "normalize the types of both the arguments and the parameters to unions of tuple types and check that every argument case is covered by a parameter case". This is a really natural approach to take - canonicalizing to a sum (union) of products (Cartesian product). If so, then yes, it is an expensive check! But that assumes the worst-case scenario. You don't need to eagerly expand out the argument nor the parameter types into unions. Keep the argument type as a tuple and only split into cases when an overload partially matches the argument type. e.g. for arguments You could also try to gather the argument type into a product of sums (e.g.
Seems like you're right! And it doesn't even give a meaningful error message! |
Overload resolution selects the first matching signature (everyone loves it when language use simple algorithms, right? right??). There have been requests to do something more involved when The one caveat to "first" is that there's an initial pass that tries to discard assignments to interface Not{
(x: any): boolean;
(x:false):true // signature 1
(x:true):false // signature 2
(x:boolean):boolean // signature 3. Unclear whether this should be declared or not
}
declare var not:Not
const p = not(true);
// ^? still 'false' |
Clever workaround, but that defeats the type annotation on the argument. interface Not2 {
<const T extends boolean> (x:T) : T extends true ? false : T extends false ? true : never
}
Somehow though, adding (or moving) the catch-all signature first results in matching the second overload, with weird results: interface Not{
(x: boolean): boolean;
(x: false): true;
(x: true): false;
(x: boolean): boolean;
}
declare var not:Not
const notAny = not(true as any) // true !?!?
// ^? It seems in intellisense that the |
This issue has been marked as "Working as Intended" and has seen no recent activity. It has been automatically closed for house-keeping purposes. |
π Search Terms
overload, multiple call signature
π Version & Regression Information
β― Playground Link
https://www.typescriptlang.org/play/?ts=5.5.2&ssl=15&ssc=1&pln=1&pc=1#code/JYOwLgpgTgZghgYwgAgHIHswG8CwAoZQ5ACgA8AueAGwGcIBKcsKAVxQHp3kbgBzEOGBZQUARnxESFZm0bU6yTtz4ChI5ACZ8EomXIAjdOioQ4IRoeOmQirj36DhKAMwA6ZAFUQCE3CjIAdwALCDAQ-zDgGm4g9BYqABNkfRQEiB8-CCT0fxBMfABffDSM9QA3P2Q8sHIMMHwEdBAaMCrMAEEQAE9kAF42sGIZFDhosy76W2RhgG5kllaaWPiklOQAA0tfEHX8JSIAPQB+bTwwLoAHFAANPuQAJVDhEAAVS4gAHjqAPimt6zm8hQwBgygcahcyCiyHQAFtgGBIAk9lxCMcGk0WgMAEJGbZ3apDVgjaL-MyTJRkkBzaBQHJQ0H2VROZDOKHROEIpEoyTovBAA
π» Code
π Actual behavior
x
types astrue
, the first signature of the overload.π Expected behavior
x
should beboolean
, the return type of signature 3.Additional information about the issue
Note that #14107 would obviate the need for signature 3.
Originally discovered based on PR Feedback.
It is unclear to this author whether the implementation signature belongs in a pure declaration.
The FAQ indicates the implementation signature will typically not be externally visible:
Previous discussion suggests the "catch-all" should actually be visible for consumers:
The text was updated successfully, but these errors were encountered: