-
Notifications
You must be signed in to change notification settings - Fork 12.8k
Passing a constrained generic type to an overloaded function with overlapping parameter types yields incorrect return type #13223
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
Another way to address this is to infer function func1(a: number[]): number;
function func1(a: boolean[]): boolean;
function func1(a: any[]): string;
function func1(a: string): Date;
function func1(a: any[] | string): number | boolean | string | Date {
// ...
}
function func2<T extends any[]>(b: T) {
return func1(b);
}
const result = func2([1, 2, 3, 4]); // `result` is inferred as `number | boolean | string` though I'm not sure, at this point, if that's really the "right" thing to do. Edit: if the function has more than a single parameter, this solution may create a challenge for figuring out what to expect from further arguments (e.g. |
The funny thing is that in practice the same problem happens even if the parameter is simply annotated as function func1(a: number[]): number;
function func1(a: any[]): string;
function func1(a: any[]): number | string {
// ...
}
function func2(b: any[]) {
return func1(b);
}
const result = func2([1, 2, 3, 4]); // `result` is incorrectly inferred as `string` but the
// actual type, at runtime is `number` Since This scenario can happen practically in every case where a type and a subtype are both annotated as the overload types of a parameter and the overloads have different return types. |
My personal thoughts: I don't think the problem here is really a lack of "soundness", because the scenario that is described consistently and deterministically yields an incorrectly inferred type even given very reasonable circumstances. It is not that "occasionally" it happens that the underlying runtime type matches the more specific overload. It is almost certain to happen since that type is a valid parameter type for the function. I'll try to be (sincerely) emphatic though, this is very unfortunate. I just hope this pattern isn't frequently used in real code though, and if it does, then programmers must know about it and how to avoid it. I would even suggest a having a compiler switch that disallows overlapping types in overloads entirely, except special circumstances, say when the return types match exactly or the more general parameter type's return type is a supertype or the more specific one. A linter rule could also help, though its output might get buried in hundreds of warnings about semicolons and brackets. |
I'm closing this issue and continuing it in a new issue #13235 that targets the more general case I described later in the comments. |
TypeScript Version: Latest nightly
Code
Expected behavior:
Shouldn't compile. The generic type
T
cannot be conclusively determined not to be anumber[]
.Actual behavior:
result
gets the incorrect return valuestring
, instead of the expectednumber
.Notes:
To clarify, the semantics of
function func2<T extends any[]>(b: T)
should be different fromfunction func2(b: any[])
. A generic type should be seen as undetermined and treated like a "black box". The constraintT extends any[]
doesn't mean thatT
can be treated as interchangeable withany[]
at all cases.This is very likely to be labeled as a "design limitation" (though I can't see any reason why it couldn't be fixed) but I wanted to file this to make sure it is understood and documented somewhere.
You can safely close the issue once you've read it, if you wish. It is also possible to fix the issue by disallowing the binding of overlapping overloaded parameter types (I mean, not disallowing in general) when constrained generics are passed to them. The backwards-compatibility impact would most likely be relatively small and fixing it would allow more bugs to be caught. It's up to you to decide.
The text was updated successfully, but these errors were encountered: