Skip to content

Commit

Permalink
Test that async-from-sync iterator closes when throw is undefined.
Browse files Browse the repository at this point in the history
These test paths from newly added call to IteratorClose in step 7.c of
 %AsyncFromSyncIteratorPrototype%.throw
as per normative changes of ecma626 PR 2600
  • Loading branch information
ioannad committed Dec 28, 2023
1 parent 35d96e4 commit e3b4caf
Show file tree
Hide file tree
Showing 4 changed files with 331 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
// Copyright (C) 2023 Igalia, S.L. All rights reserved.
// This code is governed by the BSD license found in the LICENSE file.

/*---
esid: sec-%asyncfromsynciteratorprototype%.throw
description: >
If syncIterator's "throw" method is `undefined`,
and its "return" method returns `undefined`,
the iterator will close returning the `undefined` value,
which will be ignored and instead a rejected Promise with a new TypeError is returned.
info: |
%AsyncFromSyncIteratorPrototype%.throw ( value )
...
2. Let promiseCapability be ! NewPromiseCapability(%Promise%).
...
5. Let return be GetMethod(syncIterator, "throw").
6. IfAbruptRejectPromise(throw, promiseCapability).
7. If throw is undefined, then
a. NOTE: If syncIterator does not have a throw method, close it to give it a chance to clean up before we reject the capability.
b. Let closeCompletion be Completion { [[Type]]: normal, [[Value]]: empty, [[Target]]: empty }.
c. Set result to IteratorClose(syncIteratorRecord, closeCompletion).
...
g. Perform ! Call(promiseCapability.[[Reject]], undefined, « a newly created TypeError object »).
h. Return promiseCapability.[[Promise]].
...
IteratorClose ( iterator, completion )
...
2. Let iterator be iteratorRecord.[[Iterator]].
3. Let innerResult be Completion(GetMethod(iterator, "return")).
4. If innerResult.[[Type]] is normal, then
a. Let return be innerResult.[[Value]].
b. If return is undefined, return ? completion.
...
flags: [async]
features: [async-iteration]
---*/

var returnCount = 0;

const obj = {
[Symbol.iterator]() {
return {
next() {
return {value: 1, done: false};
},
get return() {
returnCount += 1;
return undefined;
}
};
}
};

async function* wrapper() {
yield* obj;
}

var iter = wrapper();

iter.next().then(function(result) {
iter.throw().then(
function (result) {
throw new Test262Error("Promise should be rejected, got: " + result.value);
},
function (err) {
assert.sameValue(err.constructor, TypeError, "TypeError");
assert.sameValue(err.message, 'The iterator does not provide a throw method');
assert.sameValue(returnCount, 1, 'iterator closed properly');

iter.next().then(({ done, value }) => {
assert.sameValue(done, true, 'the iterator is completed');
assert.sameValue(value, undefined, 'value is undefined');
}).then($DONE, $DONE);
}
).catch($DONE);

}).catch($DONE);

Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
// Copyright (C) 2023 Igalia, S.L. All rights reserved.
// This code is governed by the BSD license found in the LICENSE file.

/*---
esid: sec-%asyncfromsynciteratorprototype%.throw
description: throw() will close the iterator and return rejected promise if sync `throw` undefined
info: |
%AsyncFromSyncIteratorPrototype%.throw ( value )
...
2. Let promiseCapability be ! NewPromiseCapability(%Promise%).
...
5. Let return be GetMethod(syncIterator, "throw").
6. IfAbruptRejectPromise(throw, promiseCapability).
7. If throw is undefined, then
a. NOTE: If syncIterator does not have a throw method, close it to give it a chance to clean up before we reject the capability.
b. Let closeCompletion be Completion { [[Type]]: normal, [[Value]]: empty, [[Target]]: empty }.
c. Set result to IteratorClose(syncIteratorRecord, closeCompletion).
d. IfAbruptRejectPromise(result, promiseCapability).
e. NOTE: The next step throws a TypeError to indicate that there was a protocol violation: syncIterator does not have a throw method.
f. NOTE: If closing syncIterator does not throw then the result of that operation is ignored, even if it yields a rejected promise.
g. Perform ! Call(promiseCapability.[[Reject]], undefined, « a newly created TypeError object »).
h. Return promiseCapability.[[Promise]].
IteratorClose ( iterator, completion )
...
2. Let iterator be iteratorRecord.[[Iterator]].
3. Let innerResult be Completion(GetMethod(iterator, "return")).
...
6. If innerResult.[[Type]] is throw, return ? innerResult.
...
IfAbruptRejectPromise ( value, capability )
1. Assert: value is a Completion Record.
2. If value is an abrupt completion, then
a. Perform ? Call(capability.[[Reject]], undefined, « value.[[Value]] »).
b. Return capability.[[Promise]].
...
flags: [async]
features: [async-iteration]
---*/

var returnCount = 0;
var thrownError = new Error("Catch me.");

const obj = {
[Symbol.iterator]() {
return {
next() {
return {value: 1, done: false};
},
get return() {
returnCount += 1;
throw thrownError;
}
};
}
};

async function* wrapper() {
yield* obj;
}

var iter = wrapper();

iter.next().then(function(result) {
iter.throw().then(
function (result) {
throw new Test262Error("Promise should be rejected, got: " + result.value);
},
function (err) {
assert.sameValue(err, thrownError, "Promise should be rejected with thrown error");
assert.sameValue(returnCount, 1, 'iterator closed properly');

iter.next().then(({ done, value }) => {
assert.sameValue(done, true, 'the iterator is completed');
assert.sameValue(value, undefined, 'value is undefined');
}).then($DONE, $DONE);
}
).catch($DONE);

}).catch($DONE);
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
// Copyright (C) 2023 Igalia, S.L. All rights reserved.
// This code is governed by the BSD license found in the LICENSE file.

/*---
esid: sec-%asyncfromsynciteratorprototype%.throw
description: >
If syncIterator's "throw" method is `undefined`,
and its "return" method returns `undefined`,
the iterator will close returning the `undefined` value,
which will be ignored and instead a rejected Promise with a new TypeError is returned.
info: |
%AsyncFromSyncIteratorPrototype%.throw ( value )
...
2. Let promiseCapability be ! NewPromiseCapability(%Promise%).
...
5. Let return be GetMethod(syncIterator, "throw").
6. IfAbruptRejectPromise(throw, promiseCapability).
7. If throw is undefined, then
a. NOTE: If syncIterator does not have a throw method, close it to give it a chance to clean up before we reject the capability.
b. Let closeCompletion be Completion { [[Type]]: normal, [[Value]]: empty, [[Target]]: empty }.
c. Set result to IteratorClose(syncIteratorRecord, closeCompletion).
d. IfAbruptRejectPromise(result, promiseCapability).
...
IteratorClose ( iterator, completion )
...
2. Let iterator be iteratorRecord.[[Iterator]].
3. Let innerResult be Completion(GetMethod(iterator, "return")).
4. If innerResult.[[Type]] is normal, then
a. Let return be innerResult.[[Value]].
...
c. Set innerResult to Completion(Call(return, iterator)).
...
7. If innerResult.[[Value]] is not an Object, throw a TypeError exception.
...
IfAbruptRejectPromise ( value, capability )
1. Assert: value is a Completion Record.
2. If value is an abrupt completion, then
a. Perform ? Call(capability.[[Reject]], undefined, « value.[[Value]] »).
b. Return capability.[[Promise]].
...
flags: [async]
features: [async-iteration]
---*/

var returnCount = 0;

const obj = {
[Symbol.iterator]() {
return {
next() {
return {value: 1, done: false};
},
return() {
returnCount += 1;
return 2;
}
};
}
};

async function* wrapper() {
yield* obj;
}

var iter = wrapper();

iter.next().then(function(result) {
iter.throw().then(
function (result) {
throw new Test262Error("Promise should be rejected, got: " + result.value);
},
function (err) {
assert.sameValue(err.constructor, TypeError, "TypeError");
assert.sameValue(err.message, '2 is not an object');
assert.sameValue(returnCount, 1, 'iterator closed properly');

iter.next().then(({ done, value }) => {
assert.sameValue(done, true, 'the iterator is completed');
assert.sameValue(value, undefined, 'value is undefined');
}).then($DONE, $DONE);
}
).catch($DONE);

}).catch($DONE);
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
// Copyright (C) 2023 Igalia, S.L. All rights reserved.
// This code is governed by the BSD license found in the LICENSE file.

/*---
esid: sec-%asyncfromsynciteratorprototype%.throw
description: >
If syncIterator's "throw" method is `undefined`,
and its "return" method returns `undefined`,
the iterator will close returning the `undefined` value,
which will be ignored and instead a rejected Promise with a new TypeError is returned.
info: |
%AsyncFromSyncIteratorPrototype%.throw ( value )
...
2. Let promiseCapability be ! NewPromiseCapability(%Promise%).
...
5. Let return be GetMethod(syncIterator, "throw").
...
7. If throw is undefined, then
a. NOTE: If syncIterator does not have a throw method, close it to give it a chance to clean up before we reject the capability.
b. Let closeCompletion be Completion { [[Type]]: normal, [[Value]]: empty, [[Target]]: empty }.
c. Set result to IteratorClose(syncIteratorRecord, closeCompletion).
...
g. Perform ! Call(promiseCapability.[[Reject]], undefined, « a newly created TypeError object »).
h. Return promiseCapability.[[Promise]].
...
IteratorClose ( iterator, completion )
...
2. Let iterator be iteratorRecord.[[Iterator]].
3. Let innerResult be Completion(GetMethod(iterator, "return")).
4. If innerResult.[[Type]] is normal, then
a. Let return be innerResult.[[Value]].
...
c. Set innerResult to Completion(Call(return, iterator)).
...
8. Return ? completion.
flags: [async]
features: [async-iteration]
---*/

var returnCount = 0;

const obj = {
[Symbol.iterator]() {
return {
next() {
return {value: 1, done: false};
},
return() {
returnCount += 1;
return {value: 2, done: true};
}
};
}
};

async function* wrapper() {
yield* obj;
}

var iter = wrapper();

iter.next().then(function(result) {
iter.throw().then(
function (result) {
throw new Test262Error("Promise should be rejected, got: " + result.value);
},
function (err) {
assert.sameValue(err.constructor, TypeError, "TypeError");
assert.sameValue(err.message, 'The iterator does not provide a throw method');
assert.sameValue(returnCount, 1, 'iterator closed properly');

iter.next().then(({ done, value }) => {
assert.sameValue(done, true, 'the iterator is completed');
assert.sameValue(value, undefined, 'value is undefined');
}).then($DONE, $DONE);
}
).catch($DONE);

}).catch($DONE);

0 comments on commit e3b4caf

Please sign in to comment.