-
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 types not working with old-style overloads #1805
Comments
Definitely reasonable to expect this to work. The main reason it doesn't work at the moment is we couldn't figure out any general solution to this problem and opted not to special case anything. It's possible we could special case this pattern (an overload set where all overloads have a single argument with the same return type) if it's reasonably common. The larger problem is in the cost/complexity when overload sets are more complicated. Consider: function foo(x: string, y: string) {}
function foo(x: number, y: number) {}
var arg: string|number;
foo(arg, arg); // should be an error |
Interesting. I would even say that you could allow that because arg can't be both, though this would not be allowed: function foo(x: string, y: string) {}
function foo(x: number, y: number) {}
var arg1: string|number;
var arg2: string|number;
foo(arg1, arg2); // should be an error because you don't know if arg1 is a string and arg2 is a number, for example. Maybe that's what you meant... |
Yes, that's the problem. Essentially the overload set was used to create information tying the type of argument 1 to the type of argument 2 (and can be used likewise to tie this to a return type). With a union type that information is lost and any combination of argument types becomes allowed. |
I agree that this is hard to solve and might be a black hole for special cases. Since this scenario isn't supported anyway, and if converting the definition to use union types would fix the problem (it would in this case), perhaps a better thing to do is to just have TypeScript raise a different (new) error when passing a union type (of any variety) to an overloaded function that is not written with union types. Something like 'Union types are not assignable to overloaded function parameters - consider rewriting the function definition to accept a union type'. Does that make any sense? Thanks, Dan. |
It seems to me that the only case where this can be allowed is when there is a single parameter for which the argument is of a union type whose members are a subset of those allowed by the overloads. All other argument types must match in the usual way. If there are two or more parameters involved in resolution (union type -> overload) then as noted above, nothing can be done and the user ought not to expect it to work. Overloads specify what combinations of types are valid. A single function declaration with union types allows all combinations. So they aren't equivalent. To put it another way: overloads continue to be a uniquely useful feature in some situations, despite the overlap with union types in other situations. For exactly the same reason, I don't think the error message should suggest to the user that they rewrite the overload to use union types in such situations - it is probably unsafe advice! Such advice would only be wise if the compiler knew it was safe advice, in which case the compiler could solve it anyway. As I noted in 1883 (the issue, not the year), the reverse mapping from overloads to union type must occur for the return type (of which fortunately there is only one), as that may vary between overloads:
So the type of r will be |
Generalising the description above: if there are arguments of union types, there must be overloads that allow every combination of those types. For two parameters being passed arguments such as In the case of a single such parameter it collapses to the simplistic description in my previous comment. |
👍 |
This is the ideal, of course, but we really don't want to implement a combinatorially explosive algorithm in the compiler. |
@RyanCavanaugh but is this algorithm explosive? It seems to be bounded (time and memory) by the number of overloads actually declared by the user, not by the number of possible overloads. It's an AND requirement, so it can be short-circuited; the compiler can quit immediately on discovering a single overload is not present. |
Consider something like this:
There are only 4 overloads, but if you try to puzzle this out manually you realize you have to enumerate through the 8 (2 * 2 * 2) possible combinations of types to find the invalid combination. There's no guarantee you find the bad one 'early' in that process. |
Ah, well, I'd count that as shorthand for 14 overloads! :) But I see what you mean. |
I am inclined to say we should just special-case the single-argument variant. Actual prevalence of the mutli-argument variants seems very low, and it is a real pain for wrapper functions. |
At least having single-argument overloads like @RyanCavanaugh said would cover most applications. |
Too late to ask for special casing the single argument variant? 😄 |
Sorry for posting in a closed issue... enum Types { A, B, C }
interface TypeA { __a; }
interface TypeB { __b; }
interface TypeC { __c; }
function createInstance(type: Types.A): TypeA;
function createInstance(type: Types.B): TypeB;
function createInstance(type: Types.C): TypeC;
function createInstance(x: Types): TypeA | TypeB | TypeC {/***/}
var c: Types.A | Types.B;
createInstance(c); // ERROR: Argument type 'Types.A | Types.B' is not assignable to parameter type 'Types.C'
// c should have type TypeA | TypeB This is useful whenever we have to parse a data structure with a "type" property and map the type of this property to the parsed object type. How about this special case? We could avoid another combinatorically explosive case ;o) function createInstance(type: Types.A): TypeA;
function createInstance(type: Types.B): TypeB;
function createInstance(type: Types.C): TypeC;
function createInstance(type: Types.A | Types.B): TypeA | TypeB;
function createInstance(type: Types.A | Types.C): TypeA | TypeC;
function createInstance(type: Types.B | Types.C): TypeB | TypeC;
function createInstance(type: Types.A | Types.B | Types.C): TypeA | TypeB | TypeC;
function createInstance(x: Types): TypeA | TypeB | TypeC {/***/} |
@RyanCavanaugh I think the "special case" of single argument functions is actually sufficient to formulate a solution to the problem in the general case. At least at the type checker level, all functions of multiple arguments can be modeled as curried functions of at most one argument (returning other functions). Exploiting this, you can soundly deal with algebraic data types involving functions:
I've set up a toy implementation of this here: https://github.com/masaeedu/overloadresolution, would appreciate your thoughts on any cases this doesn't cover. |
This is an old issue. So it's very strange that I just ran into this issue recently(this week). The same code work without any problem before(with tsc and tslint, in the past 12 months). |
I was just working with Big.js and tried to pass a union type into its constructor function. It seems that union types don't play nice with old-style overloaded constructors. Please consider the code below that errors with TypeScript 1.4. I would have expected this to work.
If I change the
new
signature underBigJS_Constructors
tonew (value: number | string | BigJS) : BigJS;
, and eliminate the other two options, it works fine, but to me the two styles should be identical in this case.My apologies if I'm doing something stupid here. Thanks!
The text was updated successfully, but these errors were encountered: