Skip to content

Improve discriminated union error messages #14006

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

Merged
merged 3 commits into from
Feb 13, 2017

Conversation

sandersn
Copy link
Member

Assignability errors for discriminated unions now check the value of the discriminant to decide which member of the union to check for assignability.

Previously, assignability didn't know about discriminated unions and would check every member, issuing errors for the last member of the union if assignability failed.

For example:

type Square = { kind: "sq", size: number }
type Rectangle = { kind: "rt", x: number, y: number }
type Circle = { kind: "cr", radius: number }
type Shape =
    | Square
    | Rectangle
    | Circle;
let shape: Shape = {
    kind: "sq",
    x: 12,
    y: 13,
}

typeRelatedToSomeType now checks whether each property in the source type is a discriminant. It finds kind and proceeds to look for the type in the target union that has kind: "sq". If it finds it, which it does in this example (Square), then it checks only assignbility to Square.

The result is that the error now says that property 'size' is missing in type { kind: "sq", x: number, y: number } instead of saying that that "sq" is not assignable to type "cr" like it did before.

Fixes #10867

Assignability errors for discriminated unions now check the value of the
discriminant to decide which member of the union to check for
assignability.

Previously, assignability didn't know about discriminated unions and
would check every member, issuing errors for the last member of the
union if assignability failed.

For example:

```ts
type Square = { kind: "sq", size: number }
type Rectangle = { kind: "rt", x: number, y: number }
type Circle = { kind: "cr", radius: number }
type Shape =
    | Square
    | Rectangle
    | Circle;
let shape: Shape = {
    kind: "sq",
    x: 12,
    y: 13,
}
```

`typeRelatedToSomeType` now checks whether each property in the source
type is a discriminant. It finds `kind` and proceeds to look for the
type in the target union that has `kind: "sq"`. If it finds it, which it
does in this example (`Square`), then it checks only assignbility to
`Square`.

The result is that the error now says that property 'size' is missing in
type `{ kind: "sq", x: number, y: number }` instead of saying that that
"sq" is not assignable to type "cr" like it did before.

Fixes #10867
@sandersn
Copy link
Member Author

@ahejlsberg do you mind taking a look too?

@@ -7771,6 +7771,11 @@ namespace ts {
if (target.flags & TypeFlags.Union && containsType(targetTypes, source)) {
return Ternary.True;
}
const discriminantType = findMatchingDiscriminantType(source, target);
if (discriminantType) {
return isRelatedTo(source, discriminantType, reportErrors);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure this approach is correct. The target might contain multiple types with overlapping discriminants (e.g. { kind: 'a' | 'b', ... } | { kind: 'a' | 'c', ... } and you need to try all of them. I think it would be better to keep the existing loop for all cases and then look for a matching discriminant only as you are about to report an error.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How would the recursive call of isRelatedTo know that it was comparing a discriminated union member to a discriminant? I guess reportErrors could take on a third value that means "only report errors if you can find a matching discriminant", but that seems like using a general-purpose flag for a special case.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would run the entire loop with false for the reportErrors parameter. Then, if nothing matches, find the first constituent with a matching discriminant and call isRelatedTo with error reporting, or call isRelatedTo on the last one if no discriminants match.

Copy link
Member

@ahejlsberg ahejlsberg left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approved with the noted change.

if (related) {
return related;
}
}
const discriminantType = findMatchingDiscriminantType(source, target);
isRelatedTo(source, discriminantType || targetTypes[targetTypes.length - 1], reportErrors);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Enclose the two lines above in if (reportErrors) { ... } and pass true as the reportErrors argument.

@sandersn sandersn merged commit ba8330c into master Feb 13, 2017
@sandersn sandersn deleted the better-discriminated-union-errors branch February 13, 2017 22:14
sandersn added a commit that referenced this pull request Jun 8, 2017
This uses the same code as #14006, which improves error messages for
discriminated unions.
@microsoft microsoft locked and limited conversation to collaborators Jun 19, 2018
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants