Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 33 additions & 4 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12002,9 +12002,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.
Expand Down Expand Up @@ -12046,7 +12046,6 @@ namespace ts {
checkNestedBlockScopedBinding(node, symbol);

const type = getDeclaredOrApparentType(localOrExportSymbol, node);
const declaration = localOrExportSymbol.valueDeclaration;
const assignmentKind = getAssignmentTargetKind(node);

if (assignmentKind) {
Expand All @@ -12060,11 +12059,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<Declaration>(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.
Expand All @@ -12083,7 +12097,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);
Expand Down Expand Up @@ -24721,4 +24735,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;
}
}
}
43 changes: 43 additions & 0 deletions tests/baselines/reference/narrowedImports.js
Original file line number Diff line number Diff line change
@@ -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;
61 changes: 61 additions & 0 deletions tests/baselines/reference/narrowedImports.symbols
Original file line number Diff line number Diff line change
@@ -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))

66 changes: 66 additions & 0 deletions tests/baselines/reference/narrowedImports.types
Original file line number Diff line number Diff line change
@@ -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

18 changes: 18 additions & 0 deletions tests/baselines/reference/narrowedImports_assumeInitialized.js
Original file line number Diff line number Diff line change
@@ -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;
Original file line number Diff line number Diff line change
@@ -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))

19 changes: 19 additions & 0 deletions tests/baselines/reference/narrowedImports_assumeInitialized.types
Original file line number Diff line number Diff line change
@@ -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

24 changes: 24 additions & 0 deletions tests/cases/compiler/narrowedImports.ts
Original file line number Diff line number Diff line change
@@ -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;
11 changes: 11 additions & 0 deletions tests/cases/compiler/narrowedImports_assumeInitialized.ts
Original file line number Diff line number Diff line change
@@ -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;