Skip to content

Add coverage for some implementation bugs in Iterator helper methods #4496

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
// Copyright (C) 2025 André Bargull. All rights reserved.
// This code is governed by the BSD license found in the LICENSE file.

/*---
esid: sec-iteratorprototype.drop
description: >
Underlying iterator's next method is called with zero arguments.
info: |
27.1.4.2 Iterator.prototype.drop ( limit )

...
10. Let closure be a new Abstract Closure with no parameters that captures
iterated and integerLimit and performs the following steps when called:
...
b. Repeat, while remaining > 0,
...
ii. Let next be ? IteratorStep(iterated).
...
c. Repeat,
i. Let value be ? IteratorStepValue(iterated).
...
...
features: [iterator-helpers]
---*/

var counter = 0;

var underlying = {
next() {
assert.sameValue(arguments.length, 0);

return {
done: false,
value: ++counter,
};
}
};

var it = Iterator.prototype.drop.call(underlying, 3);

assert.sameValue(counter, 0);

assert.sameValue(it.next().value, 4);
assert.sameValue(it.next(1).value, 5);
assert.sameValue(it.next(1, 2).value, 6);

assert.sameValue(counter, 6);
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
// Copyright (C) 2024 André Bargull. All rights reserved.
// This code is governed by the BSD license found in the LICENSE file.

/*---
esid: sec-iteratorprototype.drop
description: >
Underlying iterator's return method is called with zero arguments.
info: |
27.1.4.2 Iterator.prototype.drop ( limit )

...
10. Let closure be a new Abstract Closure with no parameters that captures
iterated and integerLimit and performs the following steps when called:
...
c. Repeat,
...
iii. Let completion be Completion(Yield(value)).
iv. IfAbruptCloseIterator(completion, iterated).
...

27.1.2.1.2 %IteratorHelperPrototype%.return ( )
...
4. If O.[[GeneratorState]] is suspended-start, then
...
c. Perform ? IteratorClose(O.[[UnderlyingIterator]], NormalCompletion(unused)).
...
5. Let C be ReturnCompletion(undefined).
6. Return ? GeneratorResumeAbrupt(O, C, "Iterator Helper").
features: [iterator-helpers]
---*/

var counter = 0;

var underlying = {
next() {
return {
done: false,
value: 0,
};
},
return() {
assert.sameValue(arguments.length, 0);

counter += 1;
return {};
}
};

// Close from "suspended-start".
{
var it = Iterator.prototype.drop.call(underlying, 0);

assert.sameValue(counter, 0);
it.return();
assert.sameValue(counter, 1);
}

{
var it = Iterator.prototype.drop.call(underlying, 0);

assert.sameValue(counter, 1);
it.return(1);
assert.sameValue(counter, 2);
}

{
var it = Iterator.prototype.drop.call(underlying, 0);

assert.sameValue(counter, 2);
it.return(1, 2);
assert.sameValue(counter, 3);
}

// Close from "suspended-yield".
{
var it = Iterator.prototype.drop.call(underlying, 0);
it.next();

assert.sameValue(counter, 3);
it.return();
assert.sameValue(counter, 4);
}

{
var it = Iterator.prototype.drop.call(underlying, 0);
it.next();

assert.sameValue(counter, 4);
it.return(1);
assert.sameValue(counter, 5);
}

{
var it = Iterator.prototype.drop.call(underlying, 0);
it.next();

assert.sameValue(counter, 5);
it.return(1, 2);
assert.sameValue(counter, 6);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
// Copyright (C) 2025 André Bargull. All rights reserved.
// This code is governed by the BSD license found in the LICENSE file.
/*---
esid: sec-iterator.prototype.drop
description: >
Generator is closed from suspended-start state and IteratorClose calls next.
info: |
27.1.2.1.2 %IteratorHelperPrototype%.return ( )
...
4. If O.[[GeneratorState]] is suspended-start, then
a. Set O.[[GeneratorState]] to completed.
...
c. Perform ? IteratorClose(O.[[UnderlyingIterator]], NormalCompletion(unused)).
d. Return CreateIteratorResultObject(undefined, true).
...

7.4.11 IteratorClose ( iteratorRecord, completion )
...
3. Let innerResult be Completion(GetMethod(iterator, "return")).
4. If innerResult is a normal completion, then
a. Let return be innerResult.[[Value]].
b. If return is undefined, return ? completion.
c. Set innerResult to Completion(Call(return, iterator)).
...

27.1.2.1.1 %IteratorHelperPrototype%.next ( )
1. Return ? GeneratorResume(this value, undefined, "Iterator Helper").

27.5.3.3 GeneratorResume ( generator, value, generatorBrand )
1. Let state be ? GeneratorValidate(generator, generatorBrand).
2. If state is completed, return CreateIteratorResultObject(undefined, true).
...

27.5.3.2 GeneratorValidate ( generator, generatorBrand )
...
5. Let state be generator.[[GeneratorState]].
6. If state is executing, throw a TypeError exception.
7. Return state.
features: [iterator-helpers]
---*/

var returnCallCount = 0;

var underlying = {
next() {
throw new Test262Error("Unexpected call to next");
},
return() {
returnCallCount += 1;

// The generator state is already set to "completed". The generator state is
// not "executing", so `GeneratorValidate` succeeds and `GeneratorResume`
// returns with `CreateIteratorResultObject()`.
var result = it.next();
assert.sameValue(result.value, undefined);
assert.sameValue(result.done, true);

return {};
},
};

var it = Iterator.prototype.drop.call(underlying, 0);

assert.sameValue(returnCallCount, 0);

// This `return()` call sets the generator state to "completed" and then calls
// `IteratorClose()`.
var result = it.return();
assert.sameValue(result.value, undefined);
assert.sameValue(result.done, true);

assert.sameValue(returnCallCount, 1);
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
// Copyright (C) 2025 André Bargull. All rights reserved.
// This code is governed by the BSD license found in the LICENSE file.
/*---
esid: sec-iterator.prototype.drop
description: >
Generator is closed from suspended-start state and IteratorClose calls return.
info: |
27.1.2.1.2 %IteratorHelperPrototype%.return ( )
...
4. If O.[[GeneratorState]] is suspended-start, then
a. Set O.[[GeneratorState]] to completed.
...
c. Perform ? IteratorClose(O.[[UnderlyingIterator]], NormalCompletion(unused)).
d. Return CreateIteratorResultObject(undefined, true).
5. Let C be ReturnCompletion(undefined).
6. Return ? GeneratorResumeAbrupt(O, C, "Iterator Helper").

7.4.11 IteratorClose ( iteratorRecord, completion )
...
3. Let innerResult be Completion(GetMethod(iterator, "return")).
4. If innerResult is a normal completion, then
a. Let return be innerResult.[[Value]].
b. If return is undefined, return ? completion.
c. Set innerResult to Completion(Call(return, iterator)).
...
8. Return ? completion.

27.5.3.4 GeneratorResumeAbrupt ( generator, abruptCompletion, generatorBrand )
1. Let state be ? GeneratorValidate(generator, generatorBrand).
2. If state is suspended-start, then
...
3. If state is completed, then
a. If abruptCompletion is a return completion, then
i. Return CreateIteratorResultObject(abruptCompletion.[[Value]], true).
...

27.5.3.2 GeneratorValidate ( generator, generatorBrand )
...
5. Let state be generator.[[GeneratorState]].
6. If state is executing, throw a TypeError exception.
7. Return state.
features: [iterator-helpers]
---*/

var returnCallCount = 0;

var underlying = {
next() {
throw new Test262Error("Unexpected call to next");
},
return() {
returnCallCount += 1;

// The generator state is already set to "completed", so this `return()`
// call proceeds to `GeneratorResumeAbrupt`. The generator state is not
// "executing", so `GeneratorValidate` succeeds and `GeneratorResumeAbrupt`
// returns with `CreateIteratorResultObject()`.
var result = it.return();
assert.sameValue(result.value, undefined);
assert.sameValue(result.done, true);

return {};
},
};

var it = Iterator.prototype.drop.call(underlying, 0);

assert.sameValue(returnCallCount, 0);

// This `return()` call sets the generator state to "completed" and then calls
// `IteratorClose()`.
var result = it.return();
assert.sameValue(result.value, undefined);
assert.sameValue(result.done, true);

assert.sameValue(returnCallCount, 1);
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
// Copyright (C) 2025 André Bargull. All rights reserved.
// This code is governed by the BSD license found in the LICENSE file.
/*---
esid: sec-iterator.prototype.drop
description: >
Generator is closed from suspended-yield state and IteratorClose calls next.
info: |
27.1.2.1.2 %IteratorHelperPrototype%.return ( )
...
5. Let C be ReturnCompletion(undefined).
6. Return ? GeneratorResumeAbrupt(O, C, "Iterator Helper").

27.5.3.4 GeneratorResumeAbrupt ( generator, abruptCompletion, generatorBrand )
1. Let state be ? GeneratorValidate(generator, generatorBrand).
...
4. Assert: state is suspended-yield.
...
8. Set generator.[[GeneratorState]] to executing.
...
10. Resume the suspended evaluation of genContext using abruptCompletion as
the result of the operation that suspended it. Let result be the
Completion Record returned by the resumed computation.
...

27.5.3.2 GeneratorValidate ( generator, generatorBrand )
...
5. Let state be generator.[[GeneratorState]].
6. If state is executing, throw a TypeError exception.
7. Return state.

27.1.4.2 Iterator.prototype.drop ( limit )
...
10. Let closure be a new Abstract Closure with no parameters that captures
iterated and integerLimit and performs the following steps when called:
...
c. Repeat,
...
iii. Let completion be Completion(Yield(value)).
iv. IfAbruptCloseIterator(completion, iterated).
...

7.4.12 IfAbruptCloseIterator ( value, iteratorRecord )
...
2. If value is an abrupt completion, return ? IteratorClose(iteratorRecord, value).
...

7.4.11 IteratorClose ( iteratorRecord, completion )
...
3. Let innerResult be Completion(GetMethod(iterator, "return")).
4. If innerResult is a normal completion, then
a. Let return be innerResult.[[Value]].
b. If return is undefined, return ? completion.
c. Set innerResult to Completion(Call(return, iterator)).
...

27.1.2.1.1 %IteratorHelperPrototype%.next ( )
1. Return ? GeneratorResume(this value, undefined, "Iterator Helper").

27.5.3.3 GeneratorResume ( generator, value, generatorBrand )
1. Let state be ? GeneratorValidate(generator, generatorBrand).
...
features: [iterator-helpers]
---*/

var returnCallCount = 0;

var underlying = {
next() {
return {value: 123, done: false};
},
return() {
returnCallCount += 1;

// The generator state is set to "executing", so this `next()` call throws
// a TypeError when `GeneratorResume` performs `GeneratorValidate`.
assert.throws(TypeError, function() {
it.next();
});

return {};
},
};

var it = Iterator.prototype.drop.call(underlying, 0);

// Move generator into "suspended-yield" state.
var result = it.next();
assert.sameValue(result.value, 123);
assert.sameValue(result.done, false);

assert.sameValue(returnCallCount, 0);

// This `return()` call continues execution in the suspended generator.
var result = it.return();
assert.sameValue(result.value, undefined);
assert.sameValue(result.done, true);

assert.sameValue(returnCallCount, 1);
Loading