diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index ddfebd84ad6..559d6ae71ba 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -9029,6 +9029,7 @@ namespace ts { function findMatchingDiscriminantType(source: Type, target: UnionOrIntersectionType) { const sourceProperties = getPropertiesOfObjectType(source); + let match: Type; if (sourceProperties) { for (const sourceProperty of sourceProperties) { if (isDiscriminantProperty(target, sourceProperty.name)) { @@ -9036,12 +9037,16 @@ namespace ts { for (const type of target.types) { const targetType = getTypeOfPropertyOfType(type, sourceProperty.name); if (targetType && isRelatedTo(sourceType, targetType)) { - return type; + if (match) { + return undefined; + } + match = type; } } } } } + return match; } function typeRelatedToEachType(source: Type, target: IntersectionType, reportErrors: boolean): Ternary { diff --git a/tests/baselines/reference/excessPropertyCheckWithUnions.errors.txt b/tests/baselines/reference/excessPropertyCheckWithUnions.errors.txt index 1a01f8cb5b8..9714c90ba61 100644 --- a/tests/baselines/reference/excessPropertyCheckWithUnions.errors.txt +++ b/tests/baselines/reference/excessPropertyCheckWithUnions.errors.txt @@ -5,9 +5,21 @@ tests/cases/compiler/excessPropertyCheckWithUnions.ts(11,21): error TS2322: Type tests/cases/compiler/excessPropertyCheckWithUnions.ts(12,1): error TS2322: Type '{ tag: "D"; }' is not assignable to type 'ADT'. Type '{ tag: "D"; }' is not assignable to type '{ tag: "D"; d20: 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20; }'. Property 'd20' is missing in type '{ tag: "D"; }'. +tests/cases/compiler/excessPropertyCheckWithUnions.ts(33,28): error TS2322: Type '{ tag: "A"; x: string; extra: number; }' is not assignable to type 'Ambiguous'. + Object literal may only specify known properties, and 'extra' does not exist in type 'Ambiguous'. +tests/cases/compiler/excessPropertyCheckWithUnions.ts(34,26): error TS2322: Type '{ tag: "A"; y: number; extra: number; }' is not assignable to type 'Ambiguous'. + Object literal may only specify known properties, and 'extra' does not exist in type 'Ambiguous'. +tests/cases/compiler/excessPropertyCheckWithUnions.ts(39,1): error TS2322: Type '{ tag: "A"; }' is not assignable to type 'Ambiguous'. + Type '{ tag: "A"; }' is not assignable to type '{ tag: "C"; }'. + Types of property 'tag' are incompatible. + Type '"A"' is not assignable to type '"C"'. +tests/cases/compiler/excessPropertyCheckWithUnions.ts(40,1): error TS2322: Type '{ tag: "A"; z: true; }' is not assignable to type 'Ambiguous'. + Type '{ tag: "A"; z: true; }' is not assignable to type '{ tag: "C"; }'. + Types of property 'tag' are incompatible. + Type '"A"' is not assignable to type '"C"'. -==== tests/cases/compiler/excessPropertyCheckWithUnions.ts (3 errors) ==== +==== tests/cases/compiler/excessPropertyCheckWithUnions.ts (7 errors) ==== type ADT = { tag: "A", a1: string @@ -30,4 +42,48 @@ tests/cases/compiler/excessPropertyCheckWithUnions.ts(12,1): error TS2322: Type !!! error TS2322: Type '{ tag: "D"; }' is not assignable to type 'ADT'. !!! error TS2322: Type '{ tag: "D"; }' is not assignable to type '{ tag: "D"; d20: 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20; }'. !!! error TS2322: Property 'd20' is missing in type '{ tag: "D"; }'. + + type Ambiguous = { + tag: "A", + x: string + } | { + tag: "A", + y: number + } | { + tag: "B", + z: boolean + } | { + tag: "C" + } + let amb: Ambiguous + // no error for ambiguous tag, even when it could satisfy both constituents at once + amb = { tag: "A", x: "hi" } + amb = { tag: "A", y: 12 } + amb = { tag: "A", x: "hi", y: 12 } + + // correctly error on excess property 'extra', even when ambiguous + amb = { tag: "A", x: "hi", extra: 12 } + ~~~~~~~~~ +!!! error TS2322: Type '{ tag: "A"; x: string; extra: number; }' is not assignable to type 'Ambiguous'. +!!! error TS2322: Object literal may only specify known properties, and 'extra' does not exist in type 'Ambiguous'. + amb = { tag: "A", y: 12, extra: 12 } + ~~~~~~~~~ +!!! error TS2322: Type '{ tag: "A"; y: number; extra: number; }' is not assignable to type 'Ambiguous'. +!!! error TS2322: Object literal may only specify known properties, and 'extra' does not exist in type 'Ambiguous'. + + // assignability errors still work. + // But note that the error for `z: true` is the fallback one of reporting on + // the last constituent since assignability error reporting can't find a single best discriminant either. + amb = { tag: "A" } + ~~~ +!!! error TS2322: Type '{ tag: "A"; }' is not assignable to type 'Ambiguous'. +!!! error TS2322: Type '{ tag: "A"; }' is not assignable to type '{ tag: "C"; }'. +!!! error TS2322: Types of property 'tag' are incompatible. +!!! error TS2322: Type '"A"' is not assignable to type '"C"'. + amb = { tag: "A", z: true } + ~~~ +!!! error TS2322: Type '{ tag: "A"; z: true; }' is not assignable to type 'Ambiguous'. +!!! error TS2322: Type '{ tag: "A"; z: true; }' is not assignable to type '{ tag: "C"; }'. +!!! error TS2322: Types of property 'tag' are incompatible. +!!! error TS2322: Type '"A"' is not assignable to type '"C"'. \ No newline at end of file diff --git a/tests/baselines/reference/excessPropertyCheckWithUnions.js b/tests/baselines/reference/excessPropertyCheckWithUnions.js index 79c30bce9c7..fb0a5a6f819 100644 --- a/tests/baselines/reference/excessPropertyCheckWithUnions.js +++ b/tests/baselines/reference/excessPropertyCheckWithUnions.js @@ -11,9 +11,50 @@ type ADT = { let wrong: ADT = { tag: "T", a1: "extra" } wrong = { tag: "A", d20: 12 } wrong = { tag: "D" } + +type Ambiguous = { + tag: "A", + x: string +} | { + tag: "A", + y: number +} | { + tag: "B", + z: boolean +} | { + tag: "C" +} +let amb: Ambiguous +// no error for ambiguous tag, even when it could satisfy both constituents at once +amb = { tag: "A", x: "hi" } +amb = { tag: "A", y: 12 } +amb = { tag: "A", x: "hi", y: 12 } + +// correctly error on excess property 'extra', even when ambiguous +amb = { tag: "A", x: "hi", extra: 12 } +amb = { tag: "A", y: 12, extra: 12 } + +// assignability errors still work. +// But note that the error for `z: true` is the fallback one of reporting on +// the last constituent since assignability error reporting can't find a single best discriminant either. +amb = { tag: "A" } +amb = { tag: "A", z: true } //// [excessPropertyCheckWithUnions.js] var wrong = { tag: "T", a1: "extra" }; wrong = { tag: "A", d20: 12 }; wrong = { tag: "D" }; +var amb; +// no error for ambiguous tag, even when it could satisfy both constituents at once +amb = { tag: "A", x: "hi" }; +amb = { tag: "A", y: 12 }; +amb = { tag: "A", x: "hi", y: 12 }; +// correctly error on excess property 'extra', even when ambiguous +amb = { tag: "A", x: "hi", extra: 12 }; +amb = { tag: "A", y: 12, extra: 12 }; +// assignability errors still work. +// But note that the error for `z: true` is the fallback one of reporting on +// the last constituent since assignability error reporting can't find a single best discriminant either. +amb = { tag: "A" }; +amb = { tag: "A", z: true }; diff --git a/tests/cases/compiler/excessPropertyCheckWithUnions.ts b/tests/cases/compiler/excessPropertyCheckWithUnions.ts index 8b1abf1b764..51f36d137fd 100644 --- a/tests/cases/compiler/excessPropertyCheckWithUnions.ts +++ b/tests/cases/compiler/excessPropertyCheckWithUnions.ts @@ -10,3 +10,31 @@ type ADT = { let wrong: ADT = { tag: "T", a1: "extra" } wrong = { tag: "A", d20: 12 } wrong = { tag: "D" } + +type Ambiguous = { + tag: "A", + x: string +} | { + tag: "A", + y: number +} | { + tag: "B", + z: boolean +} | { + tag: "C" +} +let amb: Ambiguous +// no error for ambiguous tag, even when it could satisfy both constituents at once +amb = { tag: "A", x: "hi" } +amb = { tag: "A", y: 12 } +amb = { tag: "A", x: "hi", y: 12 } + +// correctly error on excess property 'extra', even when ambiguous +amb = { tag: "A", x: "hi", extra: 12 } +amb = { tag: "A", y: 12, extra: 12 } + +// assignability errors still work. +// But note that the error for `z: true` is the fallback one of reporting on +// the last constituent since assignability error reporting can't find a single best discriminant either. +amb = { tag: "A" } +amb = { tag: "A", z: true }