From 1c7628e6534cda58de2f29eaf5a51ef9aac33ce2 Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders Date: Fri, 10 Feb 2017 14:01:47 -0800 Subject: [PATCH 1/3] Improve discriminated union error messages 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 --- src/compiler/checker.ts | 22 ++++++++++++++++++ .../discriminatedUnionErrorMessage.errors.txt | 23 +++++++++++++++++++ .../discriminatedUnionErrorMessage.js | 21 +++++++++++++++++ .../discriminatedUnionErrorMessage.ts | 12 ++++++++++ 4 files changed, 78 insertions(+) create mode 100644 tests/baselines/reference/discriminatedUnionErrorMessage.errors.txt create mode 100644 tests/baselines/reference/discriminatedUnionErrorMessage.js create mode 100644 tests/cases/compiler/discriminatedUnionErrorMessage.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index ac1135788cd..3735a01849b 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -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); + } + const len = targetTypes.length; for (let i = 0; i < len; i++) { const related = isRelatedTo(source, targetTypes[i], reportErrors && i === len - 1); @@ -7781,6 +7786,23 @@ namespace ts { return Ternary.False; } + function findMatchingDiscriminantType(source: Type, target: UnionOrIntersectionType) { + const sourceProperties = getPropertiesOfObjectType(source); + if (sourceProperties) { + for (const sourceProperty of sourceProperties) { + if (isDiscriminantProperty(target, sourceProperty.name)) { + const sourceType = getTypeOfSymbol(sourceProperty); + for (const type of target.types) { + const targetType = getTypeOfPropertyOfType(type, sourceProperty.name); + if (targetType && isRelatedTo(sourceType, targetType)) { + return type; + } + } + } + } + } + } + function typeRelatedToEachType(source: Type, target: UnionOrIntersectionType, reportErrors: boolean): Ternary { let result = Ternary.True; const targetTypes = target.types; diff --git a/tests/baselines/reference/discriminatedUnionErrorMessage.errors.txt b/tests/baselines/reference/discriminatedUnionErrorMessage.errors.txt new file mode 100644 index 00000000000..6f1eb511d37 --- /dev/null +++ b/tests/baselines/reference/discriminatedUnionErrorMessage.errors.txt @@ -0,0 +1,23 @@ +tests/cases/compiler/discriminatedUnionErrorMessage.ts(8,5): error TS2322: Type '{ kind: "sq"; x: number; y: number; }' is not assignable to type 'Shape'. + Type '{ kind: "sq"; x: number; y: number; }' is not assignable to type 'Square'. + Property 'size' is missing in type '{ kind: "sq"; x: number; y: number; }'. + + +==== tests/cases/compiler/discriminatedUnionErrorMessage.ts (1 errors) ==== + 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 = { + ~~~~~ +!!! error TS2322: Type '{ kind: "sq"; x: number; y: number; }' is not assignable to type 'Shape'. +!!! error TS2322: Type '{ kind: "sq"; x: number; y: number; }' is not assignable to type 'Square'. +!!! error TS2322: Property 'size' is missing in type '{ kind: "sq"; x: number; y: number; }'. + kind: "sq", + x: 12, + y: 13, + } + \ No newline at end of file diff --git a/tests/baselines/reference/discriminatedUnionErrorMessage.js b/tests/baselines/reference/discriminatedUnionErrorMessage.js new file mode 100644 index 00000000000..10c94b19a44 --- /dev/null +++ b/tests/baselines/reference/discriminatedUnionErrorMessage.js @@ -0,0 +1,21 @@ +//// [discriminatedUnionErrorMessage.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, +} + + +//// [discriminatedUnionErrorMessage.js] +var shape = { + kind: "sq", + x: 12, + y: 13 +}; diff --git a/tests/cases/compiler/discriminatedUnionErrorMessage.ts b/tests/cases/compiler/discriminatedUnionErrorMessage.ts new file mode 100644 index 00000000000..eb2b08e9436 --- /dev/null +++ b/tests/cases/compiler/discriminatedUnionErrorMessage.ts @@ -0,0 +1,12 @@ +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, +} From 11929e33ed3514e2dc09a29ff065e4525d331587 Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders Date: Mon, 13 Feb 2017 12:54:58 -0800 Subject: [PATCH 2/3] Address PR comments --- src/compiler/checker.ts | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 3735a01849b..6f213355679 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -7771,18 +7771,14 @@ 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); - } - - const len = targetTypes.length; - for (let i = 0; i < len; i++) { - const related = isRelatedTo(source, targetTypes[i], reportErrors && i === len - 1); + for (const type of targetTypes) { + const related = isRelatedTo(source, type, /*reportErrors*/ false); if (related) { return related; } } + const discriminantType = findMatchingDiscriminantType(source, target); + isRelatedTo(source, discriminantType || targetTypes[targetTypes.length - 1], reportErrors); return Ternary.False; } From 271ca80c75e30132cc2549a79f562a2abbb432a3 Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders Date: Mon, 13 Feb 2017 13:35:07 -0800 Subject: [PATCH 3/3] Address PR comments --- src/compiler/checker.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 6f213355679..6f352c95aec 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -7777,8 +7777,10 @@ namespace ts { return related; } } - const discriminantType = findMatchingDiscriminantType(source, target); - isRelatedTo(source, discriminantType || targetTypes[targetTypes.length - 1], reportErrors); + if (reportErrors) { + const discriminantType = findMatchingDiscriminantType(source, target); + isRelatedTo(source, discriminantType || targetTypes[targetTypes.length - 1], /*reportErrors*/ true); + } return Ternary.False; }