From 7da2b5129791ea820b9ebc4846d02c49eee5ebd2 Mon Sep 17 00:00:00 2001 From: Daniel Rosenwasser Date: Thu, 28 Jan 2021 15:51:24 -0800 Subject: [PATCH 1/3] Remove cast, reduce line length. --- src/compiler/checker.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 4293cd82cb7fc..6b9dceed52ded 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -41037,7 +41037,12 @@ namespace ts { if (sourceProperties) { const sourcePropertiesFiltered = findDiscriminantProperties(sourceProperties, target); if (sourcePropertiesFiltered) { - return discriminateTypeByDiscriminableItems(target, map(sourcePropertiesFiltered, p => ([() => getTypeOfSymbol(p), p.escapedName] as [() => Type, __String])), isRelatedTo, /*defaultValue*/ undefined, skipPartial); + return discriminateTypeByDiscriminableItems( + target, + map(sourcePropertiesFiltered, p => ([() => getTypeOfSymbol(p), p.escapedName])), + isRelatedTo, /*defaultValue*/ undefined, + skipPartial + ); } } } From 96095efa2d63cac79c46d1e07379a9f08b9a711f Mon Sep 17 00:00:00 2001 From: Daniel Rosenwasser Date: Mon, 1 Feb 2021 18:00:38 -0800 Subject: [PATCH 2/3] Don't throw away results if there are multiple discriminants. --- src/compiler/checker.ts | 34 ++++++++++++++++++++++------------ 1 file changed, 22 insertions(+), 12 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 6b9dceed52ded..29f87eb8a2142 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -18988,9 +18988,15 @@ namespace ts { findMostOverlappyType(source, target); } - function discriminateTypeByDiscriminableItems(target: UnionType, discriminators: [() => Type, __String][], related: (source: Type, target: Type) => boolean | Ternary, defaultValue?: undefined, skipPartial?: boolean): Type | undefined; - function discriminateTypeByDiscriminableItems(target: UnionType, discriminators: [() => Type, __String][], related: (source: Type, target: Type) => boolean | Ternary, defaultValue: Type, skipPartial?: boolean): Type; - function discriminateTypeByDiscriminableItems(target: UnionType, discriminators: [() => Type, __String][], related: (source: Type, target: Type) => boolean | Ternary, defaultValue?: Type, skipPartial?: boolean) { + function discriminateTypeByDiscriminableItems(target: UnionType, discriminators: [() => Type, __String][], related: (source: Type, target: Type) => boolean | Ternary, defaultValue?: undefined, skipPartial?: boolean, allowMultiples?: boolean): Type | undefined; + function discriminateTypeByDiscriminableItems(target: UnionType, discriminators: [() => Type, __String][], related: (source: Type, target: Type) => boolean | Ternary, defaultValue: Type, skipPartial?: boolean, allowMultiples?: boolean): Type; + function discriminateTypeByDiscriminableItems( + target: UnionType, + discriminators: [() => Type, __String][], + related: (source: Type, target: Type) => boolean | Ternary, + defaultValue?: Type, + skipPartial?: boolean, + allowMultiples?: boolean) { // undefined=unknown, true=discriminated, false=not discriminated // The state of each type progresses from left to right. Discriminated types stop at 'true'. const discriminable = target.types.map(_ => undefined) as (boolean | undefined)[]; @@ -19015,13 +19021,16 @@ namespace ts { if (match === -1) { return defaultValue; } + // 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; + if (!allowMultiples) { + 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); } - nextMatch = discriminable.indexOf(/*searchElement*/ true, nextMatch + 1); } return target.types[match]; } @@ -41039,10 +41048,11 @@ namespace ts { if (sourcePropertiesFiltered) { return discriminateTypeByDiscriminableItems( target, - map(sourcePropertiesFiltered, p => ([() => getTypeOfSymbol(p), p.escapedName])), - isRelatedTo, /*defaultValue*/ undefined, - skipPartial - ); + map(sourcePropertiesFiltered, p => [() => getTypeOfSymbol(p), p.escapedName]), + isRelatedTo, + /*defaultValue*/ undefined, + skipPartial, + /**/ true); } } } From a3b8ad22ebd56deec43eb07b193ed832d0a146b7 Mon Sep 17 00:00:00 2001 From: Daniel Rosenwasser Date: Mon, 1 Feb 2021 18:01:08 -0800 Subject: [PATCH 3/3] Accepted baselines. --- .../contextualTypeShouldBeLiteral.errors.txt | 130 ++++++++++++++++++ ...yCheckWithMultipleDiscriminants.errors.txt | 7 +- .../excessPropertyCheckWithUnions.errors.txt | 45 +++--- .../objectLiteralNormalization.errors.txt | 5 +- 4 files changed, 163 insertions(+), 24 deletions(-) create mode 100644 tests/baselines/reference/contextualTypeShouldBeLiteral.errors.txt diff --git a/tests/baselines/reference/contextualTypeShouldBeLiteral.errors.txt b/tests/baselines/reference/contextualTypeShouldBeLiteral.errors.txt new file mode 100644 index 0000000000000..3418c3ada4a13 --- /dev/null +++ b/tests/baselines/reference/contextualTypeShouldBeLiteral.errors.txt @@ -0,0 +1,130 @@ +tests/cases/compiler/contextualTypeShouldBeLiteral.ts(40,5): error TS2345: Argument of type '{ type2: "y"; value: "done"; method(): void; }' is not assignable to parameter of type 'X2 | Y2'. + Object literal may only specify known properties, but 'type2' does not exist in type 'X2'. Did you mean to write 'type1'? + + +==== tests/cases/compiler/contextualTypeShouldBeLiteral.ts (1 errors) ==== + interface X { + type: 'x'; + value: string; + method(): void; + } + + interface Y { + type: 'y'; + value: 'none' | 'done'; + method(): void; + } + + function foo(bar: X | Y) { } + + foo({ + type: 'y', + value: 'done', + method() { + this; + this.type; + this.value; + } + }); + + interface X2 { + type1: 'x'; + value: string; + method(): void; + } + + interface Y2 { + type2: 'y'; + value: 'none' | 'done'; + method(): void; + } + + function foo2(bar: X2 | Y2) { } + + foo2({ + type2: 'y', + ~~~~~~~~~~ +!!! error TS2345: Argument of type '{ type2: "y"; value: "done"; method(): void; }' is not assignable to parameter of type 'X2 | Y2'. +!!! error TS2345: Object literal may only specify known properties, but 'type2' does not exist in type 'X2'. Did you mean to write 'type1'? + value: 'done', + method() { + this; + this.value; + } + }); + + interface X3 { + type: 'x'; + value: 1 | 2 | 3; + xtra: number; + } + + interface Y3 { + type: 'y'; + value: 11 | 12 | 13; + ytra: number; + } + + let xy: X3 | Y3 = { + type: 'y', + value: 11, + ytra: 12 + }; + + xy; + + + interface LikeA { + x: 'x'; + y: 'y'; + value: string; + method(): void; + } + + interface LikeB { + x: 'xx'; + y: 'yy'; + value: number; + method(): void; + } + + let xyz: LikeA | LikeB = { + x: 'x', + y: 'y', + value: "foo", + method() { + this; + this.x; + this.y; + this.value; + } + }; + + xyz; + + // Repro from #29168 + + interface TestObject { + type?: 'object'; + items: { + [k: string]: TestGeneric; + }; + } + + interface TestString { + type: 'string'; + } + + type TestGeneric = (TestString | TestObject) & { [k: string]: any; }; + + const test: TestGeneric = { + items: { + hello: { type: 'string' }, + world: { + items: { + nested: { type: 'string' } + } + } + } + }; + \ No newline at end of file diff --git a/tests/baselines/reference/excessPropertyCheckWithMultipleDiscriminants.errors.txt b/tests/baselines/reference/excessPropertyCheckWithMultipleDiscriminants.errors.txt index 29575580e8087..341f41d9f377d 100644 --- a/tests/baselines/reference/excessPropertyCheckWithMultipleDiscriminants.errors.txt +++ b/tests/baselines/reference/excessPropertyCheckWithMultipleDiscriminants.errors.txt @@ -2,11 +2,13 @@ tests/cases/compiler/excessPropertyCheckWithMultipleDiscriminants.ts(30,5): erro 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(50,5): error TS2322: Type '{ p1: "left"; p2: true; p3: number; p4: string; }' is not assignable to type 'DisjointDiscriminants'. + Object literal may only specify known properties, and 'p4' does not exist in type '{ p1: "left"; p2: true; p3: number; }'. 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) ==== +==== tests/cases/compiler/excessPropertyCheckWithMultipleDiscriminants.ts (4 errors) ==== // Repro from #32657 interface Base { @@ -63,6 +65,9 @@ tests/cases/compiler/excessPropertyCheckWithMultipleDiscriminants.ts(57,5): erro p2: true, p3: 42, p4: "hello" + ~~~~~~~~~~~ +!!! error TS2322: Type '{ p1: "left"; p2: true; p3: number; p4: string; }' is not assignable to type 'DisjointDiscriminants'. +!!! error TS2322: Object literal may only specify known properties, and 'p4' does not exist in type '{ p1: "left"; p2: true; p3: number; }'. }; // This has excess error because variant two is the only applicable case diff --git a/tests/baselines/reference/excessPropertyCheckWithUnions.errors.txt b/tests/baselines/reference/excessPropertyCheckWithUnions.errors.txt index 8c34a0f5d69cf..c4991fddad55a 100644 --- a/tests/baselines/reference/excessPropertyCheckWithUnions.errors.txt +++ b/tests/baselines/reference/excessPropertyCheckWithUnions.errors.txt @@ -4,18 +4,18 @@ tests/cases/compiler/excessPropertyCheckWithUnions.ts(11,21): error TS2322: Type Object literal may only specify known properties, and 'd20' does not exist in type '{ tag: "A"; a1: string; }'. tests/cases/compiler/excessPropertyCheckWithUnions.ts(12,1): error TS2322: Type '{ tag: "D"; }' is not assignable to type 'ADT'. Property 'd20' is missing in type '{ tag: "D"; }' but required in type '{ tag: "D"; d20: 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20; }'. +tests/cases/compiler/excessPropertyCheckWithUnions.ts(29,19): error TS2322: Type '{ tag: "A"; y: number; }' is not assignable to type 'Ambiguous'. + Object literal may only specify known properties, and 'y' does not exist in type '{ tag: "A"; x: string; }'. +tests/cases/compiler/excessPropertyCheckWithUnions.ts(30,28): error TS2322: Type '{ tag: "A"; x: string; y: number; }' is not assignable to type 'Ambiguous'. + Object literal may only specify known properties, and 'y' does not exist in type '{ tag: "A"; x: string; }'. 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'. + Object literal may only specify known properties, and 'extra' does not exist in type '{ tag: "A"; x: string; }'. +tests/cases/compiler/excessPropertyCheckWithUnions.ts(34,19): error TS2322: Type '{ tag: "A"; y: number; extra: number; }' is not assignable to type 'Ambiguous'. + Object literal may only specify known properties, and 'y' does not exist in type '{ tag: "A"; x: string; }'. 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: "B"; z: boolean; }'. - Types of property 'tag' are incompatible. - Type '"A"' is not assignable to type '"B"'. + Property 'x' is missing in type '{ tag: "A"; }' but required in type '{ tag: "A"; x: string; }'. +tests/cases/compiler/excessPropertyCheckWithUnions.ts(40,19): error TS2322: Type '{ tag: "A"; z: true; }' is not assignable to type 'Ambiguous'. + Object literal may only specify known properties, and 'z' does not exist in type '{ tag: "A"; x: string; }'. tests/cases/compiler/excessPropertyCheckWithUnions.ts(49,35): error TS2322: Type '{ a: 1; b: 1; first: string; second: string; }' is not assignable to type 'Overlapping'. Object literal may only specify known properties, and 'second' does not exist in type '{ a: 1; b: 1; first: string; }'. tests/cases/compiler/excessPropertyCheckWithUnions.ts(50,35): error TS2322: Type '{ a: 1; b: 1; first: string; third: string; }' is not assignable to type 'Overlapping'. @@ -31,7 +31,7 @@ tests/cases/compiler/excessPropertyCheckWithUnions.ts(113,67): error TS2322: Typ tests/cases/compiler/excessPropertyCheckWithUnions.ts(114,63): error TS2322: Type 'string' is not assignable to type 'number'. -==== tests/cases/compiler/excessPropertyCheckWithUnions.ts (14 errors) ==== +==== tests/cases/compiler/excessPropertyCheckWithUnions.ts (16 errors) ==== type ADT = { tag: "A", a1: string @@ -71,17 +71,23 @@ tests/cases/compiler/excessPropertyCheckWithUnions.ts(114,63): error TS2322: Typ // no error for ambiguous tag, even when it could satisfy both constituents at once amb = { tag: "A", x: "hi" } amb = { tag: "A", y: 12 } + ~~~~~ +!!! error TS2322: Type '{ tag: "A"; y: number; }' is not assignable to type 'Ambiguous'. +!!! error TS2322: Object literal may only specify known properties, and 'y' does not exist in type '{ tag: "A"; x: string; }'. amb = { tag: "A", x: "hi", y: 12 } + ~~~~~ +!!! error TS2322: Type '{ tag: "A"; x: string; y: number; }' is not assignable to type 'Ambiguous'. +!!! error TS2322: Object literal may only specify known properties, and 'y' does not exist in type '{ tag: "A"; x: string; }'. // 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'. +!!! error TS2322: Object literal may only specify known properties, and 'extra' does not exist in type '{ tag: "A"; x: string; }'. 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'. +!!! error TS2322: Object literal may only specify known properties, and 'y' does not exist in type '{ tag: "A"; x: string; }'. // assignability errors still work. // But note that the error for `z: true` is the fallback one of reporting on @@ -89,15 +95,12 @@ tests/cases/compiler/excessPropertyCheckWithUnions.ts(114,63): error TS2322: Typ 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"'. +!!! error TS2322: Property 'x' is missing in type '{ tag: "A"; }' but required in type '{ tag: "A"; x: string; }'. +!!! related TS2728 tests/cases/compiler/excessPropertyCheckWithUnions.ts:16:5: 'x' is declared here. 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: "B"; z: boolean; }'. -!!! error TS2322: Types of property 'tag' are incompatible. -!!! error TS2322: Type '"A"' is not assignable to type '"B"'. +!!! error TS2322: Object literal may only specify known properties, and 'z' does not exist in type '{ tag: "A"; x: string; }'. type Overlapping = | { a: 1, b: 1, first: string } diff --git a/tests/baselines/reference/objectLiteralNormalization.errors.txt b/tests/baselines/reference/objectLiteralNormalization.errors.txt index bd14787178ba9..ffa7ebf580fff 100644 --- a/tests/baselines/reference/objectLiteralNormalization.errors.txt +++ b/tests/baselines/reference/objectLiteralNormalization.errors.txt @@ -1,6 +1,6 @@ tests/cases/conformance/expressions/objectLiterals/objectLiteralNormalization.ts(7,14): error TS2322: Type 'number' is not assignable to type 'string | undefined'. tests/cases/conformance/expressions/objectLiterals/objectLiteralNormalization.ts(8,1): error TS2322: Type '{ b: string; }' is not assignable to type '{ a: number; b?: undefined; c?: undefined; } | { a: number; b: string; c?: undefined; } | { a: number; b: string; c: boolean; }'. - Type '{ b: string; }' is missing the following properties from type '{ a: number; b: string; c: boolean; }': a, c + Property 'a' is missing in type '{ b: string; }' but required in type '{ a: number; b: string; c?: undefined; }'. tests/cases/conformance/expressions/objectLiterals/objectLiteralNormalization.ts(9,1): error TS2322: Type '{ c: true; }' is not assignable to type '{ a: number; b?: undefined; c?: undefined; } | { a: number; b: string; c?: undefined; } | { a: number; b: string; c: boolean; }'. Type '{ c: true; }' is missing the following properties from type '{ a: number; b: string; c: boolean; }': a, b tests/cases/conformance/expressions/objectLiterals/objectLiteralNormalization.ts(17,1): error TS2322: Type '{ a: string; b: number; }' is not assignable to type '{ a: number; b: number; } | { a: string; b?: undefined; } | { a?: undefined; b?: undefined; }'. @@ -27,7 +27,8 @@ tests/cases/conformance/expressions/objectLiterals/objectLiteralNormalization.ts a1 = { b: "y" }; // Error ~~ !!! error TS2322: Type '{ b: string; }' is not assignable to type '{ a: number; b?: undefined; c?: undefined; } | { a: number; b: string; c?: undefined; } | { a: number; b: string; c: boolean; }'. -!!! error TS2322: Type '{ b: string; }' is missing the following properties from type '{ a: number; b: string; c: boolean; }': a, c +!!! error TS2322: Property 'a' is missing in type '{ b: string; }' but required in type '{ a: number; b: string; c?: undefined; }'. +!!! related TS2728 tests/cases/conformance/expressions/objectLiterals/objectLiteralNormalization.ts:2:23: 'a' is declared here. a1 = { c: true }; // Error ~~ !!! error TS2322: Type '{ c: true; }' is not assignable to type '{ a: number; b?: undefined; c?: undefined; } | { a: number; b: string; c?: undefined; } | { a: number; b: string; c: boolean; }'.