From 4827728b168eb46745b28a0932211aa0b704de58 Mon Sep 17 00:00:00 2001 From: Klaus Meinhardt Date: Fri, 20 Apr 2018 16:26:53 +0200 Subject: [PATCH 1/2] binder: don't inline generator function's control flow Fixes: #23565 --- src/compiler/binder.ts | 5 +- .../reference/controlFlowIIFE.errors.txt | 89 +++++++++++++++ tests/baselines/reference/controlFlowIIFE.js | 103 +++++++++++++++++- .../reference/controlFlowIIFE.symbols | 48 ++++++++ .../baselines/reference/controlFlowIIFE.types | 70 ++++++++++++ .../controlFlow/controlFlowIIFE.ts | 29 ++++- 6 files changed, 340 insertions(+), 4 deletions(-) create mode 100644 tests/baselines/reference/controlFlowIIFE.errors.txt diff --git a/src/compiler/binder.ts b/src/compiler/binder.ts index e6e3364840d..fe77e9b4df4 100644 --- a/src/compiler/binder.ts +++ b/src/compiler/binder.ts @@ -516,8 +516,9 @@ namespace ts { const saveReturnTarget = currentReturnTarget; const saveActiveLabels = activeLabels; const saveHasExplicitReturn = hasExplicitReturn; - const isIIFE = containerFlags & ContainerFlags.IsFunctionExpression && !hasModifier(node, ModifierFlags.Async) && !!getImmediatelyInvokedFunctionExpression(node); - // A non-async IIFE is considered part of the containing control flow. Return statements behave + const isIIFE = containerFlags & ContainerFlags.IsFunctionExpression && !hasModifier(node, ModifierFlags.Async) && + !(node).asteriskToken && !!getImmediatelyInvokedFunctionExpression(node); + // A non-async, non-generator IIFE is considered part of the containing control flow. Return statements behave // similarly to break statements that exit to a label just past the statement body. if (!isIIFE) { currentFlow = { flags: FlowFlags.Start }; diff --git a/tests/baselines/reference/controlFlowIIFE.errors.txt b/tests/baselines/reference/controlFlowIIFE.errors.txt new file mode 100644 index 00000000000..f8e82aff966 --- /dev/null +++ b/tests/baselines/reference/controlFlowIIFE.errors.txt @@ -0,0 +1,89 @@ +error TS2318: Cannot find global type 'IterableIterator'. +error TS2468: Cannot find global value 'Promise'. +tests/cases/conformance/controlFlow/controlFlowIIFE.ts(64,5): error TS2454: Variable 'v' is used before being assigned. +tests/cases/conformance/controlFlow/controlFlowIIFE.ts(69,6): error TS2705: An async function or method in ES5/ES3 requires the 'Promise' constructor. Make sure you have a declaration for the 'Promise' constructor or include 'ES2015' in your `--lib` option. +tests/cases/conformance/controlFlow/controlFlowIIFE.ts(72,5): error TS2454: Variable 'v' is used before being assigned. + + +!!! error TS2318: Cannot find global type 'IterableIterator'. +!!! error TS2468: Cannot find global value 'Promise'. +==== tests/cases/conformance/controlFlow/controlFlowIIFE.ts (3 errors) ==== + declare function getStringOrNumber(): string | number; + + function f1() { + let x = getStringOrNumber(); + if (typeof x === "string") { + let n = function() { + return x.length; + }(); + } + } + + function f2() { + let x = getStringOrNumber(); + if (typeof x === "string") { + let n = (function() { + return x.length; + })(); + } + } + + function f3() { + let x = getStringOrNumber(); + let y: number; + if (typeof x === "string") { + let n = (z => x.length + y + z)(y = 1); + } + } + + // Repros from #8381 + + let maybeNumber: number | undefined; + (function () { + maybeNumber = 1; + })(); + maybeNumber++; + if (maybeNumber !== undefined) { + maybeNumber++; + } + + let test: string | undefined; + if (!test) { + throw new Error('Test is not defined'); + } + (() => { + test.slice(1); // No error + })(); + + // Repro from #23565 + + function f4() { + let v: number; + (function() { + v = 1; + })(); + v; + } + + function f5() { + let v: number; + (function*() { + yield 1; + v = 1; + })(); + v; // still undefined + ~ +!!! error TS2454: Variable 'v' is used before being assigned. + } + + function f6() { + let v: number; + (async function() { + ~~~~~ +!!! error TS2705: An async function or method in ES5/ES3 requires the 'Promise' constructor. Make sure you have a declaration for the 'Promise' constructor or include 'ES2015' in your `--lib` option. + v = await 1; + })(); + v; // still undefined + ~ +!!! error TS2454: Variable 'v' is used before being assigned. + } \ No newline at end of file diff --git a/tests/baselines/reference/controlFlowIIFE.js b/tests/baselines/reference/controlFlowIIFE.js index 20bde01cba6..dfe536bcd2a 100644 --- a/tests/baselines/reference/controlFlowIIFE.js +++ b/tests/baselines/reference/controlFlowIIFE.js @@ -44,9 +44,71 @@ if (!test) { } (() => { test.slice(1); // No error -})(); +})(); + +// Repro from #23565 + +function f4() { + let v: number; + (function() { + v = 1; + })(); + v; +} + +function f5() { + let v: number; + (function*() { + yield 1; + v = 1; + })(); + v; // still undefined +} + +function f6() { + let v: number; + (async function() { + v = await 1; + })(); + v; // still undefined +} //// [controlFlowIIFE.js] +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 = y[op[0] & 2 ? "return" : op[0] ? "throw" : "next"]) && !(t = t.call(y, op[1])).done) return t; + if (y = 0, t) op = [0, 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 f1() { var x = getStringOrNumber(); if (typeof x === "string") { @@ -86,3 +148,42 @@ if (!test) { (function () { test.slice(1); // No error })(); +// Repro from #23565 +function f4() { + var v; + (function () { + v = 1; + })(); + v; +} +function f5() { + var v; + (function () { + return __generator(this, function (_a) { + switch (_a.label) { + case 0: return [4 /*yield*/, 1]; + case 1: + _a.sent(); + v = 1; + return [2 /*return*/]; + } + }); + })(); + v; // still undefined +} +function f6() { + var v; + (function () { + return __awaiter(this, void 0, void 0, function () { + return __generator(this, function (_a) { + switch (_a.label) { + case 0: return [4 /*yield*/, 1]; + case 1: + v = _a.sent(); + return [2 /*return*/]; + } + }); + }); + })(); + v; // still undefined +} diff --git a/tests/baselines/reference/controlFlowIIFE.symbols b/tests/baselines/reference/controlFlowIIFE.symbols index 4df906e0684..619d945cca3 100644 --- a/tests/baselines/reference/controlFlowIIFE.symbols +++ b/tests/baselines/reference/controlFlowIIFE.symbols @@ -108,3 +108,51 @@ if (!test) { >slice : Symbol(String.slice, Decl(lib.d.ts, --, --)) })(); + +// Repro from #23565 + +function f4() { +>f4 : Symbol(f4, Decl(controlFlowIIFE.ts, 45, 5)) + + let v: number; +>v : Symbol(v, Decl(controlFlowIIFE.ts, 50, 7)) + + (function() { + v = 1; +>v : Symbol(v, Decl(controlFlowIIFE.ts, 50, 7)) + + })(); + v; +>v : Symbol(v, Decl(controlFlowIIFE.ts, 50, 7)) +} + +function f5() { +>f5 : Symbol(f5, Decl(controlFlowIIFE.ts, 55, 1)) + + let v: number; +>v : Symbol(v, Decl(controlFlowIIFE.ts, 58, 7)) + + (function*() { + yield 1; + v = 1; +>v : Symbol(v, Decl(controlFlowIIFE.ts, 58, 7)) + + })(); + v; // still undefined +>v : Symbol(v, Decl(controlFlowIIFE.ts, 58, 7)) +} + +function f6() { +>f6 : Symbol(f6, Decl(controlFlowIIFE.ts, 64, 1)) + + let v: number; +>v : Symbol(v, Decl(controlFlowIIFE.ts, 67, 7)) + + (async function() { + v = await 1; +>v : Symbol(v, Decl(controlFlowIIFE.ts, 67, 7)) + + })(); + v; // still undefined +>v : Symbol(v, Decl(controlFlowIIFE.ts, 67, 7)) +} diff --git a/tests/baselines/reference/controlFlowIIFE.types b/tests/baselines/reference/controlFlowIIFE.types index 1132649ae09..9fde9b4f99f 100644 --- a/tests/baselines/reference/controlFlowIIFE.types +++ b/tests/baselines/reference/controlFlowIIFE.types @@ -150,3 +150,73 @@ if (!test) { >1 : 1 })(); + +// Repro from #23565 + +function f4() { +>f4 : () => void + + let v: number; +>v : number + + (function() { +>(function() { v = 1; })() : void +>(function() { v = 1; }) : () => void +>function() { v = 1; } : () => void + + v = 1; +>v = 1 : 1 +>v : number +>1 : 1 + + })(); + v; +>v : number +} + +function f5() { +>f5 : () => void + + let v: number; +>v : number + + (function*() { +>(function*() { yield 1; v = 1; })() : {} +>(function*() { yield 1; v = 1; }) : () => {} +>function*() { yield 1; v = 1; } : () => {} + + yield 1; +>yield 1 : any +>1 : 1 + + v = 1; +>v = 1 : 1 +>v : number +>1 : 1 + + })(); + v; // still undefined +>v : number +} + +function f6() { +>f6 : () => void + + let v: number; +>v : number + + (async function() { +>(async function() { v = await 1; })() : Promise +>(async function() { v = await 1; }) : () => Promise +>async function() { v = await 1; } : () => Promise + + v = await 1; +>v = await 1 : 1 +>v : number +>await 1 : 1 +>1 : 1 + + })(); + v; // still undefined +>v : number +} diff --git a/tests/cases/conformance/controlFlow/controlFlowIIFE.ts b/tests/cases/conformance/controlFlow/controlFlowIIFE.ts index c72f038c1ec..a3ee39d623e 100644 --- a/tests/cases/conformance/controlFlow/controlFlowIIFE.ts +++ b/tests/cases/conformance/controlFlow/controlFlowIIFE.ts @@ -45,4 +45,31 @@ if (!test) { } (() => { test.slice(1); // No error -})(); \ No newline at end of file +})(); + +// Repro from #23565 + +function f4() { + let v: number; + (function() { + v = 1; + })(); + v; +} + +function f5() { + let v: number; + (function*() { + yield 1; + v = 1; + })(); + v; // still undefined +} + +function f6() { + let v: number; + (async function() { + v = await 1; + })(); + v; // still undefined +} \ No newline at end of file From 8e565fb4a6334d0e07c1d9041d144cbc2cc9a26e Mon Sep 17 00:00:00 2001 From: Klaus Meinhardt Date: Fri, 20 Apr 2018 16:39:37 +0200 Subject: [PATCH 2/2] avoid generated helpers in baseline --- .../reference/controlFlowIIFE.errors.txt | 9 +- tests/baselines/reference/controlFlowIIFE.js | 87 ++++--------------- .../reference/controlFlowIIFE.symbols | 18 ++-- .../baselines/reference/controlFlowIIFE.types | 6 +- .../controlFlow/controlFlowIIFE.ts | 1 + 5 files changed, 32 insertions(+), 89 deletions(-) diff --git a/tests/baselines/reference/controlFlowIIFE.errors.txt b/tests/baselines/reference/controlFlowIIFE.errors.txt index f8e82aff966..50053a90f2c 100644 --- a/tests/baselines/reference/controlFlowIIFE.errors.txt +++ b/tests/baselines/reference/controlFlowIIFE.errors.txt @@ -1,13 +1,8 @@ -error TS2318: Cannot find global type 'IterableIterator'. -error TS2468: Cannot find global value 'Promise'. tests/cases/conformance/controlFlow/controlFlowIIFE.ts(64,5): error TS2454: Variable 'v' is used before being assigned. -tests/cases/conformance/controlFlow/controlFlowIIFE.ts(69,6): error TS2705: An async function or method in ES5/ES3 requires the 'Promise' constructor. Make sure you have a declaration for the 'Promise' constructor or include 'ES2015' in your `--lib` option. tests/cases/conformance/controlFlow/controlFlowIIFE.ts(72,5): error TS2454: Variable 'v' is used before being assigned. -!!! error TS2318: Cannot find global type 'IterableIterator'. -!!! error TS2468: Cannot find global value 'Promise'. -==== tests/cases/conformance/controlFlow/controlFlowIIFE.ts (3 errors) ==== +==== tests/cases/conformance/controlFlow/controlFlowIIFE.ts (2 errors) ==== declare function getStringOrNumber(): string | number; function f1() { @@ -79,8 +74,6 @@ tests/cases/conformance/controlFlow/controlFlowIIFE.ts(72,5): error TS2454: Vari function f6() { let v: number; (async function() { - ~~~~~ -!!! error TS2705: An async function or method in ES5/ES3 requires the 'Promise' constructor. Make sure you have a declaration for the 'Promise' constructor or include 'ES2015' in your `--lib` option. v = await 1; })(); v; // still undefined diff --git a/tests/baselines/reference/controlFlowIIFE.js b/tests/baselines/reference/controlFlowIIFE.js index dfe536bcd2a..5c7fcb4bd52 100644 --- a/tests/baselines/reference/controlFlowIIFE.js +++ b/tests/baselines/reference/controlFlowIIFE.js @@ -74,66 +74,31 @@ function f6() { } //// [controlFlowIIFE.js] -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 = y[op[0] & 2 ? "return" : op[0] ? "throw" : "next"]) && !(t = t.call(y, op[1])).done) return t; - if (y = 0, t) op = [0, 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 f1() { - var x = getStringOrNumber(); + let x = getStringOrNumber(); if (typeof x === "string") { - var n = function () { + let n = function () { return x.length; }(); } } function f2() { - var x = getStringOrNumber(); + let x = getStringOrNumber(); if (typeof x === "string") { - var n = (function () { + let n = (function () { return x.length; })(); } } function f3() { - var x = getStringOrNumber(); - var y; + let x = getStringOrNumber(); + let y; if (typeof x === "string") { - var n = (function (z) { return x.length + y + z; })(y = 1); + let n = (z => x.length + y + z)(y = 1); } } // Repros from #8381 -var maybeNumber; +let maybeNumber; (function () { maybeNumber = 1; })(); @@ -141,49 +106,33 @@ maybeNumber++; if (maybeNumber !== undefined) { maybeNumber++; } -var test; +let test; if (!test) { throw new Error('Test is not defined'); } -(function () { +(() => { test.slice(1); // No error })(); // Repro from #23565 function f4() { - var v; + let v; (function () { v = 1; })(); v; } function f5() { - var v; - (function () { - return __generator(this, function (_a) { - switch (_a.label) { - case 0: return [4 /*yield*/, 1]; - case 1: - _a.sent(); - v = 1; - return [2 /*return*/]; - } - }); + let v; + (function* () { + yield 1; + v = 1; })(); v; // still undefined } function f6() { - var v; - (function () { - return __awaiter(this, void 0, void 0, function () { - return __generator(this, function (_a) { - switch (_a.label) { - case 0: return [4 /*yield*/, 1]; - case 1: - v = _a.sent(); - return [2 /*return*/]; - } - }); - }); + let v; + (async function () { + v = await 1; })(); v; // still undefined } diff --git a/tests/baselines/reference/controlFlowIIFE.symbols b/tests/baselines/reference/controlFlowIIFE.symbols index 619d945cca3..038d398c649 100644 --- a/tests/baselines/reference/controlFlowIIFE.symbols +++ b/tests/baselines/reference/controlFlowIIFE.symbols @@ -16,9 +16,9 @@ function f1() { >n : Symbol(n, Decl(controlFlowIIFE.ts, 5, 11)) return x.length; ->x.length : Symbol(String.length, Decl(lib.d.ts, --, --)) +>x.length : Symbol(String.length, Decl(lib.es5.d.ts, --, --)) >x : Symbol(x, Decl(controlFlowIIFE.ts, 3, 7)) ->length : Symbol(String.length, Decl(lib.d.ts, --, --)) +>length : Symbol(String.length, Decl(lib.es5.d.ts, --, --)) }(); } @@ -38,9 +38,9 @@ function f2() { >n : Symbol(n, Decl(controlFlowIIFE.ts, 14, 11)) return x.length; ->x.length : Symbol(String.length, Decl(lib.d.ts, --, --)) +>x.length : Symbol(String.length, Decl(lib.es5.d.ts, --, --)) >x : Symbol(x, Decl(controlFlowIIFE.ts, 12, 7)) ->length : Symbol(String.length, Decl(lib.d.ts, --, --)) +>length : Symbol(String.length, Decl(lib.es5.d.ts, --, --)) })(); } @@ -62,9 +62,9 @@ function f3() { let n = (z => x.length + y + z)(y = 1); >n : Symbol(n, Decl(controlFlowIIFE.ts, 24, 11)) >z : Symbol(z, Decl(controlFlowIIFE.ts, 24, 17)) ->x.length : Symbol(String.length, Decl(lib.d.ts, --, --)) +>x.length : Symbol(String.length, Decl(lib.es5.d.ts, --, --)) >x : Symbol(x, Decl(controlFlowIIFE.ts, 21, 7)) ->length : Symbol(String.length, Decl(lib.d.ts, --, --)) +>length : Symbol(String.length, Decl(lib.es5.d.ts, --, --)) >y : Symbol(y, Decl(controlFlowIIFE.ts, 22, 7)) >z : Symbol(z, Decl(controlFlowIIFE.ts, 24, 17)) >y : Symbol(y, Decl(controlFlowIIFE.ts, 22, 7)) @@ -99,13 +99,13 @@ if (!test) { >test : Symbol(test, Decl(controlFlowIIFE.ts, 39, 3)) throw new Error('Test is not defined'); ->Error : Symbol(Error, Decl(lib.d.ts, --, --), Decl(lib.d.ts, --, --)) +>Error : Symbol(Error, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --)) } (() => { test.slice(1); // No error ->test.slice : Symbol(String.slice, Decl(lib.d.ts, --, --)) +>test.slice : Symbol(String.slice, Decl(lib.es5.d.ts, --, --)) >test : Symbol(test, Decl(controlFlowIIFE.ts, 39, 3)) ->slice : Symbol(String.slice, Decl(lib.d.ts, --, --)) +>slice : Symbol(String.slice, Decl(lib.es5.d.ts, --, --)) })(); diff --git a/tests/baselines/reference/controlFlowIIFE.types b/tests/baselines/reference/controlFlowIIFE.types index 9fde9b4f99f..7aaaad159fc 100644 --- a/tests/baselines/reference/controlFlowIIFE.types +++ b/tests/baselines/reference/controlFlowIIFE.types @@ -181,9 +181,9 @@ function f5() { >v : number (function*() { ->(function*() { yield 1; v = 1; })() : {} ->(function*() { yield 1; v = 1; }) : () => {} ->function*() { yield 1; v = 1; } : () => {} +>(function*() { yield 1; v = 1; })() : IterableIterator +>(function*() { yield 1; v = 1; }) : () => IterableIterator +>function*() { yield 1; v = 1; } : () => IterableIterator yield 1; >yield 1 : any diff --git a/tests/cases/conformance/controlFlow/controlFlowIIFE.ts b/tests/cases/conformance/controlFlow/controlFlowIIFE.ts index a3ee39d623e..8cd9047cb05 100644 --- a/tests/cases/conformance/controlFlow/controlFlowIIFE.ts +++ b/tests/cases/conformance/controlFlow/controlFlowIIFE.ts @@ -1,4 +1,5 @@ // @strictNullChecks: true +// @target: ES2017 declare function getStringOrNumber(): string | number;