From a3cf4b5d768f8e6ce2d64e8569a0dded657090c9 Mon Sep 17 00:00:00 2001 From: Austin Cummings Date: Tue, 2 Jul 2019 23:34:00 -0700 Subject: [PATCH] Implement constructor check type guard --- src/compiler/checker.ts | 49 +++ .../reference/typeGuardConstructor.errors.txt | 170 ++++++++ .../reference/typeGuardConstructor.js | 251 +++++++++++ .../reference/typeGuardConstructor.symbols | 335 +++++++++++++++ .../reference/typeGuardConstructor.types | 392 ++++++++++++++++++ tests/cases/compiler/typeGuardConstructor.ts | 126 ++++++ 6 files changed, 1323 insertions(+) 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/compiler/typeGuardConstructor.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 5b5ae6b4b24ce..89fa593a1c0f7 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -17247,6 +17247,12 @@ namespace ts { if (right.kind === SyntaxKind.TypeOfExpression && isStringLiteralLike(left)) { return narrowTypeByTypeof(type, right, operator, left, assumeTrue); } + if (isConstructorAccessExpression(left) && right.kind === SyntaxKind.Identifier) { + return narrowTypeByConstructor(type, left, operator, right, assumeTrue); + } + if (isConstructorAccessExpression(right) && left.kind === SyntaxKind.Identifier) { + return narrowTypeByConstructor(type, right, operator, left, assumeTrue); + } if (isMatchingReference(reference, left)) { return narrowTypeByEquality(type, operator, right, assumeTrue); } @@ -17275,6 +17281,13 @@ namespace ts { return narrowType(type, expr.right, assumeTrue); } return type; + + function isConstructorAccessExpression(expr: Expression): expr is AccessExpression { + return ( + isPropertyAccessEntityNameExpression(expr) && idText(expr.name) === "constructor" + || isElementAccessExpression(expr) && isStringLiteralLike(expr.argumentExpression) && expr.argumentExpression.text === "constructor" + ); + } } function narrowTypeByEquality(type: Type, operator: SyntaxKind, value: Expression, assumeTrue: boolean): Type { @@ -17505,6 +17518,42 @@ namespace ts { return getTypeWithFacts(mapType(type, narrowTypeForTypeofSwitch(impliedType)), switchFacts); } + function narrowTypeByConstructor(type: Type, constructorAccessExpr: AccessExpression, operator: SyntaxKind, identifier: Expression, assumeTrue: boolean): Type { + if (!assumeTrue || operator !== SyntaxKind.EqualsEqualsToken && operator !== SyntaxKind.EqualsEqualsEqualsToken) { + return type; + } + + if (!isMatchingReference(reference, constructorAccessExpr.expression)) { + return declaredType; + } + + const identifierType = getTypeOfExpression(identifier); + if (!isTypeSubtypeOf(identifierType, globalFunctionType)) { + return type; + } + + const prototypeProperty = getPropertyOfType(identifierType, "prototype" as __String); + if (!prototypeProperty) { + return type; + } + + const prototypeType = getTypeOfSymbol(prototypeProperty); + const candidate = !isTypeAny(prototypeType) ? prototypeType : undefined; + if (!candidate || candidate === globalObjectType || candidate === globalFunctionType) { + return type; + } + + return getNarrowedType(type, candidate, assumeTrue, isConstructedBy); + + function isConstructedBy(source: Type, target: Type) { + if (source.flags & TypeFlags.Primitive) { + return areTypesComparable(source, target); + } + + return isTypeDerivedFrom(source, target); + } + } + function narrowTypeByInstanceof(type: Type, expr: BinaryExpression, assumeTrue: boolean): Type { const left = getReferenceCandidate(expr.left); if (!isMatchingReference(reference, left)) { diff --git a/tests/baselines/reference/typeGuardConstructor.errors.txt b/tests/baselines/reference/typeGuardConstructor.errors.txt new file mode 100644 index 0000000000000..545cfa971787d --- /dev/null +++ b/tests/baselines/reference/typeGuardConstructor.errors.txt @@ -0,0 +1,170 @@ +tests/cases/compiler/typeGuardConstructor.ts(33,9): error TS2339: Property 'prop1' does not exist on type 'number | Foo'. + Property 'prop1' does not exist on type 'number'. +tests/cases/compiler/typeGuardConstructor.ts(36,9): error TS2339: Property 'prop1' does not exist on type 'number | Foo'. + Property 'prop1' does not exist on type 'number'. +tests/cases/compiler/typeGuardConstructor.ts(39,9): error TS2339: Property 'prop1' does not exist on type 'number | Foo'. + Property 'prop1' does not exist on type 'number'. +tests/cases/compiler/typeGuardConstructor.ts(42,9): error TS2339: Property 'prop1' does not exist on type 'number | Foo'. + Property 'prop1' does not exist on type 'number'. +tests/cases/compiler/typeGuardConstructor.ts(45,9): error TS2339: Property 'prop1' does not exist on type 'number | Foo'. + Property 'prop1' does not exist on type 'number'. +tests/cases/compiler/typeGuardConstructor.ts(48,9): error TS2339: Property 'prop1' does not exist on type 'number | Foo'. + Property 'prop1' does not exist on type 'number'. +tests/cases/compiler/typeGuardConstructor.ts(51,9): error TS2339: Property 'prop1' does not exist on type 'number | Foo'. + Property 'prop1' does not exist on type 'number'. +tests/cases/compiler/typeGuardConstructor.ts(54,9): error TS2339: Property 'prop1' does not exist on type 'number | Foo'. + Property 'prop1' does not exist on type 'number'. + + +==== tests/cases/compiler/typeGuardConstructor.ts (8 errors) ==== + // Typical case + class Foo { + prop1: string; + } + + let foo: Foo | number; + if (foo.constructor == Foo) { + foo.prop1; // string + } + if (foo["constructor"] == Foo) { + foo.prop1; // string + } + if (foo.constructor === Foo) { + foo.prop1; // string + } + if (foo["constructor"] === Foo) { + foo.prop1; // string + } + if (Foo == foo.constructor) { + foo.prop1; // string + } + if (Foo == foo["constructor"]) { + foo.prop1; // string + } + if (Foo === foo.constructor) { + foo.prop1; // string + } + if (Foo === foo["constructor"]) { + foo.prop1; // string + } + + if (foo.constructor != Foo) { + foo.prop1; // ERROR + ~~~~~ +!!! error TS2339: Property 'prop1' does not exist on type 'number | Foo'. +!!! error TS2339: Property 'prop1' does not exist on type 'number'. + } + if (foo["constructor"] != Foo) { + foo.prop1; // ERROR + ~~~~~ +!!! error TS2339: Property 'prop1' does not exist on type 'number | Foo'. +!!! error TS2339: Property 'prop1' does not exist on type 'number'. + } + if (foo.constructor !== Foo) { + foo.prop1; // ERROR + ~~~~~ +!!! error TS2339: Property 'prop1' does not exist on type 'number | Foo'. +!!! error TS2339: Property 'prop1' does not exist on type 'number'. + } + if (foo["constructor"] !== Foo) { + foo.prop1; // ERROR + ~~~~~ +!!! error TS2339: Property 'prop1' does not exist on type 'number | Foo'. +!!! error TS2339: Property 'prop1' does not exist on type 'number'. + } + if (Foo != foo.constructor) { + foo.prop1; // ERROR + ~~~~~ +!!! error TS2339: Property 'prop1' does not exist on type 'number | Foo'. +!!! error TS2339: Property 'prop1' does not exist on type 'number'. + } + if (Foo != foo["constructor"]) { + foo.prop1; // ERROR + ~~~~~ +!!! error TS2339: Property 'prop1' does not exist on type 'number | Foo'. +!!! error TS2339: Property 'prop1' does not exist on type 'number'. + } + if (Foo !== foo.constructor) { + foo.prop1; // ERROR + ~~~~~ +!!! error TS2339: Property 'prop1' does not exist on type 'number | Foo'. +!!! error TS2339: Property 'prop1' does not exist on type 'number'. + } + if (Foo !== foo["constructor"]) { + foo.prop1; // ERROR + ~~~~~ +!!! error TS2339: Property 'prop1' does not exist on type 'number | Foo'. +!!! error TS2339: Property 'prop1' does not exist on type 'number'. + } + + + // Derived class case + class Bar extends Foo { + prop2: number; + } + + let bar: Bar | boolean; + if (bar.constructor === Bar) { + bar.prop1; // string + bar.prop2; // number + } + if (bar.constructor === Foo) { + bar.prop1; // string + bar.prop2; // number + } + + + // Union of primitives, number, arrays, and Foo + var x: number | "hello" | "world" | true | false | number[] | string[] | Foo; + + if (x.constructor === Number) { + x; // number + } + + if (x.constructor === String) { + x; // "hello" | "world" + } + + if (x.constructor === Boolean) { + x; // boolean + } + + if (x.constructor === Array) { + x; // number[] | string[] + } + + if (x.constructor === Function) { + x; // declaredType + } + + if (x.constructor === Foo) { + x; // Foo + x.prop1; // string + } + + + // Narrowing any + let a: any; + + if (a.constructor === Foo) { + a; // Foo + } + if (a.constructor === "hello") { + a; // any + } + if (a.constructor === Function) { + a; // any + } + + + // If for some reason someone defines a type with it's own constructor property + type S = { + constructor: () => void; + }; + + let s: S | string; + + if (s.constructor === String) { + s; // string + } + \ 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..5b6c022ef7263 --- /dev/null +++ b/tests/baselines/reference/typeGuardConstructor.js @@ -0,0 +1,251 @@ +//// [typeGuardConstructor.ts] +// Typical case +class Foo { + prop1: string; +} + +let foo: Foo | number; +if (foo.constructor == Foo) { + foo.prop1; // string +} +if (foo["constructor"] == Foo) { + foo.prop1; // string +} +if (foo.constructor === Foo) { + foo.prop1; // string +} +if (foo["constructor"] === Foo) { + foo.prop1; // string +} +if (Foo == foo.constructor) { + foo.prop1; // string +} +if (Foo == foo["constructor"]) { + foo.prop1; // string +} +if (Foo === foo.constructor) { + foo.prop1; // string +} +if (Foo === foo["constructor"]) { + foo.prop1; // string +} + +if (foo.constructor != Foo) { + foo.prop1; // ERROR +} +if (foo["constructor"] != Foo) { + foo.prop1; // ERROR +} +if (foo.constructor !== Foo) { + foo.prop1; // ERROR +} +if (foo["constructor"] !== Foo) { + foo.prop1; // ERROR +} +if (Foo != foo.constructor) { + foo.prop1; // ERROR +} +if (Foo != foo["constructor"]) { + foo.prop1; // ERROR +} +if (Foo !== foo.constructor) { + foo.prop1; // ERROR +} +if (Foo !== foo["constructor"]) { + foo.prop1; // ERROR +} + + +// Derived class case +class Bar extends Foo { + prop2: number; +} + +let bar: Bar | boolean; +if (bar.constructor === Bar) { + bar.prop1; // string + bar.prop2; // number +} +if (bar.constructor === Foo) { + bar.prop1; // string + bar.prop2; // number +} + + +// Union of primitives, number, arrays, and Foo +var x: number | "hello" | "world" | true | false | number[] | string[] | Foo; + +if (x.constructor === Number) { + x; // number +} + +if (x.constructor === String) { + x; // "hello" | "world" +} + +if (x.constructor === Boolean) { + x; // boolean +} + +if (x.constructor === Array) { + x; // number[] | string[] +} + +if (x.constructor === Function) { + x; // declaredType +} + +if (x.constructor === Foo) { + x; // Foo + x.prop1; // string +} + + +// Narrowing any +let a: any; + +if (a.constructor === Foo) { + a; // Foo +} +if (a.constructor === "hello") { + a; // any +} +if (a.constructor === Function) { + a; // any +} + + +// If for some reason someone defines a type with it's own constructor property +type S = { + constructor: () => void; +}; + +let s: S | string; + +if (s.constructor === String) { + s; // string +} + + +//// [typeGuardConstructor.js] +var __extends = (this && this.__extends) || (function () { + var extendStatics = function (d, b) { + 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 extendStatics(d, b); + }; + return function (d, b) { + extendStatics(d, b); + function __() { this.constructor = d; } + d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); + }; +})(); +// Typical case +var Foo = /** @class */ (function () { + function Foo() { + } + return Foo; +}()); +var foo; +if (foo.constructor == Foo) { + foo.prop1; // string +} +if (foo["constructor"] == Foo) { + foo.prop1; // string +} +if (foo.constructor === Foo) { + foo.prop1; // string +} +if (foo["constructor"] === Foo) { + foo.prop1; // string +} +if (Foo == foo.constructor) { + foo.prop1; // string +} +if (Foo == foo["constructor"]) { + foo.prop1; // string +} +if (Foo === foo.constructor) { + foo.prop1; // string +} +if (Foo === foo["constructor"]) { + foo.prop1; // string +} +if (foo.constructor != Foo) { + foo.prop1; // ERROR +} +if (foo["constructor"] != Foo) { + foo.prop1; // ERROR +} +if (foo.constructor !== Foo) { + foo.prop1; // ERROR +} +if (foo["constructor"] !== Foo) { + foo.prop1; // ERROR +} +if (Foo != foo.constructor) { + foo.prop1; // ERROR +} +if (Foo != foo["constructor"]) { + foo.prop1; // ERROR +} +if (Foo !== foo.constructor) { + foo.prop1; // ERROR +} +if (Foo !== foo["constructor"]) { + foo.prop1; // ERROR +} +// Derived class case +var Bar = /** @class */ (function (_super) { + __extends(Bar, _super); + function Bar() { + return _super !== null && _super.apply(this, arguments) || this; + } + return Bar; +}(Foo)); +var bar; +if (bar.constructor === Bar) { + bar.prop1; // string + bar.prop2; // number +} +if (bar.constructor === Foo) { + bar.prop1; // string + bar.prop2; // number +} +// Union of primitives, number, arrays, and Foo +var x; +if (x.constructor === Number) { + x; // number +} +if (x.constructor === String) { + x; // "hello" | "world" +} +if (x.constructor === Boolean) { + x; // boolean +} +if (x.constructor === Array) { + x; // number[] | string[] +} +if (x.constructor === Function) { + x; // declaredType +} +if (x.constructor === Foo) { + x; // Foo + x.prop1; // string +} +// Narrowing any +var a; +if (a.constructor === Foo) { + a; // Foo +} +if (a.constructor === "hello") { + a; // any +} +if (a.constructor === Function) { + a; // any +} +var s; +if (s.constructor === String) { + s; // string +} diff --git a/tests/baselines/reference/typeGuardConstructor.symbols b/tests/baselines/reference/typeGuardConstructor.symbols new file mode 100644 index 0000000000000..e1440b7894b8e --- /dev/null +++ b/tests/baselines/reference/typeGuardConstructor.symbols @@ -0,0 +1,335 @@ +=== tests/cases/compiler/typeGuardConstructor.ts === +// Typical case +class Foo { +>Foo : Symbol(Foo, Decl(typeGuardConstructor.ts, 0, 0)) + + prop1: string; +>prop1 : Symbol(Foo.prop1, Decl(typeGuardConstructor.ts, 1, 11)) +} + +let foo: Foo | number; +>foo : Symbol(foo, Decl(typeGuardConstructor.ts, 5, 3)) +>Foo : Symbol(Foo, Decl(typeGuardConstructor.ts, 0, 0)) + +if (foo.constructor == Foo) { +>foo.constructor : Symbol(Object.constructor, Decl(lib.es5.d.ts, --, --)) +>foo : Symbol(foo, Decl(typeGuardConstructor.ts, 5, 3)) +>constructor : Symbol(Object.constructor, Decl(lib.es5.d.ts, --, --)) +>Foo : Symbol(Foo, Decl(typeGuardConstructor.ts, 0, 0)) + + foo.prop1; // string +>foo.prop1 : Symbol(Foo.prop1, Decl(typeGuardConstructor.ts, 1, 11)) +>foo : Symbol(foo, Decl(typeGuardConstructor.ts, 5, 3)) +>prop1 : Symbol(Foo.prop1, Decl(typeGuardConstructor.ts, 1, 11)) +} +if (foo["constructor"] == Foo) { +>foo : Symbol(foo, Decl(typeGuardConstructor.ts, 5, 3)) +>"constructor" : Symbol(Object.constructor, Decl(lib.es5.d.ts, --, --)) +>Foo : Symbol(Foo, Decl(typeGuardConstructor.ts, 0, 0)) + + foo.prop1; // string +>foo.prop1 : Symbol(Foo.prop1, Decl(typeGuardConstructor.ts, 1, 11)) +>foo : Symbol(foo, Decl(typeGuardConstructor.ts, 5, 3)) +>prop1 : Symbol(Foo.prop1, Decl(typeGuardConstructor.ts, 1, 11)) +} +if (foo.constructor === Foo) { +>foo.constructor : Symbol(Object.constructor, Decl(lib.es5.d.ts, --, --)) +>foo : Symbol(foo, Decl(typeGuardConstructor.ts, 5, 3)) +>constructor : Symbol(Object.constructor, Decl(lib.es5.d.ts, --, --)) +>Foo : Symbol(Foo, Decl(typeGuardConstructor.ts, 0, 0)) + + foo.prop1; // string +>foo.prop1 : Symbol(Foo.prop1, Decl(typeGuardConstructor.ts, 1, 11)) +>foo : Symbol(foo, Decl(typeGuardConstructor.ts, 5, 3)) +>prop1 : Symbol(Foo.prop1, Decl(typeGuardConstructor.ts, 1, 11)) +} +if (foo["constructor"] === Foo) { +>foo : Symbol(foo, Decl(typeGuardConstructor.ts, 5, 3)) +>"constructor" : Symbol(Object.constructor, Decl(lib.es5.d.ts, --, --)) +>Foo : Symbol(Foo, Decl(typeGuardConstructor.ts, 0, 0)) + + foo.prop1; // string +>foo.prop1 : Symbol(Foo.prop1, Decl(typeGuardConstructor.ts, 1, 11)) +>foo : Symbol(foo, Decl(typeGuardConstructor.ts, 5, 3)) +>prop1 : Symbol(Foo.prop1, Decl(typeGuardConstructor.ts, 1, 11)) +} +if (Foo == foo.constructor) { +>Foo : Symbol(Foo, Decl(typeGuardConstructor.ts, 0, 0)) +>foo.constructor : Symbol(Object.constructor, Decl(lib.es5.d.ts, --, --)) +>foo : Symbol(foo, Decl(typeGuardConstructor.ts, 5, 3)) +>constructor : Symbol(Object.constructor, Decl(lib.es5.d.ts, --, --)) + + foo.prop1; // string +>foo.prop1 : Symbol(Foo.prop1, Decl(typeGuardConstructor.ts, 1, 11)) +>foo : Symbol(foo, Decl(typeGuardConstructor.ts, 5, 3)) +>prop1 : Symbol(Foo.prop1, Decl(typeGuardConstructor.ts, 1, 11)) +} +if (Foo == foo["constructor"]) { +>Foo : Symbol(Foo, Decl(typeGuardConstructor.ts, 0, 0)) +>foo : Symbol(foo, Decl(typeGuardConstructor.ts, 5, 3)) +>"constructor" : Symbol(Object.constructor, Decl(lib.es5.d.ts, --, --)) + + foo.prop1; // string +>foo.prop1 : Symbol(Foo.prop1, Decl(typeGuardConstructor.ts, 1, 11)) +>foo : Symbol(foo, Decl(typeGuardConstructor.ts, 5, 3)) +>prop1 : Symbol(Foo.prop1, Decl(typeGuardConstructor.ts, 1, 11)) +} +if (Foo === foo.constructor) { +>Foo : Symbol(Foo, Decl(typeGuardConstructor.ts, 0, 0)) +>foo.constructor : Symbol(Object.constructor, Decl(lib.es5.d.ts, --, --)) +>foo : Symbol(foo, Decl(typeGuardConstructor.ts, 5, 3)) +>constructor : Symbol(Object.constructor, Decl(lib.es5.d.ts, --, --)) + + foo.prop1; // string +>foo.prop1 : Symbol(Foo.prop1, Decl(typeGuardConstructor.ts, 1, 11)) +>foo : Symbol(foo, Decl(typeGuardConstructor.ts, 5, 3)) +>prop1 : Symbol(Foo.prop1, Decl(typeGuardConstructor.ts, 1, 11)) +} +if (Foo === foo["constructor"]) { +>Foo : Symbol(Foo, Decl(typeGuardConstructor.ts, 0, 0)) +>foo : Symbol(foo, Decl(typeGuardConstructor.ts, 5, 3)) +>"constructor" : Symbol(Object.constructor, Decl(lib.es5.d.ts, --, --)) + + foo.prop1; // string +>foo.prop1 : Symbol(Foo.prop1, Decl(typeGuardConstructor.ts, 1, 11)) +>foo : Symbol(foo, Decl(typeGuardConstructor.ts, 5, 3)) +>prop1 : Symbol(Foo.prop1, Decl(typeGuardConstructor.ts, 1, 11)) +} + +if (foo.constructor != Foo) { +>foo.constructor : Symbol(Object.constructor, Decl(lib.es5.d.ts, --, --)) +>foo : Symbol(foo, Decl(typeGuardConstructor.ts, 5, 3)) +>constructor : Symbol(Object.constructor, Decl(lib.es5.d.ts, --, --)) +>Foo : Symbol(Foo, Decl(typeGuardConstructor.ts, 0, 0)) + + foo.prop1; // ERROR +>foo : Symbol(foo, Decl(typeGuardConstructor.ts, 5, 3)) +} +if (foo["constructor"] != Foo) { +>foo : Symbol(foo, Decl(typeGuardConstructor.ts, 5, 3)) +>"constructor" : Symbol(Object.constructor, Decl(lib.es5.d.ts, --, --)) +>Foo : Symbol(Foo, Decl(typeGuardConstructor.ts, 0, 0)) + + foo.prop1; // ERROR +>foo : Symbol(foo, Decl(typeGuardConstructor.ts, 5, 3)) +} +if (foo.constructor !== Foo) { +>foo.constructor : Symbol(Object.constructor, Decl(lib.es5.d.ts, --, --)) +>foo : Symbol(foo, Decl(typeGuardConstructor.ts, 5, 3)) +>constructor : Symbol(Object.constructor, Decl(lib.es5.d.ts, --, --)) +>Foo : Symbol(Foo, Decl(typeGuardConstructor.ts, 0, 0)) + + foo.prop1; // ERROR +>foo : Symbol(foo, Decl(typeGuardConstructor.ts, 5, 3)) +} +if (foo["constructor"] !== Foo) { +>foo : Symbol(foo, Decl(typeGuardConstructor.ts, 5, 3)) +>"constructor" : Symbol(Object.constructor, Decl(lib.es5.d.ts, --, --)) +>Foo : Symbol(Foo, Decl(typeGuardConstructor.ts, 0, 0)) + + foo.prop1; // ERROR +>foo : Symbol(foo, Decl(typeGuardConstructor.ts, 5, 3)) +} +if (Foo != foo.constructor) { +>Foo : Symbol(Foo, Decl(typeGuardConstructor.ts, 0, 0)) +>foo.constructor : Symbol(Object.constructor, Decl(lib.es5.d.ts, --, --)) +>foo : Symbol(foo, Decl(typeGuardConstructor.ts, 5, 3)) +>constructor : Symbol(Object.constructor, Decl(lib.es5.d.ts, --, --)) + + foo.prop1; // ERROR +>foo : Symbol(foo, Decl(typeGuardConstructor.ts, 5, 3)) +} +if (Foo != foo["constructor"]) { +>Foo : Symbol(Foo, Decl(typeGuardConstructor.ts, 0, 0)) +>foo : Symbol(foo, Decl(typeGuardConstructor.ts, 5, 3)) +>"constructor" : Symbol(Object.constructor, Decl(lib.es5.d.ts, --, --)) + + foo.prop1; // ERROR +>foo : Symbol(foo, Decl(typeGuardConstructor.ts, 5, 3)) +} +if (Foo !== foo.constructor) { +>Foo : Symbol(Foo, Decl(typeGuardConstructor.ts, 0, 0)) +>foo.constructor : Symbol(Object.constructor, Decl(lib.es5.d.ts, --, --)) +>foo : Symbol(foo, Decl(typeGuardConstructor.ts, 5, 3)) +>constructor : Symbol(Object.constructor, Decl(lib.es5.d.ts, --, --)) + + foo.prop1; // ERROR +>foo : Symbol(foo, Decl(typeGuardConstructor.ts, 5, 3)) +} +if (Foo !== foo["constructor"]) { +>Foo : Symbol(Foo, Decl(typeGuardConstructor.ts, 0, 0)) +>foo : Symbol(foo, Decl(typeGuardConstructor.ts, 5, 3)) +>"constructor" : Symbol(Object.constructor, Decl(lib.es5.d.ts, --, --)) + + foo.prop1; // ERROR +>foo : Symbol(foo, Decl(typeGuardConstructor.ts, 5, 3)) +} + + +// Derived class case +class Bar extends Foo { +>Bar : Symbol(Bar, Decl(typeGuardConstructor.ts, 54, 1)) +>Foo : Symbol(Foo, Decl(typeGuardConstructor.ts, 0, 0)) + + prop2: number; +>prop2 : Symbol(Bar.prop2, Decl(typeGuardConstructor.ts, 58, 23)) +} + +let bar: Bar | boolean; +>bar : Symbol(bar, Decl(typeGuardConstructor.ts, 62, 3)) +>Bar : Symbol(Bar, Decl(typeGuardConstructor.ts, 54, 1)) + +if (bar.constructor === Bar) { +>bar.constructor : Symbol(Object.constructor, Decl(lib.es5.d.ts, --, --)) +>bar : Symbol(bar, Decl(typeGuardConstructor.ts, 62, 3)) +>constructor : Symbol(Object.constructor, Decl(lib.es5.d.ts, --, --)) +>Bar : Symbol(Bar, Decl(typeGuardConstructor.ts, 54, 1)) + + bar.prop1; // string +>bar.prop1 : Symbol(Foo.prop1, Decl(typeGuardConstructor.ts, 1, 11)) +>bar : Symbol(bar, Decl(typeGuardConstructor.ts, 62, 3)) +>prop1 : Symbol(Foo.prop1, Decl(typeGuardConstructor.ts, 1, 11)) + + bar.prop2; // number +>bar.prop2 : Symbol(Bar.prop2, Decl(typeGuardConstructor.ts, 58, 23)) +>bar : Symbol(bar, Decl(typeGuardConstructor.ts, 62, 3)) +>prop2 : Symbol(Bar.prop2, Decl(typeGuardConstructor.ts, 58, 23)) +} +if (bar.constructor === Foo) { +>bar.constructor : Symbol(Object.constructor, Decl(lib.es5.d.ts, --, --)) +>bar : Symbol(bar, Decl(typeGuardConstructor.ts, 62, 3)) +>constructor : Symbol(Object.constructor, Decl(lib.es5.d.ts, --, --)) +>Foo : Symbol(Foo, Decl(typeGuardConstructor.ts, 0, 0)) + + bar.prop1; // string +>bar.prop1 : Symbol(Foo.prop1, Decl(typeGuardConstructor.ts, 1, 11)) +>bar : Symbol(bar, Decl(typeGuardConstructor.ts, 62, 3)) +>prop1 : Symbol(Foo.prop1, Decl(typeGuardConstructor.ts, 1, 11)) + + bar.prop2; // number +>bar.prop2 : Symbol(Bar.prop2, Decl(typeGuardConstructor.ts, 58, 23)) +>bar : Symbol(bar, Decl(typeGuardConstructor.ts, 62, 3)) +>prop2 : Symbol(Bar.prop2, Decl(typeGuardConstructor.ts, 58, 23)) +} + + +// Union of primitives, number, arrays, and Foo +var x: number | "hello" | "world" | true | false | number[] | string[] | Foo; +>x : Symbol(x, Decl(typeGuardConstructor.ts, 74, 3)) +>Foo : Symbol(Foo, Decl(typeGuardConstructor.ts, 0, 0)) + +if (x.constructor === Number) { +>x.constructor : Symbol(Object.constructor, Decl(lib.es5.d.ts, --, --)) +>x : Symbol(x, Decl(typeGuardConstructor.ts, 74, 3)) +>constructor : Symbol(Object.constructor, Decl(lib.es5.d.ts, --, --)) +>Number : Symbol(Number, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --)) + + x; // number +>x : Symbol(x, Decl(typeGuardConstructor.ts, 74, 3)) +} + +if (x.constructor === String) { +>x.constructor : Symbol(Object.constructor, Decl(lib.es5.d.ts, --, --)) +>x : Symbol(x, Decl(typeGuardConstructor.ts, 74, 3)) +>constructor : Symbol(Object.constructor, Decl(lib.es5.d.ts, --, --)) +>String : Symbol(String, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --)) + + x; // "hello" | "world" +>x : Symbol(x, Decl(typeGuardConstructor.ts, 74, 3)) +} + +if (x.constructor === Boolean) { +>x.constructor : Symbol(Object.constructor, Decl(lib.es5.d.ts, --, --)) +>x : Symbol(x, Decl(typeGuardConstructor.ts, 74, 3)) +>constructor : Symbol(Object.constructor, Decl(lib.es5.d.ts, --, --)) +>Boolean : Symbol(Boolean, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --)) + + x; // boolean +>x : Symbol(x, Decl(typeGuardConstructor.ts, 74, 3)) +} + +if (x.constructor === Array) { +>x.constructor : Symbol(Object.constructor, Decl(lib.es5.d.ts, --, --)) +>x : Symbol(x, Decl(typeGuardConstructor.ts, 74, 3)) +>constructor : Symbol(Object.constructor, Decl(lib.es5.d.ts, --, --)) +>Array : Symbol(Array, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --)) + + x; // number[] | string[] +>x : Symbol(x, Decl(typeGuardConstructor.ts, 74, 3)) +} + +if (x.constructor === Function) { +>x.constructor : Symbol(Object.constructor, Decl(lib.es5.d.ts, --, --)) +>x : Symbol(x, Decl(typeGuardConstructor.ts, 74, 3)) +>constructor : Symbol(Object.constructor, Decl(lib.es5.d.ts, --, --)) +>Function : Symbol(Function, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --)) + + x; // declaredType +>x : Symbol(x, Decl(typeGuardConstructor.ts, 74, 3)) +} + +if (x.constructor === Foo) { +>x.constructor : Symbol(Object.constructor, Decl(lib.es5.d.ts, --, --)) +>x : Symbol(x, Decl(typeGuardConstructor.ts, 74, 3)) +>constructor : Symbol(Object.constructor, Decl(lib.es5.d.ts, --, --)) +>Foo : Symbol(Foo, Decl(typeGuardConstructor.ts, 0, 0)) + + x; // Foo +>x : Symbol(x, Decl(typeGuardConstructor.ts, 74, 3)) + + x.prop1; // string +>x.prop1 : Symbol(Foo.prop1, Decl(typeGuardConstructor.ts, 1, 11)) +>x : Symbol(x, Decl(typeGuardConstructor.ts, 74, 3)) +>prop1 : Symbol(Foo.prop1, Decl(typeGuardConstructor.ts, 1, 11)) +} + + +// Narrowing any +let a: any; +>a : Symbol(a, Decl(typeGuardConstructor.ts, 103, 3)) + +if (a.constructor === Foo) { +>a : Symbol(a, Decl(typeGuardConstructor.ts, 103, 3)) +>Foo : Symbol(Foo, Decl(typeGuardConstructor.ts, 0, 0)) + + a; // Foo +>a : Symbol(a, Decl(typeGuardConstructor.ts, 103, 3)) +} +if (a.constructor === "hello") { +>a : Symbol(a, Decl(typeGuardConstructor.ts, 103, 3)) + + a; // any +>a : Symbol(a, Decl(typeGuardConstructor.ts, 103, 3)) +} +if (a.constructor === Function) { +>a : Symbol(a, Decl(typeGuardConstructor.ts, 103, 3)) +>Function : Symbol(Function, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --)) + + a; // any +>a : Symbol(a, Decl(typeGuardConstructor.ts, 103, 3)) +} + + +// If for some reason someone defines a type with it's own constructor property +type S = { +>S : Symbol(S, Decl(typeGuardConstructor.ts, 113, 1)) + + constructor: () => void; +>constructor : Symbol(constructor, Decl(typeGuardConstructor.ts, 117, 10)) + +}; + +let s: S | string; +>s : Symbol(s, Decl(typeGuardConstructor.ts, 121, 3)) +>S : Symbol(S, Decl(typeGuardConstructor.ts, 113, 1)) + +if (s.constructor === String) { +>s.constructor : Symbol(constructor, Decl(lib.es5.d.ts, --, --), Decl(typeGuardConstructor.ts, 117, 10)) +>s : Symbol(s, Decl(typeGuardConstructor.ts, 121, 3)) +>constructor : Symbol(constructor, Decl(lib.es5.d.ts, --, --), Decl(typeGuardConstructor.ts, 117, 10)) +>String : Symbol(String, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --)) + + s; // string +>s : Symbol(s, Decl(typeGuardConstructor.ts, 121, 3)) +} + diff --git a/tests/baselines/reference/typeGuardConstructor.types b/tests/baselines/reference/typeGuardConstructor.types new file mode 100644 index 0000000000000..b46bb391da714 --- /dev/null +++ b/tests/baselines/reference/typeGuardConstructor.types @@ -0,0 +1,392 @@ +=== tests/cases/compiler/typeGuardConstructor.ts === +// Typical case +class Foo { +>Foo : Foo + + prop1: string; +>prop1 : string +} + +let foo: Foo | number; +>foo : number | Foo + +if (foo.constructor == Foo) { +>foo.constructor == Foo : boolean +>foo.constructor : Function +>foo : number | Foo +>constructor : Function +>Foo : typeof Foo + + foo.prop1; // string +>foo.prop1 : string +>foo : Foo +>prop1 : string +} +if (foo["constructor"] == Foo) { +>foo["constructor"] == Foo : boolean +>foo["constructor"] : Function +>foo : number | Foo +>"constructor" : "constructor" +>Foo : typeof Foo + + foo.prop1; // string +>foo.prop1 : string +>foo : Foo +>prop1 : string +} +if (foo.constructor === Foo) { +>foo.constructor === Foo : boolean +>foo.constructor : Function +>foo : number | Foo +>constructor : Function +>Foo : typeof Foo + + foo.prop1; // string +>foo.prop1 : string +>foo : Foo +>prop1 : string +} +if (foo["constructor"] === Foo) { +>foo["constructor"] === Foo : boolean +>foo["constructor"] : Function +>foo : number | Foo +>"constructor" : "constructor" +>Foo : typeof Foo + + foo.prop1; // string +>foo.prop1 : string +>foo : Foo +>prop1 : string +} +if (Foo == foo.constructor) { +>Foo == foo.constructor : boolean +>Foo : typeof Foo +>foo.constructor : Function +>foo : number | Foo +>constructor : Function + + foo.prop1; // string +>foo.prop1 : string +>foo : Foo +>prop1 : string +} +if (Foo == foo["constructor"]) { +>Foo == foo["constructor"] : boolean +>Foo : typeof Foo +>foo["constructor"] : Function +>foo : number | Foo +>"constructor" : "constructor" + + foo.prop1; // string +>foo.prop1 : string +>foo : Foo +>prop1 : string +} +if (Foo === foo.constructor) { +>Foo === foo.constructor : boolean +>Foo : typeof Foo +>foo.constructor : Function +>foo : number | Foo +>constructor : Function + + foo.prop1; // string +>foo.prop1 : string +>foo : Foo +>prop1 : string +} +if (Foo === foo["constructor"]) { +>Foo === foo["constructor"] : boolean +>Foo : typeof Foo +>foo["constructor"] : Function +>foo : number | Foo +>"constructor" : "constructor" + + foo.prop1; // string +>foo.prop1 : string +>foo : Foo +>prop1 : string +} + +if (foo.constructor != Foo) { +>foo.constructor != Foo : boolean +>foo.constructor : Function +>foo : number | Foo +>constructor : Function +>Foo : typeof Foo + + foo.prop1; // ERROR +>foo.prop1 : any +>foo : number | Foo +>prop1 : any +} +if (foo["constructor"] != Foo) { +>foo["constructor"] != Foo : boolean +>foo["constructor"] : Function +>foo : number | Foo +>"constructor" : "constructor" +>Foo : typeof Foo + + foo.prop1; // ERROR +>foo.prop1 : any +>foo : number | Foo +>prop1 : any +} +if (foo.constructor !== Foo) { +>foo.constructor !== Foo : boolean +>foo.constructor : Function +>foo : number | Foo +>constructor : Function +>Foo : typeof Foo + + foo.prop1; // ERROR +>foo.prop1 : any +>foo : number | Foo +>prop1 : any +} +if (foo["constructor"] !== Foo) { +>foo["constructor"] !== Foo : boolean +>foo["constructor"] : Function +>foo : number | Foo +>"constructor" : "constructor" +>Foo : typeof Foo + + foo.prop1; // ERROR +>foo.prop1 : any +>foo : number | Foo +>prop1 : any +} +if (Foo != foo.constructor) { +>Foo != foo.constructor : boolean +>Foo : typeof Foo +>foo.constructor : Function +>foo : number | Foo +>constructor : Function + + foo.prop1; // ERROR +>foo.prop1 : any +>foo : number | Foo +>prop1 : any +} +if (Foo != foo["constructor"]) { +>Foo != foo["constructor"] : boolean +>Foo : typeof Foo +>foo["constructor"] : Function +>foo : number | Foo +>"constructor" : "constructor" + + foo.prop1; // ERROR +>foo.prop1 : any +>foo : number | Foo +>prop1 : any +} +if (Foo !== foo.constructor) { +>Foo !== foo.constructor : boolean +>Foo : typeof Foo +>foo.constructor : Function +>foo : number | Foo +>constructor : Function + + foo.prop1; // ERROR +>foo.prop1 : any +>foo : number | Foo +>prop1 : any +} +if (Foo !== foo["constructor"]) { +>Foo !== foo["constructor"] : boolean +>Foo : typeof Foo +>foo["constructor"] : Function +>foo : number | Foo +>"constructor" : "constructor" + + foo.prop1; // ERROR +>foo.prop1 : any +>foo : number | Foo +>prop1 : any +} + + +// Derived class case +class Bar extends Foo { +>Bar : Bar +>Foo : Foo + + prop2: number; +>prop2 : number +} + +let bar: Bar | boolean; +>bar : boolean | Bar + +if (bar.constructor === Bar) { +>bar.constructor === Bar : boolean +>bar.constructor : Function +>bar : boolean | Bar +>constructor : Function +>Bar : typeof Bar + + bar.prop1; // string +>bar.prop1 : string +>bar : Bar +>prop1 : string + + bar.prop2; // number +>bar.prop2 : number +>bar : Bar +>prop2 : number +} +if (bar.constructor === Foo) { +>bar.constructor === Foo : boolean +>bar.constructor : Function +>bar : boolean | Bar +>constructor : Function +>Foo : typeof Foo + + bar.prop1; // string +>bar.prop1 : string +>bar : Bar +>prop1 : string + + bar.prop2; // number +>bar.prop2 : number +>bar : Bar +>prop2 : number +} + + +// Union of primitives, number, arrays, and Foo +var x: number | "hello" | "world" | true | false | number[] | string[] | Foo; +>x : number | boolean | Foo | "hello" | "world" | number[] | string[] +>true : true +>false : false + +if (x.constructor === Number) { +>x.constructor === Number : boolean +>x.constructor : Function +>x : number | boolean | Foo | "hello" | "world" | number[] | string[] +>constructor : Function +>Number : NumberConstructor + + x; // number +>x : number +} + +if (x.constructor === String) { +>x.constructor === String : boolean +>x.constructor : Function +>x : number | boolean | Foo | "hello" | "world" | number[] | string[] +>constructor : Function +>String : StringConstructor + + x; // "hello" | "world" +>x : "hello" | "world" +} + +if (x.constructor === Boolean) { +>x.constructor === Boolean : boolean +>x.constructor : Function +>x : number | boolean | Foo | "hello" | "world" | number[] | string[] +>constructor : Function +>Boolean : BooleanConstructor + + x; // boolean +>x : boolean +} + +if (x.constructor === Array) { +>x.constructor === Array : boolean +>x.constructor : Function +>x : number | boolean | Foo | "hello" | "world" | number[] | string[] +>constructor : Function +>Array : ArrayConstructor + + x; // number[] | string[] +>x : number[] | string[] +} + +if (x.constructor === Function) { +>x.constructor === Function : boolean +>x.constructor : Function +>x : number | boolean | Foo | "hello" | "world" | number[] | string[] +>constructor : Function +>Function : FunctionConstructor + + x; // declaredType +>x : number | boolean | Foo | "hello" | "world" | number[] | string[] +} + +if (x.constructor === Foo) { +>x.constructor === Foo : boolean +>x.constructor : Function +>x : number | boolean | Foo | "hello" | "world" | number[] | string[] +>constructor : Function +>Foo : typeof Foo + + x; // Foo +>x : Foo + + x.prop1; // string +>x.prop1 : string +>x : Foo +>prop1 : string +} + + +// Narrowing any +let a: any; +>a : any + +if (a.constructor === Foo) { +>a.constructor === Foo : boolean +>a.constructor : any +>a : any +>constructor : any +>Foo : typeof Foo + + a; // Foo +>a : Foo +} +if (a.constructor === "hello") { +>a.constructor === "hello" : boolean +>a.constructor : any +>a : any +>constructor : any +>"hello" : "hello" + + a; // any +>a : any +} +if (a.constructor === Function) { +>a.constructor === Function : boolean +>a.constructor : any +>a : any +>constructor : any +>Function : FunctionConstructor + + a; // any +>a : any +} + + +// If for some reason someone defines a type with it's own constructor property +type S = { +>S : S + + constructor: () => void; +>constructor : () => void + +}; + +let s: S | string; +>s : string | S + +if (s.constructor === String) { +>s.constructor === String : boolean +>s.constructor : Function | (() => void) +>s : string | S +>constructor : Function | (() => void) +>String : StringConstructor + + s; // string +>s : string +} + diff --git a/tests/cases/compiler/typeGuardConstructor.ts b/tests/cases/compiler/typeGuardConstructor.ts new file mode 100644 index 0000000000000..0bbfa3bfb9c46 --- /dev/null +++ b/tests/cases/compiler/typeGuardConstructor.ts @@ -0,0 +1,126 @@ +// Typical case +class Foo { + prop1: string; +} + +let foo: Foo | number; +if (foo.constructor == Foo) { + foo.prop1; // string +} +if (foo["constructor"] == Foo) { + foo.prop1; // string +} +if (foo.constructor === Foo) { + foo.prop1; // string +} +if (foo["constructor"] === Foo) { + foo.prop1; // string +} +if (Foo == foo.constructor) { + foo.prop1; // string +} +if (Foo == foo["constructor"]) { + foo.prop1; // string +} +if (Foo === foo.constructor) { + foo.prop1; // string +} +if (Foo === foo["constructor"]) { + foo.prop1; // string +} + +if (foo.constructor != Foo) { + foo.prop1; // ERROR +} +if (foo["constructor"] != Foo) { + foo.prop1; // ERROR +} +if (foo.constructor !== Foo) { + foo.prop1; // ERROR +} +if (foo["constructor"] !== Foo) { + foo.prop1; // ERROR +} +if (Foo != foo.constructor) { + foo.prop1; // ERROR +} +if (Foo != foo["constructor"]) { + foo.prop1; // ERROR +} +if (Foo !== foo.constructor) { + foo.prop1; // ERROR +} +if (Foo !== foo["constructor"]) { + foo.prop1; // ERROR +} + + +// Derived class case +class Bar extends Foo { + prop2: number; +} + +let bar: Bar | boolean; +if (bar.constructor === Bar) { + bar.prop1; // string + bar.prop2; // number +} +if (bar.constructor === Foo) { + bar.prop1; // string + bar.prop2; // number +} + + +// Union of primitives, number, arrays, and Foo +var x: number | "hello" | "world" | true | false | number[] | string[] | Foo; + +if (x.constructor === Number) { + x; // number +} + +if (x.constructor === String) { + x; // "hello" | "world" +} + +if (x.constructor === Boolean) { + x; // boolean +} + +if (x.constructor === Array) { + x; // number[] | string[] +} + +if (x.constructor === Function) { + x; // declaredType +} + +if (x.constructor === Foo) { + x; // Foo + x.prop1; // string +} + + +// Narrowing any +let a: any; + +if (a.constructor === Foo) { + a; // Foo +} +if (a.constructor === "hello") { + a; // any +} +if (a.constructor === Function) { + a; // any +} + + +// If for some reason someone defines a type with it's own constructor property +type S = { + constructor: () => void; +}; + +let s: S | string; + +if (s.constructor === String) { + s; // string +}