Skip to content

Commit 6f77154

Browse files
committed
Fix async generator return as well
1 parent 394862e commit 6f77154

15 files changed

+228
-157
lines changed

src/compiler/binder.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3712,6 +3712,10 @@ namespace ts {
37123712
break;
37133713

37143714
case SyntaxKind.ReturnStatement:
3715+
// Return statements may require an `await` in ESNext.
3716+
transformFlags |= TransformFlags.ContainsHoistedDeclarationOrCompletion | TransformFlags.AssertESNext;
3717+
break;
3718+
37153719
case SyntaxKind.ContinueStatement:
37163720
case SyntaxKind.BreakStatement:
37173721
transformFlags |= TransformFlags.ContainsHoistedDeclarationOrCompletion;

src/compiler/transformers/esnext.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,8 @@ namespace ts {
6363
return visitAwaitExpression(node as AwaitExpression);
6464
case SyntaxKind.YieldExpression:
6565
return visitYieldExpression(node as YieldExpression);
66+
case SyntaxKind.ReturnStatement:
67+
return visitReturnStatement(node as ReturnStatement);
6668
case SyntaxKind.LabeledStatement:
6769
return visitLabeledStatement(node as LabeledStatement);
6870
case SyntaxKind.ObjectLiteralExpression:
@@ -161,6 +163,16 @@ namespace ts {
161163
return visitEachChild(node, visitor, context);
162164
}
163165

166+
function visitReturnStatement(node: ReturnStatement) {
167+
if (enclosingFunctionFlags & FunctionFlags.Async && enclosingFunctionFlags & FunctionFlags.Generator) {
168+
return updateReturn(node, createDownlevelAwait(
169+
node.expression ? visitNode(node.expression, visitor, isExpression) : createVoidZero()
170+
));
171+
}
172+
173+
return visitEachChild(node, visitor, context);
174+
}
175+
164176
function visitLabeledStatement(node: LabeledStatement) {
165177
if (enclosingFunctionFlags & FunctionFlags.Async) {
166178
const statement = unwrapInnermostStatementOfLabel(node);

src/harness/evaluator.ts

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
namespace evaluator {
2+
declare var Symbol: SymbolConstructor;
3+
4+
const sourceFile = vpath.combine(vfs.srcFolder, "source.ts");
5+
6+
function compile(sourceText: string, options?: ts.CompilerOptions) {
7+
const fs = vfs.createFromFileSystem(Harness.IO, /*ignoreCase*/ false);
8+
fs.writeFileSync(sourceFile, sourceText);
9+
const compilerOptions: ts.CompilerOptions = { target: ts.ScriptTarget.ES5, module: ts.ModuleKind.CommonJS, lib: ["lib.esnext.d.ts"], ...options };
10+
const host = new fakes.CompilerHost(fs, compilerOptions);
11+
return compiler.compileFiles(host, [sourceFile], compilerOptions);
12+
}
13+
14+
function noRequire(id: string) {
15+
throw new Error(`Module '${id}' could not be found.`);
16+
}
17+
18+
// Define a custom "Symbol" constructor to attach missing built-in symbols without
19+
// modifying the global "Symbol" constructor
20+
// tslint:disable-next-line:variable-name
21+
const FakeSymbol: SymbolConstructor = ((description?: string) => Symbol(description)) as any;
22+
(<any>FakeSymbol).prototype = Symbol.prototype;
23+
for (const key of Object.getOwnPropertyNames(Symbol)) {
24+
Object.defineProperty(FakeSymbol, key, Object.getOwnPropertyDescriptor(Symbol, key)!);
25+
}
26+
27+
// Add "asyncIterator" if missing
28+
if (!ts.hasProperty(FakeSymbol, "asyncIterator")) Object.defineProperty(FakeSymbol, "asyncIterator", { value: Symbol.for("Symbol.asyncIterator"), configurable: true });
29+
30+
function evaluate(result: compiler.CompilationResult, globals?: Record<string, any>) {
31+
globals = { Symbol: FakeSymbol, ...globals };
32+
33+
const output = result.getOutput(sourceFile, "js")!;
34+
assert.isDefined(output);
35+
36+
const globalNames: string[] = [];
37+
const globalArgs: any[] = [];
38+
for (const name in globals) {
39+
if (ts.hasProperty(globals, name)) {
40+
globalNames.push(name);
41+
globalArgs.push(globals[name]);
42+
}
43+
}
44+
45+
const evaluateText = `(function (module, exports, require, __dirname, __filename, ${globalNames.join(", ")}) { ${output.text} })`;
46+
const evaluateThunk = eval(evaluateText) as (module: any, exports: any, require: (id: string) => any, dirname: string, filename: string, ...globalArgs: any[]) => void;
47+
const module: { exports: any; } = { exports: {} };
48+
evaluateThunk.call(globals, module, module.exports, noRequire, vpath.dirname(output.file), output.file, FakeSymbol, ...globalArgs);
49+
return module.exports;
50+
}
51+
52+
export function evaluateTypeScript(sourceText: string, options?: ts.CompilerOptions, globals?: Record<string, any>) {
53+
return evaluate(compile(sourceText, options), globals);
54+
}
55+
}

src/harness/tsconfig.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,7 @@
148148
"vpath.ts",
149149
"vfs.ts",
150150
"compiler.ts",
151+
"evaluator.ts",
151152
"fakes.ts",
152153

153154
"sourceMapRecorder.ts",
@@ -169,5 +170,5 @@
169170
"parallel/shared.ts",
170171
"runner.ts"
171172
],
172-
"include": ["unittests/**.ts"]
173+
"include": ["unittests/**/*.ts"]
173174
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
describe("asyncGeneratorEvaluation", () => {
2+
it("return (es5)", async () => {
3+
const result = evaluator.evaluateTypeScript(`
4+
async function * g() {
5+
return Promise.resolve(0);
6+
}
7+
export const output: any[] = [];
8+
export async function main() {
9+
output.push(await g().next());
10+
}`);
11+
await result.main();
12+
assert.deepEqual(result.output, [
13+
{ value: 0, done: true }
14+
]);
15+
});
16+
it("return (es2015)", async () => {
17+
const result = evaluator.evaluateTypeScript(`
18+
async function * g() {
19+
return Promise.resolve(0);
20+
}
21+
export const output: any[] = [];
22+
export async function main() {
23+
output.push(await g().next());
24+
}`, { target: ts.ScriptTarget.ES2015 });
25+
await result.main();
26+
assert.deepEqual(result.output, [
27+
{ value: 0, done: true }
28+
]);
29+
});
30+
});
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
describe("forAwaitOfEvaluation", () => {
2+
it("sync (es5)", async () => {
3+
const result = evaluator.evaluateTypeScript(`
4+
let i = 0;
5+
const iterator = {
6+
[Symbol.iterator]() { return this; },
7+
next() {
8+
switch (i++) {
9+
case 0: return { value: 1, done: false };
10+
case 1: return { value: Promise.resolve(2), done: false };
11+
case 2: return { value: new Promise<number>(resolve => setTimeout(resolve, 100, 3)), done: false };
12+
default: return { value: undefined: done: true };
13+
}
14+
}
15+
};
16+
export const output: any[] = [];
17+
export async function main() {
18+
for await (const item of iterator) {
19+
output.push(item);
20+
}
21+
}`);
22+
await result.main();
23+
assert.strictEqual(result.output[0], 1);
24+
assert.strictEqual(result.output[1], 2);
25+
assert.strictEqual(result.output[2], 3);
26+
});
27+
28+
it("sync (es2015)", async () => {
29+
const result = evaluator.evaluateTypeScript(`
30+
let i = 0;
31+
const iterator = {
32+
[Symbol.iterator]() { return this; },
33+
next() {
34+
switch (i++) {
35+
case 0: return { value: 1, done: false };
36+
case 1: return { value: Promise.resolve(2), done: false };
37+
case 2: return { value: new Promise<number>(resolve => setTimeout(resolve, 100, 3)), done: false };
38+
default: return { value: undefined: done: true };
39+
}
40+
}
41+
};
42+
export const output: any[] = [];
43+
export async function main() {
44+
for await (const item of iterator) {
45+
output.push(item);
46+
}
47+
}`, { target: ts.ScriptTarget.ES2015 });
48+
await result.main();
49+
assert.strictEqual(result.output[0], 1);
50+
assert.strictEqual(result.output[1], 2);
51+
assert.strictEqual(result.output[2], 3);
52+
});
53+
54+
it("async (es5)", async () => {
55+
const result = evaluator.evaluateTypeScript(`
56+
let i = 0;
57+
const iterator = {
58+
[Symbol.asyncIterator]() { return this; },
59+
async next() {
60+
switch (i++) {
61+
case 0: return { value: 1, done: false };
62+
case 1: return { value: Promise.resolve(2), done: false };
63+
case 2: return { value: new Promise<number>(resolve => setTimeout(resolve, 100, 3)), done: false };
64+
default: return { value: undefined: done: true };
65+
}
66+
}
67+
};
68+
export const output: any[] = [];
69+
export async function main() {
70+
for await (const item of iterator) {
71+
output.push(item);
72+
}
73+
}`);
74+
await result.main();
75+
assert.strictEqual(result.output[0], 1);
76+
assert.instanceOf(result.output[1], Promise);
77+
assert.instanceOf(result.output[2], Promise);
78+
});
79+
80+
it("async (es2015)", async () => {
81+
const result = evaluator.evaluateTypeScript(`
82+
let i = 0;
83+
const iterator = {
84+
[Symbol.asyncIterator]() { return this; },
85+
async next() {
86+
switch (i++) {
87+
case 0: return { value: 1, done: false };
88+
case 1: return { value: Promise.resolve(2), done: false };
89+
case 2: return { value: new Promise<number>(resolve => setTimeout(resolve, 100, 3)), done: false };
90+
default: return { value: undefined: done: true };
91+
}
92+
}
93+
};
94+
export const output: any[] = [];
95+
export async function main() {
96+
for await (const item of iterator) {
97+
output.push(item);
98+
}
99+
}`, { target: ts.ScriptTarget.ES2015 });
100+
await result.main();
101+
assert.strictEqual(result.output[0], 1);
102+
assert.instanceOf(result.output[1], Promise);
103+
assert.instanceOf(result.output[2], Promise);
104+
});
105+
});

src/harness/unittests/forAwaitOfEvaluation.ts

Lines changed: 0 additions & 148 deletions
This file was deleted.

0 commit comments

Comments
 (0)