mirror of
https://github.com/microsoft/TypeScript.git
synced 2025-11-18 17:21:48 +00:00
Fix erroneous optional chain narrowing (#36145)
* Not all optional chains are narrowable references * Add regression test
This commit is contained in:
+10
-7
@@ -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");
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user