Skip to content

Commit

Permalink
[Fix] flatMap: properly handle yielded iterables
Browse files Browse the repository at this point in the history
  • Loading branch information
ljharb committed May 1, 2023
1 parent c4a9948 commit 3a78767
Show file tree
Hide file tree
Showing 4 changed files with 114 additions and 30 deletions.
2 changes: 2 additions & 0 deletions .eslintrc
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
"func-name-matching": 0,
"id-length": 0,
"max-lines-per-function": 0,
"max-statements": 0,
"multiline-comment-style": 0,
"new-cap": [2, {
"capIsNewExceptions": [
Expand Down Expand Up @@ -39,6 +40,7 @@
"OrdinaryHasInstance",
"OrdinaryObjectCreate",
"PromiseResolve",
"StringToCodePoints",
"ThrowCompletion",
"ToBoolean",
"ToIntegerOrInfinity",
Expand Down
69 changes: 43 additions & 26 deletions Iterator.prototype.flatMap/implementation.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,50 +42,67 @@ module.exports = function flatMap(mapper) {
);
};

var sentinel = {};
var sentinel = { sentinel: true };
var counter = 0; // step 6.a
var innerIterator = sentinel;
var innerAlive = false;
var closure = function () {
// while (true) { // step 6.b
var next = IteratorStep(iterated); // step 6.b.i
if (!next) {
// return void undefined; // step 6.b.ii
return sentinel;
if (innerIterator === sentinel) {
var next = IteratorStep(iterated); // step 6.b.i
if (!next) {
innerAlive = false;
innerIterator = sentinel;
// return void undefined; // step 6.b.ii
return sentinel;
}
var value = IteratorValue(next); // step 6.b.iii
}
var value = IteratorValue(next); // step 6.b.iii
var mapped;
var innerIterator;

try {
try {
mapped = Call(mapper, void undefined, [value, counter]); // step 6.b.iv
// yield mapped // step 6.b.vi
innerIterator = GetIteratorFlattenable(mapped); // step 6.b.vi
} catch (e) {
closeIfAbrupt(ThrowCompletion(e)); // steps 6.b.v, 6.b.vii
if (innerIterator === sentinel) {
innerAlive = true; // step 6.b.viii
try {
var mapped = Call(mapper, void undefined, [value, counter]); // step 6.b.iv
// yield mapped // step 6.b.vi
innerIterator = GetIteratorFlattenable(mapped); // step 6.b.vi
} catch (e) {
innerAlive = false;
innerIterator = sentinel;
closeIfAbrupt(ThrowCompletion(e)); // steps 6.b.v, 6.b.vii
}
}
var innerAlive = true; // step 6.b.viii
while (innerAlive) { // step 6.b.ix
// while (innerAlive) { // step 6.b.ix
if (innerAlive) {
var innerNext;
try {
var innerNext = IteratorStep(innerIterator); // step 6.b.ix.1
innerNext = IteratorStep(innerIterator); // step 6.b.ix.1
} catch (e) {
innerIterator = sentinel;
closeIfAbrupt(ThrowCompletion(e)); // step 6.b.ix.2
}
if (!innerNext) {
innerAlive = false; // step 6.b.ix.3.a
} else { // step 6.b.ix.4
var innerValue;
try {
innerValue = IteratorValue(innerNext); // step 6.b.ix.4.a
} catch (e) {
closeIfAbrupt(ThrowCompletion(e)); // step 6.b.ix.4.b
}
return innerValue; // step 6.b.ix.4.c
innerIterator = sentinel;
return closure();
}
// step 6.b.ix.4
var innerValue;
try {
innerValue = IteratorValue(innerNext); // step 6.b.ix.4.a
} catch (e) {
innerAlive = false;
innerIterator = sentinel;
closeIfAbrupt(ThrowCompletion(e)); // step 6.b.ix.4.b
}
return innerValue; // step 6.b.ix.4.c
}
} finally {
counter += 1; // step 6.b.x
}
// }
return void undefined;
// return void undefined;
return sentinel;
};
SLOT.set(closure, '[[Sentinel]]', sentinel); // for the userland implementation
SLOT.set(closure, '[[CloseIfAbrupt]]', closeIfAbrupt); // for the userland implementation
Expand Down
17 changes: 13 additions & 4 deletions aos/GetIteratorFlattenable.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,16 @@ var GetIntrinsic = require('get-intrinsic');

var $TypeError = GetIntrinsic('%TypeError%');

var AdvanceStringIndex = require('es-abstract/2022/AdvanceStringIndex');
var Call = require('es-abstract/2022/Call');
var GetIterator = require('es-abstract/2022/GetIterator');
var GetIteratorDirect = require('./GetIteratorDirect');
var GetV = require('es-abstract/2022/GetV');
var IsArray = require('es-abstract/2022/IsArray');
var IsCallable = require('es-abstract/2022/IsCallable');
var Type = require('es-abstract/2022/Type');

var getIteratorMethod = require('es-abstract/helpers/getIteratorMethod');

module.exports = function GetIteratorFlattenable(obj) {
if (Type(obj) !== 'Object') {
throw new $TypeError('obj must be an Object'); // step 1
Expand All @@ -18,9 +22,14 @@ module.exports = function GetIteratorFlattenable(obj) {
var method = void undefined; // step 2

// method = Get(obj, Symbol.iterator); // step 5.a
method = function () {
return GetIterator(obj);
};
method = getIteratorMethod(
{
AdvanceStringIndex: AdvanceStringIndex,
GetMethod: GetV,
IsArray: IsArray
},
obj
);

var iterator;
if (!IsCallable(method)) { // step 3
Expand Down
56 changes: 56 additions & 0 deletions test/Iterator.prototype.flatMap.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ var forEach = require('for-each');
var debug = require('object-inspect');
var v = require('es-value-fixtures');
var hasSymbols = require('has-symbols/shams')();
var StringToCodePoints = require('es-abstract/2022/StringToCodePoints');

var index = require('../Iterator.prototype.flatMap');
var impl = require('../Iterator.prototype.flatMap/implementation');
Expand Down Expand Up @@ -57,12 +58,67 @@ module.exports = {
'non-iterable return value throws'
);

forEach(v.strings, function (string) {
st['throws'](
function () { flatMap(iterator(), function () { return string; }).next(); },
TypeError,
'non-object return value throws even if iterable (' + debug(string) + ')'
);

testIterator(
flatMap(iterator(), function () { return Object(string); }),
[].concat(StringToCodePoints(string), StringToCodePoints(string), StringToCodePoints(string)),
st,
'boxed string (' + debug(string) + ')'
);
});

testIterator(flatMap(iterator(), function (x) { return [x][Symbol.iterator](); }), [1, 2, 3], st, 'identity mapper in array iterator');
testIterator(flatMap(iterator(), function (x) { return [2 * x][Symbol.iterator](); }), [2, 4, 6], st, 'doubler mapper in array iterator');

testIterator(flatMap(iterator(), function (x) { return [[x]][Symbol.iterator](); }), [[1], [2], [3]], st, 'identity mapper in nested array iterator');
testIterator(flatMap(iterator(), function (x) { return [[2 * x]][Symbol.iterator](); }), [[2], [4], [6]], st, 'doubler mapper in nested array iterator');

testIterator(flatMap([0, 1, 2, 3][Symbol.iterator](), function (value) {
var result = [];
for (var i = 0; i < value; ++i) {
result.push(value);
}
return result;
}), [1, 2, 2, 3, 3, 3], st, 'test262: test/built-ins/Iterator/prototype/flatMap/flattens-iteratable');

testIterator(flatMap([0, 1, 2, 3][Symbol.iterator](), function (value) {
var i = 0;
return {
next: function () {
if (i < value) {
i += 1;
return {
value: value,
done: false
};
}
return {
value: undefined,
done: true
};

}
};
}), [1, 2, 2, 3, 3, 3], st, 'test262: test/built-ins/Iterator/prototype/flatMap/flattens-iterator');

testIterator(flatMap([0][Symbol.iterator](), function () {
var n = [0, 1, 2][Symbol.iterator]();

var ret = {
next: function next() {
return n.next();
}
};
ret[Symbol.iterator] = 0;
return ret;
}), [0, 1, 2], st, 'test262: test/built-ins/Iterator/prototype/flatMap/iterable-to-iterator-fallback');

st.end();
});
},
Expand Down

0 comments on commit 3a78767

Please sign in to comment.