From cc8ca7874c66dead0351128cb8e0ea54a7f067fe Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Sat, 13 Jun 2020 22:31:00 -0700 Subject: [PATCH] [Breaking] `ES2018+`: `GetIterator`, `IterableToList`, `IteratorNext`, `IteratorStep`, `IteratorClose`: use iterator records instead --- .gitattributes | 5 ---- 2018/GetIterator.js | 63 ++++++++++++++++++++++++++++++++---------- 2018/IterableToList.js | 6 ++-- 2018/IteratorClose.js | 7 +++-- 2018/IteratorNext.js | 14 +++++++--- 2018/IteratorStep.js | 7 ++--- 2019/GetIterator.js | 59 +++++++++++++++++++++++++++++---------- 2019/IterableToList.js | 6 ++-- 2019/IteratorClose.js | 7 +++-- 2019/IteratorNext.js | 14 +++++++--- 2019/IteratorStep.js | 7 ++--- test/tests.js | 46 ++++++++++++++++++++++++++++-- 12 files changed, 178 insertions(+), 63 deletions(-) diff --git a/.gitattributes b/.gitattributes index 0d00e318..b49b1cac 100644 --- a/.gitattributes +++ b/.gitattributes @@ -273,7 +273,6 @@ 2018/DeletePropertyOrThrow.js spackled linguist-generated=true 2018/FromPropertyDescriptor.js spackled linguist-generated=true 2018/Get.js spackled linguist-generated=true -2018/GetIterator.js spackled linguist-generated=true 2018/GetMethod.js spackled linguist-generated=true 2018/GetOwnPropertyKeys.js spackled linguist-generated=true 2018/GetPrototypeFromConstructor.js spackled linguist-generated=true @@ -296,11 +295,7 @@ 2018/IsPromise.js spackled linguist-generated=true 2018/IsPropertyKey.js spackled linguist-generated=true 2018/IsRegExp.js spackled linguist-generated=true -2018/IterableToList.js spackled linguist-generated=true -2018/IteratorClose.js spackled linguist-generated=true 2018/IteratorComplete.js spackled linguist-generated=true -2018/IteratorNext.js spackled linguist-generated=true -2018/IteratorStep.js spackled linguist-generated=true 2018/IteratorValue.js spackled linguist-generated=true 2018/MakeDate.js spackled linguist-generated=true 2018/MakeDay.js spackled linguist-generated=true diff --git a/2018/GetIterator.js b/2018/GetIterator.js index 7beddacc..269ade4b 100644 --- a/2018/GetIterator.js +++ b/2018/GetIterator.js @@ -2,34 +2,69 @@ var GetIntrinsic = require('../GetIntrinsic'); +var $asyncIterator = GetIntrinsic('%Symbol.asyncIterator%', true); +var $SyntaxError = GetIntrinsic('%SyntaxError%'); var $TypeError = GetIntrinsic('%TypeError%'); var getIteratorMethod = require('../helpers/getIteratorMethod'); var AdvanceStringIndex = require('./AdvanceStringIndex'); var Call = require('./Call'); +var CreateAsyncFromSyncIterator = require('./CreateAsyncFromSyncIterator'); var GetMethod = require('./GetMethod'); +var GetV = require('./GetV'); var IsArray = require('./IsArray'); var Type = require('./Type'); // https://ecma-international.org/ecma-262/6.0/#sec-getiterator -module.exports = function GetIterator(obj, method) { - var actualMethod = method; - if (arguments.length < 2) { - actualMethod = getIteratorMethod( - { - AdvanceStringIndex: AdvanceStringIndex, - GetMethod: GetMethod, - IsArray: IsArray, - Type: Type - }, - obj - ); +module.exports = function GetIterator(obj) { + var hint = arguments.length > 1 ? arguments[1] : 'sync'; + if (hint !== 'sync' && hint !== 'async') { + throw new $TypeError('Assertion failed: `hint` must be either ~sync~ or ~async~'); } - var iterator = Call(actualMethod, obj); + var method; + if (arguments.length < 3) { + if (hint === 'async') { + if (!$asyncIterator) { + method = GetMethod(obj, $asyncIterator); + } + if (typeof method !== 'undefined') { + var syncMethod = getIteratorMethod( + { + AdvanceStringIndex: AdvanceStringIndex, + GetMethod: GetMethod, + IsArray: IsArray, + Type: Type + }, + obj + ); + var syncIteratorRecord = GetIterator(obj, 'sync', syncMethod); + return CreateAsyncFromSyncIterator(syncIteratorRecord); + } + } else { + method = getIteratorMethod( + { + AdvanceStringIndex: AdvanceStringIndex, + GetMethod: GetMethod, + IsArray: IsArray, + Type: Type + }, + obj + ); + } + } + var iterator = Call(method, obj); if (Type(iterator) !== 'Object') { throw new $TypeError('iterator must return an object'); } - return iterator; + var nextMethod = GetV(iterator, 'next'); + + var iteratorRecord = { + '[[Iterator]]': iterator, + '[[NextMethod]]': nextMethod, + '[[Done]]': false + }; + + return iteratorRecord; }; diff --git a/2018/IterableToList.js b/2018/IterableToList.js index 0b8cdcfe..f7adf3ce 100644 --- a/2018/IterableToList.js +++ b/2018/IterableToList.js @@ -7,14 +7,14 @@ var GetIterator = require('./GetIterator'); var IteratorStep = require('./IteratorStep'); var IteratorValue = require('./IteratorValue'); -// https://www.ecma-international.org/ecma-262/8.0/#sec-iterabletolist +// https://www.ecma-international.org/ecma-262/9.0/#sec-iterabletolist module.exports = function IterableToList(items, method) { - var iterator = GetIterator(items, method); + var iteratorRecord = GetIterator(items, 'sync', method); var values = []; var next = true; while (next) { - next = IteratorStep(iterator); + next = IteratorStep(iteratorRecord); if (next) { var nextValue = IteratorValue(next); $arrayPush(values, nextValue); diff --git a/2018/IteratorClose.js b/2018/IteratorClose.js index 35c8c2be..33dca4d2 100644 --- a/2018/IteratorClose.js +++ b/2018/IteratorClose.js @@ -9,10 +9,10 @@ var GetMethod = require('./GetMethod'); var IsCallable = require('./IsCallable'); var Type = require('./Type'); -// https://ecma-international.org/ecma-262/6.0/#sec-iteratorclose +// https://ecma-international.org/ecma-262/9.0/#sec-iteratorclose -module.exports = function IteratorClose(iterator, completion) { - if (Type(iterator) !== 'Object') { +module.exports = function IteratorClose(iteratorRecord, completion) { + if (Type(iteratorRecord['[[Iterator]]']) !== 'Object') { throw new $TypeError('Assertion failed: Type(iterator) is not Object'); } if (!IsCallable(completion)) { @@ -20,6 +20,7 @@ module.exports = function IteratorClose(iterator, completion) { } var completionThunk = completion; + var iterator = iteratorRecord['[[Iterator]]']; var iteratorReturn = GetMethod(iterator, 'return'); if (typeof iteratorReturn === 'undefined') { diff --git a/2018/IteratorNext.js b/2018/IteratorNext.js index 7e599156..f75acfbb 100644 --- a/2018/IteratorNext.js +++ b/2018/IteratorNext.js @@ -4,13 +4,19 @@ var GetIntrinsic = require('../GetIntrinsic'); var $TypeError = GetIntrinsic('%TypeError%'); -var Invoke = require('./Invoke'); +var Call = require('./Call'); var Type = require('./Type'); -// https://ecma-international.org/ecma-262/6.0/#sec-iteratornext +// https://ecma-international.org/ecma-262/9.0/#sec-iteratornext -module.exports = function IteratorNext(iterator, value) { - var result = Invoke(iterator, 'next', arguments.length < 2 ? [] : [value]); +module.exports = function IteratorNext(iteratorRecord) { + var value; + if (arguments.length > 1) { + value = arguments[1]; + } + var result = arguments.length < 2 + ? Call(iteratorRecord['[[NextMethod]]'], iteratorRecord['[[Iterator]]'], []) + : Call(iteratorRecord['[[NextMethod]]'], iteratorRecord['[[Iterator]]'], [value]); if (Type(result) !== 'Object') { throw new $TypeError('iterator next must return an object'); } diff --git a/2018/IteratorStep.js b/2018/IteratorStep.js index 41b9d1b2..156003ec 100644 --- a/2018/IteratorStep.js +++ b/2018/IteratorStep.js @@ -3,11 +3,10 @@ var IteratorComplete = require('./IteratorComplete'); var IteratorNext = require('./IteratorNext'); -// https://ecma-international.org/ecma-262/6.0/#sec-iteratorstep +// https://ecma-international.org/ecma-262/9.0/#sec-iteratorstep -module.exports = function IteratorStep(iterator) { - var result = IteratorNext(iterator); +module.exports = function IteratorStep(iteratorRecord) { + var result = IteratorNext(iteratorRecord); var done = IteratorComplete(result); return done === true ? false : result; }; - diff --git a/2019/GetIterator.js b/2019/GetIterator.js index 7beddacc..ca1732ae 100644 --- a/2019/GetIterator.js +++ b/2019/GetIterator.js @@ -7,29 +7,60 @@ var $TypeError = GetIntrinsic('%TypeError%'); var getIteratorMethod = require('../helpers/getIteratorMethod'); var AdvanceStringIndex = require('./AdvanceStringIndex'); var Call = require('./Call'); +var CreateAsyncFromSyncIterator = require('./CreateAsyncFromSyncIterator'); var GetMethod = require('./GetMethod'); +var GetV = require('./GetV'); var IsArray = require('./IsArray'); var Type = require('./Type'); // https://ecma-international.org/ecma-262/6.0/#sec-getiterator -module.exports = function GetIterator(obj, method) { - var actualMethod = method; - if (arguments.length < 2) { - actualMethod = getIteratorMethod( - { - AdvanceStringIndex: AdvanceStringIndex, - GetMethod: GetMethod, - IsArray: IsArray, - Type: Type - }, - obj - ); +module.exports = function GetIterator(obj) { + var hint = arguments.length > 1 ? arguments[1] : 'sync'; + if (hint !== 'sync' && hint !== 'async') { + throw new $TypeError('Assertion failed: `hint` must be either ~sync~ or ~async~'); } - var iterator = Call(actualMethod, obj); + var method; + if (arguments.length < 3) { + if (hint === 'async') { + method = GetMethod(obj, Symbol.asyncIterator); + if (typeof method !== 'undefined') { + var syncMethod = getIteratorMethod( + { + AdvanceStringIndex: AdvanceStringIndex, + GetMethod: GetMethod, + IsArray: IsArray, + Type: Type + }, + obj + ); + var syncIteratorRecord = GetIterator(obj, 'sync', syncMethod); + return CreateAsyncFromSyncIterator(syncIteratorRecord); + } + } else { + method = getIteratorMethod( + { + AdvanceStringIndex: AdvanceStringIndex, + GetMethod: GetMethod, + IsArray: IsArray, + Type: Type + }, + obj + ); + } + } + var iterator = Call(method, obj); if (Type(iterator) !== 'Object') { throw new $TypeError('iterator must return an object'); } - return iterator; + var nextMethod = GetV(iterator, 'next'); + + var iteratorRecord = { + '[[Iterator]]': iterator, + '[[NextMethod]]': nextMethod, + '[[Done]]': false + }; + + return iteratorRecord; }; diff --git a/2019/IterableToList.js b/2019/IterableToList.js index 0b8cdcfe..f7adf3ce 100644 --- a/2019/IterableToList.js +++ b/2019/IterableToList.js @@ -7,14 +7,14 @@ var GetIterator = require('./GetIterator'); var IteratorStep = require('./IteratorStep'); var IteratorValue = require('./IteratorValue'); -// https://www.ecma-international.org/ecma-262/8.0/#sec-iterabletolist +// https://www.ecma-international.org/ecma-262/9.0/#sec-iterabletolist module.exports = function IterableToList(items, method) { - var iterator = GetIterator(items, method); + var iteratorRecord = GetIterator(items, 'sync', method); var values = []; var next = true; while (next) { - next = IteratorStep(iterator); + next = IteratorStep(iteratorRecord); if (next) { var nextValue = IteratorValue(next); $arrayPush(values, nextValue); diff --git a/2019/IteratorClose.js b/2019/IteratorClose.js index 35c8c2be..33dca4d2 100644 --- a/2019/IteratorClose.js +++ b/2019/IteratorClose.js @@ -9,10 +9,10 @@ var GetMethod = require('./GetMethod'); var IsCallable = require('./IsCallable'); var Type = require('./Type'); -// https://ecma-international.org/ecma-262/6.0/#sec-iteratorclose +// https://ecma-international.org/ecma-262/9.0/#sec-iteratorclose -module.exports = function IteratorClose(iterator, completion) { - if (Type(iterator) !== 'Object') { +module.exports = function IteratorClose(iteratorRecord, completion) { + if (Type(iteratorRecord['[[Iterator]]']) !== 'Object') { throw new $TypeError('Assertion failed: Type(iterator) is not Object'); } if (!IsCallable(completion)) { @@ -20,6 +20,7 @@ module.exports = function IteratorClose(iterator, completion) { } var completionThunk = completion; + var iterator = iteratorRecord['[[Iterator]]']; var iteratorReturn = GetMethod(iterator, 'return'); if (typeof iteratorReturn === 'undefined') { diff --git a/2019/IteratorNext.js b/2019/IteratorNext.js index 7e599156..f75acfbb 100644 --- a/2019/IteratorNext.js +++ b/2019/IteratorNext.js @@ -4,13 +4,19 @@ var GetIntrinsic = require('../GetIntrinsic'); var $TypeError = GetIntrinsic('%TypeError%'); -var Invoke = require('./Invoke'); +var Call = require('./Call'); var Type = require('./Type'); -// https://ecma-international.org/ecma-262/6.0/#sec-iteratornext +// https://ecma-international.org/ecma-262/9.0/#sec-iteratornext -module.exports = function IteratorNext(iterator, value) { - var result = Invoke(iterator, 'next', arguments.length < 2 ? [] : [value]); +module.exports = function IteratorNext(iteratorRecord) { + var value; + if (arguments.length > 1) { + value = arguments[1]; + } + var result = arguments.length < 2 + ? Call(iteratorRecord['[[NextMethod]]'], iteratorRecord['[[Iterator]]'], []) + : Call(iteratorRecord['[[NextMethod]]'], iteratorRecord['[[Iterator]]'], [value]); if (Type(result) !== 'Object') { throw new $TypeError('iterator next must return an object'); } diff --git a/2019/IteratorStep.js b/2019/IteratorStep.js index 41b9d1b2..156003ec 100644 --- a/2019/IteratorStep.js +++ b/2019/IteratorStep.js @@ -3,11 +3,10 @@ var IteratorComplete = require('./IteratorComplete'); var IteratorNext = require('./IteratorNext'); -// https://ecma-international.org/ecma-262/6.0/#sec-iteratorstep +// https://ecma-international.org/ecma-262/9.0/#sec-iteratorstep -module.exports = function IteratorStep(iterator) { - var result = IteratorNext(iterator); +module.exports = function IteratorStep(iteratorRecord) { + var result = IteratorNext(iteratorRecord); var done = IteratorComplete(result); return done === true ? false : result; }; - diff --git a/test/tests.js b/test/tests.js index 55ba6232..c1432017 100644 --- a/test/tests.js +++ b/test/tests.js @@ -54,7 +54,14 @@ var getArraySubclassWithSpeciesConstructor = function getArraySubclass(speciesCo return Bar; }; -var testIterator = function (t, iterator, expected) { +var testIterator = function (t, iteratorOrIteratorRecord, expected) { + var iterator; + if ('[[Iterator]]' in iteratorOrIteratorRecord) { + iterator = iteratorOrIteratorRecord['[[Iterator]]']; + } else { + iterator = iteratorOrIteratorRecord; + } + var resultCount = 0; var result; while (result = iterator.next(), !result.done) { // eslint-disable-line no-sequences @@ -1216,7 +1223,7 @@ var es2015 = function ES2015(ES, ops, expectedMissing, skips) { t.end(); }); - test('GetIterator', function (t) { + test('GetIterator', { skip: skips && skips.GetIterator }, function (t) { var arr = [1, 2]; testIterator(t, ES.GetIterator(arr), arr); @@ -3813,10 +3820,45 @@ var es2017 = function ES2017(ES, ops, expectedMissing, skips) { var es2018 = function ES2018(ES, ops, expectedMissing, skips) { es2017(ES, ops, expectedMissing, assign({}, skips, { EnumerableOwnProperties: true, + GetIterator: true, GetSubstitution: true, IsPropertyDescriptor: true })); + test('GetIterator', function (t) { + t['throws']( + function () { ES.GetIterator({}, null); }, + /^TypeError: Assertion failed: `hint` must be either ~sync~ or ~async~$/, + '`hint` must be either ~sync~ or ~async~' + ); + + var arr = [1, 2]; + testIterator(t, ES.GetIterator(arr), arr); + + testIterator(t, ES.GetIterator('abc'), 'abc'.split('')); + testIterator(t, ES.GetIterator('a' + wholePoo + 'c'), ['a', wholePoo, 'c']); + + t.test('Symbol.iterator', { skip: !v.hasSymbols }, function (st) { + var o = {}; + o[Symbol.iterator] = function () { + var i = 0; + var chars = ['_', 'a', 'b']; + return { + next: function () { + i += 1; + return [i, chars[i]]; + } + }; + }; + + testIterator(st, ES.GetIterator(o), [[1, 'a'], [2, 'b']]); + + st.end(); + }); + + t.end(); + }); + test('thisSymbolValue', function (t) { forEach(v.nonSymbolPrimitives.concat(v.objects), function (nonSymbol) { t['throws'](