-
Notifications
You must be signed in to change notification settings - Fork 12.8k
BC break for method call on union type #10025
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
Maybe similar case, also broken between 2.1.0-dev.20160716 and 2.1.0-dev.20160729: interface List<T> {
map<U>(mapper: (v: T, index: number) => U): List<U>
}
interface A {
prop1: boolean
}
interface B extends A {
prop2: number
}
type ListA = List<A>;
type ListB = List<B>;
let instance: List<A> | List<B>;
instance.map((v, index) => {
return true;
}) |
@ahejlsberg I believe this is due to the less-aggressive subtype reduction -- thoughts? |
@Strate I'm curious what the scenario is for having types like @RyanCavanaugh Yes, this is caused by a change in #9407 to not perform subtype reduction in property accesses on objects of union types. Subtype reduction is horribly expensive on large union types and it is not clear we could ever make it perform acceptably. Also, subtype reduction only helps in those cases where we can reduce a union of function types down to a single function type. The core issue really is that we are very conservative when it comes to call operations on a union type. We basically require all of the parameter types to be identical, with a small allowance matching omitted parameters to optional parameters. The real fix here would be to find a better way to check calls on unions of function types. Subtype reduction isn't the solution because there are lots of cases where we have a union of function types that aren't subtypes of each other, but where there is a meaningful way to construct calls that would satisfy all of the signatures. One possibility might be to intersect the types of parameters in corresponding positions. So, for a union of function signatures |
@ahejlsberg seems that your suggestion could solve this issue: type a = string;
type b = number;
interface List<T> {
map<U>(mapper: (item: T) => U): List<U>
}
let instance: List<a> | List<b>
instance.map(v => true) call to |
#10041 is a good example of how this can be problematic in the absence of a meaningless union type |
@Strate Yes, but as I think about it more, the idea of intersecting the parameter types still wouldn't work for overloaded signatures (as it wouldn't be clear which signatures to match with other signatures). Still curious whether this shows up in a real world scenario for you, and if so, what leads to the existence of union types where one constituent is a subtype of the other? |
It seems that there is no difference between interface A { prop1: boolean }
interface B extends A { prop2: boolean } and interface A { prop1: boolean }
interface B { prop1: boolean, prop2: boolean } from structural type system view. What about real-world scenario, I can't formalize it now, maybe our sode really could be refactored to your suggestion :) |
I had a similar break in my code: type Rule = LocalRule | GlobalRule;
interface LocalRule { enforce: () => void; }
interface GlobalRule { enforce: (state: GlobalState) => void; }
declare rules: Rule [];
rules.forEach (rule => rule.enforce (state)); // used to work, not anymore |
To be frank, the only reason I wrote it this way is because of my laziness, I didn't want to pattern match to a certain case as long as the unified signature would work as well. |
@Aleksey-Bykov laziness was smart in this case since pattern matching with rules.forEach(rule => {
if (isGlobalRule(rule)) {
rule.enforce(state);
}
else {
rule.enforce(); // ERROR: Property 'enforce' does not exist on type 'never'.
}
}); |
@yortus the way i fixed is by adding a |
I agree with @ahejlsberg s comment:
Please also see my comment in 9104 (which unlike this ticket is "open"):
On my opinion overloaded methods can be handled as well:
Of course it cannot "magically generate" new overloaded methods, but it can generate a function type, which allows for accurate compile time type checking. |
TypeScript Version: 2.1.0-dev.20160729
Code
Expected behavior:
No compilation error at least at 2.1.0-dev.20160716
Actual behavior:
TS2349: Cannot invoke an expression whose type lacks a call signature.
The text was updated successfully, but these errors were encountered: