Skip to content

Commit 1416053

Browse files
authored
Ensure call return() for an abrupt completion at for await of loop (#52754)
Co-authored-by: tm-kwon <[email protected]>
1 parent 289bd5a commit 1416053

17 files changed

+298
-319
lines changed

src/compiler/transformers/es2018.ts

Lines changed: 7 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -767,11 +767,7 @@ export function transformES2018(context: TransformationContext): (x: SourceFile
767767
const exitNonUserCodeStatement = factory.createExpressionStatement(exitNonUserCodeExpression);
768768
setSourceMapRange(exitNonUserCodeStatement, node.expression);
769769

770-
const enterNonUserCodeExpression = factory.createAssignment(nonUserCode, factory.createTrue());
771-
const enterNonUserCodeStatement = factory.createExpressionStatement(enterNonUserCodeExpression);
772-
setSourceMapRange(exitNonUserCodeStatement, node.expression);
773-
774-
const statements: Statement[] = [];
770+
const statements: Statement[] = [iteratorValueStatement, exitNonUserCodeStatement];
775771
const binding = createForOfBindingStatement(factory, node.initializer, value);
776772
statements.push(visitNode(binding, visitor, isStatement));
777773

@@ -787,28 +783,13 @@ export function transformES2018(context: TransformationContext): (x: SourceFile
787783
statements.push(statement);
788784
}
789785

790-
const body = setEmitFlags(
791-
setTextRange(
792-
factory.createBlock(
793-
setTextRange(factory.createNodeArray(statements), statementsLocation),
794-
/*multiLine*/ true
795-
),
796-
bodyLocation
786+
return setTextRange(
787+
factory.createBlock(
788+
setTextRange(factory.createNodeArray(statements), statementsLocation),
789+
/*multiLine*/ true
797790
),
798-
EmitFlags.NoSourceMap | EmitFlags.NoTokenSourceMaps
791+
bodyLocation
799792
);
800-
801-
return factory.createBlock([
802-
iteratorValueStatement,
803-
exitNonUserCodeStatement,
804-
factory.createTryStatement(
805-
body,
806-
/*catchClause*/ undefined,
807-
factory.createBlock([
808-
enterNonUserCodeStatement
809-
])
810-
)
811-
]);
812793
}
813794

814795
function createDownlevelAwait(expression: Expression) {
@@ -859,7 +840,7 @@ export function transformES2018(context: TransformationContext): (x: SourceFile
859840
factory.createAssignment(done, getDone),
860841
factory.createLogicalNot(done)
861842
]),
862-
/*incrementor*/ undefined,
843+
/*incrementor*/ factory.createAssignment(nonUserCode, factory.createTrue()),
863844
/*statement*/ convertForOfStatementHead(node, getValue, nonUserCode)
864845
),
865846
/*location*/ node

src/testRunner/unittests/evaluation/forAwaitOf.ts

Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,81 @@ describe("unittests:: evaluation:: forAwaitOfEvaluation", () => {
106106
assert.instanceOf(result.output[2], Promise);
107107
});
108108

109+
it("call return when user code return (es2015)", async () => {
110+
const result = evaluator.evaluateTypeScript(`
111+
let returnCalled = false;
112+
async function f() {
113+
const iterator = {
114+
[Symbol.asyncIterator](): AsyncIterableIterator<any> { return this; },
115+
async next() {
116+
return { value: undefined, done: false };
117+
},
118+
async return() {
119+
returnCalled = true;
120+
}
121+
};
122+
for await (const item of iterator) {
123+
return;
124+
}
125+
}
126+
export async function main() {
127+
try { await f(); } catch { }
128+
return returnCalled;
129+
}
130+
`, { target: ts.ScriptTarget.ES2015 });
131+
assert.isTrue(await result.main());
132+
});
133+
134+
it("call return when user code break (es2015)", async () => {
135+
const result = evaluator.evaluateTypeScript(`
136+
let returnCalled = false;
137+
async function f() {
138+
const iterator = {
139+
[Symbol.asyncIterator](): AsyncIterableIterator<any> { return this; },
140+
async next() {
141+
return { value: undefined, done: false };
142+
},
143+
async return() {
144+
returnCalled = true;
145+
}
146+
};
147+
for await (const item of iterator) {
148+
break;
149+
}
150+
}
151+
export async function main() {
152+
try { await f(); } catch { }
153+
return returnCalled;
154+
}
155+
`, { target: ts.ScriptTarget.ES2015 });
156+
assert.isTrue(await result.main());
157+
});
158+
159+
it("call return when user code throws (es2015)", async () => {
160+
const result = evaluator.evaluateTypeScript(`
161+
let returnCalled = false;
162+
async function f() {
163+
const iterator = {
164+
[Symbol.asyncIterator](): AsyncIterableIterator<any> { return this; },
165+
async next() {
166+
return { value: undefined, done: false };
167+
},
168+
async return() {
169+
returnCalled = true;
170+
}
171+
};
172+
for await (const item of iterator) {
173+
throw new Error();
174+
}
175+
}
176+
export async function main() {
177+
try { await f(); } catch { }
178+
return returnCalled;
179+
}
180+
`, { target: ts.ScriptTarget.ES2015 });
181+
assert.isTrue(await result.main());
182+
});
183+
109184
it("don't call return when non-user code throws (es2015)", async () => {
110185
const result = evaluator.evaluateTypeScript(`
111186
let returnCalled = false;
@@ -132,4 +207,96 @@ describe("unittests:: evaluation:: forAwaitOfEvaluation", () => {
132207
`, { target: ts.ScriptTarget.ES2015 });
133208
assert.isFalse(await result.main());
134209
});
210+
211+
it("don't call return when user code continue (es2015)", async () => {
212+
const result = evaluator.evaluateTypeScript(`
213+
let returnCalled = false;
214+
async function f() {
215+
let i = 0;
216+
const iterator = {
217+
[Symbol.asyncIterator](): AsyncIterableIterator<any> { return this; },
218+
async next() {
219+
i++;
220+
if (i < 2) return { value: undefined, done: false };
221+
throw new Error();
222+
},
223+
async return() {
224+
returnCalled = true;
225+
}
226+
};
227+
for await (const item of iterator) {
228+
continue;
229+
}
230+
}
231+
export async function main() {
232+
try { await f(); } catch { }
233+
return returnCalled;
234+
}
235+
`, { target: ts.ScriptTarget.ES2015 });
236+
assert.isFalse(await result.main());
237+
});
238+
239+
it("don't call return when user code continue to local label (es2015)", async () => {
240+
const result = evaluator.evaluateTypeScript(`
241+
let returnCalled = false;
242+
async function f() {
243+
let i = 0;
244+
const iterator = {
245+
[Symbol.asyncIterator](): AsyncIterableIterator<any> { return this; },
246+
async next() {
247+
i++;
248+
if (i < 2) return { value: undefined, done: false };
249+
throw new Error();
250+
},
251+
async return() {
252+
returnCalled = true;
253+
}
254+
};
255+
outerLoop:
256+
for (const outerItem of [1, 2, 3]) {
257+
innerLoop:
258+
for await (const item of iterator) {
259+
continue innerLoop;
260+
}
261+
}
262+
}
263+
export async function main() {
264+
try { await f(); } catch { }
265+
return returnCalled;
266+
}
267+
`, { target: ts.ScriptTarget.ES2015 });
268+
assert.isFalse(await result.main());
269+
});
270+
271+
it("call return when user code continue to non-local label (es2015)", async () => {
272+
const result = evaluator.evaluateTypeScript(`
273+
let returnCalled = false;
274+
async function f() {
275+
let i = 0;
276+
const iterator = {
277+
[Symbol.asyncIterator](): AsyncIterableIterator<any> { return this; },
278+
async next() {
279+
i++;
280+
if (i < 2) return { value: undefined, done: false };
281+
return { value: undefined, done: true };
282+
},
283+
async return() {
284+
returnCalled = true;
285+
}
286+
};
287+
outerLoop:
288+
for (const outerItem of [1, 2, 3]) {
289+
innerLoop:
290+
for await (const item of iterator) {
291+
continue outerLoop;
292+
}
293+
}
294+
}
295+
export async function main() {
296+
try { await f(); } catch { }
297+
return returnCalled;
298+
}
299+
`, { target: ts.ScriptTarget.ES2015 });
300+
assert.isTrue(await result.main());
301+
});
135302
});

tests/baselines/reference/emitter.forAwait(target=es2015).js

Lines changed: 16 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -72,15 +72,10 @@ function f1() {
7272
return __awaiter(this, void 0, void 0, function* () {
7373
let y;
7474
try {
75-
for (var _d = true, y_1 = __asyncValues(y), y_1_1; y_1_1 = yield y_1.next(), _a = y_1_1.done, !_a;) {
75+
for (var _d = true, y_1 = __asyncValues(y), y_1_1; y_1_1 = yield y_1.next(), _a = y_1_1.done, !_a; _d = true) {
7676
_c = y_1_1.value;
7777
_d = false;
78-
try {
79-
const x = _c;
80-
}
81-
finally {
82-
_d = true;
83-
}
78+
const x = _c;
8479
}
8580
}
8681
catch (e_1_1) { e_1 = { error: e_1_1 }; }
@@ -114,15 +109,10 @@ function f2() {
114109
return __awaiter(this, void 0, void 0, function* () {
115110
let x, y;
116111
try {
117-
for (var _d = true, y_1 = __asyncValues(y), y_1_1; y_1_1 = yield y_1.next(), _a = y_1_1.done, !_a;) {
112+
for (var _d = true, y_1 = __asyncValues(y), y_1_1; y_1_1 = yield y_1.next(), _a = y_1_1.done, !_a; _d = true) {
118113
_c = y_1_1.value;
119114
_d = false;
120-
try {
121-
x = _c;
122-
}
123-
finally {
124-
_d = true;
125-
}
115+
x = _c;
126116
}
127117
}
128118
catch (e_1_1) { e_1 = { error: e_1_1 }; }
@@ -159,15 +149,10 @@ function f3() {
159149
var _a, e_1, _b, _c;
160150
let y;
161151
try {
162-
for (var _d = true, y_1 = __asyncValues(y), y_1_1; y_1_1 = yield __await(y_1.next()), _a = y_1_1.done, !_a;) {
152+
for (var _d = true, y_1 = __asyncValues(y), y_1_1; y_1_1 = yield __await(y_1.next()), _a = y_1_1.done, !_a; _d = true) {
163153
_c = y_1_1.value;
164154
_d = false;
165-
try {
166-
const x = _c;
167-
}
168-
finally {
169-
_d = true;
170-
}
155+
const x = _c;
171156
}
172157
}
173158
catch (e_1_1) { e_1 = { error: e_1_1 }; }
@@ -204,15 +189,10 @@ function f4() {
204189
var _a, e_1, _b, _c;
205190
let x, y;
206191
try {
207-
for (var _d = true, y_1 = __asyncValues(y), y_1_1; y_1_1 = yield __await(y_1.next()), _a = y_1_1.done, !_a;) {
192+
for (var _d = true, y_1 = __asyncValues(y), y_1_1; y_1_1 = yield __await(y_1.next()), _a = y_1_1.done, !_a; _d = true) {
208193
_c = y_1_1.value;
209194
_d = false;
210-
try {
211-
x = _c;
212-
}
213-
finally {
214-
_d = true;
215-
}
195+
x = _c;
216196
}
217197
}
218198
catch (e_1_1) { e_1 = { error: e_1_1 }; }
@@ -247,16 +227,11 @@ function f5() {
247227
return __awaiter(this, void 0, void 0, function* () {
248228
let y;
249229
try {
250-
outer: for (var _d = true, y_1 = __asyncValues(y), y_1_1; y_1_1 = yield y_1.next(), _a = y_1_1.done, !_a;) {
230+
outer: for (var _d = true, y_1 = __asyncValues(y), y_1_1; y_1_1 = yield y_1.next(), _a = y_1_1.done, !_a; _d = true) {
251231
_c = y_1_1.value;
252232
_d = false;
253-
try {
254-
const x = _c;
255-
continue outer;
256-
}
257-
finally {
258-
_d = true;
259-
}
233+
const x = _c;
234+
continue outer;
260235
}
261236
}
262237
catch (e_1_1) { e_1 = { error: e_1_1 }; }
@@ -294,16 +269,11 @@ function f6() {
294269
var _a, e_1, _b, _c;
295270
let y;
296271
try {
297-
outer: for (var _d = true, y_1 = __asyncValues(y), y_1_1; y_1_1 = yield __await(y_1.next()), _a = y_1_1.done, !_a;) {
272+
outer: for (var _d = true, y_1 = __asyncValues(y), y_1_1; y_1_1 = yield __await(y_1.next()), _a = y_1_1.done, !_a; _d = true) {
298273
_c = y_1_1.value;
299274
_d = false;
300-
try {
301-
const x = _c;
302-
continue outer;
303-
}
304-
finally {
305-
_d = true;
306-
}
275+
const x = _c;
276+
continue outer;
307277
}
308278
}
309279
catch (e_1_1) { e_1 = { error: e_1_1 }; }
@@ -342,15 +312,10 @@ function f7() {
342312
let y;
343313
for (;;) {
344314
try {
345-
for (var _d = true, y_1 = (e_1 = void 0, __asyncValues(y)), y_1_1; y_1_1 = yield __await(y_1.next()), _a = y_1_1.done, !_a;) {
315+
for (var _d = true, y_1 = (e_1 = void 0, __asyncValues(y)), y_1_1; y_1_1 = yield __await(y_1.next()), _a = y_1_1.done, !_a; _d = true) {
346316
_c = y_1_1.value;
347317
_d = false;
348-
try {
349-
const x = _c;
350-
}
351-
finally {
352-
_d = true;
353-
}
318+
const x = _c;
354319
}
355320
}
356321
catch (e_1_1) { e_1 = { error: e_1_1 }; }

0 commit comments

Comments
 (0)