From 6501f850b76676286c62514f2a67ffadaf0cd817 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E6=96=87=E7=92=90?= Date: Mon, 23 Apr 2018 16:37:21 +0800 Subject: [PATCH 1/4] add type guard of constructor --- src/compiler/checker.ts | 32 ++- src/compiler/utilities.ts | 5 + .../reference/typeGuardConstructor.errors.txt | 91 ++++++++ .../reference/typeGuardConstructor.js | 154 +++++++++++++ .../reference/typeGuardConstructor.symbols | 184 ++++++++++++++++ .../reference/typeGuardConstructor.types | 204 ++++++++++++++++++ .../typeGuards/typeGuardConstructor.ts | 67 ++++++ 7 files changed, 735 insertions(+), 2 deletions(-) create mode 100644 tests/baselines/reference/typeGuardConstructor.errors.txt create mode 100644 tests/baselines/reference/typeGuardConstructor.js create mode 100644 tests/baselines/reference/typeGuardConstructor.symbols create mode 100644 tests/baselines/reference/typeGuardConstructor.types create mode 100644 tests/cases/conformance/expressions/typeGuards/typeGuardConstructor.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 99b3420131f79..b312aad78f8be 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -13474,6 +13474,18 @@ namespace ts { if (containsMatchingReferenceDiscriminant(reference, left) || containsMatchingReferenceDiscriminant(reference, right)) { return declaredType; } + if (isPropertyAccessExpression(left) && idText(left.name) === "constructor") { + return narrowTypeByConstructor(type, expr.right, operator, assumeTrue); + } + if (isPropertyAccessExpression(right) && idText(right.name) === "constructor") { + return narrowTypeByConstructor(type, expr.left, operator, assumeTrue); + } + if (isElementAccessExpression(left) && isStringLiteralLike(left.argumentExpression) && left.argumentExpression.text === "constructor") { + return narrowTypeByConstructor(type, expr.right, operator, assumeTrue); + } + if (isElementAccessExpression(right) && isStringLiteralLike(right.argumentExpression) && right.argumentExpression.text === "constructor") { + return narrowTypeByConstructor(type, expr.left, operator, assumeTrue); + } break; case SyntaxKind.InstanceOfKeyword: return narrowTypeByInstanceof(type, expr, assumeTrue); @@ -13493,7 +13505,7 @@ namespace ts { if (type.flags & TypeFlags.Any) { return type; } - if (operator === SyntaxKind.ExclamationEqualsToken || operator === SyntaxKind.ExclamationEqualsEqualsToken) { + if (isExclamationEqualsOrExclamationEqualsEqualsKind(operator)) { assumeTrue = !assumeTrue; } const valueType = getTypeOfExpression(value); @@ -13523,6 +13535,22 @@ namespace ts { return type; } + function narrowTypeByConstructor(type: Type, expr: Expression, operator: SyntaxKind, assumeTrue: boolean): Type { + if (!assumeTrue || isExclamationEqualsOrExclamationEqualsEqualsKind(operator)) return type; + + const rightType = getTypeOfExpression(expr); + if (!isTypeSubtypeOf(rightType, globalFunctionType)) return type; + + const prototypeProperty = getPropertyOfType(rightType, "prototype" as __String); + if (!prototypeProperty) return type; + + const prototypePropertyType = getTypeOfSymbol(prototypeProperty); + const targetType = !isTypeAny(prototypePropertyType) ? prototypePropertyType : undefined; + if (!targetType || isTypeAny(type) && (targetType === globalObjectType || targetType === globalFunctionType)) return type; + + return getNarrowedType(type, targetType, assumeTrue, isTypeDerivedFrom); + } + function narrowTypeByTypeof(type: Type, typeOfExpr: TypeOfExpression, operator: SyntaxKind, literal: LiteralExpression, assumeTrue: boolean): Type { // We have '==', '!=', '====', or !==' operator with 'typeof xxx' and string literal operands const target = getReferenceCandidate(typeOfExpr.expression); @@ -13534,7 +13562,7 @@ namespace ts { } return type; } - if (operator === SyntaxKind.ExclamationEqualsToken || operator === SyntaxKind.ExclamationEqualsEqualsToken) { + if (isExclamationEqualsOrExclamationEqualsEqualsKind(operator)) { assumeTrue = !assumeTrue; } if (assumeTrue && !(type.flags & TypeFlags.Union)) { diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 5c94745f339a3..beea33c714798 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -6335,4 +6335,9 @@ namespace ts { export function isNamedImportsOrExports(node: Node): node is NamedImportsOrExports { return node.kind === SyntaxKind.NamedImports || node.kind === SyntaxKind.NamedExports; } + + /** @internal */ + export function isExclamationEqualsOrExclamationEqualsEqualsKind(kind: SyntaxKind): boolean { + return kind === SyntaxKind.ExclamationEqualsToken || kind === SyntaxKind.ExclamationEqualsEqualsToken; + } } diff --git a/tests/baselines/reference/typeGuardConstructor.errors.txt b/tests/baselines/reference/typeGuardConstructor.errors.txt new file mode 100644 index 0000000000000..5b9a7c055321a --- /dev/null +++ b/tests/baselines/reference/typeGuardConstructor.errors.txt @@ -0,0 +1,91 @@ +tests/cases/conformance/expressions/typeGuards/typeGuardConstructor.ts(45,11): error TS2339: Property 'p2' does not exist on type 'C1 | C2'. + Property 'p2' does not exist on type 'C1'. +tests/cases/conformance/expressions/typeGuards/typeGuardConstructor.ts(49,11): error TS2339: Property 'p2' does not exist on type 'C1 | C2'. + Property 'p2' does not exist on type 'C1'. +tests/cases/conformance/expressions/typeGuards/typeGuardConstructor.ts(52,11): error TS2339: Property 'p1' does not exist on type 'C1 | C2'. + Property 'p1' does not exist on type 'C2'. +tests/cases/conformance/expressions/typeGuards/typeGuardConstructor.ts(63,11): error TS2339: Property 'p4' does not exist on type 'C1 | C2 | C3'. + Property 'p4' does not exist on type 'C1'. + + +==== tests/cases/conformance/expressions/typeGuards/typeGuardConstructor.ts (4 errors) ==== + class C1 { + p1: string; + } + class C2 { + p2: number; + } + class D1 extends C1 { + p3: number; + } + class C3 { + p4: number; + } + class D2 extends D1 { + p5: number + } + + var a: C1; + if (a.constructor === D1) { + a.p3; + } + if (a.constructor == D1) { + a.p3; + } + if (D1 === a.constructor) { + a.p3; + } + if (a["constructor"] === D1) { + a.p3; + } + if (D1 === a["constructor"]) { + a.p3; + } + + var b: C1; + if (b.constructor === D2) { + b.p3; + b.p5; + } + + var ctor3: C1 | C2; + if (ctor3.constructor === C1) { + ctor3.p1; // C1 + } + else { + ctor3.p2; // C2 + ~~ +!!! error TS2339: Property 'p2' does not exist on type 'C1 | C2'. +!!! error TS2339: Property 'p2' does not exist on type 'C1'. + } + + if (ctor3.constructor !== C1) { + ctor3.p2; // C1 + ~~ +!!! error TS2339: Property 'p2' does not exist on type 'C1 | C2'. +!!! error TS2339: Property 'p2' does not exist on type 'C1'. + } + else { + ctor3.p1; // C2 + ~~ +!!! error TS2339: Property 'p1' does not exist on type 'C1 | C2'. +!!! error TS2339: Property 'p1' does not exist on type 'C2'. + } + + var ctor4: C1 | C2 | C3; + if (ctor4.constructor === C1) { + ctor4.p1; // C1 + } + else if (ctor4.constructor === C2) { + ctor4.p2; // C2 + } + else { + ctor4.p4; // C3 + ~~ +!!! error TS2339: Property 'p4' does not exist on type 'C1 | C2 | C3'. +!!! error TS2339: Property 'p4' does not exist on type 'C1'. + } + + + + \ No newline at end of file diff --git a/tests/baselines/reference/typeGuardConstructor.js b/tests/baselines/reference/typeGuardConstructor.js new file mode 100644 index 0000000000000..32426765a188d --- /dev/null +++ b/tests/baselines/reference/typeGuardConstructor.js @@ -0,0 +1,154 @@ +//// [typeGuardConstructor.ts] +class C1 { + p1: string; +} +class C2 { + p2: number; +} +class D1 extends C1 { + p3: number; +} +class C3 { + p4: number; +} +class D2 extends D1 { + p5: number +} + +var a: C1; +if (a.constructor === D1) { + a.p3; +} +if (a.constructor == D1) { + a.p3; +} +if (D1 === a.constructor) { + a.p3; +} +if (a["constructor"] === D1) { + a.p3; +} +if (D1 === a["constructor"]) { + a.p3; +} + +var b: C1; +if (b.constructor === D2) { + b.p3; + b.p5; +} + +var ctor3: C1 | C2; +if (ctor3.constructor === C1) { + ctor3.p1; // C1 +} +else { + ctor3.p2; // C2 +} + +if (ctor3.constructor !== C1) { + ctor3.p2; // C1 +} +else { + ctor3.p1; // C2 +} + +var ctor4: C1 | C2 | C3; +if (ctor4.constructor === C1) { + ctor4.p1; // C1 +} +else if (ctor4.constructor === C2) { + ctor4.p2; // C2 +} +else { + ctor4.p4; // C3 +} + + + + + +//// [typeGuardConstructor.js] +var __extends = (this && this.__extends) || (function () { + var extendStatics = Object.setPrototypeOf || + ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || + function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; + return function (d, b) { + extendStatics(d, b); + function __() { this.constructor = d; } + d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); + }; +})(); +var C1 = /** @class */ (function () { + function C1() { + } + return C1; +}()); +var C2 = /** @class */ (function () { + function C2() { + } + return C2; +}()); +var D1 = /** @class */ (function (_super) { + __extends(D1, _super); + function D1() { + return _super !== null && _super.apply(this, arguments) || this; + } + return D1; +}(C1)); +var C3 = /** @class */ (function () { + function C3() { + } + return C3; +}()); +var D2 = /** @class */ (function (_super) { + __extends(D2, _super); + function D2() { + return _super !== null && _super.apply(this, arguments) || this; + } + return D2; +}(D1)); +var a; +if (a.constructor === D1) { + a.p3; +} +if (a.constructor == D1) { + a.p3; +} +if (D1 === a.constructor) { + a.p3; +} +if (a["constructor"] === D1) { + a.p3; +} +if (D1 === a["constructor"]) { + a.p3; +} +var b; +if (b.constructor === D2) { + b.p3; + b.p5; +} +var ctor3; +if (ctor3.constructor === C1) { + ctor3.p1; // C1 +} +else { + ctor3.p2; // C2 +} +if (ctor3.constructor !== C1) { + ctor3.p2; // C1 +} +else { + ctor3.p1; // C2 +} +var ctor4; +if (ctor4.constructor === C1) { + ctor4.p1; // C1 +} +else if (ctor4.constructor === C2) { + ctor4.p2; // C2 +} +else { + ctor4.p4; // C3 +} diff --git a/tests/baselines/reference/typeGuardConstructor.symbols b/tests/baselines/reference/typeGuardConstructor.symbols new file mode 100644 index 0000000000000..881b01230e39a --- /dev/null +++ b/tests/baselines/reference/typeGuardConstructor.symbols @@ -0,0 +1,184 @@ +=== tests/cases/conformance/expressions/typeGuards/typeGuardConstructor.ts === +class C1 { +>C1 : Symbol(C1, Decl(typeGuardConstructor.ts, 0, 0)) + + p1: string; +>p1 : Symbol(C1.p1, Decl(typeGuardConstructor.ts, 0, 10)) +} +class C2 { +>C2 : Symbol(C2, Decl(typeGuardConstructor.ts, 2, 1)) + + p2: number; +>p2 : Symbol(C2.p2, Decl(typeGuardConstructor.ts, 3, 10)) +} +class D1 extends C1 { +>D1 : Symbol(D1, Decl(typeGuardConstructor.ts, 5, 1)) +>C1 : Symbol(C1, Decl(typeGuardConstructor.ts, 0, 0)) + + p3: number; +>p3 : Symbol(D1.p3, Decl(typeGuardConstructor.ts, 6, 21)) +} +class C3 { +>C3 : Symbol(C3, Decl(typeGuardConstructor.ts, 8, 1)) + + p4: number; +>p4 : Symbol(C3.p4, Decl(typeGuardConstructor.ts, 9, 10)) +} +class D2 extends D1 { +>D2 : Symbol(D2, Decl(typeGuardConstructor.ts, 11, 1)) +>D1 : Symbol(D1, Decl(typeGuardConstructor.ts, 5, 1)) + + p5: number +>p5 : Symbol(D2.p5, Decl(typeGuardConstructor.ts, 12, 21)) +} + +var a: C1; +>a : Symbol(a, Decl(typeGuardConstructor.ts, 16, 3)) +>C1 : Symbol(C1, Decl(typeGuardConstructor.ts, 0, 0)) + +if (a.constructor === D1) { +>a.constructor : Symbol(Object.constructor, Decl(lib.d.ts, --, --)) +>a : Symbol(a, Decl(typeGuardConstructor.ts, 16, 3)) +>constructor : Symbol(Object.constructor, Decl(lib.d.ts, --, --)) +>D1 : Symbol(D1, Decl(typeGuardConstructor.ts, 5, 1)) + + a.p3; +>a.p3 : Symbol(D1.p3, Decl(typeGuardConstructor.ts, 6, 21)) +>a : Symbol(a, Decl(typeGuardConstructor.ts, 16, 3)) +>p3 : Symbol(D1.p3, Decl(typeGuardConstructor.ts, 6, 21)) +} +if (a.constructor == D1) { +>a.constructor : Symbol(Object.constructor, Decl(lib.d.ts, --, --)) +>a : Symbol(a, Decl(typeGuardConstructor.ts, 16, 3)) +>constructor : Symbol(Object.constructor, Decl(lib.d.ts, --, --)) +>D1 : Symbol(D1, Decl(typeGuardConstructor.ts, 5, 1)) + + a.p3; +>a.p3 : Symbol(D1.p3, Decl(typeGuardConstructor.ts, 6, 21)) +>a : Symbol(a, Decl(typeGuardConstructor.ts, 16, 3)) +>p3 : Symbol(D1.p3, Decl(typeGuardConstructor.ts, 6, 21)) +} +if (D1 === a.constructor) { +>D1 : Symbol(D1, Decl(typeGuardConstructor.ts, 5, 1)) +>a.constructor : Symbol(Object.constructor, Decl(lib.d.ts, --, --)) +>a : Symbol(a, Decl(typeGuardConstructor.ts, 16, 3)) +>constructor : Symbol(Object.constructor, Decl(lib.d.ts, --, --)) + + a.p3; +>a.p3 : Symbol(D1.p3, Decl(typeGuardConstructor.ts, 6, 21)) +>a : Symbol(a, Decl(typeGuardConstructor.ts, 16, 3)) +>p3 : Symbol(D1.p3, Decl(typeGuardConstructor.ts, 6, 21)) +} +if (a["constructor"] === D1) { +>a : Symbol(a, Decl(typeGuardConstructor.ts, 16, 3)) +>"constructor" : Symbol(Object.constructor, Decl(lib.d.ts, --, --)) +>D1 : Symbol(D1, Decl(typeGuardConstructor.ts, 5, 1)) + + a.p3; +>a.p3 : Symbol(D1.p3, Decl(typeGuardConstructor.ts, 6, 21)) +>a : Symbol(a, Decl(typeGuardConstructor.ts, 16, 3)) +>p3 : Symbol(D1.p3, Decl(typeGuardConstructor.ts, 6, 21)) +} +if (D1 === a["constructor"]) { +>D1 : Symbol(D1, Decl(typeGuardConstructor.ts, 5, 1)) +>a : Symbol(a, Decl(typeGuardConstructor.ts, 16, 3)) +>"constructor" : Symbol(Object.constructor, Decl(lib.d.ts, --, --)) + + a.p3; +>a.p3 : Symbol(D1.p3, Decl(typeGuardConstructor.ts, 6, 21)) +>a : Symbol(a, Decl(typeGuardConstructor.ts, 16, 3)) +>p3 : Symbol(D1.p3, Decl(typeGuardConstructor.ts, 6, 21)) +} + +var b: C1; +>b : Symbol(b, Decl(typeGuardConstructor.ts, 33, 3)) +>C1 : Symbol(C1, Decl(typeGuardConstructor.ts, 0, 0)) + +if (b.constructor === D2) { +>b.constructor : Symbol(Object.constructor, Decl(lib.d.ts, --, --)) +>b : Symbol(b, Decl(typeGuardConstructor.ts, 33, 3)) +>constructor : Symbol(Object.constructor, Decl(lib.d.ts, --, --)) +>D2 : Symbol(D2, Decl(typeGuardConstructor.ts, 11, 1)) + + b.p3; +>b.p3 : Symbol(D1.p3, Decl(typeGuardConstructor.ts, 6, 21)) +>b : Symbol(b, Decl(typeGuardConstructor.ts, 33, 3)) +>p3 : Symbol(D1.p3, Decl(typeGuardConstructor.ts, 6, 21)) + + b.p5; +>b.p5 : Symbol(D2.p5, Decl(typeGuardConstructor.ts, 12, 21)) +>b : Symbol(b, Decl(typeGuardConstructor.ts, 33, 3)) +>p5 : Symbol(D2.p5, Decl(typeGuardConstructor.ts, 12, 21)) +} + +var ctor3: C1 | C2; +>ctor3 : Symbol(ctor3, Decl(typeGuardConstructor.ts, 39, 3)) +>C1 : Symbol(C1, Decl(typeGuardConstructor.ts, 0, 0)) +>C2 : Symbol(C2, Decl(typeGuardConstructor.ts, 2, 1)) + +if (ctor3.constructor === C1) { +>ctor3.constructor : Symbol(Object.constructor, Decl(lib.d.ts, --, --)) +>ctor3 : Symbol(ctor3, Decl(typeGuardConstructor.ts, 39, 3)) +>constructor : Symbol(Object.constructor, Decl(lib.d.ts, --, --)) +>C1 : Symbol(C1, Decl(typeGuardConstructor.ts, 0, 0)) + + ctor3.p1; // C1 +>ctor3.p1 : Symbol(C1.p1, Decl(typeGuardConstructor.ts, 0, 10)) +>ctor3 : Symbol(ctor3, Decl(typeGuardConstructor.ts, 39, 3)) +>p1 : Symbol(C1.p1, Decl(typeGuardConstructor.ts, 0, 10)) +} +else { + ctor3.p2; // C2 +>ctor3 : Symbol(ctor3, Decl(typeGuardConstructor.ts, 39, 3)) +} + +if (ctor3.constructor !== C1) { +>ctor3.constructor : Symbol(Object.constructor, Decl(lib.d.ts, --, --)) +>ctor3 : Symbol(ctor3, Decl(typeGuardConstructor.ts, 39, 3)) +>constructor : Symbol(Object.constructor, Decl(lib.d.ts, --, --)) +>C1 : Symbol(C1, Decl(typeGuardConstructor.ts, 0, 0)) + + ctor3.p2; // C1 +>ctor3 : Symbol(ctor3, Decl(typeGuardConstructor.ts, 39, 3)) +} +else { + ctor3.p1; // C2 +>ctor3 : Symbol(ctor3, Decl(typeGuardConstructor.ts, 39, 3)) +} + +var ctor4: C1 | C2 | C3; +>ctor4 : Symbol(ctor4, Decl(typeGuardConstructor.ts, 54, 3)) +>C1 : Symbol(C1, Decl(typeGuardConstructor.ts, 0, 0)) +>C2 : Symbol(C2, Decl(typeGuardConstructor.ts, 2, 1)) +>C3 : Symbol(C3, Decl(typeGuardConstructor.ts, 8, 1)) + +if (ctor4.constructor === C1) { +>ctor4.constructor : Symbol(Object.constructor, Decl(lib.d.ts, --, --)) +>ctor4 : Symbol(ctor4, Decl(typeGuardConstructor.ts, 54, 3)) +>constructor : Symbol(Object.constructor, Decl(lib.d.ts, --, --)) +>C1 : Symbol(C1, Decl(typeGuardConstructor.ts, 0, 0)) + + ctor4.p1; // C1 +>ctor4.p1 : Symbol(C1.p1, Decl(typeGuardConstructor.ts, 0, 10)) +>ctor4 : Symbol(ctor4, Decl(typeGuardConstructor.ts, 54, 3)) +>p1 : Symbol(C1.p1, Decl(typeGuardConstructor.ts, 0, 10)) +} +else if (ctor4.constructor === C2) { +>ctor4.constructor : Symbol(Object.constructor, Decl(lib.d.ts, --, --)) +>ctor4 : Symbol(ctor4, Decl(typeGuardConstructor.ts, 54, 3)) +>constructor : Symbol(Object.constructor, Decl(lib.d.ts, --, --)) +>C2 : Symbol(C2, Decl(typeGuardConstructor.ts, 2, 1)) + + ctor4.p2; // C2 +>ctor4.p2 : Symbol(C2.p2, Decl(typeGuardConstructor.ts, 3, 10)) +>ctor4 : Symbol(ctor4, Decl(typeGuardConstructor.ts, 54, 3)) +>p2 : Symbol(C2.p2, Decl(typeGuardConstructor.ts, 3, 10)) +} +else { + ctor4.p4; // C3 +>ctor4 : Symbol(ctor4, Decl(typeGuardConstructor.ts, 54, 3)) +} + + + + diff --git a/tests/baselines/reference/typeGuardConstructor.types b/tests/baselines/reference/typeGuardConstructor.types new file mode 100644 index 0000000000000..d714b423efe25 --- /dev/null +++ b/tests/baselines/reference/typeGuardConstructor.types @@ -0,0 +1,204 @@ +=== tests/cases/conformance/expressions/typeGuards/typeGuardConstructor.ts === +class C1 { +>C1 : C1 + + p1: string; +>p1 : string +} +class C2 { +>C2 : C2 + + p2: number; +>p2 : number +} +class D1 extends C1 { +>D1 : D1 +>C1 : C1 + + p3: number; +>p3 : number +} +class C3 { +>C3 : C3 + + p4: number; +>p4 : number +} +class D2 extends D1 { +>D2 : D2 +>D1 : D1 + + p5: number +>p5 : number +} + +var a: C1; +>a : C1 +>C1 : C1 + +if (a.constructor === D1) { +>a.constructor === D1 : boolean +>a.constructor : Function +>a : C1 +>constructor : Function +>D1 : typeof D1 + + a.p3; +>a.p3 : number & D1 +>a : D1 +>p3 : number & D1 +} +if (a.constructor == D1) { +>a.constructor == D1 : boolean +>a.constructor : Function +>a : C1 +>constructor : Function +>D1 : typeof D1 + + a.p3; +>a.p3 : number & D1 +>a : D1 +>p3 : number & D1 +} +if (D1 === a.constructor) { +>D1 === a.constructor : boolean +>D1 : typeof D1 +>a.constructor : Function +>a : C1 +>constructor : Function + + a.p3; +>a.p3 : number & D1 +>a : D1 +>p3 : number & D1 +} +if (a["constructor"] === D1) { +>a["constructor"] === D1 : boolean +>a["constructor"] : Function +>a : C1 +>"constructor" : "constructor" +>D1 : typeof D1 + + a.p3; +>a.p3 : number & D1 +>a : D1 +>p3 : number & D1 +} +if (D1 === a["constructor"]) { +>D1 === a["constructor"] : boolean +>D1 : typeof D1 +>a["constructor"] : Function +>a : C1 +>"constructor" : "constructor" + + a.p3; +>a.p3 : number & D1 +>a : D1 +>p3 : number & D1 +} + +var b: C1; +>b : C1 +>C1 : C1 + +if (b.constructor === D2) { +>b.constructor === D2 : boolean +>b.constructor : Function +>b : C1 +>constructor : Function +>D2 : typeof D2 + + b.p3; +>b.p3 : number & D2 +>b : D2 +>p3 : number & D2 + + b.p5; +>b.p5 : number & D2 +>b : D2 +>p5 : number & D2 +} + +var ctor3: C1 | C2; +>ctor3 : C1 | C2 +>C1 : C1 +>C2 : C2 + +if (ctor3.constructor === C1) { +>ctor3.constructor === C1 : boolean +>ctor3.constructor : Function +>ctor3 : C1 | C2 +>constructor : Function +>C1 : typeof C1 + + ctor3.p1; // C1 +>ctor3.p1 : string & C1 +>ctor3 : C1 +>p1 : string & C1 +} +else { + ctor3.p2; // C2 +>ctor3.p2 : any +>ctor3 : C1 | C2 +>p2 : any +} + +if (ctor3.constructor !== C1) { +>ctor3.constructor !== C1 : boolean +>ctor3.constructor : Function +>ctor3 : C1 | C2 +>constructor : Function +>C1 : typeof C1 + + ctor3.p2; // C1 +>ctor3.p2 : any +>ctor3 : C1 | C2 +>p2 : any +} +else { + ctor3.p1; // C2 +>ctor3.p1 : any +>ctor3 : C1 | C2 +>p1 : any +} + +var ctor4: C1 | C2 | C3; +>ctor4 : C1 | C2 | C3 +>C1 : C1 +>C2 : C2 +>C3 : C3 + +if (ctor4.constructor === C1) { +>ctor4.constructor === C1 : boolean +>ctor4.constructor : Function +>ctor4 : C1 | C2 | C3 +>constructor : Function +>C1 : typeof C1 + + ctor4.p1; // C1 +>ctor4.p1 : string & C1 +>ctor4 : C1 +>p1 : string & C1 +} +else if (ctor4.constructor === C2) { +>ctor4.constructor === C2 : boolean +>ctor4.constructor : Function +>ctor4 : C1 | C2 | C3 +>constructor : Function +>C2 : typeof C2 + + ctor4.p2; // C2 +>ctor4.p2 : number & C2 +>ctor4 : C2 +>p2 : number & C2 +} +else { + ctor4.p4; // C3 +>ctor4.p4 : any +>ctor4 : C1 | C2 | C3 +>p4 : any +} + + + + diff --git a/tests/cases/conformance/expressions/typeGuards/typeGuardConstructor.ts b/tests/cases/conformance/expressions/typeGuards/typeGuardConstructor.ts new file mode 100644 index 0000000000000..6dbe32b4a680d --- /dev/null +++ b/tests/cases/conformance/expressions/typeGuards/typeGuardConstructor.ts @@ -0,0 +1,67 @@ +class C1 { + p1: string; +} +class C2 { + p2: number; +} +class D1 extends C1 { + p3: number; +} +class C3 { + p4: number; +} +class D2 extends D1 { + p5: number +} + +var a: C1; +if (a.constructor === D1) { + a.p3; +} +if (a.constructor == D1) { + a.p3; +} +if (D1 === a.constructor) { + a.p3; +} +if (a["constructor"] === D1) { + a.p3; +} +if (D1 === a["constructor"]) { + a.p3; +} + +var b: C1; +if (b.constructor === D2) { + b.p3; + b.p5; +} + +var ctor3: C1 | C2; +if (ctor3.constructor === C1) { + ctor3.p1; // C1 +} +else { + ctor3.p2; // C2 +} + +if (ctor3.constructor !== C1) { + ctor3.p2; // C1 +} +else { + ctor3.p1; // C2 +} + +var ctor4: C1 | C2 | C3; +if (ctor4.constructor === C1) { + ctor4.p1; // C1 +} +else if (ctor4.constructor === C2) { + ctor4.p2; // C2 +} +else { + ctor4.p4; // C3 +} + + + From 93cd24980e75df2613e4c068c352c8c621867385 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E6=96=87=E7=92=90?= Date: Thu, 26 Apr 2018 10:42:01 +0800 Subject: [PATCH 2/4] support primitives --- src/compiler/checker.ts | 2 +- .../reference/typeGuardConstructor.errors.txt | 14 +++++ .../reference/typeGuardConstructor.js | 27 ++++++++++ .../reference/typeGuardConstructor.symbols | 46 ++++++++++++++++ .../reference/typeGuardConstructor.types | 54 +++++++++++++++++++ .../typeGuards/typeGuardConstructor.ts | 14 +++++ 6 files changed, 156 insertions(+), 1 deletion(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index b312aad78f8be..fc270f36765c3 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -13548,7 +13548,7 @@ namespace ts { const targetType = !isTypeAny(prototypePropertyType) ? prototypePropertyType : undefined; if (!targetType || isTypeAny(type) && (targetType === globalObjectType || targetType === globalFunctionType)) return type; - return getNarrowedType(type, targetType, assumeTrue, isTypeDerivedFrom); + return getNarrowedType(type, targetType, assumeTrue, areTypesComparable); } function narrowTypeByTypeof(type: Type, typeOfExpr: TypeOfExpression, operator: SyntaxKind, literal: LiteralExpression, assumeTrue: boolean): Type { diff --git a/tests/baselines/reference/typeGuardConstructor.errors.txt b/tests/baselines/reference/typeGuardConstructor.errors.txt index 5b9a7c055321a..e896147eca2a6 100644 --- a/tests/baselines/reference/typeGuardConstructor.errors.txt +++ b/tests/baselines/reference/typeGuardConstructor.errors.txt @@ -86,6 +86,20 @@ tests/cases/conformance/expressions/typeGuards/typeGuardConstructor.ts(63,11): e !!! error TS2339: Property 'p4' does not exist on type 'C1'. } + var x: number | "hello" | "world" | true | 1[] | undefined; + if (x.constructor === String) { + x.length; + } + if (x.constructor === Number) { + x.toFixed(); + } + if (x.constructor === Boolean) { + const b = x; + } + + if(x.constructor === Array) { + const c = x[0]; + } \ No newline at end of file diff --git a/tests/baselines/reference/typeGuardConstructor.js b/tests/baselines/reference/typeGuardConstructor.js index 32426765a188d..d22fa4fd019d5 100644 --- a/tests/baselines/reference/typeGuardConstructor.js +++ b/tests/baselines/reference/typeGuardConstructor.js @@ -64,8 +64,22 @@ else { ctor4.p4; // C3 } +var x: number | "hello" | "world" | true | 1[] | undefined; +if (x.constructor === String) { + x.length; +} +if (x.constructor === Number) { + x.toFixed(); +} +if (x.constructor === Boolean) { + const b = x; +} + +if(x.constructor === Array) { + const c = x[0]; +} //// [typeGuardConstructor.js] @@ -152,3 +166,16 @@ else if (ctor4.constructor === C2) { else { ctor4.p4; // C3 } +var x; +if (x.constructor === String) { + x.length; +} +if (x.constructor === Number) { + x.toFixed(); +} +if (x.constructor === Boolean) { + var b_1 = x; +} +if (x.constructor === Array) { + var c = x[0]; +} diff --git a/tests/baselines/reference/typeGuardConstructor.symbols b/tests/baselines/reference/typeGuardConstructor.symbols index 881b01230e39a..309691a76c7cb 100644 --- a/tests/baselines/reference/typeGuardConstructor.symbols +++ b/tests/baselines/reference/typeGuardConstructor.symbols @@ -179,6 +179,52 @@ else { >ctor4 : Symbol(ctor4, Decl(typeGuardConstructor.ts, 54, 3)) } +var x: number | "hello" | "world" | true | 1[] | undefined; +>x : Symbol(x, Decl(typeGuardConstructor.ts, 65, 3)) +if (x.constructor === String) { +>x.constructor : Symbol(Object.constructor, Decl(lib.d.ts, --, --)) +>x : Symbol(x, Decl(typeGuardConstructor.ts, 65, 3)) +>constructor : Symbol(Object.constructor, Decl(lib.d.ts, --, --)) +>String : Symbol(String, Decl(lib.d.ts, --, --), Decl(lib.d.ts, --, --), Decl(lib.d.ts, --, --)) + + x.length; +>x.length : Symbol(String.length, Decl(lib.d.ts, --, --)) +>x : Symbol(x, Decl(typeGuardConstructor.ts, 65, 3)) +>length : Symbol(String.length, Decl(lib.d.ts, --, --)) +} + +if (x.constructor === Number) { +>x.constructor : Symbol(Object.constructor, Decl(lib.d.ts, --, --)) +>x : Symbol(x, Decl(typeGuardConstructor.ts, 65, 3)) +>constructor : Symbol(Object.constructor, Decl(lib.d.ts, --, --)) +>Number : Symbol(Number, Decl(lib.d.ts, --, --), Decl(lib.d.ts, --, --), Decl(lib.d.ts, --, --)) + + x.toFixed(); +>x.toFixed : Symbol(Number.toFixed, Decl(lib.d.ts, --, --)) +>x : Symbol(x, Decl(typeGuardConstructor.ts, 65, 3)) +>toFixed : Symbol(Number.toFixed, Decl(lib.d.ts, --, --)) +} +if (x.constructor === Boolean) { +>x.constructor : Symbol(Object.constructor, Decl(lib.d.ts, --, --)) +>x : Symbol(x, Decl(typeGuardConstructor.ts, 65, 3)) +>constructor : Symbol(Object.constructor, Decl(lib.d.ts, --, --)) +>Boolean : Symbol(Boolean, Decl(lib.d.ts, --, --), Decl(lib.d.ts, --, --)) + + const b = x; +>b : Symbol(b, Decl(typeGuardConstructor.ts, 75, 9)) +>x : Symbol(x, Decl(typeGuardConstructor.ts, 65, 3)) +} + +if(x.constructor === Array) { +>x.constructor : Symbol(Object.constructor, Decl(lib.d.ts, --, --)) +>x : Symbol(x, Decl(typeGuardConstructor.ts, 65, 3)) +>constructor : Symbol(Object.constructor, Decl(lib.d.ts, --, --)) +>Array : Symbol(Array, Decl(lib.d.ts, --, --), Decl(lib.d.ts, --, --)) + + const c = x[0]; +>c : Symbol(c, Decl(typeGuardConstructor.ts, 79, 9)) +>x : Symbol(x, Decl(typeGuardConstructor.ts, 65, 3)) +} diff --git a/tests/baselines/reference/typeGuardConstructor.types b/tests/baselines/reference/typeGuardConstructor.types index d714b423efe25..9521f94546663 100644 --- a/tests/baselines/reference/typeGuardConstructor.types +++ b/tests/baselines/reference/typeGuardConstructor.types @@ -199,6 +199,60 @@ else { >p4 : any } +var x: number | "hello" | "world" | true | 1[] | undefined; +>x : number | true | "hello" | "world" | 1[] +>true : true +if (x.constructor === String) { +>x.constructor === String : boolean +>x.constructor : Function +>x : number | true | "hello" | "world" | 1[] +>constructor : Function +>String : StringConstructor + + x.length; +>x.length : number & String +>x : "hello" | "world" +>length : number & String +} + +if (x.constructor === Number) { +>x.constructor === Number : boolean +>x.constructor : Function +>x : number | true | "hello" | "world" | 1[] +>constructor : Function +>Number : NumberConstructor + + x.toFixed(); +>x.toFixed() : string +>x.toFixed : (fractionDigits?: number) => string +>x : number +>toFixed : (fractionDigits?: number) => string +} +if (x.constructor === Boolean) { +>x.constructor === Boolean : boolean +>x.constructor : Function +>x : number | true | "hello" | "world" | 1[] +>constructor : Function +>Boolean : BooleanConstructor + + const b = x; +>b : true +>x : true +} + +if(x.constructor === Array) { +>x.constructor === Array : boolean +>x.constructor : Function +>x : number | true | "hello" | "world" | 1[] +>constructor : Function +>Array : ArrayConstructor + + const c = x[0]; +>c : 1 +>x[0] : 1 +>x : 1[] +>0 : 0 +} diff --git a/tests/cases/conformance/expressions/typeGuards/typeGuardConstructor.ts b/tests/cases/conformance/expressions/typeGuards/typeGuardConstructor.ts index 6dbe32b4a680d..d019db8fdeaf4 100644 --- a/tests/cases/conformance/expressions/typeGuards/typeGuardConstructor.ts +++ b/tests/cases/conformance/expressions/typeGuards/typeGuardConstructor.ts @@ -63,5 +63,19 @@ else { ctor4.p4; // C3 } +var x: number | "hello" | "world" | true | 1[] | undefined; +if (x.constructor === String) { + x.length; +} +if (x.constructor === Number) { + x.toFixed(); +} +if (x.constructor === Boolean) { + const b = x; +} + +if(x.constructor === Array) { + const c = x[0]; +} From a99c37e8a54ffc340da493a1b6b1938a6c79da64 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E6=96=87=E7=92=90?= Date: Fri, 27 Apr 2018 16:19:43 +0800 Subject: [PATCH 3/4] fix type compare --- src/compiler/checker.ts | 6 +++- .../reference/typeGuardConstructor.errors.txt | 14 ++++++++- .../reference/typeGuardConstructor.js | 28 ++++++++++++++++- .../reference/typeGuardConstructor.symbols | 29 ++++++++++++++++++ .../reference/typeGuardConstructor.types | 30 +++++++++++++++++++ .../typeGuards/typeGuardConstructor.ts | 13 ++++++++ 6 files changed, 117 insertions(+), 3 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index fc270f36765c3..83370299ed3b1 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -13548,7 +13548,11 @@ namespace ts { const targetType = !isTypeAny(prototypePropertyType) ? prototypePropertyType : undefined; if (!targetType || isTypeAny(type) && (targetType === globalObjectType || targetType === globalFunctionType)) return type; - return getNarrowedType(type, targetType, assumeTrue, areTypesComparable); + return getNarrowedType(type, targetType, assumeTrue, isConstructedBy); + + function isConstructedBy(source: Type, target: Type) { + return source.flags & TypeFlags.Primitive ? areTypesComparable(source, target) : isTypeDerivedFrom(source, target); + } } function narrowTypeByTypeof(type: Type, typeOfExpr: TypeOfExpression, operator: SyntaxKind, literal: LiteralExpression, assumeTrue: boolean): Type { diff --git a/tests/baselines/reference/typeGuardConstructor.errors.txt b/tests/baselines/reference/typeGuardConstructor.errors.txt index e896147eca2a6..c57f8a54c471b 100644 --- a/tests/baselines/reference/typeGuardConstructor.errors.txt +++ b/tests/baselines/reference/typeGuardConstructor.errors.txt @@ -102,4 +102,16 @@ tests/cases/conformance/expressions/typeGuards/typeGuardConstructor.ts(63,11): e if(x.constructor === Array) { const c = x[0]; } - \ No newline at end of file + + + class Bar { + a: string + } + + class Baz { + a: string + } + var bar: Bar | Baz; + if (bar.constructor === Baz) { + const baz = bar + } \ No newline at end of file diff --git a/tests/baselines/reference/typeGuardConstructor.js b/tests/baselines/reference/typeGuardConstructor.js index d22fa4fd019d5..ea4f1dd63659c 100644 --- a/tests/baselines/reference/typeGuardConstructor.js +++ b/tests/baselines/reference/typeGuardConstructor.js @@ -80,7 +80,19 @@ if (x.constructor === Boolean) { if(x.constructor === Array) { const c = x[0]; } - + + +class Bar { + a: string +} + +class Baz { + a: string +} +var bar: Bar | Baz; +if (bar.constructor === Baz) { + const baz = bar +} //// [typeGuardConstructor.js] var __extends = (this && this.__extends) || (function () { @@ -179,3 +191,17 @@ if (x.constructor === Boolean) { if (x.constructor === Array) { var c = x[0]; } +var Bar = /** @class */ (function () { + function Bar() { + } + return Bar; +}()); +var Baz = /** @class */ (function () { + function Baz() { + } + return Baz; +}()); +var bar; +if (bar.constructor === Baz) { + var baz = bar; +} diff --git a/tests/baselines/reference/typeGuardConstructor.symbols b/tests/baselines/reference/typeGuardConstructor.symbols index 309691a76c7cb..05a84e93688ae 100644 --- a/tests/baselines/reference/typeGuardConstructor.symbols +++ b/tests/baselines/reference/typeGuardConstructor.symbols @@ -228,3 +228,32 @@ if(x.constructor === Array) { >x : Symbol(x, Decl(typeGuardConstructor.ts, 65, 3)) } + +class Bar { +>Bar : Symbol(Bar, Decl(typeGuardConstructor.ts, 80, 1)) + + a: string +>a : Symbol(Bar.a, Decl(typeGuardConstructor.ts, 83, 11)) +} + +class Baz { +>Baz : Symbol(Baz, Decl(typeGuardConstructor.ts, 85, 1)) + + a: string +>a : Symbol(Baz.a, Decl(typeGuardConstructor.ts, 87, 11)) +} +var bar: Bar | Baz; +>bar : Symbol(bar, Decl(typeGuardConstructor.ts, 90, 3)) +>Bar : Symbol(Bar, Decl(typeGuardConstructor.ts, 80, 1)) +>Baz : Symbol(Baz, Decl(typeGuardConstructor.ts, 85, 1)) + +if (bar.constructor === Baz) { +>bar.constructor : Symbol(Object.constructor, Decl(lib.d.ts, --, --)) +>bar : Symbol(bar, Decl(typeGuardConstructor.ts, 90, 3)) +>constructor : Symbol(Object.constructor, Decl(lib.d.ts, --, --)) +>Baz : Symbol(Baz, Decl(typeGuardConstructor.ts, 85, 1)) + + const baz = bar +>baz : Symbol(baz, Decl(typeGuardConstructor.ts, 92, 9)) +>bar : Symbol(bar, Decl(typeGuardConstructor.ts, 90, 3)) +} diff --git a/tests/baselines/reference/typeGuardConstructor.types b/tests/baselines/reference/typeGuardConstructor.types index 9521f94546663..2911d6d306ea8 100644 --- a/tests/baselines/reference/typeGuardConstructor.types +++ b/tests/baselines/reference/typeGuardConstructor.types @@ -256,3 +256,33 @@ if(x.constructor === Array) { >0 : 0 } + +class Bar { +>Bar : Bar + + a: string +>a : string +} + +class Baz { +>Baz : Baz + + a: string +>a : string +} +var bar: Bar | Baz; +>bar : Bar | Baz +>Bar : Bar +>Baz : Baz + +if (bar.constructor === Baz) { +>bar.constructor === Baz : boolean +>bar.constructor : Function +>bar : Bar | Baz +>constructor : Function +>Baz : typeof Baz + + const baz = bar +>baz : Baz +>bar : Baz +} diff --git a/tests/cases/conformance/expressions/typeGuards/typeGuardConstructor.ts b/tests/cases/conformance/expressions/typeGuards/typeGuardConstructor.ts index d019db8fdeaf4..b4c8634a4e852 100644 --- a/tests/cases/conformance/expressions/typeGuards/typeGuardConstructor.ts +++ b/tests/cases/conformance/expressions/typeGuards/typeGuardConstructor.ts @@ -79,3 +79,16 @@ if (x.constructor === Boolean) { if(x.constructor === Array) { const c = x[0]; } + + +class Bar { + a: string +} + +class Baz { + a: string +} +var bar: Bar | Baz; +if (bar.constructor === Baz) { + const baz = bar +} \ No newline at end of file From b7432d1368b4c8cac29da3f2ba23da954097bc25 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E6=96=87=E7=92=90?= Date: Fri, 27 Apr 2018 16:27:30 +0800 Subject: [PATCH 4/4] rename util function --- src/compiler/checker.ts | 6 +++--- src/compiler/utilities.ts | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 83370299ed3b1..77c4ad3b4e63c 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -13505,7 +13505,7 @@ namespace ts { if (type.flags & TypeFlags.Any) { return type; } - if (isExclamationEqualsOrExclamationEqualsEqualsKind(operator)) { + if (isNegatedEqualityToken(operator)) { assumeTrue = !assumeTrue; } const valueType = getTypeOfExpression(value); @@ -13536,7 +13536,7 @@ namespace ts { } function narrowTypeByConstructor(type: Type, expr: Expression, operator: SyntaxKind, assumeTrue: boolean): Type { - if (!assumeTrue || isExclamationEqualsOrExclamationEqualsEqualsKind(operator)) return type; + if (!assumeTrue || isNegatedEqualityToken(operator)) return type; const rightType = getTypeOfExpression(expr); if (!isTypeSubtypeOf(rightType, globalFunctionType)) return type; @@ -13566,7 +13566,7 @@ namespace ts { } return type; } - if (isExclamationEqualsOrExclamationEqualsEqualsKind(operator)) { + if (isNegatedEqualityToken(operator)) { assumeTrue = !assumeTrue; } if (assumeTrue && !(type.flags & TypeFlags.Union)) { diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index beea33c714798..e36c1a463be44 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -6337,7 +6337,7 @@ namespace ts { } /** @internal */ - export function isExclamationEqualsOrExclamationEqualsEqualsKind(kind: SyntaxKind): boolean { + export function isNegatedEqualityToken(kind: SyntaxKind): boolean { return kind === SyntaxKind.ExclamationEqualsToken || kind === SyntaxKind.ExclamationEqualsEqualsToken; } }