diff --git a/test/built-ins/Iterator/zip/padding-iteration-get-iterator-abrupt-completion.js b/test/built-ins/Iterator/zip/padding-iteration-get-iterator-abrupt-completion.js new file mode 100644 index 0000000000..0a5c9b0579 --- /dev/null +++ b/test/built-ins/Iterator/zip/padding-iteration-get-iterator-abrupt-completion.js @@ -0,0 +1,144 @@ +// Copyright (C) 2025 André Bargull. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-iterator.zip +description: > + Abrupt completion for GetIterator in "padding" option iteration. +info: | + Iterator.zip ( iterables [ , options ] ) + ... + 14. If mode is "longest", then + ... + b. Else, + i. Let paddingIter be Completion(GetIterator(paddingOption, sync)). + ii. IfAbruptCloseIterators(paddingIter, iters). + ... + + IfAbruptCloseIterators ( value, iteratorRecords ) + 1. Assert: value is a Completion Record. + 2. If value is an abrupt completion, return ? IteratorCloseAll(iteratorRecords, value). + 3. Else, set value to value.[[Value]]. + + IteratorCloseAll ( iters, completion ) + 1. For each element iter of iters, in reverse List order, do + a. Set completion to Completion(IteratorClose(iter, completion)). + 2. Return ? completion. + + IteratorClose ( iteratorRecord, completion ) + 1. Assert: iteratorRecord.[[Iterator]] is an Object. + 2. Let iterator be iteratorRecord.[[Iterator]]. + 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)). + 5. If completion is a throw completion, return ? completion. + ... +includes: [compareArray.js] +features: [joint-iteration] +---*/ + +var log = []; + +var first = { + next() { + log.push("unexpected call to next method"); + }, + return() { + // Called with the correct receiver and no arguments. + assert.sameValue(this, first); + assert.sameValue(arguments.length, 0); + + // NB: Log after above asserts, because failures aren't propagated. + log.push("first return"); + + // This exception is ignored. + throw new Test262Error(); + } +}; + +var second = { + next() { + log.push("unexpected call to next method"); + }, + return() { + // Called with the correct receiver and no arguments. + assert.sameValue(this, second); + assert.sameValue(arguments.length, 0); + + // NB: Log after above asserts, because failures aren't propagated. + log.push("second return"); + + // This exception is ignored. + throw new Test262Error(); + } +}; + +var third = { + next() { + log.push("unexpected call to next method"); + }, + get return() { + // Called with the correct receiver and no arguments. + assert.sameValue(this, third); + assert.sameValue(arguments.length, 0); + + // NB: Log after above asserts, because failures aren't propagated. + log.push("third return"); + + // This exception is ignored. + throw new Test262Error(); + } +}; + +function ExpectedError() {} + +// Padding iterator throws from |Symbol.iterator|. +var padding = { + [Symbol.iterator]() { + throw new ExpectedError(); + }, + next() { + log.push("unexpected call to next method"); + }, + return() { + log.push("unexpected call to return method"); + }, +}; + +assert.throws(ExpectedError, function() { + Iterator.zip([first, second, third], {mode: "longest", padding}); +}); + +assert.compareArray(log, [ + "third return", + "second return", + "first return", +]); + +// Clear log +log.length = 0; + +// Padding iterator throws from |next|. +var padding = { + [Symbol.iterator]() { + return this; + }, + next() { + throw new ExpectedError(); + }, + return() { + log.push("unexpected call to return method"); + }, +}; + +assert.throws(ExpectedError, function() { + Iterator.zip([first, second, third], {mode: "longest", padding}); +}); + +assert.compareArray(log, [ + "third return", + "second return", + "first return", +]); diff --git a/test/built-ins/Iterator/zip/padding-iteration-iterator-close-abrupt-completion.js b/test/built-ins/Iterator/zip/padding-iteration-iterator-close-abrupt-completion.js new file mode 100644 index 0000000000..0613da2ff5 --- /dev/null +++ b/test/built-ins/Iterator/zip/padding-iteration-iterator-close-abrupt-completion.js @@ -0,0 +1,124 @@ +// Copyright (C) 2025 André Bargull. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-iterator.zip +description: > + Abrupt completion for IteratorClose in "padding" option iteration. +info: | + Iterator.zip ( iterables [ , options ] ) + ... + 14. If mode is "longest", then + ... + b. Else, + i. Let paddingIter be Completion(GetIterator(paddingOption, sync)). + ... + v. If usingIterator is true, then + 1. Let completion be Completion(IteratorClose(paddingIter, NormalCompletion(unused))). + 2. IfAbruptCloseIterators(completion, iters). + ... + + IfAbruptCloseIterators ( value, iteratorRecords ) + 1. Assert: value is a Completion Record. + 2. If value is an abrupt completion, return ? IteratorCloseAll(iteratorRecords, value). + 3. Else, set value to value.[[Value]]. + + IteratorCloseAll ( iters, completion ) + 1. For each element iter of iters, in reverse List order, do + a. Set completion to Completion(IteratorClose(iter, completion)). + 2. Return ? completion. + + IteratorClose ( iteratorRecord, completion ) + 1. Assert: iteratorRecord.[[Iterator]] is an Object. + 2. Let iterator be iteratorRecord.[[Iterator]]. + 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)). + 5. If completion is a throw completion, return ? completion. + ... +includes: [compareArray.js] +features: [joint-iteration] +---*/ + +var log = []; + +var first = { + next() { + log.push("unexpected call to next method"); + }, + return() { + // Called with the correct receiver and no arguments. + assert.sameValue(this, first); + assert.sameValue(arguments.length, 0); + + // NB: Log after above asserts, because failures aren't propagated. + log.push("first return"); + + // This exception is ignored. + throw new Test262Error(); + } +}; + +var second = { + next() { + log.push("unexpected call to next method"); + }, + return() { + // Called with the correct receiver and no arguments. + assert.sameValue(this, second); + assert.sameValue(arguments.length, 0); + + // NB: Log after above asserts, because failures aren't propagated. + log.push("second return"); + + // This exception is ignored. + throw new Test262Error(); + } +}; + +var third = { + next() { + log.push("unexpected call to next method"); + }, + get return() { + // Called with the correct receiver and no arguments. + assert.sameValue(this, third); + assert.sameValue(arguments.length, 0); + + // NB: Log after above asserts, because failures aren't propagated. + log.push("third return"); + + // This exception is ignored. + throw new Test262Error(); + } +}; + +function ExpectedError() {} + +// Padding iterator throws from |Symbol.iterator|. +var padding = { + [Symbol.iterator]() { + return this; + }, + next() { + return {done: false}; + }, + return() { + log.push("padding return"); + + throw new ExpectedError(); + }, +}; + +assert.throws(ExpectedError, function() { + Iterator.zip([first, second, third], {mode: "longest", padding}); +}); + +assert.compareArray(log, [ + "padding return", + "third return", + "second return", + "first return", +]); diff --git a/test/built-ins/Iterator/zip/padding-iteration-iterator-step-value-abrupt-completion.js b/test/built-ins/Iterator/zip/padding-iteration-iterator-step-value-abrupt-completion.js new file mode 100644 index 0000000000..833977eab1 --- /dev/null +++ b/test/built-ins/Iterator/zip/padding-iteration-iterator-step-value-abrupt-completion.js @@ -0,0 +1,187 @@ +// Copyright (C) 2025 André Bargull. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-iterator.zip +description: > + Abrupt completion for IteratorStepValue in "padding" option iteration. +info: | + Iterator.zip ( iterables [ , options ] ) + ... + 14. If mode is "longest", then + ... + b. Else, + i. Let paddingIter be Completion(GetIterator(paddingOption, sync)). + ... + iv. Perform the following steps iterCount times: + 1. If usingIterator is true, then + a. Set next to Completion(IteratorStepValue(paddingIter)). + b. IfAbruptCloseIterators(next, iters). + ... + + IfAbruptCloseIterators ( value, iteratorRecords ) + 1. Assert: value is a Completion Record. + 2. If value is an abrupt completion, return ? IteratorCloseAll(iteratorRecords, value). + 3. Else, set value to value.[[Value]]. + + IteratorCloseAll ( iters, completion ) + 1. For each element iter of iters, in reverse List order, do + a. Set completion to Completion(IteratorClose(iter, completion)). + 2. Return ? completion. + + IteratorClose ( iteratorRecord, completion ) + 1. Assert: iteratorRecord.[[Iterator]] is an Object. + 2. Let iterator be iteratorRecord.[[Iterator]]. + 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)). + 5. If completion is a throw completion, return ? completion. + ... +includes: [compareArray.js] +features: [joint-iteration] +---*/ + +var log = []; + +var first = { + next() { + log.push("unexpected call to next method"); + }, + return() { + // Called with the correct receiver and no arguments. + assert.sameValue(this, first); + assert.sameValue(arguments.length, 0); + + // NB: Log after above asserts, because failures aren't propagated. + log.push("first return"); + + // This exception is ignored. + throw new Test262Error(); + } +}; + +var second = { + next() { + log.push("unexpected call to next method"); + }, + return() { + // Called with the correct receiver and no arguments. + assert.sameValue(this, second); + assert.sameValue(arguments.length, 0); + + // NB: Log after above asserts, because failures aren't propagated. + log.push("second return"); + + // This exception is ignored. + throw new Test262Error(); + } +}; + +var third = { + next() { + log.push("unexpected call to next method"); + }, + get return() { + // Called with the correct receiver and no arguments. + assert.sameValue(this, third); + assert.sameValue(arguments.length, 0); + + // NB: Log after above asserts, because failures aren't propagated. + log.push("third return"); + + // This exception is ignored. + throw new Test262Error(); + } +}; + +function ExpectedError() {} + +// Padding iterator throws from |next|. +var padding = { + [Symbol.iterator]() { + return this; + }, + next() { + throw new ExpectedError(); + }, + return() { + log.push("unexpected call to return method"); + }, +}; + +assert.throws(ExpectedError, function() { + Iterator.zip([first, second, third], {mode: "longest", padding}); +}); + +assert.compareArray(log, [ + "third return", + "second return", + "first return", +]); + +// Clear log +log.length = 0; + +// Padding iterator throws from |done|. +var padding = { + [Symbol.iterator]() { + return this; + }, + next() { + return { + get done() { + throw new ExpectedError(); + }, + get value() { + log.push("unexpected access to value"); + }, + }; + }, + return() { + log.push("unexpected call to return method"); + }, +}; + +assert.throws(ExpectedError, function() { + Iterator.zip([first, second, third], {mode: "longest", padding}); +}); + +assert.compareArray(log, [ + "third return", + "second return", + "first return", +]); + + +// Clear log +log.length = 0; + +// Padding iterator throws from |value|. +var padding = { + [Symbol.iterator]() { + return this; + }, + next() { + return { + done: false, + get value() { + throw new ExpectedError(); + }, + }; + }, + return() { + log.push("unexpected call to return method"); + }, +}; + +assert.throws(ExpectedError, function() { + Iterator.zip([first, second, third], {mode: "longest", padding}); +}); + +assert.compareArray(log, [ + "third return", + "second return", + "first return", +]); diff --git a/test/built-ins/Iterator/zip/padding-iteration.js b/test/built-ins/Iterator/zip/padding-iteration.js new file mode 100644 index 0000000000..9797e220d5 --- /dev/null +++ b/test/built-ins/Iterator/zip/padding-iteration.js @@ -0,0 +1,135 @@ +// Copyright (C) 2025 André Bargull. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-iterator.zip +description: > + Perform iteration of the "padding" option. +info: | + Iterator.zip ( iterables [ , options ] ) + ... + 14. If mode is "longest", then + ... + b. Else, + i. Let paddingIter be Completion(GetIterator(paddingOption, sync)). + ... + iii. Let usingIterator be true. + iv. Perform the following steps iterCount times: + 1. If usingIterator is true, then + a. Set next to Completion(IteratorStepValue(paddingIter)). + ... + c. If next is done, then + i. Set usingIterator to false. + d. Else, + i. Append next to padding. + 2. If usingIterator is false, append undefined to padding. + v. If usingIterator is true, then + 1. Let completion be Completion(IteratorClose(paddingIter, NormalCompletion(unused))). + ... + ... + + GetIterator ( obj, kind ) + ... + 2. Else, + a. Let method be ? GetMethod(obj, %Symbol.iterator%). + 3. If method is undefined, throw a TypeError exception. + 4. Return ? GetIteratorFromMethod(obj, method). + + GetIteratorFromMethod ( obj, method ) + 1. Let iterator be ? Call(method, obj). + 2. If iterator is not an Object, throw a TypeError exception. + 3. Return ? GetIteratorDirect(iterator). + + GetIteratorDirect ( obj ) + 1. Let nextMethod be ? Get(obj, "next"). + 2. Let iteratorRecord be the Iterator Record { [[Iterator]]: obj, [[NextMethod]]: nextMethod, [[Done]]: false }. + 3. Return iteratorRecord. +includes: [proxyTrapsHelper.js, compareArray.js] +features: [joint-iteration] +---*/ + +function makeProxyWithGetHandler(log, name, obj) { + return new Proxy(obj, allowProxyTraps({ + get(target, propertyKey, receiver) { + log.push(`${name}::${String(propertyKey)}`); + return Reflect.get(target, propertyKey, receiver); + } + })); +} + +// "padding" option must be an iterable. +assert.throws(TypeError, function() { + Iterator.zip([], { + mode: "longest", + padding: {}, + }); +}); + +for (var n = 0; n <= 5; ++n) { + var iterables = Array(n).fill([]); + + for (var k = 0; k <= n + 2; ++k) { + var elements = Array(k).fill(0); + var elementsIter = elements.values(); + + var log = []; + + var padding = makeProxyWithGetHandler(log, "padding", { + [Symbol.iterator]() { + log.push("call iterator"); + + // Called with the correct receiver and no arguments. + assert.sameValue(this, padding); + assert.sameValue(arguments.length, 0); + + return this; + }, + next() { + log.push("call next"); + + // Called with the correct receiver and no arguments. + assert.sameValue(this, padding); + assert.sameValue(arguments.length, 0); + + return elementsIter.next(); + }, + return() { + log.push("call return"); + + // Called with the correct receiver and no arguments. + assert.sameValue(this, padding); + assert.sameValue(arguments.length, 0); + + return {}; + } + }); + + Iterator.zip(iterables, {mode: "longest", padding}); + + // Property reads and calls from GetIterator. + var expected = [ + "padding::Symbol(Symbol.iterator)", + "call iterator", + "padding::next", + ]; + + // Call the "next" method |n| times until the padding iterator is exhausted. + for (var i = 0; i < Math.min(n, k); ++i) { + expected.push("call next"); + } + + // If |n| is larger than |k|, then there was one final call to the "next" + // method. Otherwise the "return" method was called to close the padding + // iterator. + if (n > k) { + expected.push("call next"); + } else { + expected.push( + "padding::return", + "call return" + ); + } + + assert.compareArray(log, expected); + } +}