diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 469ecf80c84..36f9bbb654b 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -5351,17 +5351,39 @@ module ts { } // Target type is type of prototype property let prototypeProperty = getPropertyOfType(rightType, "prototype"); - if (!prototypeProperty) { - return type; + if (prototypeProperty) { + let targetType = getTypeOfSymbol(prototypeProperty); + if (targetType !== anyType) { + // Narrow to target type if it is a subtype of current type + if (isTypeSubtypeOf(targetType, type)) { + return targetType; + } + // If current type is a union type, remove all constituents that aren't subtypes of target type + if (type.flags & TypeFlags.Union) { + return getUnionType(filter((type).types, t => isTypeSubtypeOf(t, targetType))); + } + } } - let targetType = getTypeOfSymbol(prototypeProperty); - // Narrow to target type if it is a subtype of current type - if (isTypeSubtypeOf(targetType, type)) { - return targetType; + // Target type is type of constructor signiture + let constructorSignitures: Signature[]; + if (rightType.flags & TypeFlags.Interface) { + constructorSignitures = (rightType).declaredConstructSignatures; } - // If current type is a union type, remove all constituents that aren't subtypes of target type - if (type.flags & TypeFlags.Union) { - return getUnionType(filter((type).types, t => isTypeSubtypeOf(t, targetType))); + if (rightType.flags & TypeFlags.Anonymous) { + constructorSignitures = (rightType).constructSignatures; + } + if (constructorSignitures) { + let constructorType = getUnionType(map(constructorSignitures, constructorSignature => { + if (constructorSignature.typeParameters && constructorSignature.typeParameters.length !== 0) { + // TODO convert type parameters to any or empty object types. e.g. Sample -> Sample or Sample<{}>. + } + return constructorSignature.resolvedReturnType; + })); + // Pickup type from union types + if (type.flags & TypeFlags.Union) { + return getUnionType(filter((type).types, t => isTypeSubtypeOf(t, constructorType))); + } + return constructorType; } return type; } diff --git a/tests/baselines/reference/typeGuardsWithInstanceOfByConstructorSignature.errors.txt b/tests/baselines/reference/typeGuardsWithInstanceOfByConstructorSignature.errors.txt new file mode 100644 index 00000000000..c7146541705 --- /dev/null +++ b/tests/baselines/reference/typeGuardsWithInstanceOfByConstructorSignature.errors.txt @@ -0,0 +1,111 @@ +tests/cases/conformance/expressions/typeGuards/typeGuardsWithInstanceOfByConstructorSignature.ts(12,10): error TS2339: Property 'bar' does not exist on type 'A'. +tests/cases/conformance/expressions/typeGuards/typeGuardsWithInstanceOfByConstructorSignature.ts(32,10): error TS2339: Property 'foo' does not exist on type '{}'. +tests/cases/conformance/expressions/typeGuards/typeGuardsWithInstanceOfByConstructorSignature.ts(33,10): error TS2339: Property 'foo' does not exist on type '{}'. +tests/cases/conformance/expressions/typeGuards/typeGuardsWithInstanceOfByConstructorSignature.ts(34,10): error TS2339: Property 'bar' does not exist on type '{}'. +tests/cases/conformance/expressions/typeGuards/typeGuardsWithInstanceOfByConstructorSignature.ts(63,10): error TS2339: Property 'bar2' does not exist on type 'C1'. +tests/cases/conformance/expressions/typeGuards/typeGuardsWithInstanceOfByConstructorSignature.ts(82,10): error TS2339: Property 'bar' does not exist on type 'D'. + + +==== tests/cases/conformance/expressions/typeGuards/typeGuardsWithInstanceOfByConstructorSignature.ts (6 errors) ==== + interface AConstructor { + new (): A; + } + interface A { + foo: string; + } + declare var A: AConstructor; + + var obj1: A | string; + if (obj1 instanceof A) { // narrowed to A. + obj1.foo; + obj1.bar; + ~~~ +!!! error TS2339: Property 'bar' does not exist on type 'A'. + } + + var obj2: any; + if (obj2 instanceof A) { // can't type narrowing from any. + obj2.foo; + obj2.bar; + } + + // with generics + interface BConstructor { + new (): B; + } + interface B { + foo: T; + } + declare var B: BConstructor; + + var obj3: B | A; + if (obj3 instanceof B) { // narrowed to B. + obj3.foo = "str"; + ~~~ +!!! error TS2339: Property 'foo' does not exist on type '{}'. + obj3.foo = 1; + ~~~ +!!! error TS2339: Property 'foo' does not exist on type '{}'. + obj3.bar = "str"; + ~~~ +!!! error TS2339: Property 'bar' does not exist on type '{}'. + } + + var obj4: any; + if (obj4 instanceof B) { // can't type narrowing from any. + obj4.foo = "str"; + obj4.foo = 1; + obj4.bar = "str"; + } + + // has multiple constructor signature + interface CConstructor { + new (value: string): C1; + new (value: number): C2; + } + interface C1 { + foo: string; + bar1: number; + } + interface C2 { + foo: string; + bar2: number; + } + declare var C: CConstructor; + + var obj5: C1 | A; + if (obj5 instanceof C) { // narrowed to C1. + obj5.foo; + obj5.bar1; + obj5.bar2; + ~~~~ +!!! error TS2339: Property 'bar2' does not exist on type 'C1'. + } + + var obj6: any; + if (obj6 instanceof C) { // can't type narrowing from any. + obj6.foo; + obj6.bar1; + obj6.bar2; + } + + // with object type literal + interface D { + foo: string; + } + declare var D: { new (): D; }; + + var obj7: D | string; + if (obj7 instanceof D) { // narrowed to D. + obj7.foo; + obj7.bar; + ~~~ +!!! error TS2339: Property 'bar' does not exist on type 'D'. + } + + var obj8: any; + if (obj8 instanceof D) { // can't type narrowing from any. + obj8.foo; + obj8.bar; + } + \ No newline at end of file diff --git a/tests/baselines/reference/typeGuardsWithInstanceOfByConstructorSignature.js b/tests/baselines/reference/typeGuardsWithInstanceOfByConstructorSignature.js new file mode 100644 index 00000000000..c795126b0ff --- /dev/null +++ b/tests/baselines/reference/typeGuardsWithInstanceOfByConstructorSignature.js @@ -0,0 +1,137 @@ +//// [typeGuardsWithInstanceOfByConstructorSignature.ts] +interface AConstructor { + new (): A; +} +interface A { + foo: string; +} +declare var A: AConstructor; + +var obj1: A | string; +if (obj1 instanceof A) { // narrowed to A. + obj1.foo; + obj1.bar; +} + +var obj2: any; +if (obj2 instanceof A) { // can't type narrowing from any. + obj2.foo; + obj2.bar; +} + +// with generics +interface BConstructor { + new (): B; +} +interface B { + foo: T; +} +declare var B: BConstructor; + +var obj3: B | A; +if (obj3 instanceof B) { // narrowed to B. + obj3.foo = "str"; + obj3.foo = 1; + obj3.bar = "str"; +} + +var obj4: any; +if (obj4 instanceof B) { // can't type narrowing from any. + obj4.foo = "str"; + obj4.foo = 1; + obj4.bar = "str"; +} + +// has multiple constructor signature +interface CConstructor { + new (value: string): C1; + new (value: number): C2; +} +interface C1 { + foo: string; + bar1: number; +} +interface C2 { + foo: string; + bar2: number; +} +declare var C: CConstructor; + +var obj5: C1 | A; +if (obj5 instanceof C) { // narrowed to C1. + obj5.foo; + obj5.bar1; + obj5.bar2; +} + +var obj6: any; +if (obj6 instanceof C) { // can't type narrowing from any. + obj6.foo; + obj6.bar1; + obj6.bar2; +} + +// with object type literal +interface D { + foo: string; +} +declare var D: { new (): D; }; + +var obj7: D | string; +if (obj7 instanceof D) { // narrowed to D. + obj7.foo; + obj7.bar; +} + +var obj8: any; +if (obj8 instanceof D) { // can't type narrowing from any. + obj8.foo; + obj8.bar; +} + + +//// [typeGuardsWithInstanceOfByConstructorSignature.js] +var obj1; +if (obj1 instanceof A) { + obj1.foo; + obj1.bar; +} +var obj2; +if (obj2 instanceof A) { + obj2.foo; + obj2.bar; +} +var obj3; +if (obj3 instanceof B) { + obj3.foo = "str"; + obj3.foo = 1; + obj3.bar = "str"; +} +var obj4; +if (obj4 instanceof B) { + obj4.foo = "str"; + obj4.foo = 1; + obj4.bar = "str"; +} +var obj5; +if (obj5 instanceof C) { + obj5.foo; + obj5.bar1; + obj5.bar2; +} +var obj6; +if (obj6 instanceof C) { + obj6.foo; + obj6.bar1; + obj6.bar2; +} +var obj7; +if (obj7 instanceof D) { + obj7.foo; + obj7.bar; +} +var obj8; +if (obj8 instanceof D) { + obj8.foo; + obj8.bar; +} diff --git a/tests/cases/conformance/expressions/typeGuards/typeGuardsWithInstanceOfByConstructorSignature.ts b/tests/cases/conformance/expressions/typeGuards/typeGuardsWithInstanceOfByConstructorSignature.ts new file mode 100644 index 00000000000..b28d92e2c8e --- /dev/null +++ b/tests/cases/conformance/expressions/typeGuards/typeGuardsWithInstanceOfByConstructorSignature.ts @@ -0,0 +1,89 @@ +interface AConstructor { + new (): A; +} +interface A { + foo: string; +} +declare var A: AConstructor; + +var obj1: A | string; +if (obj1 instanceof A) { // narrowed to A. + obj1.foo; + obj1.bar; +} + +var obj2: any; +if (obj2 instanceof A) { // can't type narrowing from any. + obj2.foo; + obj2.bar; +} + +// with generics +interface BConstructor { + new (): B; +} +interface B { + foo: T; +} +declare var B: BConstructor; + +var obj3: B | A; +if (obj3 instanceof B) { // narrowed to B. + obj3.foo = "str"; + obj3.foo = 1; + obj3.bar = "str"; +} + +var obj4: any; +if (obj4 instanceof B) { // can't type narrowing from any. + obj4.foo = "str"; + obj4.foo = 1; + obj4.bar = "str"; +} + +// has multiple constructor signature +interface CConstructor { + new (value: string): C1; + new (value: number): C2; +} +interface C1 { + foo: string; + bar1: number; +} +interface C2 { + foo: string; + bar2: number; +} +declare var C: CConstructor; + +var obj5: C1 | A; +if (obj5 instanceof C) { // narrowed to C1. + obj5.foo; + obj5.bar1; + obj5.bar2; +} + +var obj6: any; +if (obj6 instanceof C) { // can't type narrowing from any. + obj6.foo; + obj6.bar1; + obj6.bar2; +} + +// with object type literal +interface D { + foo: string; +} +declare var D: { new (): D; }; + +var obj7: D | string; +if (obj7 instanceof D) { // narrowed to D. + obj7.foo; + obj7.bar; +} + +var obj8: any; +if (obj8 instanceof D) { // can't type narrowing from any. + obj8.foo; + obj8.bar; +}