-
Notifications
You must be signed in to change notification settings - Fork 12.6k
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
Union array types do not infer common array prototype methods #10620
Comments
I think this would be because there is no way to infer a common type between |
Workaround: const foo = (): (number | string)[] => ... (Note this unveils an expected second error in the snippet because |
@jwbay That works, but it's semantically different - (Also, you're right - it compiles but the reliance on implicit type coercion was lazy. I've amended my example.) |
This is a bit subtle, but There is not a meaningful way to describe the general case for merging unmatching signatures; while So the general case, is there is no common signatures between the two types, and thus the resulting union type does have a |
I think the more natural intuition is not to try to merge the signatures into one at all. Instead, they should be tried one after the other for conformance with the given arguments and if a call is not possible, then that's a type error according to the existing rules. This way an appropriate massage can be displayed as an error (i.e. the offending signature) and the return type is a union of all return types. With the above example in mind: const foo: number[] | string[] = null as any; The signature for filter: ((calbackFn: (value: string, index: number, array: string[]) => any) => string[])
| ((calbackFn: (value: number, index: number, array: number[]) => any) => number[]) And the provided function is compatible with both overloads, thus this call should be valid. foo.filter((x: string|number): boolean => Number(x) % 2 === 1)
// => string[] | number[] (union of the return types of both overloads) On the other hand, the signature for push is: push: ((...items: string[]) => number)
| ((...items: number[]) => number) Clearly
I suspect a signature unifying solution might be also possible, but it would be much harder to implement, will have to take into account variances and deriving a digestible error messages would be much harder. |
@mhegazy I don't know if there is already an issue to track this, but in general, given a union of two function signatures:
It should be possible to invoke to invoke the function with arguments that are assignable to an intersection of each parameter type:
In the case given above, I feel like this is a special case of a more general structural subtyping rule, but can't quite formulate how. |
@masaeedu there are simple answers for one-off cases but your proposed fix doesn't address the case where there are multiple signatures in each union constituent |
@RyanCavanaugh By multiple signatures, do you mean multiple arguments, or are you referring to overloads? |
Overloads |
Okay, lets see. Here's an overloaded function signature:
In this case, continuing the suggested pattern of allowing intersections and recalling that the implementation of an overloaded function must be written to deal with a union of each argument's types in every overload:
This at least handwavily makes sense; if the argument implements the required interface for at least one overload from both possible function signatures, then it is acceptable to pass it to |
@RyanCavanaugh Not to derail the discussion, but this does bring up the tangentially related issue of why does this not compile?
I would expect it to compile and for |
Overloads are intersections, not unions. It's closer to |
@isiahmeadows That may be so, but when you're invoking an overloaded function, each positional argument must be assignable to a union of the corresponding positional parameter types of the constituent functions, not an intersection. This is why I said, I would expect let x: number | string;
let result = flipflop(x); to compile. That said, I do think removing "overloads" as a distinct concept in favor of type algebra on functions is a good idea. You may be interested in this: #1805 (comment) |
@isiahmeadows Sorry, I think I misunderstood which comment you were responding to. I think you might have been responding to:
In that case, yes; overloads are not unions. However it should still be possible to construct a union of two functions and invoke it (with an intersection of their argument types). Put another way, overloads are merely a special case of the ability to do type algebra with function types. |
See also #7294 |
TypeScript Version: 2.0.2rc
Code
Expected behavior:
No errors.
Actual behavior:
Compiler gives:
error TS2349: Cannot invoke an expression whose type lacks a call signature.
This issue seems to be array-specific. The following code compiles as expected:
The text was updated successfully, but these errors were encountered: