TypeScript
TypeScript copied to clipboard
Still only the last overloaded signature is picked when passing an overloaded function to Array.map
🔎 Search Terms
map overload overloaded
🕗 Version & Regression Information
- This is the behavior in every version I tried, and I reviewed the FAQ for entries about overloading
⏯ Playground Link
https://www.typescriptlang.org/play/?noImplicitAny=false#code/GYVwdgxgLglg9mABMOcAUAPAXIsIC2ARgKYBOAlDnkWQNwBQoksCyqmOAzlKTGAOaVE3XgIZNo8JCnQZyiAN71EKxKWJQQpJBgYBfevQgJuiAIalSVAiVIBtALqIAvIjsBGADQAmTwGYHADp8MwAHNBlyWiA
Bug report
This code fails to compile:
function foo(x: number): number;
function foo(x: string): string;
function foo(x) {
return x;
}
const arr: number[] = [1,2,3].map(foo);
with the following errors:
Type 'string[]' is not assignable to type 'number[]'.
Type 'string' is not assignable to type 'number'.
and
Argument of type '{ (x: number): number; (x: string): string; }' is not assignable to parameter of type '(value: number, index: number, array: number[]) => string'.
Type 'number' is not assignable to type 'string'.
This doesn't make much sense, since there's no reason (so far as I can see) that TypeScript shouldn't be able to recognise that foo will only be passed numbers as arguments when called via [1,2,3].map(foo);, and that therefore (per the two overload signatures) will only return numbers, not strings. Furthermore, it can in fact infer this if you use an arrow function instead of passing foo as an argument directly, i.e. this compiles just fine:
function foo(x: number): number;
function foo(x: string): string;
function foo(x) {
return x;
}
const arr: number[] = [1,2,3].map(x => foo(x));
Especially odd is that the original code also compiles fine if you swap the order in which the overloads are defined:
function foo(x: string): string;
function foo(x: number): number;
function foo(x) {
return x;
}
const arr: number[] = [1,2,3].map(foo);
The explanation appears to me to be that posited by https://github.com/microsoft/TypeScript/issues/55840 - that, when an overloaded function is passed as a parameter to a function like Array.map, TypeScript simply (and arbitrarily, and incorrectly) treats its final overload signature as the signature of the function. It should instead infer from context which overload signature is applicable, as it successfully does if you wrap the call in an arrow function.
#55840 was closed as a dupe of https://github.com/microsoft/TypeScript/issues/47571, which was closed as having been completed, and indeed the specific example given in that issue no longer seems to reproduce the bug (so I guess that something got changed to fix at least that case), but the underlying bug doesn't seem to have truly been fixed since the trivial example I give at the start of this issue still reproduces it.
@MartinJohns hmm, so it is. But is that either 1. actually accurate or 2. the reason for closure, which happened years later on the grounds that the issue was completed (not that it wouldn't be fixed)?
I don't see an explanation there of why such a design limitation exists, and naively it seems like there shouldn't be such a limitation; if TypeScript can do the inference correctly for the arrow function example, why not the basically-equivalent one where foo is passed directly?
EDIT: Sorry, somehow the prev 2 comments did not show up for me until after I posted this
indeed the specific example given in that issue no longer seems to reproduce the bug
Are you sure? Still looks like it picks the last overload to me. #47571 was a design limitation and closed presumably because there's nothing to be done, it's a design limitation.
I can't pretend to speak authoritatively about why they can't resolve overloads for callbacks, but presumably it would trigger a lot of extra analysis which could snowball in unmanageable ways. I'd think such an explanation is in some GitHub issue somewhere, or maybe someone can add such an explanation here?
the reason for closure, which happened years later on the grounds that the issue was completed (not that it wouldn't be fixed)?
At some point the team started to close all issues where no work is to be done anymore. Don't pay attention to the closing status of GitHub, pay attention to the tags added by the team and comments if there are any.
#52944 and #53057 are relevant? Looks like something was implemented and there was a big performance hit for material-ui, and I can't pretend to understand everything in the design meeting summary.
Are you sure? Still looks like it picks the last overload to me.
Huh, I must've done something wrong when I tested this locally; it seems it still does this in the playground on the latest version.
https://github.com/microsoft/TypeScript/issues/47571 was a design limitation ... I can't pretend to speak authoritatively about why they can't resolve overloads for callbacks, but presumably it would trigger a lot of extra analysis which could snowball in unmanageable ways
I guess I'm sceptical of this for two reasons:
-
Though I definitely might be missing something, this seems to me like it'd be fixable or partly-fixable in principle just with a preprocessing step that roughly rewrites calls like
f(g)intof((a,b,c) => g(a,b,c))(in the case wheregis typed as a function andfexpects a function as first parameter). Obviously there are some annoying nuances that make this not totally trivial (determining how many arguments the arrow function should take would require considering the signatures offandg, and there'd be some annoying stuff involvingthisthat I'm not considering) but nothing fundamentally impossible. The fact that the version of the code with arrow functions works fine shows that the underlying logic to do this sort of inference already exists and works, and it only takes an in-theory-automatable refactor to convert my currently non-compiling code into equivalent code that triggers that inference. -
Even if there is some reason none of us can yet see that proper inference isn't possible, the approach of using the final overload is completely arbitrary. It would be better - less fragile, less confusing - to just always emit an error in circumstances where inference can't be used and TypeScript is having to resort to arbitrarily using the last overload. (That error could advise rewriting with an arrow function to make inference work.)
They're not going to always emit an error and break a bunch of real world code. The last overload is chosen because it is most likely to be the least restrictive, catch-all signature (like, it's the lowest priority signature so it should only be chosen after all prev signatures have failed). It's not completely arbitrary. Of course overloaded functions aren't required to have a catch-call signature, so for the ones that don't, choosing the last overload isn't any more helpful than choosing one at random.
Anyway this behavior is what it is, has been for a long time, and many issues have been raised and closed about it. They're not going to just change it here. At best you'll get an explanation for why it is this way, which I'm guessing has to do with performance. Since I'm mostly just speculating and repeating myself I'll bow out now. Good luck!
Why are design limitations closed -> https://github.com/Microsoft/TypeScript/wiki/FAQ#this-is-closed-but-should-be-open-or-vice-versa
@RyanCavanaugh so, just to be clear, the intended reason for closure is that the team doesn't know how to fix it, not that it's completed as per the closure status? Let's post something to that effect on the issue, then.
GitHub added the "why was this closed (completed/not done)" bit after we closed a bunch of stuff, and auto-marked a bunch of stuff as "completed" as a result. Also for a long time, there was no way from the API to specify the close reason, so anything closed via automation has the wrong status. As a result I would really love it if people could just ignore that bit; I don't have the time nor mental capacity to go back through 40,000 issues and validate their "close reason" post facto.
Cool, I've written up a bulleted summary of my understanding of the posts above at https://github.com/microsoft/TypeScript/issues/47571 for the benefit of anyone else landing there from search.