From 9ed73ebbbfcc90a9e7969a1da9fa54dd3d3c5c9b Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Tue, 25 Feb 2020 16:14:00 -0800 Subject: [PATCH] Properly handle control flows from returns in try/catch within IIFE (#36901) * Properly handle control flows from returns in try/catch within IIFE * Accept new baselines * Add tests * Accept new baselines * When end of finally is unreachable, end of try statement is too * Add additional test case --- src/compiler/binder.ts | 85 ++--- src/compiler/checker.ts | 51 +-- src/compiler/types.ts | 13 +- src/debug/dbg.ts | 31 +- .../reference/api/tsserverlibrary.d.ts | 12 +- tests/baselines/reference/api/typescript.d.ts | 12 +- .../tryCatchFinallyControlFlow.errors.txt | 148 +++++++- .../reference/tryCatchFinallyControlFlow.js | 259 ++++++++++++++ .../tryCatchFinallyControlFlow.symbols | 237 ++++++++++++- .../tryCatchFinallyControlFlow.types | 327 ++++++++++++++++++ .../compiler/tryCatchFinallyControlFlow.ts | 133 +++++++ 11 files changed, 1202 insertions(+), 106 deletions(-) diff --git a/src/compiler/binder.ts b/src/compiler/binder.ts index 650485a9f0a..54e7c3cdbd4 100644 --- a/src/compiler/binder.ts +++ b/src/compiler/binder.ts @@ -952,6 +952,10 @@ namespace ts { return initFlowNode({ flags: FlowFlags.LoopLabel, antecedents: undefined }); } + function createReduceLabel(target: FlowLabel, antecedents: FlowNode[], antecedent: FlowNode): FlowReduceLabel { + return initFlowNode({ flags: FlowFlags.ReduceLabel, target, antecedents, antecedent }); + } + function setFlowNodeReferenced(flow: FlowNode) { // On first reference we set the Referenced flag, thereafter we set the Shared flag flow.flags |= flow.flags & FlowFlags.Referenced ? FlowFlags.Shared : FlowFlags.Referenced; @@ -1209,35 +1213,36 @@ namespace ts { } function bindTryStatement(node: TryStatement): void { - const preFinallyLabel = createBranchLabel(); // We conservatively assume that *any* code in the try block can cause an exception, but we only need // to track code that causes mutations (because only mutations widen the possible control flow type of - // a variable). The currentExceptionTarget is the target label for control flows that result from - // exceptions. We add all mutation flow nodes as antecedents of this label such that we can analyze them - // as possible antecedents of the start of catch or finally blocks. Furthermore, we add the current - // control flow to represent exceptions that occur before any mutations. + // a variable). The exceptionLabel is the target label for control flows that result from exceptions. + // We add all mutation flow nodes as antecedents of this label such that we can analyze them as possible + // antecedents of the start of catch or finally blocks. Furthermore, we add the current control flow to + // represent exceptions that occur before any mutations. const saveReturnTarget = currentReturnTarget; const saveExceptionTarget = currentExceptionTarget; - currentReturnTarget = createBranchLabel(); - currentExceptionTarget = node.catchClause ? createBranchLabel() : currentReturnTarget; - addAntecedent(currentExceptionTarget, currentFlow); + const normalExitLabel = createBranchLabel(); + const returnLabel = createBranchLabel(); + let exceptionLabel = createBranchLabel(); + if (node.finallyBlock) { + currentReturnTarget = returnLabel; + } + addAntecedent(exceptionLabel, currentFlow); + currentExceptionTarget = exceptionLabel; bind(node.tryBlock); - addAntecedent(preFinallyLabel, currentFlow); - const flowAfterTry = currentFlow; - let flowAfterCatch = unreachableFlow; + addAntecedent(normalExitLabel, currentFlow); if (node.catchClause) { // Start of catch clause is the target of exceptions from try block. - currentFlow = finishFlowLabel(currentExceptionTarget); + currentFlow = finishFlowLabel(exceptionLabel); // The currentExceptionTarget now represents control flows from exceptions in the catch clause. // Effectively, in a try-catch-finally, if an exception occurs in the try block, the catch block // acts like a second try block. - currentExceptionTarget = currentReturnTarget; - addAntecedent(currentExceptionTarget, currentFlow); + exceptionLabel = createBranchLabel(); + addAntecedent(exceptionLabel, currentFlow); + currentExceptionTarget = exceptionLabel; bind(node.catchClause); - addAntecedent(preFinallyLabel, currentFlow); - flowAfterCatch = currentFlow; + addAntecedent(normalExitLabel, currentFlow); } - const exceptionTarget = finishFlowLabel(currentExceptionTarget); currentReturnTarget = saveReturnTarget; currentExceptionTarget = saveExceptionTarget; if (node.finallyBlock) { @@ -1250,35 +1255,33 @@ namespace ts { // When analyzing a control flow graph that starts inside a finally block we want to consider all // five possibilities above. However, when analyzing a control flow graph that starts outside (past) // the finally block, we only want to consider the first two (if we're past a finally block then it - // must have completed normally). To make this possible, we inject two extra nodes into the control - // flow graph: An after-finally with an antecedent of the control flow at the end of the finally - // block, and a pre-finally with an antecedent that represents all exceptional control flows. The - // 'lock' property of the pre-finally references the after-finally, and the after-finally has a - // boolean 'locked' property that we set to true when analyzing a control flow that contained the - // the after-finally node. When the lock associated with a pre-finally is locked, the antecedent of - // the pre-finally (i.e. the exceptional control flows) are skipped. - const preFinallyFlow: PreFinallyFlow = initFlowNode({ flags: FlowFlags.PreFinally, antecedent: exceptionTarget, lock: {} }); - addAntecedent(preFinallyLabel, preFinallyFlow); - currentFlow = finishFlowLabel(preFinallyLabel); + // must have completed normally). Likewise, when analyzing a control flow graph from return statements + // in try or catch blocks in an IIFE, we only want to consider the third. To make this possible, we + // inject a ReduceLabel node into the control flow graph. This node contains an alternate reduced + // set of antecedents for the pre-finally label. As control flow analysis passes by a ReduceLabel + // node, the pre-finally label is temporarily switched to the reduced antecedent set. + const finallyLabel = createBranchLabel(); + finallyLabel.antecedents = concatenate(concatenate(normalExitLabel.antecedents, exceptionLabel.antecedents), returnLabel.antecedents); + currentFlow = finallyLabel; bind(node.finallyBlock); - // If the end of the finally block is reachable, but the end of the try and catch blocks are not, - // convert the current flow to unreachable. For example, 'try { return 1; } finally { ... }' should - // result in an unreachable current control flow. - if (!(currentFlow.flags & FlowFlags.Unreachable)) { - if ((flowAfterTry.flags & FlowFlags.Unreachable) && (flowAfterCatch.flags & FlowFlags.Unreachable)) { - currentFlow = flowAfterTry === reportedUnreachableFlow || flowAfterCatch === reportedUnreachableFlow - ? reportedUnreachableFlow - : unreachableFlow; - } + if (currentFlow.flags & FlowFlags.Unreachable) { + // If the end of the finally block is unreachable, the end of the entire try statement is unreachable. + currentFlow = unreachableFlow; } - if (!(currentFlow.flags & FlowFlags.Unreachable)) { - const afterFinallyFlow: AfterFinallyFlow = initFlowNode({ flags: FlowFlags.AfterFinally, antecedent: currentFlow }); - preFinallyFlow.lock = afterFinallyFlow; - currentFlow = afterFinallyFlow; + else { + // If we have an IIFE return target and return statements in the try or catch blocks, add a control + // flow that goes back through the finally block and back through only the return statements. + if (currentReturnTarget && returnLabel.antecedents) { + addAntecedent(currentReturnTarget, createReduceLabel(finallyLabel, returnLabel.antecedents, currentFlow)); + } + // If the end of the finally block is reachable, but the end of the try and catch blocks are not, + // convert the current flow to unreachable. For example, 'try { return 1; } finally { ... }' should + // result in an unreachable current control flow. + currentFlow = normalExitLabel.antecedents ? createReduceLabel(finallyLabel, normalExitLabel.antecedents, currentFlow) : unreachableFlow; } } else { - currentFlow = finishFlowLabel(preFinallyLabel); + currentFlow = finishFlowLabel(normalExitLabel); } } diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 98e1a8255eb..3192ad7ea05 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -19437,16 +19437,12 @@ namespace ts { } function isReachableFlowNode(flow: FlowNode) { - const result = isReachableFlowNodeWorker(flow, /*skipCacheCheck*/ false); + const result = isReachableFlowNodeWorker(flow, /*noCacheCheck*/ false); lastFlowNode = flow; lastFlowNodeReachable = result; return result; } - function isUnlockedReachableFlowNode(flow: FlowNode) { - return !(flow.flags & FlowFlags.PreFinally && (flow).lock.locked) && isReachableFlowNodeWorker(flow, /*skipCacheCheck*/ false); - } - function isFalseExpression(expr: Expression): boolean { const node = skipParentheses(expr); return node.kind === SyntaxKind.FalseKeyword || node.kind === SyntaxKind.BinaryExpression && ( @@ -19464,11 +19460,11 @@ namespace ts { if (!noCacheCheck) { const id = getFlowNodeId(flow); const reachable = flowNodeReachable[id]; - return reachable !== undefined ? reachable : (flowNodeReachable[id] = isReachableFlowNodeWorker(flow, /*skipCacheCheck*/ true)); + return reachable !== undefined ? reachable : (flowNodeReachable[id] = isReachableFlowNodeWorker(flow, /*noCacheCheck*/ true)); } noCacheCheck = false; } - if (flags & (FlowFlags.Assignment | FlowFlags.Condition | FlowFlags.ArrayMutation | FlowFlags.PreFinally)) { + if (flags & (FlowFlags.Assignment | FlowFlags.Condition | FlowFlags.ArrayMutation)) { flow = (flow).antecedent; } else if (flags & FlowFlags.Call) { @@ -19489,7 +19485,7 @@ namespace ts { } else if (flags & FlowFlags.BranchLabel) { // A branching point is reachable if any branch is reachable. - return some((flow).antecedents, isUnlockedReachableFlowNode); + return some((flow).antecedents, f => isReachableFlowNodeWorker(f, /*noCacheCheck*/ false)); } else if (flags & FlowFlags.LoopLabel) { // A loop is reachable if the control flow path that leads to the top is reachable. @@ -19503,12 +19499,14 @@ namespace ts { } flow = (flow).antecedent; } - else if (flags & FlowFlags.AfterFinally) { - // Cache is unreliable once we start locking nodes + else if (flags & FlowFlags.ReduceLabel) { + // Cache is unreliable once we start adjusting labels lastFlowNode = undefined; - (flow).locked = true; - const result = isReachableFlowNodeWorker((flow).antecedent, /*skipCacheCheck*/ false); - (flow).locked = false; + const target = (flow).target; + const saveAntecedents = target.antecedents; + target.antecedents = (flow).antecedents; + const result = isReachableFlowNodeWorker((flow).antecedent, /*noCacheCheck*/ false); + target.antecedents = saveAntecedents; return result; } else { @@ -19572,19 +19570,7 @@ namespace ts { } } let type: FlowType | undefined; - if (flags & FlowFlags.AfterFinally) { - // block flow edge: finally -> pre-try (for larger explanation check comment in binder.ts - bindTryStatement - (flow).locked = true; - type = getTypeAtFlowNode((flow).antecedent); - (flow).locked = false; - } - else if (flags & FlowFlags.PreFinally) { - // locked pre-finally flows are filtered out in getTypeAtFlowBranchLabel - // so here just redirect to antecedent - flow = (flow).antecedent; - continue; - } - else if (flags & FlowFlags.Assignment) { + if (flags & FlowFlags.Assignment) { type = getTypeAtFlowAssignment(flow); if (!type) { flow = (flow).antecedent; @@ -19620,6 +19606,13 @@ namespace ts { continue; } } + else if (flags & FlowFlags.ReduceLabel) { + const target = (flow).target; + const saveAntecedents = target.antecedents; + target.antecedents = (flow).antecedents; + type = getTypeAtFlowNode((flow).antecedent); + target.antecedents = saveAntecedents; + } else if (flags & FlowFlags.Start) { // Check if we should continue with the control flow of the containing function. const container = (flow).node; @@ -19831,12 +19824,6 @@ namespace ts { let seenIncomplete = false; let bypassFlow: FlowSwitchClause | undefined; for (const antecedent of flow.antecedents!) { - if (antecedent.flags & FlowFlags.PreFinally && (antecedent).lock.locked) { - // if flow correspond to branch from pre-try to finally and this branch is locked - this means that - // we initially have started following the flow outside the finally block. - // in this case we should ignore this branch. - continue; - } if (!bypassFlow && antecedent.flags & FlowFlags.SwitchClause && (antecedent).clauseStart === (antecedent).clauseEnd) { // The antecedent is the bypass branch of a potentially exhaustive switch statement. bypassFlow = antecedent; diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 5158684e0ad..d5ee8d7581e 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -2770,10 +2770,9 @@ namespace ts { SwitchClause = 1 << 7, // Switch statement clause ArrayMutation = 1 << 8, // Potential array mutation Call = 1 << 9, // Potential assertion call - Referenced = 1 << 10, // Referenced as antecedent once - Shared = 1 << 11, // Referenced as antecedent more than once - PreFinally = 1 << 12, // Injected edge that links pre-finally label and pre-try flow - AfterFinally = 1 << 13, // Injected edge that links post-finally flow with the rest of the graph + ReduceLabel = 1 << 10, // Temporarily reduce antecedents of label + Referenced = 1 << 11, // Referenced as antecedent once + Shared = 1 << 12, // Referenced as antecedent more than once Label = BranchLabel | LoopLabel, Condition = TrueCondition | FalseCondition, @@ -2853,6 +2852,12 @@ namespace ts { antecedent: FlowNode; } + export interface FlowReduceLabel extends FlowNodeBase { + target: FlowLabel; + antecedents: FlowNode[]; + antecedent: FlowNode; + } + export type FlowType = Type | IncompleteType; // Incomplete types occur during control flow analysis of loops. An IncompleteType diff --git a/src/debug/dbg.ts b/src/debug/dbg.ts index 4314be10a49..fc8a5cdafc8 100644 --- a/src/debug/dbg.ts +++ b/src/debug/dbg.ts @@ -46,10 +46,9 @@ namespace Debug { readonly SwitchClause: number, readonly ArrayMutation: number, readonly Call: number, + readonly ReduceLabel: number, readonly Referenced: number, readonly Shared: number, - readonly PreFinally: number, - readonly AfterFinally: number, readonly Label: number, readonly Condition: number, }; @@ -69,6 +68,7 @@ namespace Debug { | FlowCondition | FlowSwitchClause | FlowArrayMutation + | FlowReduceLabel ; interface FlowNodeBase { @@ -119,6 +119,12 @@ namespace Debug { antecedent: FlowNode; } + export interface FlowReduceLabel extends FlowNodeBase { + target: FlowLabel; + antecedents: FlowNode[]; + antecedent: FlowNode; + } + type FlowFlags = number; let FlowFlags: TypeScriptModule["FlowFlags"]; let getSourceFileOfNode: TypeScriptModule["getSourceFileOfNode"]; @@ -199,8 +205,7 @@ namespace Debug { FlowFlags.SwitchClause | FlowFlags.ArrayMutation | FlowFlags.Call | - FlowFlags.PreFinally | - FlowFlags.AfterFinally; + FlowFlags.ReduceLabel; const hasNodeFlags = FlowFlags.Start | @@ -264,17 +269,14 @@ namespace Debug { if (!graphNode) { links[id] = graphNode = { id, flowNode, edges: [], text: renderFlowNode(flowNode), lane: -1, endLane: -1, level: -1 }; nodes.push(graphNode); - if (!(flowNode.flags & FlowFlags.PreFinally)) { - if (hasAntecedents(flowNode)) { - - for (const antecedent of flowNode.antecedents) { - buildGraphEdge(graphNode, antecedent); - } - } - else if (hasAntecedent(flowNode)) { - buildGraphEdge(graphNode, flowNode.antecedent); + if (hasAntecedents(flowNode)) { + for (const antecedent of flowNode.antecedents) { + buildGraphEdge(graphNode, antecedent); } } + else if (hasAntecedent(flowNode)) { + buildGraphEdge(graphNode, flowNode.antecedent); + } } return graphNode; } @@ -341,8 +343,7 @@ namespace Debug { if (flags & FlowFlags.SwitchClause) return "SwitchClause"; if (flags & FlowFlags.ArrayMutation) return "ArrayMutation"; if (flags & FlowFlags.Call) return "Call"; - if (flags & FlowFlags.PreFinally) return "PreFinally"; - if (flags & FlowFlags.AfterFinally) return "AfterFinally"; + if (flags & FlowFlags.ReduceLabel) return "ReduceLabel"; if (flags & FlowFlags.Unreachable) return "Unreachable"; throw new Error(); } diff --git a/tests/baselines/reference/api/tsserverlibrary.d.ts b/tests/baselines/reference/api/tsserverlibrary.d.ts index 68951b4717f..45f4d38414e 100644 --- a/tests/baselines/reference/api/tsserverlibrary.d.ts +++ b/tests/baselines/reference/api/tsserverlibrary.d.ts @@ -1726,10 +1726,9 @@ declare namespace ts { SwitchClause = 128, ArrayMutation = 256, Call = 512, - Referenced = 1024, - Shared = 2048, - PreFinally = 4096, - AfterFinally = 8192, + ReduceLabel = 1024, + Referenced = 2048, + Shared = 4096, Label = 12, Condition = 96 } @@ -1776,6 +1775,11 @@ declare namespace ts { node: CallExpression | BinaryExpression; antecedent: FlowNode; } + export interface FlowReduceLabel extends FlowNodeBase { + target: FlowLabel; + antecedents: FlowNode[]; + antecedent: FlowNode; + } export type FlowType = Type | IncompleteType; export interface IncompleteType { flags: TypeFlags; diff --git a/tests/baselines/reference/api/typescript.d.ts b/tests/baselines/reference/api/typescript.d.ts index 9bf35111888..83e2a6ac5d4 100644 --- a/tests/baselines/reference/api/typescript.d.ts +++ b/tests/baselines/reference/api/typescript.d.ts @@ -1726,10 +1726,9 @@ declare namespace ts { SwitchClause = 128, ArrayMutation = 256, Call = 512, - Referenced = 1024, - Shared = 2048, - PreFinally = 4096, - AfterFinally = 8192, + ReduceLabel = 1024, + Referenced = 2048, + Shared = 4096, Label = 12, Condition = 96 } @@ -1776,6 +1775,11 @@ declare namespace ts { node: CallExpression | BinaryExpression; antecedent: FlowNode; } + export interface FlowReduceLabel extends FlowNodeBase { + target: FlowLabel; + antecedents: FlowNode[]; + antecedent: FlowNode; + } export type FlowType = Type | IncompleteType; export interface IncompleteType { flags: TypeFlags; diff --git a/tests/baselines/reference/tryCatchFinallyControlFlow.errors.txt b/tests/baselines/reference/tryCatchFinallyControlFlow.errors.txt index 11ec1515aca..b9be0305827 100644 --- a/tests/baselines/reference/tryCatchFinallyControlFlow.errors.txt +++ b/tests/baselines/reference/tryCatchFinallyControlFlow.errors.txt @@ -1,7 +1,11 @@ tests/cases/compiler/tryCatchFinallyControlFlow.ts(105,5): error TS7027: Unreachable code detected. +tests/cases/compiler/tryCatchFinallyControlFlow.ts(118,9): error TS7027: Unreachable code detected. +tests/cases/compiler/tryCatchFinallyControlFlow.ts(218,13): error TS7027: Unreachable code detected. +tests/cases/compiler/tryCatchFinallyControlFlow.ts(220,9): error TS7027: Unreachable code detected. +tests/cases/compiler/tryCatchFinallyControlFlow.ts(255,9): error TS7027: Unreachable code detected. -==== tests/cases/compiler/tryCatchFinallyControlFlow.ts (1 errors) ==== +==== tests/cases/compiler/tryCatchFinallyControlFlow.ts (5 errors) ==== // Repro from #34797 function f1() { @@ -111,6 +115,131 @@ tests/cases/compiler/tryCatchFinallyControlFlow.ts(105,5): error TS7027: Unreach !!! error TS7027: Unreachable code detected. } + function f8() { + let x: 0 | 1 = 0; + (() => { + try { + x = 1; + return; + } + finally { + x; // 0 | 1 + } + x; // Unreachable + ~~ +!!! error TS7027: Unreachable code detected. + })(); + x; // 1 + } + + function f9() { + let x: 0 | 1 | 2 = 0; + (() => { + try { + if (!!true) { + x = 1; + return; + } + } + finally { + x; // 0 | 1 + } + x; // 0 + x = 2; + })(); + x; // 1 | 2 + } + + function f10() { + let x: 0 | 1 | 2 | 3 = 0; + (() => { + try { + x = 1; + return; + } + catch (e) { + x = 2; + } + finally { + x; // 0 | 1 | 2 + } + x; // 2 + x = 3; + })(); + x; // 1 | 3 + } + + function f11() { + let x: 0 | 1 | 2 | 3 | 4 | 5 = 0; + (() => { + try { + if (!!true) { + x = 1; + return; + } + if (!!true) { + x = 2; + throw 0; + } + } + catch (e) { + x; // 0 | 1 | 2 + x = 3; + } + finally { + x; // 0 | 1 | 2 | 3 + if (!!true) { + x = 4; + } + } + x; // 0 | 3 | 4 + x = 5; + })(); + x; // 1 | 4 | 5 + } + + function f12() { + let x: 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 = 0; + (() => { + try { + if (!!true) { + x = 1; + return; + } + if (!!true) { + x = 2; + throw 0; + } + } + catch (e) { + x; // 0 | 1 | 2 + x = 3; + } + finally { + x; // 0 | 1 | 2 | 3 + if (!!true) { + x = 4; + return; + } + if (!!true) { + x = 5; + return; + } + x = 6; + return; + x; // unreachable + ~~ +!!! error TS7027: Unreachable code detected. + } + x; // unreachable + ~~~~~~~~~~~~~~~~~ + x = 7; // no effect + ~~~~~~~~~~~~~~ +!!! error TS7027: Unreachable code detected. + })(); + x; // 4 | 5 | 6 + } + // Repro from #35644 const main = () => { @@ -129,4 +258,21 @@ tests/cases/compiler/tryCatchFinallyControlFlow.ts(105,5): error TS7027: Unreach return; } } + + // Repro from #36828 + + function t1() { + const x = (() => { + try { + return 'x'; + } + catch (e) { + return null; + } + x; // Unreachable + ~~ +!!! error TS7027: Unreachable code detected. + })(); + x; // Reachable + } \ No newline at end of file diff --git a/tests/baselines/reference/tryCatchFinallyControlFlow.js b/tests/baselines/reference/tryCatchFinallyControlFlow.js index 79d9a3d5972..2ccecd0cf20 100644 --- a/tests/baselines/reference/tryCatchFinallyControlFlow.js +++ b/tests/baselines/reference/tryCatchFinallyControlFlow.js @@ -106,6 +106,124 @@ function f7() { x; // Unreachable } +function f8() { + let x: 0 | 1 = 0; + (() => { + try { + x = 1; + return; + } + finally { + x; // 0 | 1 + } + x; // Unreachable + })(); + x; // 1 +} + +function f9() { + let x: 0 | 1 | 2 = 0; + (() => { + try { + if (!!true) { + x = 1; + return; + } + } + finally { + x; // 0 | 1 + } + x; // 0 + x = 2; + })(); + x; // 1 | 2 +} + +function f10() { + let x: 0 | 1 | 2 | 3 = 0; + (() => { + try { + x = 1; + return; + } + catch (e) { + x = 2; + } + finally { + x; // 0 | 1 | 2 + } + x; // 2 + x = 3; + })(); + x; // 1 | 3 +} + +function f11() { + let x: 0 | 1 | 2 | 3 | 4 | 5 = 0; + (() => { + try { + if (!!true) { + x = 1; + return; + } + if (!!true) { + x = 2; + throw 0; + } + } + catch (e) { + x; // 0 | 1 | 2 + x = 3; + } + finally { + x; // 0 | 1 | 2 | 3 + if (!!true) { + x = 4; + } + } + x; // 0 | 3 | 4 + x = 5; + })(); + x; // 1 | 4 | 5 +} + +function f12() { + let x: 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 = 0; + (() => { + try { + if (!!true) { + x = 1; + return; + } + if (!!true) { + x = 2; + throw 0; + } + } + catch (e) { + x; // 0 | 1 | 2 + x = 3; + } + finally { + x; // 0 | 1 | 2 | 3 + if (!!true) { + x = 4; + return; + } + if (!!true) { + x = 5; + return; + } + x = 6; + return; + x; // unreachable + } + x; // unreachable + x = 7; // no effect + })(); + x; // 4 | 5 | 6 +} + // Repro from #35644 const main = () => { @@ -124,6 +242,21 @@ const main = () => { return; } } + +// Repro from #36828 + +function t1() { + const x = (() => { + try { + return 'x'; + } + catch (e) { + return null; + } + x; // Unreachable + })(); + x; // Reachable +} //// [tryCatchFinallyControlFlow.js] @@ -227,6 +360,119 @@ function f7() { } x; // Unreachable } +function f8() { + var x = 0; + (function () { + try { + x = 1; + return; + } + finally { + x; // 0 | 1 + } + x; // Unreachable + })(); + x; // 1 +} +function f9() { + var x = 0; + (function () { + try { + if (!!true) { + x = 1; + return; + } + } + finally { + x; // 0 | 1 + } + x; // 0 + x = 2; + })(); + x; // 1 | 2 +} +function f10() { + var x = 0; + (function () { + try { + x = 1; + return; + } + catch (e) { + x = 2; + } + finally { + x; // 0 | 1 | 2 + } + x; // 2 + x = 3; + })(); + x; // 1 | 3 +} +function f11() { + var x = 0; + (function () { + try { + if (!!true) { + x = 1; + return; + } + if (!!true) { + x = 2; + throw 0; + } + } + catch (e) { + x; // 0 | 1 | 2 + x = 3; + } + finally { + x; // 0 | 1 | 2 | 3 + if (!!true) { + x = 4; + } + } + x; // 0 | 3 | 4 + x = 5; + })(); + x; // 1 | 4 | 5 +} +function f12() { + var x = 0; + (function () { + try { + if (!!true) { + x = 1; + return; + } + if (!!true) { + x = 2; + throw 0; + } + } + catch (e) { + x; // 0 | 1 | 2 + x = 3; + } + finally { + x; // 0 | 1 | 2 | 3 + if (!!true) { + x = 4; + return; + } + if (!!true) { + x = 5; + return; + } + x = 6; + return; + x; // unreachable + } + x; // unreachable + x = 7; // no effect + })(); + x; // 4 | 5 | 6 +} // Repro from #35644 var main = function () { var hoge = undefined; @@ -244,3 +490,16 @@ var main = function () { return; } }; +// Repro from #36828 +function t1() { + var x = (function () { + try { + return 'x'; + } + catch (e) { + return null; + } + x; // Unreachable + })(); + x; // Reachable +} diff --git a/tests/baselines/reference/tryCatchFinallyControlFlow.symbols b/tests/baselines/reference/tryCatchFinallyControlFlow.symbols index 054151d6328..30e6ea3da35 100644 --- a/tests/baselines/reference/tryCatchFinallyControlFlow.symbols +++ b/tests/baselines/reference/tryCatchFinallyControlFlow.symbols @@ -187,18 +187,221 @@ function f7() { >x : Symbol(x, Decl(tryCatchFinallyControlFlow.ts, 92, 7)) } +function f8() { +>f8 : Symbol(f8, Decl(tryCatchFinallyControlFlow.ts, 105, 1)) + + let x: 0 | 1 = 0; +>x : Symbol(x, Decl(tryCatchFinallyControlFlow.ts, 108, 7)) + + (() => { + try { + x = 1; +>x : Symbol(x, Decl(tryCatchFinallyControlFlow.ts, 108, 7)) + + return; + } + finally { + x; // 0 | 1 +>x : Symbol(x, Decl(tryCatchFinallyControlFlow.ts, 108, 7)) + } + x; // Unreachable +>x : Symbol(x, Decl(tryCatchFinallyControlFlow.ts, 108, 7)) + + })(); + x; // 1 +>x : Symbol(x, Decl(tryCatchFinallyControlFlow.ts, 108, 7)) +} + +function f9() { +>f9 : Symbol(f9, Decl(tryCatchFinallyControlFlow.ts, 120, 1)) + + let x: 0 | 1 | 2 = 0; +>x : Symbol(x, Decl(tryCatchFinallyControlFlow.ts, 123, 7)) + + (() => { + try { + if (!!true) { + x = 1; +>x : Symbol(x, Decl(tryCatchFinallyControlFlow.ts, 123, 7)) + + return; + } + } + finally { + x; // 0 | 1 +>x : Symbol(x, Decl(tryCatchFinallyControlFlow.ts, 123, 7)) + } + x; // 0 +>x : Symbol(x, Decl(tryCatchFinallyControlFlow.ts, 123, 7)) + + x = 2; +>x : Symbol(x, Decl(tryCatchFinallyControlFlow.ts, 123, 7)) + + })(); + x; // 1 | 2 +>x : Symbol(x, Decl(tryCatchFinallyControlFlow.ts, 123, 7)) +} + +function f10() { +>f10 : Symbol(f10, Decl(tryCatchFinallyControlFlow.ts, 138, 1)) + + let x: 0 | 1 | 2 | 3 = 0; +>x : Symbol(x, Decl(tryCatchFinallyControlFlow.ts, 141, 7)) + + (() => { + try { + x = 1; +>x : Symbol(x, Decl(tryCatchFinallyControlFlow.ts, 141, 7)) + + return; + } + catch (e) { +>e : Symbol(e, Decl(tryCatchFinallyControlFlow.ts, 147, 15)) + + x = 2; +>x : Symbol(x, Decl(tryCatchFinallyControlFlow.ts, 141, 7)) + } + finally { + x; // 0 | 1 | 2 +>x : Symbol(x, Decl(tryCatchFinallyControlFlow.ts, 141, 7)) + } + x; // 2 +>x : Symbol(x, Decl(tryCatchFinallyControlFlow.ts, 141, 7)) + + x = 3; +>x : Symbol(x, Decl(tryCatchFinallyControlFlow.ts, 141, 7)) + + })(); + x; // 1 | 3 +>x : Symbol(x, Decl(tryCatchFinallyControlFlow.ts, 141, 7)) +} + +function f11() { +>f11 : Symbol(f11, Decl(tryCatchFinallyControlFlow.ts, 157, 1)) + + let x: 0 | 1 | 2 | 3 | 4 | 5 = 0; +>x : Symbol(x, Decl(tryCatchFinallyControlFlow.ts, 160, 7)) + + (() => { + try { + if (!!true) { + x = 1; +>x : Symbol(x, Decl(tryCatchFinallyControlFlow.ts, 160, 7)) + + return; + } + if (!!true) { + x = 2; +>x : Symbol(x, Decl(tryCatchFinallyControlFlow.ts, 160, 7)) + + throw 0; + } + } + catch (e) { +>e : Symbol(e, Decl(tryCatchFinallyControlFlow.ts, 172, 15)) + + x; // 0 | 1 | 2 +>x : Symbol(x, Decl(tryCatchFinallyControlFlow.ts, 160, 7)) + + x = 3; +>x : Symbol(x, Decl(tryCatchFinallyControlFlow.ts, 160, 7)) + } + finally { + x; // 0 | 1 | 2 | 3 +>x : Symbol(x, Decl(tryCatchFinallyControlFlow.ts, 160, 7)) + + if (!!true) { + x = 4; +>x : Symbol(x, Decl(tryCatchFinallyControlFlow.ts, 160, 7)) + } + } + x; // 0 | 3 | 4 +>x : Symbol(x, Decl(tryCatchFinallyControlFlow.ts, 160, 7)) + + x = 5; +>x : Symbol(x, Decl(tryCatchFinallyControlFlow.ts, 160, 7)) + + })(); + x; // 1 | 4 | 5 +>x : Symbol(x, Decl(tryCatchFinallyControlFlow.ts, 160, 7)) +} + +function f12() { +>f12 : Symbol(f12, Decl(tryCatchFinallyControlFlow.ts, 186, 1)) + + let x: 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 = 0; +>x : Symbol(x, Decl(tryCatchFinallyControlFlow.ts, 189, 7)) + + (() => { + try { + if (!!true) { + x = 1; +>x : Symbol(x, Decl(tryCatchFinallyControlFlow.ts, 189, 7)) + + return; + } + if (!!true) { + x = 2; +>x : Symbol(x, Decl(tryCatchFinallyControlFlow.ts, 189, 7)) + + throw 0; + } + } + catch (e) { +>e : Symbol(e, Decl(tryCatchFinallyControlFlow.ts, 201, 15)) + + x; // 0 | 1 | 2 +>x : Symbol(x, Decl(tryCatchFinallyControlFlow.ts, 189, 7)) + + x = 3; +>x : Symbol(x, Decl(tryCatchFinallyControlFlow.ts, 189, 7)) + } + finally { + x; // 0 | 1 | 2 | 3 +>x : Symbol(x, Decl(tryCatchFinallyControlFlow.ts, 189, 7)) + + if (!!true) { + x = 4; +>x : Symbol(x, Decl(tryCatchFinallyControlFlow.ts, 189, 7)) + + return; + } + if (!!true) { + x = 5; +>x : Symbol(x, Decl(tryCatchFinallyControlFlow.ts, 189, 7)) + + return; + } + x = 6; +>x : Symbol(x, Decl(tryCatchFinallyControlFlow.ts, 189, 7)) + + return; + x; // unreachable +>x : Symbol(x, Decl(tryCatchFinallyControlFlow.ts, 189, 7)) + } + x; // unreachable +>x : Symbol(x, Decl(tryCatchFinallyControlFlow.ts, 189, 7)) + + x = 7; // no effect +>x : Symbol(x, Decl(tryCatchFinallyControlFlow.ts, 189, 7)) + + })(); + x; // 4 | 5 | 6 +>x : Symbol(x, Decl(tryCatchFinallyControlFlow.ts, 189, 7)) +} + // Repro from #35644 const main = () => { ->main : Symbol(main, Decl(tryCatchFinallyControlFlow.ts, 109, 5)) +>main : Symbol(main, Decl(tryCatchFinallyControlFlow.ts, 227, 5)) let hoge: string | undefined = undefined; ->hoge : Symbol(hoge, Decl(tryCatchFinallyControlFlow.ts, 110, 7)) +>hoge : Symbol(hoge, Decl(tryCatchFinallyControlFlow.ts, 228, 7)) >undefined : Symbol(undefined) try { hoge = 'hoge!'; ->hoge : Symbol(hoge, Decl(tryCatchFinallyControlFlow.ts, 110, 7)) +>hoge : Symbol(hoge, Decl(tryCatchFinallyControlFlow.ts, 228, 7)) return; } @@ -207,14 +410,38 @@ const main = () => { } finally { if (hoge) { ->hoge : Symbol(hoge, Decl(tryCatchFinallyControlFlow.ts, 110, 7)) +>hoge : Symbol(hoge, Decl(tryCatchFinallyControlFlow.ts, 228, 7)) hoge.length; >hoge.length : Symbol(String.length, Decl(lib.es5.d.ts, --, --)) ->hoge : Symbol(hoge, Decl(tryCatchFinallyControlFlow.ts, 110, 7)) +>hoge : Symbol(hoge, Decl(tryCatchFinallyControlFlow.ts, 228, 7)) >length : Symbol(String.length, Decl(lib.es5.d.ts, --, --)) } return; } } +// Repro from #36828 + +function t1() { +>t1 : Symbol(t1, Decl(tryCatchFinallyControlFlow.ts, 242, 1)) + + const x = (() => { +>x : Symbol(x, Decl(tryCatchFinallyControlFlow.ts, 247, 9)) + + try { + return 'x'; + } + catch (e) { +>e : Symbol(e, Decl(tryCatchFinallyControlFlow.ts, 251, 15)) + + return null; + } + x; // Unreachable +>x : Symbol(x, Decl(tryCatchFinallyControlFlow.ts, 247, 9)) + + })(); + x; // Reachable +>x : Symbol(x, Decl(tryCatchFinallyControlFlow.ts, 247, 9)) +} + diff --git a/tests/baselines/reference/tryCatchFinallyControlFlow.types b/tests/baselines/reference/tryCatchFinallyControlFlow.types index d29a00750d7..70c11b87dca 100644 --- a/tests/baselines/reference/tryCatchFinallyControlFlow.types +++ b/tests/baselines/reference/tryCatchFinallyControlFlow.types @@ -228,6 +228,304 @@ function f7() { >x : 0 | 1 | 2 | 3 } +function f8() { +>f8 : () => void + + let x: 0 | 1 = 0; +>x : 0 | 1 +>0 : 0 + + (() => { +>(() => { try { x = 1; return; } finally { x; // 0 | 1 } x; // Unreachable })() : void +>(() => { try { x = 1; return; } finally { x; // 0 | 1 } x; // Unreachable }) : () => void +>() => { try { x = 1; return; } finally { x; // 0 | 1 } x; // Unreachable } : () => void + + try { + x = 1; +>x = 1 : 1 +>x : 0 | 1 +>1 : 1 + + return; + } + finally { + x; // 0 | 1 +>x : 0 | 1 + } + x; // Unreachable +>x : 0 | 1 + + })(); + x; // 1 +>x : 1 +} + +function f9() { +>f9 : () => void + + let x: 0 | 1 | 2 = 0; +>x : 0 | 1 | 2 +>0 : 0 + + (() => { +>(() => { try { if (!!true) { x = 1; return; } } finally { x; // 0 | 1 } x; // 0 x = 2; })() : void +>(() => { try { if (!!true) { x = 1; return; } } finally { x; // 0 | 1 } x; // 0 x = 2; }) : () => void +>() => { try { if (!!true) { x = 1; return; } } finally { x; // 0 | 1 } x; // 0 x = 2; } : () => void + + try { + if (!!true) { +>!!true : true +>!true : false +>true : true + + x = 1; +>x = 1 : 1 +>x : 0 | 1 | 2 +>1 : 1 + + return; + } + } + finally { + x; // 0 | 1 +>x : 0 | 1 + } + x; // 0 +>x : 0 + + x = 2; +>x = 2 : 2 +>x : 0 | 1 | 2 +>2 : 2 + + })(); + x; // 1 | 2 +>x : 1 | 2 +} + +function f10() { +>f10 : () => void + + let x: 0 | 1 | 2 | 3 = 0; +>x : 0 | 1 | 2 | 3 +>0 : 0 + + (() => { +>(() => { try { x = 1; return; } catch (e) { x = 2; } finally { x; // 0 | 1 | 2 } x; // 2 x = 3; })() : void +>(() => { try { x = 1; return; } catch (e) { x = 2; } finally { x; // 0 | 1 | 2 } x; // 2 x = 3; }) : () => void +>() => { try { x = 1; return; } catch (e) { x = 2; } finally { x; // 0 | 1 | 2 } x; // 2 x = 3; } : () => void + + try { + x = 1; +>x = 1 : 1 +>x : 0 | 1 | 2 | 3 +>1 : 1 + + return; + } + catch (e) { +>e : any + + x = 2; +>x = 2 : 2 +>x : 0 | 1 | 2 | 3 +>2 : 2 + } + finally { + x; // 0 | 1 | 2 +>x : 0 | 1 | 2 + } + x; // 2 +>x : 2 + + x = 3; +>x = 3 : 3 +>x : 0 | 1 | 2 | 3 +>3 : 3 + + })(); + x; // 1 | 3 +>x : 1 | 3 +} + +function f11() { +>f11 : () => void + + let x: 0 | 1 | 2 | 3 | 4 | 5 = 0; +>x : 0 | 1 | 2 | 3 | 4 | 5 +>0 : 0 + + (() => { +>(() => { try { if (!!true) { x = 1; return; } if (!!true) { x = 2; throw 0; } } catch (e) { x; // 0 | 1 | 2 x = 3; } finally { x; // 0 | 1 | 2 | 3 if (!!true) { x = 4; } } x; // 0 | 3 | 4 x = 5; })() : void +>(() => { try { if (!!true) { x = 1; return; } if (!!true) { x = 2; throw 0; } } catch (e) { x; // 0 | 1 | 2 x = 3; } finally { x; // 0 | 1 | 2 | 3 if (!!true) { x = 4; } } x; // 0 | 3 | 4 x = 5; }) : () => void +>() => { try { if (!!true) { x = 1; return; } if (!!true) { x = 2; throw 0; } } catch (e) { x; // 0 | 1 | 2 x = 3; } finally { x; // 0 | 1 | 2 | 3 if (!!true) { x = 4; } } x; // 0 | 3 | 4 x = 5; } : () => void + + try { + if (!!true) { +>!!true : true +>!true : false +>true : true + + x = 1; +>x = 1 : 1 +>x : 0 | 1 | 2 | 3 | 4 | 5 +>1 : 1 + + return; + } + if (!!true) { +>!!true : true +>!true : false +>true : true + + x = 2; +>x = 2 : 2 +>x : 0 | 1 | 2 | 3 | 4 | 5 +>2 : 2 + + throw 0; +>0 : 0 + } + } + catch (e) { +>e : any + + x; // 0 | 1 | 2 +>x : 0 | 1 | 2 + + x = 3; +>x = 3 : 3 +>x : 0 | 1 | 2 | 3 | 4 | 5 +>3 : 3 + } + finally { + x; // 0 | 1 | 2 | 3 +>x : 0 | 1 | 2 | 3 + + if (!!true) { +>!!true : true +>!true : false +>true : true + + x = 4; +>x = 4 : 4 +>x : 0 | 1 | 2 | 3 | 4 | 5 +>4 : 4 + } + } + x; // 0 | 3 | 4 +>x : 0 | 3 | 4 + + x = 5; +>x = 5 : 5 +>x : 0 | 1 | 2 | 3 | 4 | 5 +>5 : 5 + + })(); + x; // 1 | 4 | 5 +>x : 1 | 4 | 5 +} + +function f12() { +>f12 : () => void + + let x: 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 = 0; +>x : 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 +>0 : 0 + + (() => { +>(() => { try { if (!!true) { x = 1; return; } if (!!true) { x = 2; throw 0; } } catch (e) { x; // 0 | 1 | 2 x = 3; } finally { x; // 0 | 1 | 2 | 3 if (!!true) { x = 4; return; } if (!!true) { x = 5; return; } x = 6; return; x; // unreachable } x; // unreachable x = 7; // no effect })() : void +>(() => { try { if (!!true) { x = 1; return; } if (!!true) { x = 2; throw 0; } } catch (e) { x; // 0 | 1 | 2 x = 3; } finally { x; // 0 | 1 | 2 | 3 if (!!true) { x = 4; return; } if (!!true) { x = 5; return; } x = 6; return; x; // unreachable } x; // unreachable x = 7; // no effect }) : () => void +>() => { try { if (!!true) { x = 1; return; } if (!!true) { x = 2; throw 0; } } catch (e) { x; // 0 | 1 | 2 x = 3; } finally { x; // 0 | 1 | 2 | 3 if (!!true) { x = 4; return; } if (!!true) { x = 5; return; } x = 6; return; x; // unreachable } x; // unreachable x = 7; // no effect } : () => void + + try { + if (!!true) { +>!!true : true +>!true : false +>true : true + + x = 1; +>x = 1 : 1 +>x : 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 +>1 : 1 + + return; + } + if (!!true) { +>!!true : true +>!true : false +>true : true + + x = 2; +>x = 2 : 2 +>x : 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 +>2 : 2 + + throw 0; +>0 : 0 + } + } + catch (e) { +>e : any + + x; // 0 | 1 | 2 +>x : 0 | 1 | 2 + + x = 3; +>x = 3 : 3 +>x : 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 +>3 : 3 + } + finally { + x; // 0 | 1 | 2 | 3 +>x : 0 | 1 | 2 | 3 + + if (!!true) { +>!!true : true +>!true : false +>true : true + + x = 4; +>x = 4 : 4 +>x : 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 +>4 : 4 + + return; + } + if (!!true) { +>!!true : true +>!true : false +>true : true + + x = 5; +>x = 5 : 5 +>x : 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 +>5 : 5 + + return; + } + x = 6; +>x = 6 : 6 +>x : 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 +>6 : 6 + + return; + x; // unreachable +>x : 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 + } + x; // unreachable +>x : 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 + + x = 7; // no effect +>x = 7 : 7 +>x : 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 +>7 : 7 + + })(); + x; // 4 | 5 | 6 +>x : 4 | 5 | 6 +} + // Repro from #35644 const main = () => { @@ -262,3 +560,32 @@ const main = () => { } } +// Repro from #36828 + +function t1() { +>t1 : () => void + + const x = (() => { +>x : "x" | null +>(() => { try { return 'x'; } catch (e) { return null; } x; // Unreachable })() : "x" | null +>(() => { try { return 'x'; } catch (e) { return null; } x; // Unreachable }) : () => "x" | null +>() => { try { return 'x'; } catch (e) { return null; } x; // Unreachable } : () => "x" | null + + try { + return 'x'; +>'x' : "x" + } + catch (e) { +>e : any + + return null; +>null : null + } + x; // Unreachable +>x : "x" | null + + })(); + x; // Reachable +>x : "x" | null +} + diff --git a/tests/cases/compiler/tryCatchFinallyControlFlow.ts b/tests/cases/compiler/tryCatchFinallyControlFlow.ts index 694d9f0c4d5..83909b5ddb0 100644 --- a/tests/cases/compiler/tryCatchFinallyControlFlow.ts +++ b/tests/cases/compiler/tryCatchFinallyControlFlow.ts @@ -108,6 +108,124 @@ function f7() { x; // Unreachable } +function f8() { + let x: 0 | 1 = 0; + (() => { + try { + x = 1; + return; + } + finally { + x; // 0 | 1 + } + x; // Unreachable + })(); + x; // 1 +} + +function f9() { + let x: 0 | 1 | 2 = 0; + (() => { + try { + if (!!true) { + x = 1; + return; + } + } + finally { + x; // 0 | 1 + } + x; // 0 + x = 2; + })(); + x; // 1 | 2 +} + +function f10() { + let x: 0 | 1 | 2 | 3 = 0; + (() => { + try { + x = 1; + return; + } + catch (e) { + x = 2; + } + finally { + x; // 0 | 1 | 2 + } + x; // 2 + x = 3; + })(); + x; // 1 | 3 +} + +function f11() { + let x: 0 | 1 | 2 | 3 | 4 | 5 = 0; + (() => { + try { + if (!!true) { + x = 1; + return; + } + if (!!true) { + x = 2; + throw 0; + } + } + catch (e) { + x; // 0 | 1 | 2 + x = 3; + } + finally { + x; // 0 | 1 | 2 | 3 + if (!!true) { + x = 4; + } + } + x; // 0 | 3 | 4 + x = 5; + })(); + x; // 1 | 4 | 5 +} + +function f12() { + let x: 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 = 0; + (() => { + try { + if (!!true) { + x = 1; + return; + } + if (!!true) { + x = 2; + throw 0; + } + } + catch (e) { + x; // 0 | 1 | 2 + x = 3; + } + finally { + x; // 0 | 1 | 2 | 3 + if (!!true) { + x = 4; + return; + } + if (!!true) { + x = 5; + return; + } + x = 6; + return; + x; // unreachable + } + x; // unreachable + x = 7; // no effect + })(); + x; // 4 | 5 | 6 +} + // Repro from #35644 const main = () => { @@ -126,3 +244,18 @@ const main = () => { return; } } + +// Repro from #36828 + +function t1() { + const x = (() => { + try { + return 'x'; + } + catch (e) { + return null; + } + x; // Unreachable + })(); + x; // Reachable +}