From e2c996979ea36e6e1dce3d8ab1dfdd92716edd0b Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders <293473+sandersn@users.noreply.github.com> Date: Wed, 7 Aug 2019 13:08:22 -0700 Subject: [PATCH 1/5] Target types in excess property checking must match all discriminable properties This allows fewer types to be discriminated in excess properties, which fixes some examples. --- src/compiler/checker.ts | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index b8f80084482d1..22737f09fdf7e 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -14269,20 +14269,25 @@ namespace ts { function discriminateTypeByDiscriminableItems(target: UnionType, discriminators: [() => Type, __String][], related: (source: Type, target: Type) => boolean | Ternary): Type | undefined; function discriminateTypeByDiscriminableItems(target: UnionType, discriminators: [() => Type, __String][], related: (source: Type, target: Type) => boolean | Ternary, defaultValue: Type): Type; function discriminateTypeByDiscriminableItems(target: UnionType, discriminators: [() => Type, __String][], related: (source: Type, target: Type) => boolean | Ternary, defaultValue?: Type) { - let match: Type | undefined; + // undefined=unknown, true=discriminated, false=not discriminated + // The state of each type progresses from left to right. Discriminated types stop at 'true'. + let discriminable = target.types.map(_ => undefined) as (boolean | undefined)[]; for (const [getDiscriminatingType, propertyName] of discriminators) { + let i = 0 for (const type of target.types) { const targetType = getTypeOfPropertyOfType(type, propertyName); if (targetType && related(getDiscriminatingType(), targetType)) { - if (match) { - if (type === match) continue; // Finding multiple fields which discriminate to the same type is fine - return defaultValue; - } - match = type; + discriminable[i] = discriminable[i] === undefined ? true : discriminable[i] + } + else { + discriminable[i] = false } + i++ } } - return match || defaultValue; + let match = discriminable.indexOf(true); + // make sure exactly 1 matches before returning it + return match === -1 || discriminable.indexOf(true, match + 1) !== -1 ? defaultValue : target.types[match]; } /** From d7fdbcdc3042ca86996df4a54e4bc3f146de1a10 Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders <293473+sandersn@users.noreply.github.com> Date: Wed, 7 Aug 2019 13:13:52 -0700 Subject: [PATCH 2/5] Add excess property test --- .../excessPropertyCheckWithUnions.errors.txt | 20 ++++++++++- .../excessPropertyCheckWithUnions.js | 19 +++++++++++ .../excessPropertyCheckWithUnions.symbols | 33 ++++++++++++++++++ .../excessPropertyCheckWithUnions.types | 34 +++++++++++++++++++ .../compiler/excessPropertyCheckWithUnions.ts | 13 +++++++ 5 files changed, 118 insertions(+), 1 deletion(-) diff --git a/tests/baselines/reference/excessPropertyCheckWithUnions.errors.txt b/tests/baselines/reference/excessPropertyCheckWithUnions.errors.txt index 469b6d089b696..72da324caf759 100644 --- a/tests/baselines/reference/excessPropertyCheckWithUnions.errors.txt +++ b/tests/baselines/reference/excessPropertyCheckWithUnions.errors.txt @@ -23,9 +23,11 @@ tests/cases/compiler/excessPropertyCheckWithUnions.ts(50,35): error TS2322: Type tests/cases/compiler/excessPropertyCheckWithUnions.ts(66,9): error TS2326: Types of property 'n' are incompatible. Type '{ a: string; b: string; }' is not assignable to type 'AN'. Object literal may only specify known properties, and 'b' does not exist in type 'AN'. +tests/cases/compiler/excessPropertyCheckWithUnions.ts(87,5): error TS2322: Type '{ tag: "button"; type: "submit"; href: string; }' is not assignable to type 'Union'. + Object literal may only specify known properties, and 'href' does not exist in type 'Button'. -==== tests/cases/compiler/excessPropertyCheckWithUnions.ts (10 errors) ==== +==== tests/cases/compiler/excessPropertyCheckWithUnions.ts (11 errors) ==== type ADT = { tag: "A", a1: string @@ -137,4 +139,20 @@ tests/cases/compiler/excessPropertyCheckWithUnions.ts(66,9): error TS2326: Types c: "c", // ok -- kind: "A", an: { a: string } | { c: string } } } + + // Excess property checks must match all discriminable properties + type Button = { tag: 'button'; type?: 'submit'; }; + type Anchor = { tag: 'a'; type?: string; href: string }; + + type Union = Button | Anchor; + const obj: Union = { + tag: 'button', + type: 'submit', + + // should have error here + href: 'foo', + ~~~~~~~~~~~ +!!! error TS2322: Type '{ tag: "button"; type: "submit"; href: string; }' is not assignable to type 'Union'. +!!! error TS2322: Object literal may only specify known properties, and 'href' does not exist in type 'Button'. + }; \ No newline at end of file diff --git a/tests/baselines/reference/excessPropertyCheckWithUnions.js b/tests/baselines/reference/excessPropertyCheckWithUnions.js index c76ad750601c7..691a64f65b808 100644 --- a/tests/baselines/reference/excessPropertyCheckWithUnions.js +++ b/tests/baselines/reference/excessPropertyCheckWithUnions.js @@ -74,6 +74,19 @@ const abac: AB = { c: "c", // ok -- kind: "A", an: { a: string } | { c: string } } } + +// Excess property checks must match all discriminable properties +type Button = { tag: 'button'; type?: 'submit'; }; +type Anchor = { tag: 'a'; type?: string; href: string }; + +type Union = Button | Anchor; +const obj: Union = { + tag: 'button', + type: 'submit', + + // should have error here + href: 'foo', +}; //// [excessPropertyCheckWithUnions.js] @@ -125,3 +138,9 @@ var abac = { c: "c" } }; +var obj = { + tag: 'button', + type: 'submit', + // should have error here + href: 'foo' +}; diff --git a/tests/baselines/reference/excessPropertyCheckWithUnions.symbols b/tests/baselines/reference/excessPropertyCheckWithUnions.symbols index beee933a2f1b2..749e359d4a1cc 100644 --- a/tests/baselines/reference/excessPropertyCheckWithUnions.symbols +++ b/tests/baselines/reference/excessPropertyCheckWithUnions.symbols @@ -221,3 +221,36 @@ const abac: AB = { } } +// Excess property checks must match all discriminable properties +type Button = { tag: 'button'; type?: 'submit'; }; +>Button : Symbol(Button, Decl(excessPropertyCheckWithUnions.ts, 74, 1)) +>tag : Symbol(tag, Decl(excessPropertyCheckWithUnions.ts, 77, 15)) +>type : Symbol(type, Decl(excessPropertyCheckWithUnions.ts, 77, 30)) + +type Anchor = { tag: 'a'; type?: string; href: string }; +>Anchor : Symbol(Anchor, Decl(excessPropertyCheckWithUnions.ts, 77, 50)) +>tag : Symbol(tag, Decl(excessPropertyCheckWithUnions.ts, 78, 15)) +>type : Symbol(type, Decl(excessPropertyCheckWithUnions.ts, 78, 25)) +>href : Symbol(href, Decl(excessPropertyCheckWithUnions.ts, 78, 40)) + +type Union = Button | Anchor; +>Union : Symbol(Union, Decl(excessPropertyCheckWithUnions.ts, 78, 56)) +>Button : Symbol(Button, Decl(excessPropertyCheckWithUnions.ts, 74, 1)) +>Anchor : Symbol(Anchor, Decl(excessPropertyCheckWithUnions.ts, 77, 50)) + +const obj: Union = { +>obj : Symbol(obj, Decl(excessPropertyCheckWithUnions.ts, 81, 5)) +>Union : Symbol(Union, Decl(excessPropertyCheckWithUnions.ts, 78, 56)) + + tag: 'button', +>tag : Symbol(tag, Decl(excessPropertyCheckWithUnions.ts, 81, 20)) + + type: 'submit', +>type : Symbol(type, Decl(excessPropertyCheckWithUnions.ts, 82, 18)) + + // should have error here + href: 'foo', +>href : Symbol(href, Decl(excessPropertyCheckWithUnions.ts, 83, 19)) + +}; + diff --git a/tests/baselines/reference/excessPropertyCheckWithUnions.types b/tests/baselines/reference/excessPropertyCheckWithUnions.types index 83542716c6588..f56aa287daa7e 100644 --- a/tests/baselines/reference/excessPropertyCheckWithUnions.types +++ b/tests/baselines/reference/excessPropertyCheckWithUnions.types @@ -278,3 +278,37 @@ const abac: AB = { } } +// Excess property checks must match all discriminable properties +type Button = { tag: 'button'; type?: 'submit'; }; +>Button : Button +>tag : "button" +>type : "submit" | undefined + +type Anchor = { tag: 'a'; type?: string; href: string }; +>Anchor : Anchor +>tag : "a" +>type : string | undefined +>href : string + +type Union = Button | Anchor; +>Union : Union + +const obj: Union = { +>obj : Union +>{ tag: 'button', type: 'submit', // should have error here href: 'foo',} : { tag: "button"; type: "submit"; href: string; } + + tag: 'button', +>tag : "button" +>'button' : "button" + + type: 'submit', +>type : "submit" +>'submit' : "submit" + + // should have error here + href: 'foo', +>href : string +>'foo' : "foo" + +}; + diff --git a/tests/cases/compiler/excessPropertyCheckWithUnions.ts b/tests/cases/compiler/excessPropertyCheckWithUnions.ts index 027e67e4ec9fd..bb3e59151fc2a 100644 --- a/tests/cases/compiler/excessPropertyCheckWithUnions.ts +++ b/tests/cases/compiler/excessPropertyCheckWithUnions.ts @@ -74,3 +74,16 @@ const abac: AB = { c: "c", // ok -- kind: "A", an: { a: string } | { c: string } } } + +// Excess property checks must match all discriminable properties +type Button = { tag: 'button'; type?: 'submit'; }; +type Anchor = { tag: 'a'; type?: string; href: string }; + +type Union = Button | Anchor; +const obj: Union = { + tag: 'button', + type: 'submit', + + // should have error here + href: 'foo', +}; From f2c926092033a6c1fbb51834809c3c1fe059fb6f Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders <293473+sandersn@users.noreply.github.com> Date: Thu, 8 Aug 2019 13:37:45 -0700 Subject: [PATCH 3/5] Fix semicolon lint --- src/compiler/checker.ts | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 22737f09fdf7e..f53e2f37d10de 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -14271,23 +14271,23 @@ namespace ts { function discriminateTypeByDiscriminableItems(target: UnionType, discriminators: [() => Type, __String][], related: (source: Type, target: Type) => boolean | Ternary, defaultValue?: Type) { // undefined=unknown, true=discriminated, false=not discriminated // The state of each type progresses from left to right. Discriminated types stop at 'true'. - let discriminable = target.types.map(_ => undefined) as (boolean | undefined)[]; + const discriminable = target.types.map(_ => undefined) as (boolean | undefined)[]; for (const [getDiscriminatingType, propertyName] of discriminators) { - let i = 0 + let i = 0; for (const type of target.types) { const targetType = getTypeOfPropertyOfType(type, propertyName); if (targetType && related(getDiscriminatingType(), targetType)) { - discriminable[i] = discriminable[i] === undefined ? true : discriminable[i] + discriminable[i] = discriminable[i] === undefined ? true : discriminable[i]; } else { - discriminable[i] = false + discriminable[i] = false; } - i++ + i++; } } - let match = discriminable.indexOf(true); + const match = discriminable.indexOf(/*searchElement*/ true);; // make sure exactly 1 matches before returning it - return match === -1 || discriminable.indexOf(true, match + 1) !== -1 ? defaultValue : target.types[match]; + return match === -1 || discriminable.indexOf(/*searchElement*/ true, match + 1) !== -1 ? defaultValue : target.types[match]; } /** From a333f98be88db77e8a88f5f8c021e9b6c9854759 Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders <293473+sandersn@users.noreply.github.com> Date: Thu, 8 Aug 2019 14:25:13 -0700 Subject: [PATCH 4/5] Remove extra semicolon! --- src/compiler/checker.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index e58978b142bdc..96b02a2dcea79 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -14285,7 +14285,7 @@ namespace ts { i++; } } - const match = discriminable.indexOf(/*searchElement*/ true);; + const match = discriminable.indexOf(/*searchElement*/ true); // make sure exactly 1 matches before returning it return match === -1 || discriminable.indexOf(/*searchElement*/ true, match + 1) !== -1 ? defaultValue : target.types[match]; } From 4aaefbf06a54a4776f553f3bf8365070124fc43d Mon Sep 17 00:00:00 2001 From: Jack Williams Date: Mon, 5 Aug 2019 15:58:21 +0100 Subject: [PATCH 5/5] Improve EPC for unions with multiple discriminants --- ...yCheckWithMultipleDiscriminants.errors.txt | 78 ++++++++++ ...sPropertyCheckWithMultipleDiscriminants.js | 91 +++++++++++ ...ertyCheckWithMultipleDiscriminants.symbols | 143 ++++++++++++++++++ ...opertyCheckWithMultipleDiscriminants.types | 141 +++++++++++++++++ ...sPropertyCheckWithMultipleDiscriminants.ts | 59 ++++++++ 5 files changed, 512 insertions(+) create mode 100644 tests/baselines/reference/excessPropertyCheckWithMultipleDiscriminants.errors.txt create mode 100644 tests/baselines/reference/excessPropertyCheckWithMultipleDiscriminants.js create mode 100644 tests/baselines/reference/excessPropertyCheckWithMultipleDiscriminants.symbols create mode 100644 tests/baselines/reference/excessPropertyCheckWithMultipleDiscriminants.types create mode 100644 tests/cases/compiler/excessPropertyCheckWithMultipleDiscriminants.ts diff --git a/tests/baselines/reference/excessPropertyCheckWithMultipleDiscriminants.errors.txt b/tests/baselines/reference/excessPropertyCheckWithMultipleDiscriminants.errors.txt new file mode 100644 index 0000000000000..29575580e8087 --- /dev/null +++ b/tests/baselines/reference/excessPropertyCheckWithMultipleDiscriminants.errors.txt @@ -0,0 +1,78 @@ +tests/cases/compiler/excessPropertyCheckWithMultipleDiscriminants.ts(30,5): error TS2322: Type '{ type: "number"; value: number; multipleOf: number; format: string; }' is not assignable to type 'Primitive'. + Object literal may only specify known properties, and 'multipleOf' does not exist in type 'Float'. +tests/cases/compiler/excessPropertyCheckWithMultipleDiscriminants.ts(41,5): error TS2322: Type '{ p1: "left"; p2: false; p3: number; p4: string; }' is not assignable to type 'DisjointDiscriminants'. + Object literal may only specify known properties, and 'p3' does not exist in type '{ p1: "left"; p2: boolean; }'. +tests/cases/compiler/excessPropertyCheckWithMultipleDiscriminants.ts(57,5): error TS2322: Type '{ p1: "right"; p2: false; p3: number; p4: string; }' is not assignable to type 'DisjointDiscriminants'. + Object literal may only specify known properties, and 'p3' does not exist in type '{ p1: "right"; p2: false; p4: string; }'. + + +==== tests/cases/compiler/excessPropertyCheckWithMultipleDiscriminants.ts (3 errors) ==== + // Repro from #32657 + + interface Base { + value: T; + } + + interface Int extends Base { + type: "integer"; + multipleOf?: number; + } + + interface Float extends Base { + type: "number"; + } + + interface Str extends Base { + type: "string"; + format?: string; + } + + interface Bool extends Base { + type: "boolean"; + } + + type Primitive = Int | Float | Str | Bool; + + const foo: Primitive = { + type: "number", + value: 10, + multipleOf: 5, // excess property + ~~~~~~~~~~~~~ +!!! error TS2322: Type '{ type: "number"; value: number; multipleOf: number; format: string; }' is not assignable to type 'Primitive'. +!!! error TS2322: Object literal may only specify known properties, and 'multipleOf' does not exist in type 'Float'. + format: "what?" + } + + + type DisjointDiscriminants = { p1: 'left'; p2: true; p3: number } | { p1: 'right'; p2: false; p4: string } | { p1: 'left'; p2: boolean }; + + // This has excess error because variant three is the only applicable case. + const a: DisjointDiscriminants = { + p1: 'left', + p2: false, + p3: 42, + ~~~~~~ +!!! error TS2322: Type '{ p1: "left"; p2: false; p3: number; p4: string; }' is not assignable to type 'DisjointDiscriminants'. +!!! error TS2322: Object literal may only specify known properties, and 'p3' does not exist in type '{ p1: "left"; p2: boolean; }'. + p4: "hello" + }; + + // This has no excess error because variant one and three are both applicable. + const b: DisjointDiscriminants = { + p1: 'left', + p2: true, + p3: 42, + p4: "hello" + }; + + // This has excess error because variant two is the only applicable case + const c: DisjointDiscriminants = { + p1: 'right', + p2: false, + p3: 42, + ~~~~~~ +!!! error TS2322: Type '{ p1: "right"; p2: false; p3: number; p4: string; }' is not assignable to type 'DisjointDiscriminants'. +!!! error TS2322: Object literal may only specify known properties, and 'p3' does not exist in type '{ p1: "right"; p2: false; p4: string; }'. + p4: "hello" + }; + \ No newline at end of file diff --git a/tests/baselines/reference/excessPropertyCheckWithMultipleDiscriminants.js b/tests/baselines/reference/excessPropertyCheckWithMultipleDiscriminants.js new file mode 100644 index 0000000000000..a847e63aa5a1f --- /dev/null +++ b/tests/baselines/reference/excessPropertyCheckWithMultipleDiscriminants.js @@ -0,0 +1,91 @@ +//// [excessPropertyCheckWithMultipleDiscriminants.ts] +// Repro from #32657 + +interface Base { + value: T; +} + +interface Int extends Base { + type: "integer"; + multipleOf?: number; +} + +interface Float extends Base { + type: "number"; +} + +interface Str extends Base { + type: "string"; + format?: string; +} + +interface Bool extends Base { + type: "boolean"; +} + +type Primitive = Int | Float | Str | Bool; + +const foo: Primitive = { + type: "number", + value: 10, + multipleOf: 5, // excess property + format: "what?" +} + + +type DisjointDiscriminants = { p1: 'left'; p2: true; p3: number } | { p1: 'right'; p2: false; p4: string } | { p1: 'left'; p2: boolean }; + +// This has excess error because variant three is the only applicable case. +const a: DisjointDiscriminants = { + p1: 'left', + p2: false, + p3: 42, + p4: "hello" +}; + +// This has no excess error because variant one and three are both applicable. +const b: DisjointDiscriminants = { + p1: 'left', + p2: true, + p3: 42, + p4: "hello" +}; + +// This has excess error because variant two is the only applicable case +const c: DisjointDiscriminants = { + p1: 'right', + p2: false, + p3: 42, + p4: "hello" +}; + + +//// [excessPropertyCheckWithMultipleDiscriminants.js] +// Repro from #32657 +var foo = { + type: "number", + value: 10, + multipleOf: 5, + format: "what?" +}; +// This has excess error because variant three is the only applicable case. +var a = { + p1: 'left', + p2: false, + p3: 42, + p4: "hello" +}; +// This has no excess error because variant one and three are both applicable. +var b = { + p1: 'left', + p2: true, + p3: 42, + p4: "hello" +}; +// This has excess error because variant two is the only applicable case +var c = { + p1: 'right', + p2: false, + p3: 42, + p4: "hello" +}; diff --git a/tests/baselines/reference/excessPropertyCheckWithMultipleDiscriminants.symbols b/tests/baselines/reference/excessPropertyCheckWithMultipleDiscriminants.symbols new file mode 100644 index 0000000000000..7a1b06b045a74 --- /dev/null +++ b/tests/baselines/reference/excessPropertyCheckWithMultipleDiscriminants.symbols @@ -0,0 +1,143 @@ +=== tests/cases/compiler/excessPropertyCheckWithMultipleDiscriminants.ts === +// Repro from #32657 + +interface Base { +>Base : Symbol(Base, Decl(excessPropertyCheckWithMultipleDiscriminants.ts, 0, 0)) +>T : Symbol(T, Decl(excessPropertyCheckWithMultipleDiscriminants.ts, 2, 15)) + + value: T; +>value : Symbol(Base.value, Decl(excessPropertyCheckWithMultipleDiscriminants.ts, 2, 19)) +>T : Symbol(T, Decl(excessPropertyCheckWithMultipleDiscriminants.ts, 2, 15)) +} + +interface Int extends Base { +>Int : Symbol(Int, Decl(excessPropertyCheckWithMultipleDiscriminants.ts, 4, 1)) +>Base : Symbol(Base, Decl(excessPropertyCheckWithMultipleDiscriminants.ts, 0, 0)) + + type: "integer"; +>type : Symbol(Int.type, Decl(excessPropertyCheckWithMultipleDiscriminants.ts, 6, 36)) + + multipleOf?: number; +>multipleOf : Symbol(Int.multipleOf, Decl(excessPropertyCheckWithMultipleDiscriminants.ts, 7, 20)) +} + +interface Float extends Base { +>Float : Symbol(Float, Decl(excessPropertyCheckWithMultipleDiscriminants.ts, 9, 1)) +>Base : Symbol(Base, Decl(excessPropertyCheckWithMultipleDiscriminants.ts, 0, 0)) + + type: "number"; +>type : Symbol(Float.type, Decl(excessPropertyCheckWithMultipleDiscriminants.ts, 11, 38)) +} + +interface Str extends Base { +>Str : Symbol(Str, Decl(excessPropertyCheckWithMultipleDiscriminants.ts, 13, 1)) +>Base : Symbol(Base, Decl(excessPropertyCheckWithMultipleDiscriminants.ts, 0, 0)) + + type: "string"; +>type : Symbol(Str.type, Decl(excessPropertyCheckWithMultipleDiscriminants.ts, 15, 36)) + + format?: string; +>format : Symbol(Str.format, Decl(excessPropertyCheckWithMultipleDiscriminants.ts, 16, 19)) +} + +interface Bool extends Base { +>Bool : Symbol(Bool, Decl(excessPropertyCheckWithMultipleDiscriminants.ts, 18, 1)) +>Base : Symbol(Base, Decl(excessPropertyCheckWithMultipleDiscriminants.ts, 0, 0)) + + type: "boolean"; +>type : Symbol(Bool.type, Decl(excessPropertyCheckWithMultipleDiscriminants.ts, 20, 38)) +} + +type Primitive = Int | Float | Str | Bool; +>Primitive : Symbol(Primitive, Decl(excessPropertyCheckWithMultipleDiscriminants.ts, 22, 1)) +>Int : Symbol(Int, Decl(excessPropertyCheckWithMultipleDiscriminants.ts, 4, 1)) +>Float : Symbol(Float, Decl(excessPropertyCheckWithMultipleDiscriminants.ts, 9, 1)) +>Str : Symbol(Str, Decl(excessPropertyCheckWithMultipleDiscriminants.ts, 13, 1)) +>Bool : Symbol(Bool, Decl(excessPropertyCheckWithMultipleDiscriminants.ts, 18, 1)) + +const foo: Primitive = { +>foo : Symbol(foo, Decl(excessPropertyCheckWithMultipleDiscriminants.ts, 26, 5)) +>Primitive : Symbol(Primitive, Decl(excessPropertyCheckWithMultipleDiscriminants.ts, 22, 1)) + + type: "number", +>type : Symbol(type, Decl(excessPropertyCheckWithMultipleDiscriminants.ts, 26, 24)) + + value: 10, +>value : Symbol(value, Decl(excessPropertyCheckWithMultipleDiscriminants.ts, 27, 19)) + + multipleOf: 5, // excess property +>multipleOf : Symbol(multipleOf, Decl(excessPropertyCheckWithMultipleDiscriminants.ts, 28, 14)) + + format: "what?" +>format : Symbol(format, Decl(excessPropertyCheckWithMultipleDiscriminants.ts, 29, 18)) +} + + +type DisjointDiscriminants = { p1: 'left'; p2: true; p3: number } | { p1: 'right'; p2: false; p4: string } | { p1: 'left'; p2: boolean }; +>DisjointDiscriminants : Symbol(DisjointDiscriminants, Decl(excessPropertyCheckWithMultipleDiscriminants.ts, 31, 1)) +>p1 : Symbol(p1, Decl(excessPropertyCheckWithMultipleDiscriminants.ts, 34, 30)) +>p2 : Symbol(p2, Decl(excessPropertyCheckWithMultipleDiscriminants.ts, 34, 42)) +>p3 : Symbol(p3, Decl(excessPropertyCheckWithMultipleDiscriminants.ts, 34, 52)) +>p1 : Symbol(p1, Decl(excessPropertyCheckWithMultipleDiscriminants.ts, 34, 69)) +>p2 : Symbol(p2, Decl(excessPropertyCheckWithMultipleDiscriminants.ts, 34, 82)) +>p4 : Symbol(p4, Decl(excessPropertyCheckWithMultipleDiscriminants.ts, 34, 93)) +>p1 : Symbol(p1, Decl(excessPropertyCheckWithMultipleDiscriminants.ts, 34, 110)) +>p2 : Symbol(p2, Decl(excessPropertyCheckWithMultipleDiscriminants.ts, 34, 122)) + +// This has excess error because variant three is the only applicable case. +const a: DisjointDiscriminants = { +>a : Symbol(a, Decl(excessPropertyCheckWithMultipleDiscriminants.ts, 37, 5)) +>DisjointDiscriminants : Symbol(DisjointDiscriminants, Decl(excessPropertyCheckWithMultipleDiscriminants.ts, 31, 1)) + + p1: 'left', +>p1 : Symbol(p1, Decl(excessPropertyCheckWithMultipleDiscriminants.ts, 37, 34)) + + p2: false, +>p2 : Symbol(p2, Decl(excessPropertyCheckWithMultipleDiscriminants.ts, 38, 15)) + + p3: 42, +>p3 : Symbol(p3, Decl(excessPropertyCheckWithMultipleDiscriminants.ts, 39, 14)) + + p4: "hello" +>p4 : Symbol(p4, Decl(excessPropertyCheckWithMultipleDiscriminants.ts, 40, 11)) + +}; + +// This has no excess error because variant one and three are both applicable. +const b: DisjointDiscriminants = { +>b : Symbol(b, Decl(excessPropertyCheckWithMultipleDiscriminants.ts, 45, 5)) +>DisjointDiscriminants : Symbol(DisjointDiscriminants, Decl(excessPropertyCheckWithMultipleDiscriminants.ts, 31, 1)) + + p1: 'left', +>p1 : Symbol(p1, Decl(excessPropertyCheckWithMultipleDiscriminants.ts, 45, 34)) + + p2: true, +>p2 : Symbol(p2, Decl(excessPropertyCheckWithMultipleDiscriminants.ts, 46, 15)) + + p3: 42, +>p3 : Symbol(p3, Decl(excessPropertyCheckWithMultipleDiscriminants.ts, 47, 13)) + + p4: "hello" +>p4 : Symbol(p4, Decl(excessPropertyCheckWithMultipleDiscriminants.ts, 48, 11)) + +}; + +// This has excess error because variant two is the only applicable case +const c: DisjointDiscriminants = { +>c : Symbol(c, Decl(excessPropertyCheckWithMultipleDiscriminants.ts, 53, 5)) +>DisjointDiscriminants : Symbol(DisjointDiscriminants, Decl(excessPropertyCheckWithMultipleDiscriminants.ts, 31, 1)) + + p1: 'right', +>p1 : Symbol(p1, Decl(excessPropertyCheckWithMultipleDiscriminants.ts, 53, 34)) + + p2: false, +>p2 : Symbol(p2, Decl(excessPropertyCheckWithMultipleDiscriminants.ts, 54, 16)) + + p3: 42, +>p3 : Symbol(p3, Decl(excessPropertyCheckWithMultipleDiscriminants.ts, 55, 14)) + + p4: "hello" +>p4 : Symbol(p4, Decl(excessPropertyCheckWithMultipleDiscriminants.ts, 56, 11)) + +}; + diff --git a/tests/baselines/reference/excessPropertyCheckWithMultipleDiscriminants.types b/tests/baselines/reference/excessPropertyCheckWithMultipleDiscriminants.types new file mode 100644 index 0000000000000..1f5d85b10a5b4 --- /dev/null +++ b/tests/baselines/reference/excessPropertyCheckWithMultipleDiscriminants.types @@ -0,0 +1,141 @@ +=== tests/cases/compiler/excessPropertyCheckWithMultipleDiscriminants.ts === +// Repro from #32657 + +interface Base { + value: T; +>value : T +} + +interface Int extends Base { + type: "integer"; +>type : "integer" + + multipleOf?: number; +>multipleOf : number +} + +interface Float extends Base { + type: "number"; +>type : "number" +} + +interface Str extends Base { + type: "string"; +>type : "string" + + format?: string; +>format : string +} + +interface Bool extends Base { + type: "boolean"; +>type : "boolean" +} + +type Primitive = Int | Float | Str | Bool; +>Primitive : Primitive + +const foo: Primitive = { +>foo : Primitive +>{ type: "number", value: 10, multipleOf: 5, // excess property format: "what?"} : { type: "number"; value: number; multipleOf: number; format: string; } + + type: "number", +>type : "number" +>"number" : "number" + + value: 10, +>value : number +>10 : 10 + + multipleOf: 5, // excess property +>multipleOf : number +>5 : 5 + + format: "what?" +>format : string +>"what?" : "what?" +} + + +type DisjointDiscriminants = { p1: 'left'; p2: true; p3: number } | { p1: 'right'; p2: false; p4: string } | { p1: 'left'; p2: boolean }; +>DisjointDiscriminants : DisjointDiscriminants +>p1 : "left" +>p2 : true +>true : true +>p3 : number +>p1 : "right" +>p2 : false +>false : false +>p4 : string +>p1 : "left" +>p2 : boolean + +// This has excess error because variant three is the only applicable case. +const a: DisjointDiscriminants = { +>a : DisjointDiscriminants +>{ p1: 'left', p2: false, p3: 42, p4: "hello"} : { p1: "left"; p2: false; p3: number; p4: string; } + + p1: 'left', +>p1 : "left" +>'left' : "left" + + p2: false, +>p2 : false +>false : false + + p3: 42, +>p3 : number +>42 : 42 + + p4: "hello" +>p4 : string +>"hello" : "hello" + +}; + +// This has no excess error because variant one and three are both applicable. +const b: DisjointDiscriminants = { +>b : DisjointDiscriminants +>{ p1: 'left', p2: true, p3: 42, p4: "hello"} : { p1: "left"; p2: true; p3: number; p4: string; } + + p1: 'left', +>p1 : "left" +>'left' : "left" + + p2: true, +>p2 : true +>true : true + + p3: 42, +>p3 : number +>42 : 42 + + p4: "hello" +>p4 : string +>"hello" : "hello" + +}; + +// This has excess error because variant two is the only applicable case +const c: DisjointDiscriminants = { +>c : DisjointDiscriminants +>{ p1: 'right', p2: false, p3: 42, p4: "hello"} : { p1: "right"; p2: false; p3: number; p4: string; } + + p1: 'right', +>p1 : "right" +>'right' : "right" + + p2: false, +>p2 : false +>false : false + + p3: 42, +>p3 : number +>42 : 42 + + p4: "hello" +>p4 : string +>"hello" : "hello" + +}; + diff --git a/tests/cases/compiler/excessPropertyCheckWithMultipleDiscriminants.ts b/tests/cases/compiler/excessPropertyCheckWithMultipleDiscriminants.ts new file mode 100644 index 0000000000000..5f7abedc1f42a --- /dev/null +++ b/tests/cases/compiler/excessPropertyCheckWithMultipleDiscriminants.ts @@ -0,0 +1,59 @@ +// Repro from #32657 + +interface Base { + value: T; +} + +interface Int extends Base { + type: "integer"; + multipleOf?: number; +} + +interface Float extends Base { + type: "number"; +} + +interface Str extends Base { + type: "string"; + format?: string; +} + +interface Bool extends Base { + type: "boolean"; +} + +type Primitive = Int | Float | Str | Bool; + +const foo: Primitive = { + type: "number", + value: 10, + multipleOf: 5, // excess property + format: "what?" +} + + +type DisjointDiscriminants = { p1: 'left'; p2: true; p3: number } | { p1: 'right'; p2: false; p4: string } | { p1: 'left'; p2: boolean }; + +// This has excess error because variant three is the only applicable case. +const a: DisjointDiscriminants = { + p1: 'left', + p2: false, + p3: 42, + p4: "hello" +}; + +// This has no excess error because variant one and three are both applicable. +const b: DisjointDiscriminants = { + p1: 'left', + p2: true, + p3: 42, + p4: "hello" +}; + +// This has excess error because variant two is the only applicable case +const c: DisjointDiscriminants = { + p1: 'right', + p2: false, + p3: 42, + p4: "hello" +};