-
Notifications
You must be signed in to change notification settings - Fork 12.8k
Union with non-distinct discriminating property breaks inference somewhat (confusing error messages, invalid intellisense) #40934
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
I expect this would all work out fine if somehow TypeScript could reduce: type Action = {
type: 'action1';
payload: {
property1: string;
};
} | {
type: 'action1';
payload: {
property2: number;
};
} | {
type: 'action2';
payload: {
property3: boolean;
};
} To: type Action = {
type: 'action1';
payload: {
property1: string;
} | {
property2: number;
};
} | {
type: 'action2';
payload: {
property3: boolean;
};
} Although, I don't know enough to be sure that is a valid reduction all of the time. |
The completions bit does seem like a bug, and I 100% agree with your logic about the error message, though I’m not super optimistic that it can easily be generalized. Worth taking a look, though. (cc @DanielRosenwasser for thoughts on error elaboration) |
I think it is similar to this #39438 issue related to contextual type in a completion list |
I have ideas to improve the current error message, but I don't know how we could turn it into an excess property error. For that, I'd have to ask @sandersn for input. So right now, the message is
The most obvious problem to me is that we're trying to match something with a // make sure exactly 1 matches before returning it
let nextMatch = discriminable.indexOf(/*searchElement*/ true, match + 1);
while (nextMatch !== -1) {
if (!isTypeIdenticalTo(target.types[match], target.types[nextMatch])) {
return defaultValue;
}
nextMatch = discriminable.indexOf(/*searchElement*/ true, nextMatch + 1);
} That doesn't really make sense to me - if you have ties, you're still better off going with one of them than none of them. Maybe @weswigham has more input on the intuition here. Apart from that, I think there's another issue with our heuristics where instead of filtering down unions, they just keep trying to find a "best" type and giving up. function getBestMatchingType(source: Type, target: UnionOrIntersectionType, isRelatedTo = compareTypesAssignable) {
return findMatchingDiscriminantType(source, target, isRelatedTo, /*skipPartial*/ true) ||
findMatchingTypeReferenceOrTypeAliasReference(source, target) ||
findBestTypeForObjectLiteral(source, target) ||
findBestTypeForInvokable(source, target) ||
findMostOverlappyType(source, target); That's a little harder to re-orchestrate in a simple way, but it wouldn't be the craziest. |
Removing my assignment and milestone because @a-tarasyuk rightly pointed out that the completions part of this report is an exact duplicate of #39438. |
Just historical precedent. When we discriminate, we choose to do so to a single item, and not a category of items. Discriminating to a set of applicable elements rather than a single one, in theory, I think would be fine, so long as we were actually set up to handle it everywhere. |
I think this is because it indicates that the discriminable item might be technically discriminable but not intended as such. Certainly In other words, I think the specific example isn't a convincing reason to change the single-matching-type rule since it's a possibly-incorrect discriminated union. I'd hope for an union that legitimately needs to have a discriminant property with two or more identical types. |
After looking again, I actually believe this is because of contextual typing, where signatures can't be contextually typed by a union. Seems like the easiest thing to do is add a flag for this specific use-case. |
Uh oh!
There was an error while loading. Please reload this page.
TypeScript Version: 4.0.2
Search Terms: discriminated union, error messages
Code
Expected behavior:
Expected error at
action.payload.property3
declaration, perhaps something like:Also, once the object has been refined by the type
action1
, intellisense should only provideproperty1
andproperty2
as a suggestion when declaring thepayload
.Actual behavior:
An error message at the first line of the declaration:
Also, once the object has been refined by the type
action1
, intellisense still providesproperty3
as a suggestion when declaring thepayload
.Playground Link: https://www.typescriptlang.org/play?#code/C4TwDgpgBAggxsAlgewHZQLxQN4CgoFSiQBcUA5AIYIqoCM5A3PoWJSADbKUAmZehQVDAAnZJBGg6ZAM7ARiVAHNmggL7M1UAD44WBYhDJUaaBqtbsuvfvqGjxESSABMZVAFcAtgCMnFgg1cLV0BQkNjaiQ0FyY7Nk5uPj0hVjEJUABmMh9kZA4ISlQAqCC1XFw4NDkoKNoyeGj0LDCDcCMKOrNyABp4qyTbVIIHDJBsohEPCDtytSA
The text was updated successfully, but these errors were encountered: