Fix erroneous optional chain narrowing (#36145)

* Not all optional chains are narrowable references

* Add regression test
This commit is contained in:
Anders Hejlsberg
2020-01-16 16:49:51 -08:00
committed by GitHub
parent b2a7d42032
commit 8517df6fa2
6 changed files with 128 additions and 8 deletions
+10 -7
View File
@@ -861,7 +861,7 @@ namespace ts {
case SyntaxKind.ThisKeyword:
case SyntaxKind.PropertyAccessExpression:
case SyntaxKind.ElementAccessExpression:
return isNarrowableReference(expr);
return containsNarrowableReference(expr);
case SyntaxKind.CallExpression:
return hasNarrowableArgument(<CallExpression>expr);
case SyntaxKind.ParenthesizedExpression:
@@ -879,20 +879,23 @@ namespace ts {
function isNarrowableReference(expr: Expression): boolean {
return expr.kind === SyntaxKind.Identifier || expr.kind === SyntaxKind.ThisKeyword || expr.kind === SyntaxKind.SuperKeyword ||
(isPropertyAccessExpression(expr) || isNonNullExpression(expr) || isParenthesizedExpression(expr)) && isNarrowableReference(expr.expression) ||
isElementAccessExpression(expr) && isStringOrNumericLiteralLike(expr.argumentExpression) && isNarrowableReference(expr.expression) ||
isOptionalChain(expr);
isElementAccessExpression(expr) && isStringOrNumericLiteralLike(expr.argumentExpression) && isNarrowableReference(expr.expression);
}
function containsNarrowableReference(expr: Expression): boolean {
return isNarrowableReference(expr) || isOptionalChain(expr) && containsNarrowableReference(expr.expression);
}
function hasNarrowableArgument(expr: CallExpression) {
if (expr.arguments) {
for (const argument of expr.arguments) {
if (isNarrowableReference(argument)) {
if (containsNarrowableReference(argument)) {
return true;
}
}
}
if (expr.expression.kind === SyntaxKind.PropertyAccessExpression &&
isNarrowableReference((<PropertyAccessExpression>expr.expression).expression)) {
containsNarrowableReference((<PropertyAccessExpression>expr.expression).expression)) {
return true;
}
return false;
@@ -909,7 +912,7 @@ namespace ts {
function isNarrowingBinaryExpression(expr: BinaryExpression) {
switch (expr.operatorToken.kind) {
case SyntaxKind.EqualsToken:
return isNarrowableReference(expr.left);
return containsNarrowableReference(expr.left);
case SyntaxKind.EqualsEqualsToken:
case SyntaxKind.ExclamationEqualsToken:
case SyntaxKind.EqualsEqualsEqualsToken:
@@ -938,7 +941,7 @@ namespace ts {
return isNarrowableOperand((<BinaryExpression>expr).right);
}
}
return isNarrowableReference(expr);
return containsNarrowableReference(expr);
}
function createBranchLabel(): FlowLabel {
@@ -764,4 +764,16 @@ tests/cases/conformance/controlFlow/controlFlowOptionalChain.ts(567,21): error T
someFunction(someObject);
someFunction(undefined);
// Repro from #35970
let i = 0;
declare const arr: { tag: ("left" | "right") }[];
while (arr[i]?.tag === "left") {
i += 1;
if (arr[i]?.tag === "right") {
console.log("I should ALSO be reachable");
}
}
@@ -576,11 +576,23 @@ const someObject: SomeObject = {
someFunction(someObject);
someFunction(undefined);
// Repro from #35970
let i = 0;
declare const arr: { tag: ("left" | "right") }[];
while (arr[i]?.tag === "left") {
i += 1;
if (arr[i]?.tag === "right") {
console.log("I should ALSO be reachable");
}
}
//// [controlFlowOptionalChain.js]
"use strict";
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t;
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u, _v;
var a;
o === null || o === void 0 ? void 0 : o[a = 1];
a.toString();
@@ -1071,3 +1083,11 @@ var someObject = {
};
someFunction(someObject);
someFunction(undefined);
// Repro from #35970
var i = 0;
while (((_u = arr[i]) === null || _u === void 0 ? void 0 : _u.tag) === "left") {
i += 1;
if (((_v = arr[i]) === null || _v === void 0 ? void 0 : _v.tag) === "right") {
console.log("I should ALSO be reachable");
}
}
@@ -1801,3 +1801,34 @@ someFunction(undefined);
>someFunction : Symbol(someFunction, Decl(controlFlowOptionalChain.ts, 561, 42))
>undefined : Symbol(undefined)
// Repro from #35970
let i = 0;
>i : Symbol(i, Decl(controlFlowOptionalChain.ts, 580, 3))
declare const arr: { tag: ("left" | "right") }[];
>arr : Symbol(arr, Decl(controlFlowOptionalChain.ts, 581, 13))
>tag : Symbol(tag, Decl(controlFlowOptionalChain.ts, 581, 20))
while (arr[i]?.tag === "left") {
>arr[i]?.tag : Symbol(tag, Decl(controlFlowOptionalChain.ts, 581, 20))
>arr : Symbol(arr, Decl(controlFlowOptionalChain.ts, 581, 13))
>i : Symbol(i, Decl(controlFlowOptionalChain.ts, 580, 3))
>tag : Symbol(tag, Decl(controlFlowOptionalChain.ts, 581, 20))
i += 1;
>i : Symbol(i, Decl(controlFlowOptionalChain.ts, 580, 3))
if (arr[i]?.tag === "right") {
>arr[i]?.tag : Symbol(tag, Decl(controlFlowOptionalChain.ts, 581, 20))
>arr : Symbol(arr, Decl(controlFlowOptionalChain.ts, 581, 13))
>i : Symbol(i, Decl(controlFlowOptionalChain.ts, 580, 3))
>tag : Symbol(tag, Decl(controlFlowOptionalChain.ts, 581, 20))
console.log("I should ALSO be reachable");
>console.log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --))
>console : Symbol(console, Decl(lib.dom.d.ts, --, --))
>log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --))
}
}
@@ -2031,3 +2031,45 @@ someFunction(undefined);
>someFunction : (someOptionalObject: SomeObject | undefined) => void
>undefined : undefined
// Repro from #35970
let i = 0;
>i : number
>0 : 0
declare const arr: { tag: ("left" | "right") }[];
>arr : { tag: "left" | "right"; }[]
>tag : "left" | "right"
while (arr[i]?.tag === "left") {
>arr[i]?.tag === "left" : boolean
>arr[i]?.tag : "left" | "right"
>arr[i] : { tag: "left" | "right"; }
>arr : { tag: "left" | "right"; }[]
>i : number
>tag : "left" | "right"
>"left" : "left"
i += 1;
>i += 1 : number
>i : number
>1 : 1
if (arr[i]?.tag === "right") {
>arr[i]?.tag === "right" : boolean
>arr[i]?.tag : "left" | "right"
>arr[i] : { tag: "left" | "right"; }
>arr : { tag: "left" | "right"; }[]
>i : number
>tag : "left" | "right"
>"right" : "right"
console.log("I should ALSO be reachable");
>console.log("I should ALSO be reachable") : void
>console.log : (message?: any, ...optionalParams: any[]) => void
>console : Console
>log : (message?: any, ...optionalParams: any[]) => void
>"I should ALSO be reachable" : "I should ALSO be reachable"
}
}
@@ -578,3 +578,15 @@ const someObject: SomeObject = {
someFunction(someObject);
someFunction(undefined);
// Repro from #35970
let i = 0;
declare const arr: { tag: ("left" | "right") }[];
while (arr[i]?.tag === "left") {
i += 1;
if (arr[i]?.tag === "right") {
console.log("I should ALSO be reachable");
}
}