From 39c4e8ad1f63b54d642d7896874f7c0f98cf4699 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Fri, 3 Jun 2016 17:30:49 -0700 Subject: [PATCH 1/3] Remove unnecessary restrictions in property access narrowing --- src/compiler/checker.ts | 20 +++++--------------- 1 file changed, 5 insertions(+), 15 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index b8f078ea9a5..c538134e6d8 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -9991,24 +9991,14 @@ namespace ts { } const propType = getTypeOfSymbol(prop); + // Only compute control flow type if this is a property access expression that isn't an + // assignment target, and the referenced property was declared as a variable, property, + // accessor, or optional method. if (node.kind !== SyntaxKind.PropertyAccessExpression || isAssignmentTarget(node) || - !(propType.flags & TypeFlags.Union) && !(prop.flags & (SymbolFlags.Variable | SymbolFlags.Property | SymbolFlags.Accessor))) { + !(prop.flags & (SymbolFlags.Variable | SymbolFlags.Property | SymbolFlags.Accessor)) && + !(prop.flags & SymbolFlags.Method && propType.flags & TypeFlags.Union)) { return propType; } - const leftmostNode = getLeftmostIdentifierOrThis(node); - if (!leftmostNode) { - return propType; - } - if (leftmostNode.kind === SyntaxKind.Identifier) { - const leftmostSymbol = getExportSymbolOfValueSymbolIfExported(getResolvedSymbol(leftmostNode)); - if (!leftmostSymbol) { - return propType; - } - const declaration = leftmostSymbol.valueDeclaration; - if (!declaration || declaration.kind !== SyntaxKind.VariableDeclaration && declaration.kind !== SyntaxKind.Parameter && declaration.kind !== SyntaxKind.BindingElement) { - return propType; - } - } return getFlowTypeOfReference(node, propType, /*assumeInitialized*/ true, /*includeOuterFunctions*/ false); } From a5e9071a2f533ff18c51974c73ffaeeb686230b1 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Fri, 3 Jun 2016 17:31:16 -0700 Subject: [PATCH 2/3] Fix fourslash test --- .../quickInfoOnNarrowedTypeInModule.ts | 32 +++++++++++++------ 1 file changed, 22 insertions(+), 10 deletions(-) diff --git a/tests/cases/fourslash/quickInfoOnNarrowedTypeInModule.ts b/tests/cases/fourslash/quickInfoOnNarrowedTypeInModule.ts index 1f95de35403..cfeaabfbb86 100644 --- a/tests/cases/fourslash/quickInfoOnNarrowedTypeInModule.ts +++ b/tests/cases/fourslash/quickInfoOnNarrowedTypeInModule.ts @@ -50,14 +50,26 @@ goTo.marker('6'); verify.quickInfoIs('var m.exportedStrOrNum: string'); verify.completionListContains("exportedStrOrNum", "var m.exportedStrOrNum: string"); -['7', '8', '9'].forEach((marker, index, arr) => { - goTo.marker(marker); - verify.quickInfoIs('var m.exportedStrOrNum: string | number'); - verify.completionListContains("exportedStrOrNum", "var m.exportedStrOrNum: string | number"); -}); +goTo.marker('7'); +verify.quickInfoIs('var m.exportedStrOrNum: string | number'); +verify.completionListContains("exportedStrOrNum", "var m.exportedStrOrNum: string | number"); -['7', '8', '9'].forEach((marker, index, arr) => { - goTo.marker(marker); - verify.quickInfoIs('var m.exportedStrOrNum: string | number'); - verify.memberListContains("exportedStrOrNum", "var m.exportedStrOrNum: string | number"); -}); \ No newline at end of file +goTo.marker('8'); +verify.quickInfoIs('var m.exportedStrOrNum: number'); +verify.completionListContains("exportedStrOrNum", "var m.exportedStrOrNum: number"); + +goTo.marker('9'); +verify.quickInfoIs('var m.exportedStrOrNum: string'); +verify.completionListContains("exportedStrOrNum", "var m.exportedStrOrNum: string"); + +goTo.marker('7'); +verify.quickInfoIs('var m.exportedStrOrNum: string | number'); +verify.memberListContains("exportedStrOrNum", "var m.exportedStrOrNum: string | number"); + +goTo.marker('8'); +verify.quickInfoIs('var m.exportedStrOrNum: number'); +verify.memberListContains("exportedStrOrNum", "var m.exportedStrOrNum: number"); + +goTo.marker('9'); +verify.quickInfoIs('var m.exportedStrOrNum: string'); +verify.memberListContains("exportedStrOrNum", "var m.exportedStrOrNum: string"); \ No newline at end of file From 87ee72b25a10588ca5573c377e3fb24eeca5a096 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Fri, 3 Jun 2016 17:31:28 -0700 Subject: [PATCH 3/3] Add regression test --- .../reference/classStaticPropertyTypeGuard.js | 32 +++++++++++++++++++ .../classStaticPropertyTypeGuard.symbols | 29 +++++++++++++++++ .../classStaticPropertyTypeGuard.types | 31 ++++++++++++++++++ .../compiler/classStaticPropertyTypeGuard.ts | 15 +++++++++ 4 files changed, 107 insertions(+) create mode 100644 tests/baselines/reference/classStaticPropertyTypeGuard.js create mode 100644 tests/baselines/reference/classStaticPropertyTypeGuard.symbols create mode 100644 tests/baselines/reference/classStaticPropertyTypeGuard.types create mode 100644 tests/cases/compiler/classStaticPropertyTypeGuard.ts diff --git a/tests/baselines/reference/classStaticPropertyTypeGuard.js b/tests/baselines/reference/classStaticPropertyTypeGuard.js new file mode 100644 index 00000000000..872f78db4c4 --- /dev/null +++ b/tests/baselines/reference/classStaticPropertyTypeGuard.js @@ -0,0 +1,32 @@ +//// [classStaticPropertyTypeGuard.ts] + +// Repro from #8923 + +class A { + private static _a: string | undefined; + + public get a(): string { + if (A._a) { + return A._a; // is possibly null or undefined. + } + return A._a = 'helloworld'; + } +} + +//// [classStaticPropertyTypeGuard.js] +// Repro from #8923 +var A = (function () { + function A() { + } + Object.defineProperty(A.prototype, "a", { + get: function () { + if (A._a) { + return A._a; // is possibly null or undefined. + } + return A._a = 'helloworld'; + }, + enumerable: true, + configurable: true + }); + return A; +}()); diff --git a/tests/baselines/reference/classStaticPropertyTypeGuard.symbols b/tests/baselines/reference/classStaticPropertyTypeGuard.symbols new file mode 100644 index 00000000000..c4d2acd50b0 --- /dev/null +++ b/tests/baselines/reference/classStaticPropertyTypeGuard.symbols @@ -0,0 +1,29 @@ +=== tests/cases/compiler/classStaticPropertyTypeGuard.ts === + +// Repro from #8923 + +class A { +>A : Symbol(A, Decl(classStaticPropertyTypeGuard.ts, 0, 0)) + + private static _a: string | undefined; +>_a : Symbol(A._a, Decl(classStaticPropertyTypeGuard.ts, 3, 9)) + + public get a(): string { +>a : Symbol(A.a, Decl(classStaticPropertyTypeGuard.ts, 4, 42)) + + if (A._a) { +>A._a : Symbol(A._a, Decl(classStaticPropertyTypeGuard.ts, 3, 9)) +>A : Symbol(A, Decl(classStaticPropertyTypeGuard.ts, 0, 0)) +>_a : Symbol(A._a, Decl(classStaticPropertyTypeGuard.ts, 3, 9)) + + return A._a; // is possibly null or undefined. +>A._a : Symbol(A._a, Decl(classStaticPropertyTypeGuard.ts, 3, 9)) +>A : Symbol(A, Decl(classStaticPropertyTypeGuard.ts, 0, 0)) +>_a : Symbol(A._a, Decl(classStaticPropertyTypeGuard.ts, 3, 9)) + } + return A._a = 'helloworld'; +>A._a : Symbol(A._a, Decl(classStaticPropertyTypeGuard.ts, 3, 9)) +>A : Symbol(A, Decl(classStaticPropertyTypeGuard.ts, 0, 0)) +>_a : Symbol(A._a, Decl(classStaticPropertyTypeGuard.ts, 3, 9)) + } +} diff --git a/tests/baselines/reference/classStaticPropertyTypeGuard.types b/tests/baselines/reference/classStaticPropertyTypeGuard.types new file mode 100644 index 00000000000..f89854c919d --- /dev/null +++ b/tests/baselines/reference/classStaticPropertyTypeGuard.types @@ -0,0 +1,31 @@ +=== tests/cases/compiler/classStaticPropertyTypeGuard.ts === + +// Repro from #8923 + +class A { +>A : A + + private static _a: string | undefined; +>_a : string | undefined + + public get a(): string { +>a : string + + if (A._a) { +>A._a : string | undefined +>A : typeof A +>_a : string | undefined + + return A._a; // is possibly null or undefined. +>A._a : string +>A : typeof A +>_a : string + } + return A._a = 'helloworld'; +>A._a = 'helloworld' : string +>A._a : string | undefined +>A : typeof A +>_a : string | undefined +>'helloworld' : string + } +} diff --git a/tests/cases/compiler/classStaticPropertyTypeGuard.ts b/tests/cases/compiler/classStaticPropertyTypeGuard.ts new file mode 100644 index 00000000000..93655cfb092 --- /dev/null +++ b/tests/cases/compiler/classStaticPropertyTypeGuard.ts @@ -0,0 +1,15 @@ +// @strictNullChecks: true +// @target: ES5 + +// Repro from #8923 + +class A { + private static _a: string | undefined; + + public get a(): string { + if (A._a) { + return A._a; // is possibly null or undefined. + } + return A._a = 'helloworld'; + } +} \ No newline at end of file