diff --git a/src/compiler/binder.ts b/src/compiler/binder.ts index 647ff65143a..af63463cdd2 100644 --- a/src/compiler/binder.ts +++ b/src/compiler/binder.ts @@ -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(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((expr.expression).expression)) { + containsNarrowableReference((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((expr).right); } } - return isNarrowableReference(expr); + return containsNarrowableReference(expr); } function createBranchLabel(): FlowLabel { diff --git a/tests/baselines/reference/controlFlowOptionalChain.errors.txt b/tests/baselines/reference/controlFlowOptionalChain.errors.txt index 3606034699b..df3c5a55534 100644 --- a/tests/baselines/reference/controlFlowOptionalChain.errors.txt +++ b/tests/baselines/reference/controlFlowOptionalChain.errors.txt @@ -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"); + } + } \ No newline at end of file diff --git a/tests/baselines/reference/controlFlowOptionalChain.js b/tests/baselines/reference/controlFlowOptionalChain.js index cd7b9a92377..dc2361d8609 100644 --- a/tests/baselines/reference/controlFlowOptionalChain.js +++ b/tests/baselines/reference/controlFlowOptionalChain.js @@ -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"); + } +} diff --git a/tests/baselines/reference/controlFlowOptionalChain.symbols b/tests/baselines/reference/controlFlowOptionalChain.symbols index 42c32487f6e..5354a1ef95b 100644 --- a/tests/baselines/reference/controlFlowOptionalChain.symbols +++ b/tests/baselines/reference/controlFlowOptionalChain.symbols @@ -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, --, --)) + } +} + diff --git a/tests/baselines/reference/controlFlowOptionalChain.types b/tests/baselines/reference/controlFlowOptionalChain.types index 37a688ca25c..23aa740074c 100644 --- a/tests/baselines/reference/controlFlowOptionalChain.types +++ b/tests/baselines/reference/controlFlowOptionalChain.types @@ -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" + } +} + diff --git a/tests/cases/conformance/controlFlow/controlFlowOptionalChain.ts b/tests/cases/conformance/controlFlow/controlFlowOptionalChain.ts index 443716ef8c3..e5495a313aa 100644 --- a/tests/cases/conformance/controlFlow/controlFlowOptionalChain.ts +++ b/tests/cases/conformance/controlFlow/controlFlowOptionalChain.ts @@ -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"); + } +}