-
Notifications
You must be signed in to change notification settings - Fork 12.8k
Wrong unification of union when the alternative types have some intersection. #11403
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
This is a case of subtype reduction. |
@yortus ok, I should have restated the title. Yes, it is valid to simplify ( A | (A & B)) to A in the sense it won't accept invalid program, however it is rejecting some valid ones (which is less mandatory, I agree). It is correct but I find it non desirable as it looses some information when you want to use the case A & B. I've been beaten by that when porting a Js code that optionally set some properties on an object and later check for their existence.. I was not able to change the code logic (not owning it) so it became a blocker and resulted into some 'any' jungling which make me loose type safety.. :( In short: this subtype reduction loose some information resulting into rejecting a valid program. I understand it is required to keep types simples and help with unification space / time. |
Yes TypeScript does reject some valid programs due to a combination of subtype reduction and the way it does type narrowing. Another example is #10471. It is indeed a problem sometimes. Could you provide a code snippet showing the kind of valid program rejection you are encountering? I'm sure it would be useful feedback for the TS team. |
I've narrowed it down to that:
foo should be of type equivalent to: Note that it should not be Currently with typescript, this is not possible AFAIK without forcing the type system. |
OK, supposing the let record: { name: string, age: number } | { name: string, age: number, userId: number } = foo('bob', 42);
function hasUserId<T extends {userId?}>(x: T): x is T & { userId: number; } {
return x && typeof x.userId === 'number';
}
if (hasUserId(record)) {
record.name // OK
record.age // OK
record.userId // OK
}
else {
record.name // OK
record.age // OK
record.userId // ERROR
} ...and that type guard works just fine with the current |
Sorry but this example does not compile (maybe due to my compiler options). A working implementation of the type guard in my context is:
But I find that to be a lot of ceremony where one would just like to rely on cheap and lean workflow type inference. Also, the type is not guiding the developper into knowing what value would the function returns, not helping with type directed development. This issue is all about tradeoffs. |
This issue has been marked 'Working as Intended' and has seen no recent activity. It has been automatically closed for house-keeping purposes. |
TypeScript Version: 2.0.3
Code
Expected behavior:
foo return type is inferred to A | (A & B)
Actual behavior:
foo return type is inferred to A
The text was updated successfully, but these errors were encountered: