Merge pull request #10396 from Microsoft/fixNestedLoopTypeGuards

Fix nested loop type guards
This commit is contained in:
Anders Hejlsberg
2016-08-17 14:05:02 -07:00
committed by GitHub
5 changed files with 269 additions and 2 deletions

View File

@@ -8366,13 +8366,18 @@ namespace ts {
// each antecedent code path.
const antecedentTypes: Type[] = [];
let subtypeReduction = false;
let firstAntecedentType: FlowType;
flowLoopNodes[flowLoopCount] = flow;
flowLoopKeys[flowLoopCount] = key;
flowLoopTypes[flowLoopCount] = antecedentTypes;
for (const antecedent of flow.antecedents) {
flowLoopCount++;
const type = getTypeFromFlowType(getTypeAtFlowNode(antecedent));
const flowType = getTypeAtFlowNode(antecedent);
flowLoopCount--;
if (!firstAntecedentType) {
firstAntecedentType = flowType;
}
const type = getTypeFromFlowType(flowType);
// If we see a value appear in the cache it is a sign that control flow analysis
// was restarted and completed by checkExpressionCached. We can simply pick up
// the resulting type and bail out.
@@ -8395,7 +8400,13 @@ namespace ts {
break;
}
}
return cache[key] = getUnionType(antecedentTypes, subtypeReduction);
// The result is incomplete if the first antecedent (the non-looping control flow path)
// is incomplete.
const result = getUnionType(antecedentTypes, subtypeReduction);
if (isIncomplete(firstAntecedentType)) {
return createFlowType(result, /*incomplete*/ true);
}
return cache[key] = result;
}
function isMatchingReferenceDiscriminant(expr: Expression) {

View File

@@ -0,0 +1,63 @@
//// [nestedLoopTypeGuards.ts]
// Repros from #10378
function f1() {
var a: boolean | number | string;
if (typeof a !== 'boolean') {
// a is narrowed to "number | string"
for (var i = 0; i < 1; i++) {
for (var j = 0; j < 1; j++) {}
if (typeof a === 'string') {
// a is narrowed to "string'
for (var j = 0; j < 1; j++) {
a.length; // Should not error here
}
}
}
}
}
function f2() {
var a: string | number;
if (typeof a === 'string') {
while (1) {
while (1) {}
if (typeof a === 'string') {
while (1) {
a.length; // Should not error here
}
}
}
}
}
//// [nestedLoopTypeGuards.js]
// Repros from #10378
function f1() {
var a;
if (typeof a !== 'boolean') {
// a is narrowed to "number | string"
for (var i = 0; i < 1; i++) {
for (var j = 0; j < 1; j++) { }
if (typeof a === 'string') {
// a is narrowed to "string'
for (var j = 0; j < 1; j++) {
a.length; // Should not error here
}
}
}
}
}
function f2() {
var a;
if (typeof a === 'string') {
while (1) {
while (1) { }
if (typeof a === 'string') {
while (1) {
a.length; // Should not error here
}
}
}
}
}

View File

@@ -0,0 +1,66 @@
=== tests/cases/compiler/nestedLoopTypeGuards.ts ===
// Repros from #10378
function f1() {
>f1 : Symbol(f1, Decl(nestedLoopTypeGuards.ts, 0, 0))
var a: boolean | number | string;
>a : Symbol(a, Decl(nestedLoopTypeGuards.ts, 3, 7))
if (typeof a !== 'boolean') {
>a : Symbol(a, Decl(nestedLoopTypeGuards.ts, 3, 7))
// a is narrowed to "number | string"
for (var i = 0; i < 1; i++) {
>i : Symbol(i, Decl(nestedLoopTypeGuards.ts, 6, 16))
>i : Symbol(i, Decl(nestedLoopTypeGuards.ts, 6, 16))
>i : Symbol(i, Decl(nestedLoopTypeGuards.ts, 6, 16))
for (var j = 0; j < 1; j++) {}
>j : Symbol(j, Decl(nestedLoopTypeGuards.ts, 7, 20), Decl(nestedLoopTypeGuards.ts, 10, 24))
>j : Symbol(j, Decl(nestedLoopTypeGuards.ts, 7, 20), Decl(nestedLoopTypeGuards.ts, 10, 24))
>j : Symbol(j, Decl(nestedLoopTypeGuards.ts, 7, 20), Decl(nestedLoopTypeGuards.ts, 10, 24))
if (typeof a === 'string') {
>a : Symbol(a, Decl(nestedLoopTypeGuards.ts, 3, 7))
// a is narrowed to "string'
for (var j = 0; j < 1; j++) {
>j : Symbol(j, Decl(nestedLoopTypeGuards.ts, 7, 20), Decl(nestedLoopTypeGuards.ts, 10, 24))
>j : Symbol(j, Decl(nestedLoopTypeGuards.ts, 7, 20), Decl(nestedLoopTypeGuards.ts, 10, 24))
>j : Symbol(j, Decl(nestedLoopTypeGuards.ts, 7, 20), Decl(nestedLoopTypeGuards.ts, 10, 24))
a.length; // Should not error here
>a.length : Symbol(String.length, Decl(lib.d.ts, --, --))
>a : Symbol(a, Decl(nestedLoopTypeGuards.ts, 3, 7))
>length : Symbol(String.length, Decl(lib.d.ts, --, --))
}
}
}
}
}
function f2() {
>f2 : Symbol(f2, Decl(nestedLoopTypeGuards.ts, 16, 1))
var a: string | number;
>a : Symbol(a, Decl(nestedLoopTypeGuards.ts, 19, 7))
if (typeof a === 'string') {
>a : Symbol(a, Decl(nestedLoopTypeGuards.ts, 19, 7))
while (1) {
while (1) {}
if (typeof a === 'string') {
>a : Symbol(a, Decl(nestedLoopTypeGuards.ts, 19, 7))
while (1) {
a.length; // Should not error here
>a.length : Symbol(String.length, Decl(lib.d.ts, --, --))
>a : Symbol(a, Decl(nestedLoopTypeGuards.ts, 19, 7))
>length : Symbol(String.length, Decl(lib.d.ts, --, --))
}
}
}
}
}

View File

@@ -0,0 +1,96 @@
=== tests/cases/compiler/nestedLoopTypeGuards.ts ===
// Repros from #10378
function f1() {
>f1 : () => void
var a: boolean | number | string;
>a : string | number | boolean
if (typeof a !== 'boolean') {
>typeof a !== 'boolean' : boolean
>typeof a : string
>a : string | number | boolean
>'boolean' : "boolean"
// a is narrowed to "number | string"
for (var i = 0; i < 1; i++) {
>i : number
>0 : number
>i < 1 : boolean
>i : number
>1 : number
>i++ : number
>i : number
for (var j = 0; j < 1; j++) {}
>j : number
>0 : number
>j < 1 : boolean
>j : number
>1 : number
>j++ : number
>j : number
if (typeof a === 'string') {
>typeof a === 'string' : boolean
>typeof a : string
>a : string | number
>'string' : "string"
// a is narrowed to "string'
for (var j = 0; j < 1; j++) {
>j : number
>0 : number
>j < 1 : boolean
>j : number
>1 : number
>j++ : number
>j : number
a.length; // Should not error here
>a.length : number
>a : string
>length : number
}
}
}
}
}
function f2() {
>f2 : () => void
var a: string | number;
>a : string | number
if (typeof a === 'string') {
>typeof a === 'string' : boolean
>typeof a : string
>a : string | number
>'string' : "string"
while (1) {
>1 : number
while (1) {}
>1 : number
if (typeof a === 'string') {
>typeof a === 'string' : boolean
>typeof a : string
>a : string
>'string' : "string"
while (1) {
>1 : number
a.length; // Should not error here
>a.length : number
>a : string
>length : number
}
}
}
}
}

View File

@@ -0,0 +1,31 @@
// Repros from #10378
function f1() {
var a: boolean | number | string;
if (typeof a !== 'boolean') {
// a is narrowed to "number | string"
for (var i = 0; i < 1; i++) {
for (var j = 0; j < 1; j++) {}
if (typeof a === 'string') {
// a is narrowed to "string'
for (var j = 0; j < 1; j++) {
a.length; // Should not error here
}
}
}
}
}
function f2() {
var a: string | number;
if (typeof a === 'string') {
while (1) {
while (1) {}
if (typeof a === 'string') {
while (1) {
a.length; // Should not error here
}
}
}
}
}