From 20285e66e9dc426d3c1c250fa78d70971dc1d397 Mon Sep 17 00:00:00 2001 From: Wesley Wigham Date: Fri, 18 Jan 2019 14:43:31 -0800 Subject: [PATCH] Include all flow nodes made within `try` blocks as antecedents for `catch` or `finally` blocks (#29466) * Include all flow nodes made within `try` blocks as antecedents for `catch` or `finally` blocks * Fix typo --- src/compiler/binder.ts | 45 +++++- .../controlFlowForCatchAndFinally.js | 142 ++++++++++++++++++ .../controlFlowForCatchAndFinally.symbols | 135 +++++++++++++++++ .../controlFlowForCatchAndFinally.types | 142 ++++++++++++++++++ .../compiler/controlFlowForCatchAndFinally.ts | 42 ++++++ 5 files changed, 500 insertions(+), 6 deletions(-) create mode 100644 tests/baselines/reference/controlFlowForCatchAndFinally.js create mode 100644 tests/baselines/reference/controlFlowForCatchAndFinally.symbols create mode 100644 tests/baselines/reference/controlFlowForCatchAndFinally.types create mode 100644 tests/cases/compiler/controlFlowForCatchAndFinally.ts diff --git a/src/compiler/binder.ts b/src/compiler/binder.ts index f0e172618108b..27f85896a3bd4 100644 --- a/src/compiler/binder.ts +++ b/src/compiler/binder.ts @@ -100,6 +100,8 @@ namespace ts { IsObjectLiteralOrClassExpressionMethod = 1 << 7, } + let flowNodeCreated: (node: T) => T = identity; + const binder = createBinder(); export function bindSourceFile(file: SourceFile, options: CompilerOptions) { @@ -530,6 +532,7 @@ namespace ts { blockScopeContainer.locals = undefined; } if (containerFlags & ContainerFlags.IsControlFlowContainer) { + const saveFlowNodeCreated = flowNodeCreated; const saveCurrentFlow = currentFlow; const saveBreakTarget = currentBreakTarget; const saveContinueTarget = currentContinueTarget; @@ -553,6 +556,7 @@ namespace ts { currentContinueTarget = undefined; activeLabels = undefined; hasExplicitReturn = false; + flowNodeCreated = identity; bindChildren(node); // Reset all reachability check related flags on node (for incremental scenarios) node.flags &= ~NodeFlags.ReachabilityAndEmitFlags; @@ -579,6 +583,7 @@ namespace ts { currentReturnTarget = saveReturnTarget; activeLabels = saveActiveLabels; hasExplicitReturn = saveHasExplicitReturn; + flowNodeCreated = saveFlowNodeCreated; } else if (containerFlags & ContainerFlags.IsInterface) { seenThisKeyword = false; @@ -858,7 +863,7 @@ namespace ts { return antecedent; } setFlowNodeReferenced(antecedent); - return { flags, expression, antecedent }; + return flowNodeCreated({ flags, expression, antecedent }); } function createFlowSwitchClause(antecedent: FlowNode, switchStatement: SwitchStatement, clauseStart: number, clauseEnd: number): FlowNode { @@ -866,17 +871,17 @@ namespace ts { return antecedent; } setFlowNodeReferenced(antecedent); - return { flags: FlowFlags.SwitchClause, switchStatement, clauseStart, clauseEnd, antecedent }; + return flowNodeCreated({ flags: FlowFlags.SwitchClause, switchStatement, clauseStart, clauseEnd, antecedent }); } function createFlowAssignment(antecedent: FlowNode, node: Expression | VariableDeclaration | BindingElement): FlowNode { setFlowNodeReferenced(antecedent); - return { flags: FlowFlags.Assignment, antecedent, node }; + return flowNodeCreated({ flags: FlowFlags.Assignment, antecedent, node }); } function createFlowArrayMutation(antecedent: FlowNode, node: CallExpression | BinaryExpression): FlowNode { setFlowNodeReferenced(antecedent); - const res: FlowArrayMutation = { flags: FlowFlags.ArrayMutation, antecedent, node }; + const res: FlowArrayMutation = flowNodeCreated({ flags: FlowFlags.ArrayMutation, antecedent, node }); return res; } @@ -1080,8 +1085,16 @@ namespace ts { function bindTryStatement(node: TryStatement): void { const preFinallyLabel = createBranchLabel(); const preTryFlow = currentFlow; - // TODO: Every statement in try block is potentially an exit point! + const tryPriors: FlowNode[] = []; + const oldFlowNodeCreated = flowNodeCreated; + // We hook the creation of all flow nodes within the `try` scope and store them so we can add _all_ of them + // as possible antecedents of the start of the `catch` or `finally` blocks. + // Don't bother intercepting the call if there's no finally or catch block that needs the information + if (node.catchClause || node.finallyBlock) { + flowNodeCreated = node => (tryPriors.push(node), node); + } bind(node.tryBlock); + flowNodeCreated = oldFlowNodeCreated; addAntecedent(preFinallyLabel, currentFlow); const flowAfterTry = currentFlow; @@ -1089,12 +1102,32 @@ namespace ts { if (node.catchClause) { currentFlow = preTryFlow; + if (tryPriors.length) { + const preCatchFlow = createBranchLabel(); + addAntecedent(preCatchFlow, currentFlow); + for (const p of tryPriors) { + addAntecedent(preCatchFlow, p); + } + currentFlow = finishFlowLabel(preCatchFlow); + } + bind(node.catchClause); addAntecedent(preFinallyLabel, currentFlow); flowAfterCatch = currentFlow; } if (node.finallyBlock) { + // We add the nodes within the `try` block to the `finally`'s antecedents if there's no catch block + // (If there is a `catch` block, it will have all these antecedents instead, and the `finally` will + // have the end of the `try` block and the end of the `catch` block) + if (!node.catchClause) { + if (tryPriors.length) { + for (const p of tryPriors) { + addAntecedent(preFinallyLabel, p); + } + } + } + // in finally flow is combined from pre-try/flow from try/flow from catch // pre-flow is necessary to make sure that finally is reachable even if finally flows in both try and finally blocks are unreachable @@ -1142,7 +1175,7 @@ namespace ts { } } if (!(currentFlow.flags & FlowFlags.Unreachable)) { - const afterFinallyFlow: AfterFinallyFlow = { flags: FlowFlags.AfterFinally, antecedent: currentFlow }; + const afterFinallyFlow: AfterFinallyFlow = flowNodeCreated({ flags: FlowFlags.AfterFinally, antecedent: currentFlow }); preFinallyFlow.lock = afterFinallyFlow; currentFlow = afterFinallyFlow; } diff --git a/tests/baselines/reference/controlFlowForCatchAndFinally.js b/tests/baselines/reference/controlFlowForCatchAndFinally.js new file mode 100644 index 0000000000000..b3dfc4e7bb87a --- /dev/null +++ b/tests/baselines/reference/controlFlowForCatchAndFinally.js @@ -0,0 +1,142 @@ +//// [controlFlowForCatchAndFinally.ts] +type Page = {close(): Promise; content(): Promise}; +type Browser = {close(): Promise}; +declare function test1(): Promise; +declare function test2(obj: Browser): Promise; +async function test(): Promise { + let browser: Browser | undefined = undefined; + let page: Page | undefined = undefined; + try { + browser = await test1(); + page = await test2(browser); + return await page.content();; + } finally { + if (page) { + await page.close(); // ok + } + + if (browser) { + await browser.close(); // ok + } + } +} + +declare class Aborter { abort(): void }; +class Foo { + abortController: Aborter | undefined = undefined; + + async operation() { + if (this.abortController !== undefined) { + this.abortController.abort(); + this.abortController = undefined; + } + try { + this.abortController = new Aborter(); + } catch (error) { + if (this.abortController !== undefined) { + this.abortController.abort(); // ok + } + } + } +} + +//// [controlFlowForCatchAndFinally.js] +"use strict"; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +var __generator = (this && this.__generator) || function (thisArg, body) { + var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; + return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; + function verb(n) { return function (v) { return step([n, v]); }; } + function step(op) { + if (f) throw new TypeError("Generator is already executing."); + while (_) try { + if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; + if (y = 0, t) op = [op[0] & 2, t.value]; + switch (op[0]) { + case 0: case 1: t = op; break; + case 4: _.label++; return { value: op[1], done: false }; + case 5: _.label++; y = op[1]; op = [0]; continue; + case 7: op = _.ops.pop(); _.trys.pop(); continue; + default: + if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } + if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } + if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } + if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } + if (t[2]) _.ops.pop(); + _.trys.pop(); continue; + } + op = body.call(thisArg, _); + } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } + if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; + } +}; +function test() { + return __awaiter(this, void 0, void 0, function () { + var browser, page; + return __generator(this, function (_a) { + switch (_a.label) { + case 0: + browser = undefined; + page = undefined; + _a.label = 1; + case 1: + _a.trys.push([1, , 5, 10]); + return [4 /*yield*/, test1()]; + case 2: + browser = _a.sent(); + return [4 /*yield*/, test2(browser)]; + case 3: + page = _a.sent(); + return [4 /*yield*/, page.content()]; + case 4: return [2 /*return*/, _a.sent()]; + case 5: + if (!page) return [3 /*break*/, 7]; + return [4 /*yield*/, page.close()]; + case 6: + _a.sent(); // ok + _a.label = 7; + case 7: + if (!browser) return [3 /*break*/, 9]; + return [4 /*yield*/, browser.close()]; + case 8: + _a.sent(); // ok + _a.label = 9; + case 9: return [7 /*endfinally*/]; + case 10: return [2 /*return*/]; + } + }); + }); +} +; +var Foo = /** @class */ (function () { + function Foo() { + this.abortController = undefined; + } + Foo.prototype.operation = function () { + return __awaiter(this, void 0, void 0, function () { + return __generator(this, function (_a) { + if (this.abortController !== undefined) { + this.abortController.abort(); + this.abortController = undefined; + } + try { + this.abortController = new Aborter(); + } + catch (error) { + if (this.abortController !== undefined) { + this.abortController.abort(); // ok + } + } + return [2 /*return*/]; + }); + }); + }; + return Foo; +}()); diff --git a/tests/baselines/reference/controlFlowForCatchAndFinally.symbols b/tests/baselines/reference/controlFlowForCatchAndFinally.symbols new file mode 100644 index 0000000000000..f6a6574f36871 --- /dev/null +++ b/tests/baselines/reference/controlFlowForCatchAndFinally.symbols @@ -0,0 +1,135 @@ +=== tests/cases/compiler/controlFlowForCatchAndFinally.ts === +type Page = {close(): Promise; content(): Promise}; +>Page : Symbol(Page, Decl(controlFlowForCatchAndFinally.ts, 0, 0)) +>close : Symbol(close, Decl(controlFlowForCatchAndFinally.ts, 0, 13)) +>Promise : Symbol(Promise, Decl(lib.es5.d.ts, --, --), Decl(lib.es2015.iterable.d.ts, --, --), Decl(lib.es2015.promise.d.ts, --, --), Decl(lib.es2015.symbol.wellknown.d.ts, --, --)) +>content : Symbol(content, Decl(controlFlowForCatchAndFinally.ts, 0, 36)) +>Promise : Symbol(Promise, Decl(lib.es5.d.ts, --, --), Decl(lib.es2015.iterable.d.ts, --, --), Decl(lib.es2015.promise.d.ts, --, --), Decl(lib.es2015.symbol.wellknown.d.ts, --, --)) + +type Browser = {close(): Promise}; +>Browser : Symbol(Browser, Decl(controlFlowForCatchAndFinally.ts, 0, 65)) +>close : Symbol(close, Decl(controlFlowForCatchAndFinally.ts, 1, 16)) +>Promise : Symbol(Promise, Decl(lib.es5.d.ts, --, --), Decl(lib.es2015.iterable.d.ts, --, --), Decl(lib.es2015.promise.d.ts, --, --), Decl(lib.es2015.symbol.wellknown.d.ts, --, --)) + +declare function test1(): Promise; +>test1 : Symbol(test1, Decl(controlFlowForCatchAndFinally.ts, 1, 40)) +>Promise : Symbol(Promise, Decl(lib.es5.d.ts, --, --), Decl(lib.es2015.iterable.d.ts, --, --), Decl(lib.es2015.promise.d.ts, --, --), Decl(lib.es2015.symbol.wellknown.d.ts, --, --)) +>Browser : Symbol(Browser, Decl(controlFlowForCatchAndFinally.ts, 0, 65)) + +declare function test2(obj: Browser): Promise; +>test2 : Symbol(test2, Decl(controlFlowForCatchAndFinally.ts, 2, 43)) +>obj : Symbol(obj, Decl(controlFlowForCatchAndFinally.ts, 3, 23)) +>Browser : Symbol(Browser, Decl(controlFlowForCatchAndFinally.ts, 0, 65)) +>Promise : Symbol(Promise, Decl(lib.es5.d.ts, --, --), Decl(lib.es2015.iterable.d.ts, --, --), Decl(lib.es2015.promise.d.ts, --, --), Decl(lib.es2015.symbol.wellknown.d.ts, --, --)) +>Page : Symbol(Page, Decl(controlFlowForCatchAndFinally.ts, 0, 0)) + +async function test(): Promise { +>test : Symbol(test, Decl(controlFlowForCatchAndFinally.ts, 3, 52)) +>Promise : Symbol(Promise, Decl(lib.es5.d.ts, --, --), Decl(lib.es2015.iterable.d.ts, --, --), Decl(lib.es2015.promise.d.ts, --, --), Decl(lib.es2015.symbol.wellknown.d.ts, --, --)) + + let browser: Browser | undefined = undefined; +>browser : Symbol(browser, Decl(controlFlowForCatchAndFinally.ts, 5, 7)) +>Browser : Symbol(Browser, Decl(controlFlowForCatchAndFinally.ts, 0, 65)) +>undefined : Symbol(undefined) + + let page: Page | undefined = undefined; +>page : Symbol(page, Decl(controlFlowForCatchAndFinally.ts, 6, 7)) +>Page : Symbol(Page, Decl(controlFlowForCatchAndFinally.ts, 0, 0)) +>undefined : Symbol(undefined) + + try { + browser = await test1(); +>browser : Symbol(browser, Decl(controlFlowForCatchAndFinally.ts, 5, 7)) +>test1 : Symbol(test1, Decl(controlFlowForCatchAndFinally.ts, 1, 40)) + + page = await test2(browser); +>page : Symbol(page, Decl(controlFlowForCatchAndFinally.ts, 6, 7)) +>test2 : Symbol(test2, Decl(controlFlowForCatchAndFinally.ts, 2, 43)) +>browser : Symbol(browser, Decl(controlFlowForCatchAndFinally.ts, 5, 7)) + + return await page.content();; +>page.content : Symbol(content, Decl(controlFlowForCatchAndFinally.ts, 0, 36)) +>page : Symbol(page, Decl(controlFlowForCatchAndFinally.ts, 6, 7)) +>content : Symbol(content, Decl(controlFlowForCatchAndFinally.ts, 0, 36)) + + } finally { + if (page) { +>page : Symbol(page, Decl(controlFlowForCatchAndFinally.ts, 6, 7)) + + await page.close(); // ok +>page.close : Symbol(close, Decl(controlFlowForCatchAndFinally.ts, 0, 13)) +>page : Symbol(page, Decl(controlFlowForCatchAndFinally.ts, 6, 7)) +>close : Symbol(close, Decl(controlFlowForCatchAndFinally.ts, 0, 13)) + } + + if (browser) { +>browser : Symbol(browser, Decl(controlFlowForCatchAndFinally.ts, 5, 7)) + + await browser.close(); // ok +>browser.close : Symbol(close, Decl(controlFlowForCatchAndFinally.ts, 1, 16)) +>browser : Symbol(browser, Decl(controlFlowForCatchAndFinally.ts, 5, 7)) +>close : Symbol(close, Decl(controlFlowForCatchAndFinally.ts, 1, 16)) + } + } +} + +declare class Aborter { abort(): void }; +>Aborter : Symbol(Aborter, Decl(controlFlowForCatchAndFinally.ts, 20, 1)) +>abort : Symbol(Aborter.abort, Decl(controlFlowForCatchAndFinally.ts, 22, 23)) + +class Foo { +>Foo : Symbol(Foo, Decl(controlFlowForCatchAndFinally.ts, 22, 40)) + + abortController: Aborter | undefined = undefined; +>abortController : Symbol(Foo.abortController, Decl(controlFlowForCatchAndFinally.ts, 23, 11)) +>Aborter : Symbol(Aborter, Decl(controlFlowForCatchAndFinally.ts, 20, 1)) +>undefined : Symbol(undefined) + + async operation() { +>operation : Symbol(Foo.operation, Decl(controlFlowForCatchAndFinally.ts, 24, 53)) + + if (this.abortController !== undefined) { +>this.abortController : Symbol(Foo.abortController, Decl(controlFlowForCatchAndFinally.ts, 23, 11)) +>this : Symbol(Foo, Decl(controlFlowForCatchAndFinally.ts, 22, 40)) +>abortController : Symbol(Foo.abortController, Decl(controlFlowForCatchAndFinally.ts, 23, 11)) +>undefined : Symbol(undefined) + + this.abortController.abort(); +>this.abortController.abort : Symbol(Aborter.abort, Decl(controlFlowForCatchAndFinally.ts, 22, 23)) +>this.abortController : Symbol(Foo.abortController, Decl(controlFlowForCatchAndFinally.ts, 23, 11)) +>this : Symbol(Foo, Decl(controlFlowForCatchAndFinally.ts, 22, 40)) +>abortController : Symbol(Foo.abortController, Decl(controlFlowForCatchAndFinally.ts, 23, 11)) +>abort : Symbol(Aborter.abort, Decl(controlFlowForCatchAndFinally.ts, 22, 23)) + + this.abortController = undefined; +>this.abortController : Symbol(Foo.abortController, Decl(controlFlowForCatchAndFinally.ts, 23, 11)) +>this : Symbol(Foo, Decl(controlFlowForCatchAndFinally.ts, 22, 40)) +>abortController : Symbol(Foo.abortController, Decl(controlFlowForCatchAndFinally.ts, 23, 11)) +>undefined : Symbol(undefined) + } + try { + this.abortController = new Aborter(); +>this.abortController : Symbol(Foo.abortController, Decl(controlFlowForCatchAndFinally.ts, 23, 11)) +>this : Symbol(Foo, Decl(controlFlowForCatchAndFinally.ts, 22, 40)) +>abortController : Symbol(Foo.abortController, Decl(controlFlowForCatchAndFinally.ts, 23, 11)) +>Aborter : Symbol(Aborter, Decl(controlFlowForCatchAndFinally.ts, 20, 1)) + + } catch (error) { +>error : Symbol(error, Decl(controlFlowForCatchAndFinally.ts, 33, 17)) + + if (this.abortController !== undefined) { +>this.abortController : Symbol(Foo.abortController, Decl(controlFlowForCatchAndFinally.ts, 23, 11)) +>this : Symbol(Foo, Decl(controlFlowForCatchAndFinally.ts, 22, 40)) +>abortController : Symbol(Foo.abortController, Decl(controlFlowForCatchAndFinally.ts, 23, 11)) +>undefined : Symbol(undefined) + + this.abortController.abort(); // ok +>this.abortController.abort : Symbol(Aborter.abort, Decl(controlFlowForCatchAndFinally.ts, 22, 23)) +>this.abortController : Symbol(Foo.abortController, Decl(controlFlowForCatchAndFinally.ts, 23, 11)) +>this : Symbol(Foo, Decl(controlFlowForCatchAndFinally.ts, 22, 40)) +>abortController : Symbol(Foo.abortController, Decl(controlFlowForCatchAndFinally.ts, 23, 11)) +>abort : Symbol(Aborter.abort, Decl(controlFlowForCatchAndFinally.ts, 22, 23)) + } + } + } +} diff --git a/tests/baselines/reference/controlFlowForCatchAndFinally.types b/tests/baselines/reference/controlFlowForCatchAndFinally.types new file mode 100644 index 0000000000000..a041cac8ce09a --- /dev/null +++ b/tests/baselines/reference/controlFlowForCatchAndFinally.types @@ -0,0 +1,142 @@ +=== tests/cases/compiler/controlFlowForCatchAndFinally.ts === +type Page = {close(): Promise; content(): Promise}; +>Page : Page +>close : () => Promise +>content : () => Promise + +type Browser = {close(): Promise}; +>Browser : Browser +>close : () => Promise + +declare function test1(): Promise; +>test1 : () => Promise + +declare function test2(obj: Browser): Promise; +>test2 : (obj: Browser) => Promise +>obj : Browser + +async function test(): Promise { +>test : () => Promise + + let browser: Browser | undefined = undefined; +>browser : Browser | undefined +>undefined : undefined + + let page: Page | undefined = undefined; +>page : Page | undefined +>undefined : undefined + + try { + browser = await test1(); +>browser = await test1() : Browser +>browser : Browser | undefined +>await test1() : Browser +>test1() : Promise +>test1 : () => Promise + + page = await test2(browser); +>page = await test2(browser) : Page +>page : Page | undefined +>await test2(browser) : Page +>test2(browser) : Promise +>test2 : (obj: Browser) => Promise +>browser : Browser + + return await page.content();; +>await page.content() : string +>page.content() : Promise +>page.content : () => Promise +>page : Page +>content : () => Promise + + } finally { + if (page) { +>page : Page | undefined + + await page.close(); // ok +>await page.close() : void +>page.close() : Promise +>page.close : () => Promise +>page : Page +>close : () => Promise + } + + if (browser) { +>browser : Browser | undefined + + await browser.close(); // ok +>await browser.close() : void +>browser.close() : Promise +>browser.close : () => Promise +>browser : Browser +>close : () => Promise + } + } +} + +declare class Aborter { abort(): void }; +>Aborter : Aborter +>abort : () => void + +class Foo { +>Foo : Foo + + abortController: Aborter | undefined = undefined; +>abortController : Aborter | undefined +>undefined : undefined + + async operation() { +>operation : () => Promise + + if (this.abortController !== undefined) { +>this.abortController !== undefined : boolean +>this.abortController : Aborter | undefined +>this : this +>abortController : Aborter | undefined +>undefined : undefined + + this.abortController.abort(); +>this.abortController.abort() : void +>this.abortController.abort : () => void +>this.abortController : Aborter +>this : this +>abortController : Aborter +>abort : () => void + + this.abortController = undefined; +>this.abortController = undefined : undefined +>this.abortController : Aborter | undefined +>this : this +>abortController : Aborter | undefined +>undefined : undefined + } + try { + this.abortController = new Aborter(); +>this.abortController = new Aborter() : Aborter +>this.abortController : Aborter | undefined +>this : this +>abortController : Aborter | undefined +>new Aborter() : Aborter +>Aborter : typeof Aborter + + } catch (error) { +>error : any + + if (this.abortController !== undefined) { +>this.abortController !== undefined : boolean +>this.abortController : Aborter | undefined +>this : this +>abortController : Aborter | undefined +>undefined : undefined + + this.abortController.abort(); // ok +>this.abortController.abort() : void +>this.abortController.abort : () => void +>this.abortController : Aborter +>this : this +>abortController : Aborter +>abort : () => void + } + } + } +} diff --git a/tests/cases/compiler/controlFlowForCatchAndFinally.ts b/tests/cases/compiler/controlFlowForCatchAndFinally.ts new file mode 100644 index 0000000000000..b9a3facbf103f --- /dev/null +++ b/tests/cases/compiler/controlFlowForCatchAndFinally.ts @@ -0,0 +1,42 @@ +// @strict: true +// @lib: es6 +type Page = {close(): Promise; content(): Promise}; +type Browser = {close(): Promise}; +declare function test1(): Promise; +declare function test2(obj: Browser): Promise; +async function test(): Promise { + let browser: Browser | undefined = undefined; + let page: Page | undefined = undefined; + try { + browser = await test1(); + page = await test2(browser); + return await page.content();; + } finally { + if (page) { + await page.close(); // ok + } + + if (browser) { + await browser.close(); // ok + } + } +} + +declare class Aborter { abort(): void }; +class Foo { + abortController: Aborter | undefined = undefined; + + async operation() { + if (this.abortController !== undefined) { + this.abortController.abort(); + this.abortController = undefined; + } + try { + this.abortController = new Aborter(); + } catch (error) { + if (this.abortController !== undefined) { + this.abortController.abort(); // ok + } + } + } +} \ No newline at end of file