diff --git a/test/language/statements/async-generator/return-undefined-implicit-and-explicit.js b/test/language/statements/async-generator/return-undefined-implicit-and-explicit.js new file mode 100644 index 00000000000..990428573e1 --- /dev/null +++ b/test/language/statements/async-generator/return-undefined-implicit-and-explicit.js @@ -0,0 +1,80 @@ +// Copyright (C) 2019 André Bargull. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-return-statement-runtime-semantics-evaluation +description: > + Return with an explicit return value awaits this value. +info: | + 13.10.1 Runtime Semantics: Evaluation + + ReturnStatement : return; + 1. Return Completion { [[Type]]: return, [[Value]]: undefined, [[Target]]: empty }. + + ReturnStatement : return Expression ; + 1. Let exprRef be the result of evaluating Expression. + 2. Let exprValue be ? GetValue(exprRef). + 3. If ! GetGeneratorKind() is async, set exprValue to ? Await(exprValue). + 4. Return Completion { [[Type]]: return, [[Value]]: exprValue, [[Target]]: empty }. + + 25.5.3.2 AsyncGeneratorStart ( generator, generatorBody ) + + ... + 5. Set the code evaluation state of genContext such that when evaluation is resumed for that + execution context the following steps will be performed: + a. Let result be the result of evaluating generatorBody. + ... + e. If result is a normal completion, let resultValue be undefined. + ... + g. Return ! AsyncGeneratorResolve(generator, resultValue, true). + +includes: [compareArray.js] +flags: [async] +features: [async-iteration] +---*/ + +// 25.5.3.2, step 5.e: |generatorBody| execution ends with a normal completion. +async function* g1() { + // no return +} + +// 13.10.1: No expression form means direct return. +async function* g2() { + return; +} + +// 13.10.1: Explicit expression requires Await. +async function* g3() { + return undefined; // Return undefined via global value `undefined`. +} + +// 13.10.1: Explicit expression requires Await. +async function* g4() { + return void 0; // Return undefined via void expression. +} + +var expected = [ + "tick 1", + + "g1 ret", + "g2 ret", + + "tick 2", + + "g3 ret", + "g4 ret", +]; + +var actual = []; + +Promise.resolve(0) + .then(() => actual.push("tick 1")) + .then(() => actual.push("tick 2")) + .then(() => { + assert.compareArray(actual, expected, "Ticks for implicit and explicit return undefined"); +}).then($DONE, $DONE); + +g1().next().then(v => actual.push("g1 ret")); +g2().next().then(v => actual.push("g2 ret")); +g3().next().then(v => actual.push("g3 ret")); +g4().next().then(v => actual.push("g4 ret")); diff --git a/test/language/statements/async-generator/yield-return-then-getter-ticks.js b/test/language/statements/async-generator/yield-return-then-getter-ticks.js new file mode 100644 index 00000000000..d93a7f7c1d0 --- /dev/null +++ b/test/language/statements/async-generator/yield-return-then-getter-ticks.js @@ -0,0 +1,104 @@ +// Copyright (C) 2019 André Bargull. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-generator-function-definitions-runtime-semantics-evaluation +description: > + Return resumption value is awaited upon and hence is treated as a thenable. +info: | + 14.4.14 Runtime Semantics: Evaluation + YieldExpression : yield AssignmentExpression + + ... + 3. Let value be ? GetValue(exprRef). + 4. If generatorKind is async, then return ? AsyncGeneratorYield(value). + ... + + 25.5.3.7 AsyncGeneratorYield ( value ) + ... + 5. Set value to ? Await(value). + ... + 8. Set the code evaluation state of genContext such that when evaluation is resumed with a + Completion resumptionValue the following steps will be performed: + ... + b. Let awaited be Await(resumptionValue.[[Value]]). + ... + e. Return Completion { [[Type]]: return, [[Value]]: awaited.[[Value]], [[Target]]: empty }. + ... + + 6.2.3.1 Await + ... + 2. Let promise be ? PromiseResolve(%Promise%, « value »). + ... + + 25.6.4.5.1 PromiseResolve ( C, x ) + ... + 3. Let promiseCapability be ? NewPromiseCapability(C). + 4. Perform ? Call(promiseCapability.[[Resolve]], undefined, « x »). + ... + + 25.6.1.5 NewPromiseCapability ( C ) + ... + 7. Let promise be ? Construct(C, « executor »). + ... + + 25.6.3.1 Promise ( executor ) + ... + 8. Let resolvingFunctions be CreateResolvingFunctions(promise). + ... + + 25.6.1.3 CreateResolvingFunctions ( promise ) + ... + 2. Let stepsResolve be the algorithm steps defined in Promise Resolve Functions (25.6.1.3.2). + 3. Let resolve be CreateBuiltinFunction(stepsResolve, « [[Promise]], [[AlreadyResolved]] »). + ... + + 25.6.1.3.2 Promise Resolve Functions + ... + 9. Let then be Get(resolution, "then"). + ... + +includes: [compareArray.js] +flags: [async] +features: [async-iteration] +---*/ + +var expected = [ + "start", + + // `Await(value)` promise resolved. + "tick 1", + + // "then" of `resumptionValue.[[Value]]` accessed. + "get then", + + // `Await(resumptionValue.[[Value]])` promise resolved. + "tick 2", +]; + +var actual = []; + +async function* f() { + actual.push("start"); + yield 123; + actual.push("stop - never reached"); +} + +Promise.resolve(0) + .then(() => actual.push("tick 1")) + .then(() => actual.push("tick 2")) + .then(() => { + assert.compareArray(actual, expected, "Ticks for return with thenable getter"); +}).then($DONE, $DONE); + +var it = f(); + +// Start generator execution. +it.next(); + +// Stop generator execution. +it.return({ + get then() { + actual.push("get then"); + } +}); diff --git a/test/language/statements/async-generator/yield-star-async-from-sync-iterator-inaccessible.js b/test/language/statements/async-generator/yield-star-async-from-sync-iterator-inaccessible.js new file mode 100644 index 00000000000..ba343de7903 --- /dev/null +++ b/test/language/statements/async-generator/yield-star-async-from-sync-iterator-inaccessible.js @@ -0,0 +1,55 @@ +// Copyright (C) 2019 André Bargull. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-createasyncfromsynciterator +description: > + Async-from-Sync Iterator instances are not accessible from user code. +info: | + 25.1.4.1 CreateAsyncFromSyncIterator ( syncIteratorRecord ) + 1. Let asyncIterator be ! ObjectCreate(%AsyncFromSyncIteratorPrototype%, « [[SyncIteratorRecord]] »). + 2. Set asyncIterator.[[SyncIteratorRecord]] to syncIteratorRecord. + 3. Let nextMethod be ! Get(asyncIterator, "next"). + 4. Let iteratorRecord be Record { [[Iterator]]: asyncIterator, [[NextMethod]]: nextMethod, [[Done]]: false }. + 5. Return iteratorRecord. + + 14.4.14 Runtime Semantics: Evaluation + YieldExpression : yield * AssignmentExpression + 1. Let generatorKind be ! GetGeneratorKind(). + ... + 4. Let iteratorRecord be ? GetIterator(value, generatorKind). + ... + + 7.4.1 GetIterator ( obj [ , hint [ , method ] ] ) + ... + 3. If method is not present, then + a. If hint is async, then + i. Set method to ? GetMethod(obj, @@asyncIterator). + ii. If method is undefined, then + 1. Let syncMethod be ? GetMethod(obj, @@iterator). + 2. Let syncIteratorRecord be ? GetIterator(obj, sync, syncMethod). + 3. Return ? CreateAsyncFromSyncIterator(syncIteratorRecord). + ... + +flags: [async] +features: [async-iteration] +---*/ + +var AsyncIteratorPrototype = Object.getPrototypeOf(async function*(){}.constructor.prototype.prototype); + +Object.defineProperty(AsyncIteratorPrototype, Symbol.iterator, { + get() { + throw new Error("@@iterator accessed"); + } +}); + +Object.defineProperty(AsyncIteratorPrototype, Symbol.asyncIterator, { + get() { + throw new Error("@@asyncIterator accessed"); + } +}); + +async function* g() { + yield* []; +} +g().next().then(() => $DONE(), $DONE); diff --git a/test/language/statements/async-generator/yield-star-normal-notdone-iter-value-throws.js b/test/language/statements/async-generator/yield-star-normal-notdone-iter-value-throws.js new file mode 100644 index 00000000000..b2035b35fa6 --- /dev/null +++ b/test/language/statements/async-generator/yield-star-normal-notdone-iter-value-throws.js @@ -0,0 +1,53 @@ +// Copyright (C) 2019 André Bargull. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-generator-function-definitions-runtime-semantics-evaluation +description: > + Abrupt completion when calling IteratorValue is propagated when received.[[Type]] is normal. +info: | + 14.4.14 Runtime Semantics: Evaluation + YieldExpression : yield* AssignmentExpression + + ... + 7. Repeat, + a. If received.[[Type]] is normal, then + ... + vi. If generatorKind is async, then set received to AsyncGeneratorYield(? IteratorValue(innerResult)). + ... + +flags: [async] +features: [async-iteration] +---*/ + +var token = {}; + +var asyncIter = { + [Symbol.asyncIterator]() { + return this; + }, + next() { + return { + done: false, + get value() { + throw token; + } + }; + } +}; + +async function* f() { + var thrown; + try { + yield* asyncIter; + } catch (e) { + thrown = e; + } + return thrown; +} + +var iter = f(); + +iter.next().then(({value}) => { + assert.sameValue(value, token); +}).then($DONE, $DONE); diff --git a/test/language/statements/async-generator/yield-star-return-notdone-iter-value-throws.js b/test/language/statements/async-generator/yield-star-return-notdone-iter-value-throws.js new file mode 100644 index 00000000000..bdfceda8d1a --- /dev/null +++ b/test/language/statements/async-generator/yield-star-return-notdone-iter-value-throws.js @@ -0,0 +1,63 @@ +// Copyright (C) 2019 André Bargull. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-generator-function-definitions-runtime-semantics-evaluation +description: > + Abrupt completion when calling IteratorValue is propagated when received.[[Type]] is return. +info: | + 14.4.14 Runtime Semantics: Evaluation + YieldExpression : yield* AssignmentExpression + + ... + 7. Repeat, + ... + c. Else, + i. Assert: received.[[Type]] is return. + ... + ix. If generatorKind is async, then set received to AsyncGeneratorYield(? IteratorValue(innerReturnResult)). + ... + +flags: [async] +features: [async-iteration] +---*/ + +var token = {}; + +var asyncIter = { + [Symbol.asyncIterator]() { + return this; + }, + next() { + return { + done: false, + value: undefined, + }; + }, + return() { + return { + done: false, + get value() { + throw token; + } + }; + } +}; + +async function* f() { + var thrown; + try { + yield* asyncIter; + } catch (e) { + thrown = e; + } + return thrown; +} + +var iter = f(); + +iter.next().then(() => { + iter.return().then(({value}) => { + assert.sameValue(value, token); + }).then($DONE, $DONE); +}).catch($DONE); diff --git a/test/language/statements/async-generator/yield-star-return-then-getter-ticks.js b/test/language/statements/async-generator/yield-star-return-then-getter-ticks.js new file mode 100644 index 00000000000..9981f2bfa06 --- /dev/null +++ b/test/language/statements/async-generator/yield-star-return-then-getter-ticks.js @@ -0,0 +1,145 @@ +// Copyright (C) 2019 André Bargull. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-generator-function-definitions-runtime-semantics-evaluation +description: > + Return resumption value is awaited upon and hence is treated as a thenable. +info: | + 14.4.14 Runtime Semantics: Evaluation + YieldExpression : yield* AssignmentExpression + + ... + 7. Repeat, + a. If received.[[Type]] is normal, then + i. Let innerResult be ? Call(iteratorRecord.[[NextMethod]], iteratorRecord.[[Iterator]], + « received.[[Value]] »). + ii. If generatorKind is async, then set innerResult to ? Await(innerResult). + ... + vi. If generatorKind is async, then set received to AsyncGeneratorYield(? IteratorValue(innerResult)). + ... + ... + c. Else, + i. Assert: received.[[Type]] is return. + ii. Let return be ? GetMethod(iterator, "return"). + iii. If return is undefined, then + 1. If generatorKind is async, then set received.[[Value]] to ? Await(received.[[Value]]). + 2. Return Completion(received). + ... + + 25.5.3.7 AsyncGeneratorYield ( value ) + ... + 5. Set value to ? Await(value). + ... + 8. Set the code evaluation state of genContext such that when evaluation is resumed with a + Completion resumptionValue the following steps will be performed: + ... + b. Let awaited be Await(resumptionValue.[[Value]]). + ... + e. Return Completion { [[Type]]: return, [[Value]]: awaited.[[Value]], [[Target]]: empty }. + ... + + 6.2.3.1 Await + ... + 2. Let promise be ? PromiseResolve(%Promise%, « value »). + ... + + 25.6.4.5.1 PromiseResolve ( C, x ) + ... + 3. Let promiseCapability be ? NewPromiseCapability(C). + 4. Perform ? Call(promiseCapability.[[Resolve]], undefined, « x »). + ... + + 25.6.1.5 NewPromiseCapability ( C ) + ... + 7. Let promise be ? Construct(C, « executor »). + ... + + 25.6.3.1 Promise ( executor ) + ... + 8. Let resolvingFunctions be CreateResolvingFunctions(promise). + ... + + 25.6.1.3 CreateResolvingFunctions ( promise ) + ... + 2. Let stepsResolve be the algorithm steps defined in Promise Resolve Functions (25.6.1.3.2). + 3. Let resolve be CreateBuiltinFunction(stepsResolve, « [[Promise]], [[AlreadyResolved]] »). + ... + + 25.6.1.3.2 Promise Resolve Functions + ... + 9. Let then be Get(resolution, "then"). + ... + +includes: [compareArray.js] +flags: [async] +features: [async-iteration] +---*/ + +var expected = [ + "start", + + // `Await(innerResult)` promise resolved. + "tick 1", + + // `Await(value)` promise resolved. + "tick 2", + + // "then" of `resumptionValue.[[Value]]` accessed. + "get then", + + // `Await(resumptionValue.[[Value]])` promise resolved. + "tick 3", + + // Get iterator "return" method. + "get return", + + // "then" of `received.[[Value]]` accessed. + "get then", + + // `Await(received.[[Value]])` promise resolved. + "tick 4", +]; + +var actual = []; + +var asyncIter = { + [Symbol.asyncIterator]() { + return this; + }, + next() { + return { + done: false, + }; + }, + get return() { + actual.push("get return"); + } +}; + +async function* f() { + actual.push("start"); + yield* asyncIter; + actual.push("stop - never reached"); +} + +Promise.resolve(0) + .then(() => actual.push("tick 1")) + .then(() => actual.push("tick 2")) + .then(() => actual.push("tick 3")) + .then(() => actual.push("tick 4")) + .then(() => { + assert.compareArray(actual, expected, "Ticks for return with thenable getter"); +}).then($DONE, $DONE); + +var it = f(); + +// Start generator execution. +it.next(); + +// Stop generator execution. +it.return({ + get then() { + actual.push("get then"); + } +}); diff --git a/test/language/statements/async-generator/yield-star-throw-notdone-iter-value-throws.js b/test/language/statements/async-generator/yield-star-throw-notdone-iter-value-throws.js new file mode 100644 index 00000000000..dba6e808d3d --- /dev/null +++ b/test/language/statements/async-generator/yield-star-throw-notdone-iter-value-throws.js @@ -0,0 +1,64 @@ +// Copyright (C) 2019 André Bargull. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-generator-function-definitions-runtime-semantics-evaluation +description: > + Abrupt completion when calling IteratorValue is propagated when received.[[Type]] is throw. +info: | + 14.4.14 Runtime Semantics: Evaluation + YieldExpression : yield* AssignmentExpression + + ... + 7. Repeat, + ... + b. Else if received.[[Type]] is throw, then + ... + ii. If throw is not undefined, then + ... + 7. If generatorKind is async, then set received to AsyncGeneratorYield(? IteratorValue(innerResult)). + ... + +flags: [async] +features: [async-iteration] +---*/ + +var token = {}; + +var asyncIter = { + [Symbol.asyncIterator]() { + return this; + }, + next() { + return { + done: false, + value: undefined, + }; + }, + throw() { + return { + done: false, + get value() { + throw token; + } + }; + } +}; + +async function* f() { + var thrown; + try { + yield* asyncIter; + } catch (e) { + thrown = e; + } + return thrown; +} + +var iter = f(); + +iter.next().then(() => { + iter.throw().then(({value}) => { + assert.sameValue(value, token); + }).then($DONE, $DONE); +}).catch($DONE); diff --git a/test/language/statements/for-await-of/async-from-sync-iterator-continuation-abrupt-completion-get-constructor.js b/test/language/statements/for-await-of/async-from-sync-iterator-continuation-abrupt-completion-get-constructor.js new file mode 100644 index 00000000000..ebdf1583099 --- /dev/null +++ b/test/language/statements/for-await-of/async-from-sync-iterator-continuation-abrupt-completion-get-constructor.js @@ -0,0 +1,54 @@ +// Copyright (C) 2019 André Bargull. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-asyncfromsynciteratorcontinuation +description: > + Reject promise when PromiseResolve in AsyncFromSyncIteratorContinuation throws. +info: | + 25.1.4.4 AsyncFromSyncIteratorContinuation ( result, promiseCapability ) + ... + 5. Let valueWrapper be PromiseResolve(%Promise%, « value »). + 6. IfAbruptRejectPromise(valueWrapper, promiseCapability). + ... + +includes: [compareArray.js] +flags: [async] +features: [async-iteration] +---*/ + +var expected = [ + "start", + + // `valueWrapper` promise rejected. + "tick 1", + + // `Await(nextResult)` in 13.7.5.13 done. + "tick 2", + + // catch handler executed. + "catch", +]; + +var actual = []; + +async function f() { + var p = Promise.resolve(0); + Object.defineProperty(p, "constructor", { + get() { + throw new Error(); + } + }); + actual.push("start"); + for await (var x of [p]); + actual.push("never reached"); +} + +Promise.resolve(0) + .then(() => actual.push("tick 1")) + .then(() => actual.push("tick 2")) + .then(() => { + assert.compareArray(actual, expected); +}).then($DONE, $DONE); + +f().catch(() => actual.push("catch")); diff --git a/test/language/statements/for-await-of/ticks-with-async-iter-resolved-promise-and-constructor-lookup-two.js b/test/language/statements/for-await-of/ticks-with-async-iter-resolved-promise-and-constructor-lookup-two.js new file mode 100644 index 00000000000..15e92851855 --- /dev/null +++ b/test/language/statements/for-await-of/ticks-with-async-iter-resolved-promise-and-constructor-lookup-two.js @@ -0,0 +1,91 @@ +// Copyright (C) 2019 André Bargull. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-runtime-semantics-forin-div-ofbodyevaluation-lhs-stmt-iterator-lhskind-labelset +description: > + Ensure the number of ticks and Promise constructor lookups is correct with custom async iterator. +info: | + 13.7.5.13 Runtime Semantics: ForIn/OfBodyEvaluation ( lhs, stmt, iteratorRecord, iterationKind, + lhsKind, labelSet [ , iteratorKind ] ) + 25.6.4.5.1 PromiseResolve + 6.2.3.1 Await + +includes: [compareArray.js] +flags: [async] +features: [async-iteration] +---*/ + +// The expected event log. +var expected = [ + // Before entering loop. + "pre", + + // Await + // -> PromiseResolve + "constructor", + + // Await promise resolved. + "tick 1", + + // In loop body. + "loop", + + // Await + // -> PromiseResolve + "constructor", + + // Await promise resolved + "tick 2", + + // After exiting loop. + "post", +]; + +// The actual event log. +var actual = []; + +// Custom async iterator returning the result of the synchronous iterator wrapped in a Promise. +function toAsyncIterator(iterable) { + return { + [Symbol.asyncIterator]() { + var iter = iterable[Symbol.iterator](); + return { + next() { + return Promise.resolve(iter.next()); + } + }; + } + }; +} + +// Test function using for-await with a single, already resolved Promise. +async function f() { + var p = Promise.resolve(0); + actual.push("pre"); + for await (var x of toAsyncIterator([p])) { + actual.push("loop"); + } + actual.push("post"); +} + +// Count the number of ticks needed to complete the loop and compare the actual log. +Promise.resolve(0) + .then(() => actual.push("tick 1")) + .then(() => actual.push("tick 2")) + .then(() => { + assert.compareArray(actual, expected, "Ticks and constructor lookups"); +}).then($DONE, $DONE); + +// Redefine `Promise.constructor` in order to intercept "constructor" lookups from PromiseResolve. +// (Perform last so that the lookups from SpeciesConstructor in `then` aren't logged.) +Object.defineProperty(Promise.prototype, "constructor", { + get() { + actual.push("constructor"); + return Promise; + }, + configurable: true, +}); + +// Start the asynchronous function. +f(); diff --git a/test/language/statements/for-await-of/ticks-with-async-iter-resolved-promise-and-constructor-lookup.js b/test/language/statements/for-await-of/ticks-with-async-iter-resolved-promise-and-constructor-lookup.js new file mode 100644 index 00000000000..a86b620deec --- /dev/null +++ b/test/language/statements/for-await-of/ticks-with-async-iter-resolved-promise-and-constructor-lookup.js @@ -0,0 +1,77 @@ +// Copyright (C) 2019 André Bargull. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-runtime-semantics-forin-div-ofbodyevaluation-lhs-stmt-iterator-lhskind-labelset +description: > + Ensure the number of ticks and Promise constructor lookups is correct with custom async iterator. +info: | + 13.7.5.13 Runtime Semantics: ForIn/OfBodyEvaluation ( lhs, stmt, iteratorRecord, iterationKind, + lhsKind, labelSet [ , iteratorKind ] ) + 6.2.3.1 Await + +includes: [compareArray.js] +flags: [async] +features: [async-iteration] +---*/ + +// The expected event log. +var expected = [ + // Before entering loop. + "pre", + + // Await promise resolved. + "tick 1", + + // In loop body. + "loop", + + // Await promise resolved + "tick 2", + + // After exiting loop. + "post", +]; + +// The actual event log. +var actual = []; + +// Custom async iterator directly using a synchronous iterator. +function toAsyncIterator(iterable) { + return { + [Symbol.asyncIterator]() { + return iterable[Symbol.iterator](); + } + }; +} + +// Test function using for-await with a single, already resolved Promise. +async function f() { + var p = Promise.resolve(0); + actual.push("pre"); + for await (var x of toAsyncIterator([p])) { + actual.push("loop"); + } + actual.push("post"); +} + +// Count the number of ticks needed to complete the loop and compare the actual log. +Promise.resolve(0) + .then(() => actual.push("tick 1")) + .then(() => actual.push("tick 2")) + .then(() => { + assert.compareArray(actual, expected, "Ticks and constructor lookups"); +}).then($DONE, $DONE); + +// Redefine `Promise.constructor` in order to intercept "constructor" lookups from PromiseResolve. +// (Perform last so that the lookups from SpeciesConstructor in `then` aren't logged.) +Object.defineProperty(Promise.prototype, "constructor", { + get() { + actual.push("constructor"); + return Promise; + }, + configurable: true, +}); + +// Start the asynchronous function. +f(); diff --git a/test/language/statements/for-await-of/ticks-with-sync-iter-resolved-promise-and-constructor-lookup.js b/test/language/statements/for-await-of/ticks-with-sync-iter-resolved-promise-and-constructor-lookup.js new file mode 100644 index 00000000000..468377cc186 --- /dev/null +++ b/test/language/statements/for-await-of/ticks-with-sync-iter-resolved-promise-and-constructor-lookup.js @@ -0,0 +1,92 @@ +// Copyright (C) 2019 André Bargull. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-runtime-semantics-forin-div-ofbodyevaluation-lhs-stmt-iterator-lhskind-labelset +description: > + Ensure the number of ticks and Promise constructor lookups is correct with a Async-from-Sync iterator. +info: | + 13.7.5.13 Runtime Semantics: ForIn/OfBodyEvaluation ( lhs, stmt, iteratorRecord, iterationKind, + lhsKind, labelSet [ , iteratorKind ] ) + 25.1.4.2.1 %AsyncFromSyncIteratorPrototype%.next + 25.1.4.4 AsyncFromSyncIteratorContinuation + 25.6.4.5.1 PromiseResolve + 6.2.3.1 Await + +includes: [compareArray.js] +flags: [async] +features: [async-iteration] +---*/ + +// The expected event log. +var expected = [ + // Before entering loop. + "pre", + + // %AsyncFromSyncIteratorPrototype%.next + // -> AsyncFromSyncIteratorContinuation + // -> PromiseResolve + "constructor", + + // Await + // -> PromiseResolve + "constructor", + + // Async-from-Sync Iterator promise resolved. + "tick 1", + + // Await promise resolved. + "tick 2", + + // In loop body. + "loop", + + // Await + // -> PromiseResolve + "constructor", + + // Async-from-Sync Iterator promise resolved. + "tick 3", + + // Await promise resolved + "tick 4", + + // After exiting loop. + "post", +]; + +// The actual event log. +var actual = []; + +// Test function using for-await with a single, already resolved Promise. +async function f() { + var p = Promise.resolve(0); + actual.push("pre"); + for await (var x of [p]) { + actual.push("loop"); + } + actual.push("post"); +} + +// Count the number of ticks needed to complete the loop and compare the actual log. +Promise.resolve(0) + .then(() => actual.push("tick 1")) + .then(() => actual.push("tick 2")) + .then(() => actual.push("tick 3")) + .then(() => actual.push("tick 4")) + .then(() => { + assert.compareArray(actual, expected, "Ticks and constructor lookups"); +}).then($DONE, $DONE); + +// Redefine `Promise.constructor` in order to intercept "constructor" lookups from PromiseResolve. +// (Perform last so that the lookups from SpeciesConstructor in `then` aren't logged.) +Object.defineProperty(Promise.prototype, "constructor", { + get() { + actual.push("constructor"); + return Promise; + }, + configurable: true, +}); + +// Start the asynchronous function. +f();