diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 7358eb8b771..d96701041d2 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -12009,9 +12009,9 @@ namespace ts { } const localOrExportSymbol = getExportSymbolOfValueSymbolIfExported(symbol); + let declaration = localOrExportSymbol.valueDeclaration; if (localOrExportSymbol.flags & SymbolFlags.Class) { - const declaration = localOrExportSymbol.valueDeclaration; // Due to the emit for class decorators, any reference to the class from inside of the class body // must instead be rewritten to point to a temporary variable to avoid issues with the double-bind // behavior of class names in ES6. @@ -12053,7 +12053,6 @@ namespace ts { checkNestedBlockScopedBinding(node, symbol); const type = getDeclaredOrApparentType(localOrExportSymbol, node); - const declaration = localOrExportSymbol.valueDeclaration; const assignmentKind = getAssignmentTargetKind(node); if (assignmentKind) { @@ -12067,11 +12066,26 @@ namespace ts { } } + const isAlias = localOrExportSymbol.flags & SymbolFlags.Alias; + // We only narrow variables and parameters occurring in a non-assignment position. For all other // entities we simply return the declared type. - if (!(localOrExportSymbol.flags & SymbolFlags.Variable) || assignmentKind === AssignmentKind.Definite || !declaration) { + if (localOrExportSymbol.flags & SymbolFlags.Variable) { + if (assignmentKind === AssignmentKind.Definite) { + return type; + } + } + else if (isAlias) { + declaration = find(symbol.declarations, isSomeImportDeclaration); + } + else { return type; } + + if (!declaration) { + return type; + } + // The declaration container is the innermost function that encloses the declaration of the variable // or parameter. The flow container is the innermost function starting with which we analyze the control // flow graph to determine the control flow based type. @@ -12090,7 +12104,7 @@ namespace ts { // We only look for uninitialized variables in strict null checking mode, and only when we can analyze // the entire control flow graph from the variable's declaration (i.e. when the flow container and // declaration container are the same). - const assumeInitialized = isParameter || isOuterVariable || + const assumeInitialized = isParameter || isAlias || isOuterVariable || type !== autoType && type !== autoArrayType && (!strictNullChecks || (type.flags & TypeFlags.Any) !== 0 || isInTypeQuery(node) || node.parent.kind === SyntaxKind.ExportSpecifier) || node.parent.kind === SyntaxKind.NonNullExpression || isInAmbientContext(declaration); @@ -24776,4 +24790,19 @@ namespace ts { return isDeclarationName(name); } } + + function isSomeImportDeclaration(decl: Node): boolean { + switch (decl.kind) { + case SyntaxKind.ImportClause: // For default import + case SyntaxKind.ImportEqualsDeclaration: + case SyntaxKind.NamespaceImport: + case SyntaxKind.ImportSpecifier: // For rename import `x as y` + return true; + case SyntaxKind.Identifier: + // For regular import, `decl` is an Identifier under the ImportSpecifier. + return decl.parent.kind === SyntaxKind.ImportSpecifier; + default: + return false; + } + } } diff --git a/tests/baselines/reference/narrowedImports.js b/tests/baselines/reference/narrowedImports.js new file mode 100644 index 00000000000..5dba68e84e5 --- /dev/null +++ b/tests/baselines/reference/narrowedImports.js @@ -0,0 +1,43 @@ +//// [tests/cases/compiler/narrowedImports.ts] //// + +//// [a.d.ts] +declare const a0: number | undefined; +export default a0; +export const a1: number | undefined; + +//// [b.d.ts] +declare const b: number | undefined; +declare namespace b {} +export = b; + +//// [x.ts] +import a0, { a1, a1 as a2 } from "./a"; +import * as b0 from "./b"; +import b1 = require("./b"); + +let x: number; + +if (a0) x = a0; +if (a1) x = a1; +if (a2) x = a2; +if (b0) x = b0; +if (b1) x = b1; + + +//// [x.js] +"use strict"; +exports.__esModule = true; +var a_1 = require("./a"); +var b0 = require("./b"); +var b1 = require("./b"); +var x; +if (a_1["default"]) + x = a_1["default"]; +if (a_1.a1) + x = a_1.a1; +if (a_1.a1) + x = a_1.a1; +if (b0) + x = b0; +if (b1) + x = b1; diff --git a/tests/baselines/reference/narrowedImports.symbols b/tests/baselines/reference/narrowedImports.symbols new file mode 100644 index 00000000000..86dc798c837 --- /dev/null +++ b/tests/baselines/reference/narrowedImports.symbols @@ -0,0 +1,61 @@ +=== /x.ts === +import a0, { a1, a1 as a2 } from "./a"; +>a0 : Symbol(a0, Decl(x.ts, 0, 6)) +>a1 : Symbol(a1, Decl(x.ts, 0, 12)) +>a1 : Symbol(a2, Decl(x.ts, 0, 16)) +>a2 : Symbol(a2, Decl(x.ts, 0, 16)) + +import * as b0 from "./b"; +>b0 : Symbol(b0, Decl(x.ts, 1, 6)) + +import b1 = require("./b"); +>b1 : Symbol(b1, Decl(x.ts, 1, 26)) + +let x: number; +>x : Symbol(x, Decl(x.ts, 4, 3)) + +if (a0) x = a0; +>a0 : Symbol(a0, Decl(x.ts, 0, 6)) +>x : Symbol(x, Decl(x.ts, 4, 3)) +>a0 : Symbol(a0, Decl(x.ts, 0, 6)) + +if (a1) x = a1; +>a1 : Symbol(a1, Decl(x.ts, 0, 12)) +>x : Symbol(x, Decl(x.ts, 4, 3)) +>a1 : Symbol(a1, Decl(x.ts, 0, 12)) + +if (a2) x = a2; +>a2 : Symbol(a2, Decl(x.ts, 0, 16)) +>x : Symbol(x, Decl(x.ts, 4, 3)) +>a2 : Symbol(a2, Decl(x.ts, 0, 16)) + +if (b0) x = b0; +>b0 : Symbol(b0, Decl(x.ts, 1, 6)) +>x : Symbol(x, Decl(x.ts, 4, 3)) +>b0 : Symbol(b0, Decl(x.ts, 1, 6)) + +if (b1) x = b1; +>b1 : Symbol(b1, Decl(x.ts, 1, 26)) +>x : Symbol(x, Decl(x.ts, 4, 3)) +>b1 : Symbol(b1, Decl(x.ts, 1, 26)) + +=== /a.d.ts === +declare const a0: number | undefined; +>a0 : Symbol(a0, Decl(a.d.ts, 0, 13)) + +export default a0; +>a0 : Symbol(a0, Decl(a.d.ts, 0, 13)) + +export const a1: number | undefined; +>a1 : Symbol(a1, Decl(a.d.ts, 2, 12)) + +=== /b.d.ts === +declare const b: number | undefined; +>b : Symbol(b, Decl(b.d.ts, 0, 13), Decl(b.d.ts, 0, 36)) + +declare namespace b {} +>b : Symbol(b, Decl(b.d.ts, 0, 13), Decl(b.d.ts, 0, 36)) + +export = b; +>b : Symbol(b, Decl(b.d.ts, 0, 13), Decl(b.d.ts, 0, 36)) + diff --git a/tests/baselines/reference/narrowedImports.types b/tests/baselines/reference/narrowedImports.types new file mode 100644 index 00000000000..153e653046d --- /dev/null +++ b/tests/baselines/reference/narrowedImports.types @@ -0,0 +1,66 @@ +=== /x.ts === +import a0, { a1, a1 as a2 } from "./a"; +>a0 : number | undefined +>a1 : number | undefined +>a1 : number | undefined +>a2 : number | undefined + +import * as b0 from "./b"; +>b0 : number | undefined + +import b1 = require("./b"); +>b1 : number | undefined + +let x: number; +>x : number + +if (a0) x = a0; +>a0 : number | undefined +>x = a0 : number +>x : number +>a0 : number + +if (a1) x = a1; +>a1 : number | undefined +>x = a1 : number +>x : number +>a1 : number + +if (a2) x = a2; +>a2 : number | undefined +>x = a2 : number +>x : number +>a2 : number + +if (b0) x = b0; +>b0 : number | undefined +>x = b0 : number +>x : number +>b0 : number + +if (b1) x = b1; +>b1 : number | undefined +>x = b1 : number +>x : number +>b1 : number + +=== /a.d.ts === +declare const a0: number | undefined; +>a0 : number | undefined + +export default a0; +>a0 : number | undefined + +export const a1: number | undefined; +>a1 : number | undefined + +=== /b.d.ts === +declare const b: number | undefined; +>b : number | undefined + +declare namespace b {} +>b : number | undefined + +export = b; +>b : number | undefined + diff --git a/tests/baselines/reference/narrowedImports_assumeInitialized.js b/tests/baselines/reference/narrowedImports_assumeInitialized.js new file mode 100644 index 00000000000..7a45b05382a --- /dev/null +++ b/tests/baselines/reference/narrowedImports_assumeInitialized.js @@ -0,0 +1,18 @@ +//// [tests/cases/compiler/narrowedImports_assumeInitialized.ts] //// + +//// [a.d.ts] +declare namespace a { + export const x: number; +} +export = a; + +//// [b.ts] +import a = require("./a"); +a.x; + + +//// [b.js] +"use strict"; +exports.__esModule = true; +var a = require("./a"); +a.x; diff --git a/tests/baselines/reference/narrowedImports_assumeInitialized.symbols b/tests/baselines/reference/narrowedImports_assumeInitialized.symbols new file mode 100644 index 00000000000..e7f67d7dd33 --- /dev/null +++ b/tests/baselines/reference/narrowedImports_assumeInitialized.symbols @@ -0,0 +1,19 @@ +=== /b.ts === +import a = require("./a"); +>a : Symbol(a, Decl(b.ts, 0, 0)) + +a.x; +>a.x : Symbol(a.x, Decl(a.d.ts, 1, 16)) +>a : Symbol(a, Decl(b.ts, 0, 0)) +>x : Symbol(a.x, Decl(a.d.ts, 1, 16)) + +=== /a.d.ts === +declare namespace a { +>a : Symbol(a, Decl(a.d.ts, 0, 0)) + + export const x: number; +>x : Symbol(x, Decl(a.d.ts, 1, 16)) +} +export = a; +>a : Symbol(a, Decl(a.d.ts, 0, 0)) + diff --git a/tests/baselines/reference/narrowedImports_assumeInitialized.types b/tests/baselines/reference/narrowedImports_assumeInitialized.types new file mode 100644 index 00000000000..9f6b6fe8bbe --- /dev/null +++ b/tests/baselines/reference/narrowedImports_assumeInitialized.types @@ -0,0 +1,19 @@ +=== /b.ts === +import a = require("./a"); +>a : typeof a + +a.x; +>a.x : number +>a : typeof a +>x : number + +=== /a.d.ts === +declare namespace a { +>a : typeof a + + export const x: number; +>x : number +} +export = a; +>a : typeof a + diff --git a/tests/cases/compiler/narrowedImports.ts b/tests/cases/compiler/narrowedImports.ts new file mode 100644 index 00000000000..7049abfb4ff --- /dev/null +++ b/tests/cases/compiler/narrowedImports.ts @@ -0,0 +1,24 @@ +// @strictNullChecks: true + +// @Filename: /a.d.ts +declare const a0: number | undefined; +export default a0; +export const a1: number | undefined; + +// @Filename: /b.d.ts +declare const b: number | undefined; +declare namespace b {} +export = b; + +// @Filename: /x.ts +import a0, { a1, a1 as a2 } from "./a"; +import * as b0 from "./b"; +import b1 = require("./b"); + +let x: number; + +if (a0) x = a0; +if (a1) x = a1; +if (a2) x = a2; +if (b0) x = b0; +if (b1) x = b1; diff --git a/tests/cases/compiler/narrowedImports_assumeInitialized.ts b/tests/cases/compiler/narrowedImports_assumeInitialized.ts new file mode 100644 index 00000000000..ec651ba9ca6 --- /dev/null +++ b/tests/cases/compiler/narrowedImports_assumeInitialized.ts @@ -0,0 +1,11 @@ +// @strictNullChecks: true + +// @Filename: /a.d.ts +declare namespace a { + export const x: number; +} +export = a; + +// @Filename: /b.ts +import a = require("./a"); +a.x;