diff --git a/src/compiler/binder.ts b/src/compiler/binder.ts index 7dd4ed1a39a..5b48a69fa04 100644 --- a/src/compiler/binder.ts +++ b/src/compiler/binder.ts @@ -3712,6 +3712,10 @@ namespace ts { break; case SyntaxKind.ReturnStatement: + // Return statements may require an `await` in ESNext. + transformFlags |= TransformFlags.ContainsHoistedDeclarationOrCompletion | TransformFlags.AssertESNext; + break; + case SyntaxKind.ContinueStatement: case SyntaxKind.BreakStatement: transformFlags |= TransformFlags.ContainsHoistedDeclarationOrCompletion; diff --git a/src/compiler/transformers/esnext.ts b/src/compiler/transformers/esnext.ts index db6069fd5ac..49453139947 100644 --- a/src/compiler/transformers/esnext.ts +++ b/src/compiler/transformers/esnext.ts @@ -63,6 +63,8 @@ namespace ts { return visitAwaitExpression(node as AwaitExpression); case SyntaxKind.YieldExpression: return visitYieldExpression(node as YieldExpression); + case SyntaxKind.ReturnStatement: + return visitReturnStatement(node as ReturnStatement); case SyntaxKind.LabeledStatement: return visitLabeledStatement(node as LabeledStatement); case SyntaxKind.ObjectLiteralExpression: @@ -161,6 +163,16 @@ namespace ts { return visitEachChild(node, visitor, context); } + function visitReturnStatement(node: ReturnStatement) { + if (enclosingFunctionFlags & FunctionFlags.Async && enclosingFunctionFlags & FunctionFlags.Generator) { + return updateReturn(node, createDownlevelAwait( + node.expression ? visitNode(node.expression, visitor, isExpression) : createVoidZero() + )); + } + + return visitEachChild(node, visitor, context); + } + function visitLabeledStatement(node: LabeledStatement) { if (enclosingFunctionFlags & FunctionFlags.Async) { const statement = unwrapInnermostStatementOfLabel(node); diff --git a/src/harness/evaluator.ts b/src/harness/evaluator.ts new file mode 100644 index 00000000000..793fb800f6a --- /dev/null +++ b/src/harness/evaluator.ts @@ -0,0 +1,55 @@ +namespace evaluator { + declare var Symbol: SymbolConstructor; + + const sourceFile = vpath.combine(vfs.srcFolder, "source.ts"); + + function compile(sourceText: string, options?: ts.CompilerOptions) { + const fs = vfs.createFromFileSystem(Harness.IO, /*ignoreCase*/ false); + fs.writeFileSync(sourceFile, sourceText); + const compilerOptions: ts.CompilerOptions = { target: ts.ScriptTarget.ES5, module: ts.ModuleKind.CommonJS, lib: ["lib.esnext.d.ts"], ...options }; + const host = new fakes.CompilerHost(fs, compilerOptions); + return compiler.compileFiles(host, [sourceFile], compilerOptions); + } + + function noRequire(id: string) { + throw new Error(`Module '${id}' could not be found.`); + } + + // Define a custom "Symbol" constructor to attach missing built-in symbols without + // modifying the global "Symbol" constructor + // tslint:disable-next-line:variable-name + const FakeSymbol: SymbolConstructor = ((description?: string) => Symbol(description)) as any; + (FakeSymbol).prototype = Symbol.prototype; + for (const key of Object.getOwnPropertyNames(Symbol)) { + Object.defineProperty(FakeSymbol, key, Object.getOwnPropertyDescriptor(Symbol, key)!); + } + + // Add "asyncIterator" if missing + if (!ts.hasProperty(FakeSymbol, "asyncIterator")) Object.defineProperty(FakeSymbol, "asyncIterator", { value: Symbol.for("Symbol.asyncIterator"), configurable: true }); + + function evaluate(result: compiler.CompilationResult, globals?: Record) { + globals = { Symbol: FakeSymbol, ...globals }; + + const output = result.getOutput(sourceFile, "js")!; + assert.isDefined(output); + + const globalNames: string[] = []; + const globalArgs: any[] = []; + for (const name in globals) { + if (ts.hasProperty(globals, name)) { + globalNames.push(name); + globalArgs.push(globals[name]); + } + } + + const evaluateText = `(function (module, exports, require, __dirname, __filename, ${globalNames.join(", ")}) { ${output.text} })`; + const evaluateThunk = eval(evaluateText) as (module: any, exports: any, require: (id: string) => any, dirname: string, filename: string, ...globalArgs: any[]) => void; + const module: { exports: any; } = { exports: {} }; + evaluateThunk.call(globals, module, module.exports, noRequire, vpath.dirname(output.file), output.file, FakeSymbol, ...globalArgs); + return module.exports; + } + + export function evaluateTypeScript(sourceText: string, options?: ts.CompilerOptions, globals?: Record) { + return evaluate(compile(sourceText, options), globals); + } +} \ No newline at end of file diff --git a/src/harness/tsconfig.json b/src/harness/tsconfig.json index 102bddf91c7..64dea2c789e 100644 --- a/src/harness/tsconfig.json +++ b/src/harness/tsconfig.json @@ -148,6 +148,7 @@ "vpath.ts", "vfs.ts", "compiler.ts", + "evaluator.ts", "fakes.ts", "sourceMapRecorder.ts", @@ -169,5 +170,5 @@ "parallel/shared.ts", "runner.ts" ], - "include": ["unittests/**.ts"] + "include": ["unittests/**/*.ts"] } diff --git a/src/harness/unittests/evaluation/asyncGenerator.ts b/src/harness/unittests/evaluation/asyncGenerator.ts new file mode 100644 index 00000000000..9963ea921fa --- /dev/null +++ b/src/harness/unittests/evaluation/asyncGenerator.ts @@ -0,0 +1,30 @@ +describe("asyncGeneratorEvaluation", () => { + it("return (es5)", async () => { + const result = evaluator.evaluateTypeScript(` + async function * g() { + return Promise.resolve(0); + } + export const output: any[] = []; + export async function main() { + output.push(await g().next()); + }`); + await result.main(); + assert.deepEqual(result.output, [ + { value: 0, done: true } + ]); + }); + it("return (es2015)", async () => { + const result = evaluator.evaluateTypeScript(` + async function * g() { + return Promise.resolve(0); + } + export const output: any[] = []; + export async function main() { + output.push(await g().next()); + }`, { target: ts.ScriptTarget.ES2015 }); + await result.main(); + assert.deepEqual(result.output, [ + { value: 0, done: true } + ]); + }); +}); \ No newline at end of file diff --git a/src/harness/unittests/evaluation/forAwaitOf.ts b/src/harness/unittests/evaluation/forAwaitOf.ts new file mode 100644 index 00000000000..b02a45860b9 --- /dev/null +++ b/src/harness/unittests/evaluation/forAwaitOf.ts @@ -0,0 +1,105 @@ +describe("forAwaitOfEvaluation", () => { + it("sync (es5)", async () => { + const result = evaluator.evaluateTypeScript(` + let i = 0; + const iterator = { + [Symbol.iterator]() { return this; }, + next() { + switch (i++) { + case 0: return { value: 1, done: false }; + case 1: return { value: Promise.resolve(2), done: false }; + case 2: return { value: new Promise(resolve => setTimeout(resolve, 100, 3)), done: false }; + default: return { value: undefined: done: true }; + } + } + }; + export const output: any[] = []; + export async function main() { + for await (const item of iterator) { + output.push(item); + } + }`); + await result.main(); + assert.strictEqual(result.output[0], 1); + assert.strictEqual(result.output[1], 2); + assert.strictEqual(result.output[2], 3); + }); + + it("sync (es2015)", async () => { + const result = evaluator.evaluateTypeScript(` + let i = 0; + const iterator = { + [Symbol.iterator]() { return this; }, + next() { + switch (i++) { + case 0: return { value: 1, done: false }; + case 1: return { value: Promise.resolve(2), done: false }; + case 2: return { value: new Promise(resolve => setTimeout(resolve, 100, 3)), done: false }; + default: return { value: undefined: done: true }; + } + } + }; + export const output: any[] = []; + export async function main() { + for await (const item of iterator) { + output.push(item); + } + }`, { target: ts.ScriptTarget.ES2015 }); + await result.main(); + assert.strictEqual(result.output[0], 1); + assert.strictEqual(result.output[1], 2); + assert.strictEqual(result.output[2], 3); + }); + + it("async (es5)", async () => { + const result = evaluator.evaluateTypeScript(` + let i = 0; + const iterator = { + [Symbol.asyncIterator]() { return this; }, + async next() { + switch (i++) { + case 0: return { value: 1, done: false }; + case 1: return { value: Promise.resolve(2), done: false }; + case 2: return { value: new Promise(resolve => setTimeout(resolve, 100, 3)), done: false }; + default: return { value: undefined: done: true }; + } + } + }; + export const output: any[] = []; + export async function main() { + for await (const item of iterator) { + output.push(item); + } + }`); + await result.main(); + assert.strictEqual(result.output[0], 1); + assert.instanceOf(result.output[1], Promise); + assert.instanceOf(result.output[2], Promise); + }); + + it("async (es2015)", async () => { + const result = evaluator.evaluateTypeScript(` + let i = 0; + const iterator = { + [Symbol.asyncIterator]() { return this; }, + async next() { + switch (i++) { + case 0: return { value: 1, done: false }; + case 1: return { value: Promise.resolve(2), done: false }; + case 2: return { value: new Promise(resolve => setTimeout(resolve, 100, 3)), done: false }; + default: return { value: undefined: done: true }; + } + } + }; + export const output: any[] = []; + export async function main() { + for await (const item of iterator) { + output.push(item); + } + }`, { target: ts.ScriptTarget.ES2015 }); + await result.main(); + assert.strictEqual(result.output[0], 1); + assert.instanceOf(result.output[1], Promise); + assert.instanceOf(result.output[2], Promise); + }); +}); \ No newline at end of file diff --git a/src/harness/unittests/forAwaitOfEvaluation.ts b/src/harness/unittests/forAwaitOfEvaluation.ts deleted file mode 100644 index 339e0ca664b..00000000000 --- a/src/harness/unittests/forAwaitOfEvaluation.ts +++ /dev/null @@ -1,148 +0,0 @@ -/// - -namespace ts { - declare var Symbol: SymbolConstructor; - - describe("forAwaitOfEvaluation", () => { - const sourceFile = vpath.combine(vfs.srcFolder, "source.ts"); - - function compile(sourceText: string, options?: CompilerOptions) { - const fs = vfs.createFromFileSystem(Harness.IO, /*ignoreCase*/ false); - fs.writeFileSync(sourceFile, sourceText); - const compilerOptions: CompilerOptions = { target: ScriptTarget.ES5, module: ModuleKind.CommonJS, lib: ["lib.esnext.d.ts"], ...options }; - const host = new fakes.CompilerHost(fs, compilerOptions); - return compiler.compileFiles(host, [sourceFile], compilerOptions); - } - - function noRequire(id: string) { - throw new Error(`Module '${id}' could not be found.`); - } - - // Define a custom "Symbol" constructor to attach missing built-in symbols without - // modifying the global "Symbol" constructor - // tslint:disable-next-line:variable-name - const FakeSymbol: SymbolConstructor = ((description?: string) => Symbol(description)) as any; - (FakeSymbol).prototype = Symbol.prototype; - for (const key of Object.getOwnPropertyNames(Symbol)) { - Object.defineProperty(FakeSymbol, key, Object.getOwnPropertyDescriptor(Symbol, key)!); - } - - // Add "asyncIterator" if missing - if (!hasProperty(FakeSymbol, "asyncIterator")) Object.defineProperty(FakeSymbol, "asyncIterator", { value: Symbol.for("Symbol.asyncIterator"), configurable: true }); - - function evaluate(result: compiler.CompilationResult) { - const output = result.getOutput(sourceFile, "js")!; - assert.isDefined(output); - - const evaluateText = `(function (module, exports, require, __dirname, __filename, Symbol) { ${output.text} })`; - const evaluateThunk = eval(evaluateText) as (module: any, exports: any, require: (id: string) => any, dirname: string, filename: string, symbolConstructor: SymbolConstructor) => void; - const module: { exports: any; } = { exports: {} }; - evaluateThunk(module, module.exports, noRequire, vpath.dirname(output.file), output.file, FakeSymbol); - return module; - } - - it("sync (es5)", async () => { - const module = evaluate(compile(` - let i = 0; - const iterator = { - [Symbol.iterator]() { return this; }, - next() { - switch (i++) { - case 0: return { value: 1, done: false }; - case 1: return { value: Promise.resolve(2), done: false }; - case 2: return { value: new Promise(resolve => setTimeout(resolve, 100, 3)), done: false }; - default: return { value: undefined: done: true }; - } - } - }; - export const output: any[] = []; - export async function main() { - for await (const item of iterator) { - output.push(item); - } - }`)); - await module.exports.main(); - assert.strictEqual(module.exports.output[0], 1); - assert.strictEqual(module.exports.output[1], 2); - assert.strictEqual(module.exports.output[2], 3); - }); - - it("sync (es2015)", async () => { - const module = evaluate(compile(` - let i = 0; - const iterator = { - [Symbol.iterator]() { return this; }, - next() { - switch (i++) { - case 0: return { value: 1, done: false }; - case 1: return { value: Promise.resolve(2), done: false }; - case 2: return { value: new Promise(resolve => setTimeout(resolve, 100, 3)), done: false }; - default: return { value: undefined: done: true }; - } - } - }; - export const output: any[] = []; - export async function main() { - for await (const item of iterator) { - output.push(item); - } - }`, { target: ScriptTarget.ES2015 })); - await module.exports.main(); - assert.strictEqual(module.exports.output[0], 1); - assert.strictEqual(module.exports.output[1], 2); - assert.strictEqual(module.exports.output[2], 3); - }); - - it("async (es5)", async () => { - const module = evaluate(compile(` - let i = 0; - const iterator = { - [Symbol.asyncIterator]() { return this; }, - async next() { - switch (i++) { - case 0: return { value: 1, done: false }; - case 1: return { value: Promise.resolve(2), done: false }; - case 2: return { value: new Promise(resolve => setTimeout(resolve, 100, 3)), done: false }; - default: return { value: undefined: done: true }; - } - } - }; - export const output: any[] = []; - export async function main() { - for await (const item of iterator) { - output.push(item); - } - }`)); - await module.exports.main(); - assert.strictEqual(module.exports.output[0], 1); - assert.instanceOf(module.exports.output[1], Promise); - assert.instanceOf(module.exports.output[2], Promise); - }); - - it("async (es2015)", async () => { - const module = evaluate(compile(` - let i = 0; - const iterator = { - [Symbol.asyncIterator]() { return this; }, - async next() { - switch (i++) { - case 0: return { value: 1, done: false }; - case 1: return { value: Promise.resolve(2), done: false }; - case 2: return { value: new Promise(resolve => setTimeout(resolve, 100, 3)), done: false }; - default: return { value: undefined: done: true }; - } - } - }; - export const output: any[] = []; - export async function main() { - for await (const item of iterator) { - output.push(item); - } - }`, { target: ScriptTarget.ES2015 })); - await module.exports.main(); - assert.strictEqual(module.exports.output[0], 1); - assert.instanceOf(module.exports.output[1], Promise); - assert.instanceOf(module.exports.output[2], Promise); - }); - }); -} \ No newline at end of file diff --git a/tests/baselines/reference/emitter.asyncGenerators.classMethods.es2015.js b/tests/baselines/reference/emitter.asyncGenerators.classMethods.es2015.js index 821bd29afab..33b80e78d8e 100644 --- a/tests/baselines/reference/emitter.asyncGenerators.classMethods.es2015.js +++ b/tests/baselines/reference/emitter.asyncGenerators.classMethods.es2015.js @@ -219,7 +219,7 @@ var __asyncGenerator = (this && this.__asyncGenerator) || function (thisArg, _ar class C7 { f() { return __asyncGenerator(this, arguments, function* f_1() { - return 1; + return yield __await(1); }); } } diff --git a/tests/baselines/reference/emitter.asyncGenerators.classMethods.es5.js b/tests/baselines/reference/emitter.asyncGenerators.classMethods.es5.js index f3eb2cbdbd0..675e149a1da 100644 --- a/tests/baselines/reference/emitter.asyncGenerators.classMethods.es5.js +++ b/tests/baselines/reference/emitter.asyncGenerators.classMethods.es5.js @@ -504,7 +504,10 @@ var C7 = /** @class */ (function () { C7.prototype.f = function () { return __asyncGenerator(this, arguments, function f_1() { return __generator(this, function (_a) { - return [2 /*return*/, 1]; + switch (_a.label) { + case 0: return [4 /*yield*/, __await(1)]; + case 1: return [2 /*return*/, _a.sent()]; + } }); }); }; diff --git a/tests/baselines/reference/emitter.asyncGenerators.functionDeclarations.es2015.js b/tests/baselines/reference/emitter.asyncGenerators.functionDeclarations.es2015.js index c0a16fa076b..4b0d75d61fd 100644 --- a/tests/baselines/reference/emitter.asyncGenerators.functionDeclarations.es2015.js +++ b/tests/baselines/reference/emitter.asyncGenerators.functionDeclarations.es2015.js @@ -175,6 +175,6 @@ var __asyncGenerator = (this && this.__asyncGenerator) || function (thisArg, _ar }; function f7() { return __asyncGenerator(this, arguments, function* f7_1() { - return 1; + return yield __await(1); }); } diff --git a/tests/baselines/reference/emitter.asyncGenerators.functionDeclarations.es5.js b/tests/baselines/reference/emitter.asyncGenerators.functionDeclarations.es5.js index cf6ecefb307..d4f6cd74132 100644 --- a/tests/baselines/reference/emitter.asyncGenerators.functionDeclarations.es5.js +++ b/tests/baselines/reference/emitter.asyncGenerators.functionDeclarations.es5.js @@ -440,7 +440,10 @@ var __asyncGenerator = (this && this.__asyncGenerator) || function (thisArg, _ar function f7() { return __asyncGenerator(this, arguments, function f7_1() { return __generator(this, function (_a) { - return [2 /*return*/, 1]; + switch (_a.label) { + case 0: return [4 /*yield*/, __await(1)]; + case 1: return [2 /*return*/, _a.sent()]; + } }); }); } diff --git a/tests/baselines/reference/emitter.asyncGenerators.functionExpressions.es2015.js b/tests/baselines/reference/emitter.asyncGenerators.functionExpressions.es2015.js index 890ab5e89b6..367a405f3bb 100644 --- a/tests/baselines/reference/emitter.asyncGenerators.functionExpressions.es2015.js +++ b/tests/baselines/reference/emitter.asyncGenerators.functionExpressions.es2015.js @@ -175,6 +175,6 @@ var __asyncGenerator = (this && this.__asyncGenerator) || function (thisArg, _ar }; const f7 = function () { return __asyncGenerator(this, arguments, function* () { - return 1; + return yield __await(1); }); }; diff --git a/tests/baselines/reference/emitter.asyncGenerators.functionExpressions.es5.js b/tests/baselines/reference/emitter.asyncGenerators.functionExpressions.es5.js index 3b61c202cbe..55efe59aeba 100644 --- a/tests/baselines/reference/emitter.asyncGenerators.functionExpressions.es5.js +++ b/tests/baselines/reference/emitter.asyncGenerators.functionExpressions.es5.js @@ -440,7 +440,10 @@ var __asyncGenerator = (this && this.__asyncGenerator) || function (thisArg, _ar var f7 = function () { return __asyncGenerator(this, arguments, function () { return __generator(this, function (_a) { - return [2 /*return*/, 1]; + switch (_a.label) { + case 0: return [4 /*yield*/, __await(1)]; + case 1: return [2 /*return*/, _a.sent()]; + } }); }); }; diff --git a/tests/baselines/reference/emitter.asyncGenerators.objectLiteralMethods.es2015.js b/tests/baselines/reference/emitter.asyncGenerators.objectLiteralMethods.es2015.js index 43203397b0f..8cd2a47fbb9 100644 --- a/tests/baselines/reference/emitter.asyncGenerators.objectLiteralMethods.es2015.js +++ b/tests/baselines/reference/emitter.asyncGenerators.objectLiteralMethods.es2015.js @@ -202,7 +202,7 @@ var __asyncGenerator = (this && this.__asyncGenerator) || function (thisArg, _ar const o7 = { f() { return __asyncGenerator(this, arguments, function* f_1() { - return 1; + return yield __await(1); }); } }; diff --git a/tests/baselines/reference/emitter.asyncGenerators.objectLiteralMethods.es5.js b/tests/baselines/reference/emitter.asyncGenerators.objectLiteralMethods.es5.js index a8c4dfdff92..845d84e2c29 100644 --- a/tests/baselines/reference/emitter.asyncGenerators.objectLiteralMethods.es5.js +++ b/tests/baselines/reference/emitter.asyncGenerators.objectLiteralMethods.es5.js @@ -467,7 +467,10 @@ var o7 = { f: function () { return __asyncGenerator(this, arguments, function f_1() { return __generator(this, function (_a) { - return [2 /*return*/, 1]; + switch (_a.label) { + case 0: return [4 /*yield*/, __await(1)]; + case 1: return [2 /*return*/, _a.sent()]; + } }); }); }