diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 52c3ff453b7..3b06389a744 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -10869,7 +10869,7 @@ namespace ts { const prop = getPropertyOfType(apparentType, right.text); if (!prop) { if (right.text && !checkAndReportErrorForExtendingInterface(node)) { - error(right, Diagnostics.Property_0_does_not_exist_on_type_1, declarationNameToString(right), typeToString(type.flags & TypeFlags.ThisType ? apparentType : type)); + reportNonexistentProperty(right, type.flags & TypeFlags.ThisType ? apparentType : type); } return unknownType; } @@ -10903,6 +10903,20 @@ namespace ts { return propType; } return getFlowTypeOfReference(node, propType, /*assumeInitialized*/ true, /*flowContainer*/ undefined); + + function reportNonexistentProperty(propNode: Identifier, containingType: Type) { + let errorInfo: DiagnosticMessageChain; + if (containingType.flags & TypeFlags.Union && !(containingType.flags & TypeFlags.Primitive)) { + for (const subtype of (containingType as UnionType).types) { + if (!getPropertyOfType(subtype, propNode.text)) { + errorInfo = chainDiagnosticMessages(errorInfo, Diagnostics.Property_0_does_not_exist_on_type_1, declarationNameToString(propNode), typeToString(subtype)); + break; + } + } + } + errorInfo = chainDiagnosticMessages(errorInfo, Diagnostics.Property_0_does_not_exist_on_type_1, declarationNameToString(propNode), typeToString(containingType)); + diagnostics.add(createDiagnosticForNodeFromMessageChain(propNode, errorInfo)); + } } function isValidPropertyAccess(node: PropertyAccessExpression | QualifiedName, propertyName: string): boolean { diff --git a/tests/baselines/reference/typeGuardsWithInstanceOfByConstructorSignature.errors.txt b/tests/baselines/reference/typeGuardsWithInstanceOfByConstructorSignature.errors.txt index 9c20377e7a6..31db51bf57e 100644 --- a/tests/baselines/reference/typeGuardsWithInstanceOfByConstructorSignature.errors.txt +++ b/tests/baselines/reference/typeGuardsWithInstanceOfByConstructorSignature.errors.txt @@ -5,14 +5,20 @@ tests/cases/conformance/expressions/typeGuards/typeGuardsWithInstanceOfByConstru tests/cases/conformance/expressions/typeGuards/typeGuardsWithInstanceOfByConstructorSignature.ts(41,10): error TS2339: Property 'bar' does not exist on type 'B'. tests/cases/conformance/expressions/typeGuards/typeGuardsWithInstanceOfByConstructorSignature.ts(66,10): error TS2339: Property 'bar2' does not exist on type 'C1'. tests/cases/conformance/expressions/typeGuards/typeGuardsWithInstanceOfByConstructorSignature.ts(72,10): error TS2339: Property 'bar1' does not exist on type 'C1 | C2'. + Property 'bar1' does not exist on type 'C2'. tests/cases/conformance/expressions/typeGuards/typeGuardsWithInstanceOfByConstructorSignature.ts(73,10): error TS2339: Property 'bar2' does not exist on type 'C1 | C2'. + Property 'bar2' does not exist on type 'C1'. tests/cases/conformance/expressions/typeGuards/typeGuardsWithInstanceOfByConstructorSignature.ts(85,10): error TS2339: Property 'bar' does not exist on type 'D'. tests/cases/conformance/expressions/typeGuards/typeGuardsWithInstanceOfByConstructorSignature.ts(91,10): error TS2339: Property 'bar' does not exist on type 'D'. tests/cases/conformance/expressions/typeGuards/typeGuardsWithInstanceOfByConstructorSignature.ts(112,10): error TS2339: Property 'bar2' does not exist on type 'E1'. tests/cases/conformance/expressions/typeGuards/typeGuardsWithInstanceOfByConstructorSignature.ts(118,11): error TS2339: Property 'bar1' does not exist on type 'E1 | E2'. + Property 'bar1' does not exist on type 'E2'. tests/cases/conformance/expressions/typeGuards/typeGuardsWithInstanceOfByConstructorSignature.ts(119,11): error TS2339: Property 'bar2' does not exist on type 'E1 | E2'. + Property 'bar2' does not exist on type 'E1'. tests/cases/conformance/expressions/typeGuards/typeGuardsWithInstanceOfByConstructorSignature.ts(134,11): error TS2339: Property 'foo' does not exist on type 'string | F'. + Property 'foo' does not exist on type 'string'. tests/cases/conformance/expressions/typeGuards/typeGuardsWithInstanceOfByConstructorSignature.ts(135,11): error TS2339: Property 'bar' does not exist on type 'string | F'. + Property 'bar' does not exist on type 'string'. tests/cases/conformance/expressions/typeGuards/typeGuardsWithInstanceOfByConstructorSignature.ts(160,11): error TS2339: Property 'foo2' does not exist on type 'G1'. tests/cases/conformance/expressions/typeGuards/typeGuardsWithInstanceOfByConstructorSignature.ts(166,11): error TS2339: Property 'foo2' does not exist on type 'G1'. tests/cases/conformance/expressions/typeGuards/typeGuardsWithInstanceOfByConstructorSignature.ts(182,11): error TS2339: Property 'bar' does not exist on type 'H'. @@ -107,9 +113,11 @@ tests/cases/conformance/expressions/typeGuards/typeGuardsWithInstanceOfByConstru obj6.bar1; ~~~~ !!! error TS2339: Property 'bar1' does not exist on type 'C1 | C2'. +!!! error TS2339: Property 'bar1' does not exist on type 'C2'. obj6.bar2; ~~~~ !!! error TS2339: Property 'bar2' does not exist on type 'C1 | C2'. +!!! error TS2339: Property 'bar2' does not exist on type 'C1'. } // with object type literal @@ -163,9 +171,11 @@ tests/cases/conformance/expressions/typeGuards/typeGuardsWithInstanceOfByConstru obj10.bar1; ~~~~ !!! error TS2339: Property 'bar1' does not exist on type 'E1 | E2'. +!!! error TS2339: Property 'bar1' does not exist on type 'E2'. obj10.bar2; ~~~~ !!! error TS2339: Property 'bar2' does not exist on type 'E1 | E2'. +!!! error TS2339: Property 'bar2' does not exist on type 'E1'. } // a construct signature that returns any @@ -183,9 +193,11 @@ tests/cases/conformance/expressions/typeGuards/typeGuardsWithInstanceOfByConstru obj11.foo; ~~~ !!! error TS2339: Property 'foo' does not exist on type 'string | F'. +!!! error TS2339: Property 'foo' does not exist on type 'string'. obj11.bar; ~~~ !!! error TS2339: Property 'bar' does not exist on type 'string | F'. +!!! error TS2339: Property 'bar' does not exist on type 'string'. } var obj12: any; diff --git a/tests/baselines/reference/unionPropertyExistence.errors.txt b/tests/baselines/reference/unionPropertyExistence.errors.txt new file mode 100644 index 00000000000..c13aa7d4d15 --- /dev/null +++ b/tests/baselines/reference/unionPropertyExistence.errors.txt @@ -0,0 +1,84 @@ +tests/cases/compiler/unionPropertyExistence.ts(27,3): error TS2339: Property 'nope' does not exist on type '"foo" | "bar"'. + Property 'nope' does not exist on type '"foo"'. +tests/cases/compiler/unionPropertyExistence.ts(28,6): error TS2339: Property 'onlyInB' does not exist on type 'B | "foo"'. + Property 'onlyInB' does not exist on type '"foo"'. +tests/cases/compiler/unionPropertyExistence.ts(30,6): error TS2339: Property 'length' does not exist on type 'B | "foo"'. + Property 'length' does not exist on type 'B'. +tests/cases/compiler/unionPropertyExistence.ts(32,4): error TS2339: Property 'onlyInB' does not exist on type 'AB'. + Property 'onlyInB' does not exist on type 'A'. +tests/cases/compiler/unionPropertyExistence.ts(35,5): error TS2339: Property 'notInC' does not exist on type 'ABC'. + Property 'notInC' does not exist on type 'C'. +tests/cases/compiler/unionPropertyExistence.ts(36,4): error TS2339: Property 'notInB' does not exist on type 'AB'. + Property 'notInB' does not exist on type 'B'. +tests/cases/compiler/unionPropertyExistence.ts(37,5): error TS2339: Property 'notInB' does not exist on type 'ABC'. + Property 'notInB' does not exist on type 'B'. +tests/cases/compiler/unionPropertyExistence.ts(40,5): error TS2339: Property 'inNone' does not exist on type 'ABC'. + Property 'inNone' does not exist on type 'A'. + + +==== tests/cases/compiler/unionPropertyExistence.ts (8 errors) ==== + interface A { + inAll: string; + notInB: string; + notInC: string; + } + + interface B { + inAll: boolean; + onlyInB: number; + notInC: string; + } + + interface C { + inAll: number; + notInB: string; + } + + type AB = A | B; + type ABC = C | AB; + + var ab: AB; + var abc: ABC; + + declare const x: "foo" | "bar"; + declare const bFoo: B | "foo"; + + x.nope(); + ~~~~ +!!! error TS2339: Property 'nope' does not exist on type '"foo" | "bar"'. +!!! error TS2339: Property 'nope' does not exist on type '"foo"'. + bFoo.onlyInB; + ~~~~~~~ +!!! error TS2339: Property 'onlyInB' does not exist on type 'B | "foo"'. +!!! error TS2339: Property 'onlyInB' does not exist on type '"foo"'. + x.length; // Ok + bFoo.length; + ~~~~~~ +!!! error TS2339: Property 'length' does not exist on type 'B | "foo"'. +!!! error TS2339: Property 'length' does not exist on type 'B'. + + ab.onlyInB; + ~~~~~~~ +!!! error TS2339: Property 'onlyInB' does not exist on type 'AB'. +!!! error TS2339: Property 'onlyInB' does not exist on type 'A'. + + ab.notInC; // Ok + abc.notInC; + ~~~~~~ +!!! error TS2339: Property 'notInC' does not exist on type 'ABC'. +!!! error TS2339: Property 'notInC' does not exist on type 'C'. + ab.notInB; + ~~~~~~ +!!! error TS2339: Property 'notInB' does not exist on type 'AB'. +!!! error TS2339: Property 'notInB' does not exist on type 'B'. + abc.notInB; + ~~~~~~ +!!! error TS2339: Property 'notInB' does not exist on type 'ABC'. +!!! error TS2339: Property 'notInB' does not exist on type 'B'. + + abc.inAll; // Ok + abc.inNone; + ~~~~~~ +!!! error TS2339: Property 'inNone' does not exist on type 'ABC'. +!!! error TS2339: Property 'inNone' does not exist on type 'A'. + \ No newline at end of file diff --git a/tests/baselines/reference/unionPropertyExistence.js b/tests/baselines/reference/unionPropertyExistence.js new file mode 100644 index 00000000000..80174a3d363 --- /dev/null +++ b/tests/baselines/reference/unionPropertyExistence.js @@ -0,0 +1,57 @@ +//// [unionPropertyExistence.ts] +interface A { + inAll: string; + notInB: string; + notInC: string; +} + +interface B { + inAll: boolean; + onlyInB: number; + notInC: string; +} + +interface C { + inAll: number; + notInB: string; +} + +type AB = A | B; +type ABC = C | AB; + +var ab: AB; +var abc: ABC; + +declare const x: "foo" | "bar"; +declare const bFoo: B | "foo"; + +x.nope(); +bFoo.onlyInB; +x.length; // Ok +bFoo.length; + +ab.onlyInB; + +ab.notInC; // Ok +abc.notInC; +ab.notInB; +abc.notInB; + +abc.inAll; // Ok +abc.inNone; + + +//// [unionPropertyExistence.js] +var ab; +var abc; +x.nope(); +bFoo.onlyInB; +x.length; // Ok +bFoo.length; +ab.onlyInB; +ab.notInC; // Ok +abc.notInC; +ab.notInB; +abc.notInB; +abc.inAll; // Ok +abc.inNone; diff --git a/tests/baselines/reference/unionTypeMembers.errors.txt b/tests/baselines/reference/unionTypeMembers.errors.txt index 21f1204ac6d..e4f73a51f77 100644 --- a/tests/baselines/reference/unionTypeMembers.errors.txt +++ b/tests/baselines/reference/unionTypeMembers.errors.txt @@ -1,8 +1,12 @@ tests/cases/conformance/types/union/unionTypeMembers.ts(44,1): error TS2349: Cannot invoke an expression whose type lacks a call signature. tests/cases/conformance/types/union/unionTypeMembers.ts(51,3): error TS2339: Property 'propertyOnlyInI1' does not exist on type 'I1 | I2'. + Property 'propertyOnlyInI1' does not exist on type 'I2'. tests/cases/conformance/types/union/unionTypeMembers.ts(52,3): error TS2339: Property 'propertyOnlyInI2' does not exist on type 'I1 | I2'. + Property 'propertyOnlyInI2' does not exist on type 'I1'. tests/cases/conformance/types/union/unionTypeMembers.ts(53,3): error TS2339: Property 'methodOnlyInI1' does not exist on type 'I1 | I2'. + Property 'methodOnlyInI1' does not exist on type 'I2'. tests/cases/conformance/types/union/unionTypeMembers.ts(54,3): error TS2339: Property 'methodOnlyInI2' does not exist on type 'I1 | I2'. + Property 'methodOnlyInI2' does not exist on type 'I1'. ==== tests/cases/conformance/types/union/unionTypeMembers.ts (5 errors) ==== @@ -61,12 +65,16 @@ tests/cases/conformance/types/union/unionTypeMembers.ts(54,3): error TS2339: Pro x.propertyOnlyInI1; // error ~~~~~~~~~~~~~~~~ !!! error TS2339: Property 'propertyOnlyInI1' does not exist on type 'I1 | I2'. +!!! error TS2339: Property 'propertyOnlyInI1' does not exist on type 'I2'. x.propertyOnlyInI2; // error ~~~~~~~~~~~~~~~~ !!! error TS2339: Property 'propertyOnlyInI2' does not exist on type 'I1 | I2'. +!!! error TS2339: Property 'propertyOnlyInI2' does not exist on type 'I1'. x.methodOnlyInI1("hello"); // error ~~~~~~~~~~~~~~ !!! error TS2339: Property 'methodOnlyInI1' does not exist on type 'I1 | I2'. +!!! error TS2339: Property 'methodOnlyInI1' does not exist on type 'I2'. x.methodOnlyInI2(10); // error ~~~~~~~~~~~~~~ -!!! error TS2339: Property 'methodOnlyInI2' does not exist on type 'I1 | I2'. \ No newline at end of file +!!! error TS2339: Property 'methodOnlyInI2' does not exist on type 'I1 | I2'. +!!! error TS2339: Property 'methodOnlyInI2' does not exist on type 'I1'. \ No newline at end of file diff --git a/tests/baselines/reference/unionTypeReadonly.errors.txt b/tests/baselines/reference/unionTypeReadonly.errors.txt index 0875b2b5af3..ec660fc3a81 100644 --- a/tests/baselines/reference/unionTypeReadonly.errors.txt +++ b/tests/baselines/reference/unionTypeReadonly.errors.txt @@ -3,6 +3,7 @@ tests/cases/conformance/types/union/unionTypeReadonly.ts(19,1): error TS2450: Le tests/cases/conformance/types/union/unionTypeReadonly.ts(21,1): error TS2450: Left-hand side of assignment expression cannot be a constant or a read-only property. tests/cases/conformance/types/union/unionTypeReadonly.ts(23,1): error TS2450: Left-hand side of assignment expression cannot be a constant or a read-only property. tests/cases/conformance/types/union/unionTypeReadonly.ts(25,15): error TS2339: Property 'value' does not exist on type 'Base | DifferentName'. + Property 'value' does not exist on type 'DifferentName'. ==== tests/cases/conformance/types/union/unionTypeReadonly.ts (5 errors) ==== @@ -41,5 +42,6 @@ tests/cases/conformance/types/union/unionTypeReadonly.ts(25,15): error TS2339: P differentName.value = 12; // error, property 'value' doesn't exist ~~~~~ !!! error TS2339: Property 'value' does not exist on type 'Base | DifferentName'. +!!! error TS2339: Property 'value' does not exist on type 'DifferentName'. \ No newline at end of file diff --git a/tests/cases/compiler/unionPropertyExistence.ts b/tests/cases/compiler/unionPropertyExistence.ts new file mode 100644 index 00000000000..65937088898 --- /dev/null +++ b/tests/cases/compiler/unionPropertyExistence.ts @@ -0,0 +1,40 @@ +interface A { + inAll: string; + notInB: string; + notInC: string; +} + +interface B { + inAll: boolean; + onlyInB: number; + notInC: string; +} + +interface C { + inAll: number; + notInB: string; +} + +type AB = A | B; +type ABC = C | AB; + +var ab: AB; +var abc: ABC; + +declare const x: "foo" | "bar"; +declare const bFoo: B | "foo"; + +x.nope(); +bFoo.onlyInB; +x.length; // Ok +bFoo.length; + +ab.onlyInB; + +ab.notInC; // Ok +abc.notInC; +ab.notInB; +abc.notInB; + +abc.inAll; // Ok +abc.inNone;