Allow to narrow the type of an import (#16658)

* Allow to narrow the type of an import

* Assume alias is initialized
This commit is contained in:
Andy 2017-07-10 09:18:35 -07:00 committed by GitHub
parent ff5d245dcb
commit 12163cc02e
9 changed files with 294 additions and 4 deletions

View file

@ -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<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.
@ -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;
}
}
}

View file

@ -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;

View file

@ -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))

View file

@ -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

View file

@ -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;

View file

@ -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))

View file

@ -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

View file

@ -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;

View file

@ -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;