From 1a70fd33ab81738ad2b748812a431d51e0c4d758 Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Thu, 14 Nov 2024 14:59:04 -0800 Subject: [PATCH] [Fix] `Iterator.concat`: rewrite implementation to match updated spec text Tests from https://github.com/tc39/test262/pull/4326 --- Iterator.concat/implementation.js | 88 +++++++++------------ test/Iterator.concat.js | 123 ++++++++++++++++++++++++++++++ 2 files changed, 158 insertions(+), 53 deletions(-) diff --git a/Iterator.concat/implementation.js b/Iterator.concat/implementation.js index ca75c0f..435413c 100644 --- a/Iterator.concat/implementation.js +++ b/Iterator.concat/implementation.js @@ -11,16 +11,11 @@ var GetMethod = require('es-abstract/2024/GetMethod'); var IsArray = require('es-abstract/2024/IsArray'); var IteratorCloseAll = require('../aos/IteratorCloseAll'); var IteratorStepValue = require('es-abstract/2024/IteratorStepValue'); -var ThrowCompletion = require('es-abstract/2024/ThrowCompletion'); var Type = require('es-abstract/2024/Type'); var forEach = require('es-abstract/helpers/forEach'); var getIteratorMethod = require('es-abstract/helpers/getIteratorMethod'); -var callBound = require('call-bind/callBound'); -var $indexOf = callBound('Array.prototype.indexOf'); -var $splice = callBound('Array.prototype.splice'); - var iterHelperProto = require('../IteratorHelperPrototype'); var SLOT = require('internal-slot'); @@ -51,65 +46,52 @@ module.exports = function concat() { iterables[iterables.length] = { '[[OpenMethod]]': method, '[[Iterable]]': item }; // step 2.4 }); - var openIters = []; // step 3 var sentinel = {}; + var iterablesIndex = 0; + var iteratorRecord; + var innerAlive = false; + var closure = function () { // step 3 + if (iterablesIndex >= iterables.length) { + return sentinel; + } + var iterable = iterables[iterablesIndex]; // step 3.a + if (!innerAlive) { + var iter = Call(iterable['[[OpenMethod]]'], iterable['[[Iterable]]']); // step 3.a.i + if (Type(iter) !== 'Object') { + throw new $TypeError('`Iterator.concat` iterator method did not return an object'); // step 3.a.ii + } + iteratorRecord = GetIteratorDirect(iter); // step 3.a.iii + innerAlive = true; // step 3.a.iv + } + + if (innerAlive) { // step 3.a.v + var innerValue = IteratorStepValue(iteratorRecord); // step 3.a.v.1 + if (iteratorRecord['[[Done]]']) { // step 3.a.v.2 + innerAlive = false; // step 3.a.v.2.a + } else { // step 3.a.v.3 + // 1. Let completion be Completion(Yield(innerValue)). + return innerValue; // step 3.a.v.3.a + } + } + + iterablesIndex += 1; + return closure(); + }; + var closeIfAbrupt = function (abruptCompletion) { if (!(abruptCompletion instanceof CompletionRecord)) { throw new $TypeError('`abruptCompletion` must be a Completion Record'); } - if (openIters.length > 0) { + iterablesIndex = iterables.length; + innerAlive = false; + if (iteratorRecord) { IteratorCloseAll( - openIters, + [iteratorRecord], abruptCompletion ); } }; - var index = 0; - var closure = function () { // step 4 - if (index < iterables.length) { - // forEach(iterables, function (iterable) { // step 4.a - var iteratorRecord; - if (openIters.length === 0) { - var iterable = iterables[index]; - var iter = Call(iterable['[[OpenMethod]]'], iterable['[[Iterable]]']); // step 4.a.i - if (Type(iter) !== 'Object') { - closeIfAbrupt(ThrowCompletion(new $TypeError('???'))); // step 4.a.ii - } - iteratorRecord = GetIteratorDirect(iter); // step 4.a.iii - iteratorRecord.obj = iterable['[[Iterable]]']; - openIters[openIters.length] = iteratorRecord; // step 4.a.iv - } else { - iteratorRecord = openIters[0]; - } - - // var innerAlive = true; // step 4.a.v - // while (innerAlive) { // step 4.a.vi - { // eslint-disable-line no-lone-blocks - // step 4.a.vi.3.a - var innerValue; - try { - innerValue = IteratorStepValue(iteratorRecord); // step 5.b.ix.4.a - } catch (e) { - // innerAlive = false; - $splice(openIters, $indexOf(openIters, iteratorRecord), 1); // step 4.a.vi.2.a - index += 1; - closeIfAbrupt(ThrowCompletion(e)); // step 4.a.vi.3.b - } - if (iteratorRecord['[[Done]]']) { - // innerAlive = false; - $splice(openIters, $indexOf(openIters, iteratorRecord), 1); - index += 1; - return closure(); - } - return innerValue; // // step 4.a.vi.3.a - } - // }); - } - - // return ReturnCompletion(undefined); // step 4.b - return sentinel; - }; SLOT.set(closure, '[[Sentinel]]', sentinel); // for the userland implementation SLOT.set(closure, '[[CloseIfAbrupt]]', closeIfAbrupt); // for the userland implementation diff --git a/test/Iterator.concat.js b/test/Iterator.concat.js index 85ba5d6..984a560 100644 --- a/test/Iterator.concat.js +++ b/test/Iterator.concat.js @@ -278,6 +278,53 @@ module.exports = { s2t.end(); }); + st.test('test262: test/built-ins/Iterator/concat/next-method-returns-non-object', function (s2t) { + var nonObjectIterator = { + next: function () { + return null; + } + }; + + var iterable = {}; + iterable[Symbol.iterator] = function () { + return nonObjectIterator; + }; + + var iterator = concat(iterable); + + s2t['throws'](function () { iterator.next(); }, TypeError); + + s2t.end(); + }); + + st.test('test262: test/built-ins/Iterator/concat/next-method-returns-throwing-done', { skip: !hasPropertyDescriptors }, function (s2t) { + var throwingIterator = { + next: function () { + var result = { done: null, value: 1 }; + Object.defineProperty(result, 'done', { + get: function () { + throw new EvalError(); + } + }); + return result; + }, + 'return': function () { + throw new Error(); + } + }; + + var iterable = {}; + iterable[Symbol.iterator] = function () { + return throwingIterator; + }; + + var iterator = concat(iterable); + + s2t['throws'](function () { iterator.next(); }, EvalError); + + s2t.end(); + }); + st.test('test262: test/built-ins/Iterator/concat/next-method-returns-throwing-value-done', { skip: !hasPropertyDescriptors }, function (s2t) { function ReturnCalledError() {} function ValueGetterError() {} @@ -312,6 +359,53 @@ module.exports = { s2t.end(); }); + st.test('test262: test/built-ins/Iterator/concat/next-method-returns-throwing-value', { skip: !hasPropertyDescriptors }, function (s2t) { + var throwingIterator = { + next: function () { + var result = { value: null, done: false }; + Object.defineProperty(result, 'value', { + get: function () { + throw new EvalError(); + } + }); + return result; + }, + 'return': function () { + throw new Error(); + } + }; + + var iterable = {}; + iterable[Symbol.iterator] = function () { + return throwingIterator; + }; + + var iterator = concat(iterable); + + s2t['throws'](function () { iterator.next(); }, EvalError); + + s2t.end(); + }); + + st.test('test262: test/built-ins/Iterator/concat/next-method-throws', function (s2t) { + var throwingIterator = { + next: function () { + throw new EvalError(); + } + }; + + var iterable = {}; + iterable[Symbol.iterator] = function () { + return throwingIterator; + }; + + var iterator = concat(iterable); + + s2t['throws'](function () { iterator.next(); }, EvalError); + + s2t.end(); + }); + st.test('test262: test/built-ins/Iterator/concat/return-is-not-forwarded-after-exhaustion', function (s2t) { var testIterator1 = { next: function () { @@ -411,6 +505,35 @@ module.exports = { s2t.end(); }); + st.test('test262: test/built-ins/Iterator/concat/throws-typeerror-when-generator-is-running-next', function (s2t) { + var enterCount = 0; + + var iterator; + + var testIterator1 = { + next: function () { + enterCount += 1; + iterator.next(); + return { done: false }; + } + }; + + var iterable = {}; + iterable[Symbol.iterator] = function () { + return testIterator1; + }; + + iterator = concat(iterable); + + s2t.equal(enterCount, 0); + + s2t['throws'](function () { iterator.next(); }, TypeError); + + s2t.equal(enterCount, 1); + + s2t.end(); + }); + st.end(); }); },