Skip to content

Commit

Permalink
[Fix] close underlying iterator when helper is closed
Browse files Browse the repository at this point in the history
  • Loading branch information
ljharb committed Mar 21, 2023
1 parent 7d0ba59 commit f5372c7
Show file tree
Hide file tree
Showing 10 changed files with 263 additions and 14 deletions.
1 change: 1 addition & 0 deletions .eslintrc
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
"GetIteratorDirect",
"GetIteratorFlattenable",
"GetMethod",
"IsArray",
"IsCallable",
"IteratorClose",
"IteratorComplete",
Expand Down
6 changes: 5 additions & 1 deletion Iterator.prototype.drop/implementation.js
Original file line number Diff line number Diff line change
Expand Up @@ -79,5 +79,9 @@ module.exports = function drop(limit) {
SLOT.set(closure, '[[Sentinel]]', sentinel); // for the userland implementation
SLOT.set(closure, '[[CloseIfAbrupt]]', closeIfAbrupt); // for the userland implementation

return CreateIteratorFromClosure(closure, 'Iterator Helper', iterHelperProto); // step 4
var result = CreateIteratorFromClosure(closure, 'Iterator Helper', iterHelperProto, ['[[UnderlyingIterator]]']); // step 4

SLOT.set(result, '[[UnderlyingIterator]]', iterated); // step 5

return result; // step 6
};
6 changes: 5 additions & 1 deletion Iterator.prototype.filter/implementation.js
Original file line number Diff line number Diff line change
Expand Up @@ -66,5 +66,9 @@ module.exports = function filter(predicate) {
SLOT.set(closure, '[[Sentinel]]', sentinel); // for the userland implementation
SLOT.set(closure, '[[CloseIfAbrupt]]', closeIfAbrupt); // for the userland implementation

return CreateIteratorFromClosure(closure, 'Iterator Helper', iterHelperProto); // step 4
var result = CreateIteratorFromClosure(closure, 'Iterator Helper', iterHelperProto, ['[[UnderlyingIterator]]']); // step 4

SLOT.set(result, '[[UnderlyingIterator]]', iterated); // step 5

return result; // step 6
};
6 changes: 5 additions & 1 deletion Iterator.prototype.flatMap/implementation.js
Original file line number Diff line number Diff line change
Expand Up @@ -84,5 +84,9 @@ module.exports = function flatMap(mapper) {
SLOT.set(closure, '[[Sentinel]]', sentinel); // for the userland implementation
SLOT.set(closure, '[[CloseIfAbrupt]]', closeIfAbrupt); // for the userland implementation

return CreateIteratorFromClosure(closure, 'Iterator Helper', iterHelperProto); // step 4
var result = CreateIteratorFromClosure(closure, 'Iterator Helper', iterHelperProto, ['[[UnderlyingIterator]]']); // step 4

SLOT.set(result, '[[UnderlyingIterator]]', iterated); // step 5

return result; // step 6
};
6 changes: 5 additions & 1 deletion Iterator.prototype.map/implementation.js
Original file line number Diff line number Diff line change
Expand Up @@ -63,5 +63,9 @@ module.exports = function map(mapper) {
SLOT.set(closure, '[[Sentinel]]', sentinel); // for the userland implementation
SLOT.set(closure, '[[CloseIfAbrupt]]', closeIfAbrupt); // for the userland implementation

return CreateIteratorFromClosure(closure, 'Iterator Helper', iterHelperProto); // step 4
var result = CreateIteratorFromClosure(closure, 'Iterator Helper', iterHelperProto, ['[[UnderlyingIterator]]']); // step 4

SLOT.set(result, '[[UnderlyingIterator]]', iterated); // step 5

return result; // step 6
};
6 changes: 5 additions & 1 deletion Iterator.prototype.take/implementation.js
Original file line number Diff line number Diff line change
Expand Up @@ -71,5 +71,9 @@ module.exports = function take(limit) {
SLOT.set(closure, '[[Sentinel]]', sentinel); // for the userland implementation
SLOT.set(closure, '[[CloseIfAbrupt]]', closeIfAbrupt); // for the userland implementation

return CreateIteratorFromClosure(closure, 'Iterator Helper', iterHelperProto); // step 4
var result = CreateIteratorFromClosure(closure, 'Iterator Helper', iterHelperProto, ['[[UnderlyingIterator]]']); // step 4

SLOT.set(result, '[[UnderlyingIterator]]', iterated); // step 5

return result; // step 6
};
24 changes: 20 additions & 4 deletions IteratorHelperPrototype/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,15 @@

var setToStringTag = require('es-set-tostringtag');
var hasProto = require('has-proto')();
var iterProto = require('iterator.prototype');
var SLOT = require('internal-slot');

var CompletionRecord = require('es-abstract/2022/CompletionRecord');
var CreateIterResultObject = require('es-abstract/2015/CreateIterResultObject');
var GeneratorResume = require('../aos/GeneratorResume');
var GeneratorResumeAbrupt = require('../aos/GeneratorResumeAbrupt');

var iterProto = require('iterator.prototype');
var IteratorClose = require('../aos/IteratorClose');
var NormalCompletion = require('es-abstract/2022/NormalCompletion');

var implementation;
if (hasProto) {
Expand All @@ -17,8 +20,21 @@ if (hasProto) {
return GeneratorResume(this, void undefined, 'Iterator Helper');
},
'return': function () {
var C = new CompletionRecord('return', void undefined); // step 1
return GeneratorResumeAbrupt(this, C, 'Iterator Helper');
var O = this; // step 1

SLOT.assert(O, '[[UnderlyingIterator]]'); // step 2

SLOT.assert(O, '[[GeneratorState]]'); // step 3

if (SLOT.get(O, '[[GeneratorState]]') === 'suspendedStart') { // step 4
SLOT.set(O, '[[GeneratorState]]', 'completed'); // step 4.a
IteratorClose(SLOT.get(O, '[[UnderlyingIterator]]'), NormalCompletion('unused')); // step 4.c
return CreateIterResultObject(void undefined, true); // step 4.d
}

var C = new CompletionRecord('return', void undefined); // step 5

return GeneratorResumeAbrupt(O, C, 'Iterator Helper'); // step 6
}
};
setToStringTag(implementation, 'Iterator Helper');
Expand Down
30 changes: 25 additions & 5 deletions aos/CreateIteratorFromClosure.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,22 +4,42 @@ var GetIntrinsic = require('get-intrinsic');

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

var GeneratorStart = require('./GeneratorStart');
var IsArray = require('es-abstract/2022/IsArray');
var IsCallable = require('es-abstract/2022/IsCallable');
var OrdinaryObjectCreate = require('es-abstract/2022/OrdinaryObjectCreate');
var Type = require('es-abstract/2022/Type');

var GeneratorStart = require('./GeneratorStart');
var every = require('es-abstract/helpers/every');

var callBound = require('call-bind/callBound');
var SLOT = require('internal-slot');

module.exports = function CreateIteratorFromClosure(closure, brand, proto) {
var $concat = callBound('Array.prototype.concat');

var isString = function isString(slot) {
return Type(slot) === 'String';
};

module.exports = function CreateIteratorFromClosure(closure, generatorBrand, proto) {
if (!IsCallable(closure)) {
throw new $TypeError('`closure` must be a function');
}
var generator = OrdinaryObjectCreate(proto, ['[[GeneratorContext]]', '[[GeneratorBrand]]', '[[GeneratorState]]']); // steps 3, 5
SLOT.set(generator, '[[GeneratorBrand]]', brand); // step 4
if (Type(generatorBrand) !== 'String') {
throw new $TypeError('`generatorBrand` must be a string');
}
var extraSlots = arguments.length > 3 ? arguments[3] : [];
if (arguments.length > 3) {
if (!IsArray(extraSlots) || !every(extraSlots, isString)) {
throw new $TypeError('`extraSlots` must be a List of String internal slot names');
}
}
var internalSlotsList = $concat(extraSlots, ['[[GeneratorContext]]', '[[GeneratorBrand]]', '[[GeneratorState]]']); // step 3
var generator = OrdinaryObjectCreate(proto, internalSlotsList); // steps 4, 6
SLOT.set(generator, '[[GeneratorBrand]]', generatorBrand); // step 5

SLOT.assert(closure, '[[Sentinel]]'); // our userland slot
SLOT.set(generator, '[[Sentinel]]'); // our userland slot
SLOT.set(generator, '[[Sentinel]]', SLOT.get(closure, '[[Sentinel]]')); // our userland slot
SLOT.assert(closure, '[[CloseIfAbrupt]]'); // our second userland slot
SLOT.set(generator, '[[CloseIfAbrupt]]', SLOT.get(closure, '[[CloseIfAbrupt]]')); // our second userland slot

Expand Down
122 changes: 122 additions & 0 deletions test/Iterator.prototype.drop.js
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,128 @@ module.exports = {

st.end();
});

t.test('262: test/built-ins/Iterator/prototype/drop/return-is-forwarded', function (st) {
var returnCount = 0;

var makeBadIterator = function makeBadIterator() {
return {
next: function next() {
return {
done: false,
value: 1
};
},
'return': function () {
returnCount += 1;
return {};
}
};
};

var iter1 = drop(makeBadIterator(), 0);
st.equal(returnCount, 0, 'iter1, before return()');
iter1['return']();
st.equal(returnCount, 1, 'iter1, after return()');

var iter2 = drop(makeBadIterator(), 1);
st.equal(returnCount, 1, 'iter2, before return()');
iter2['return']();
st.equal(returnCount, 2, 'iter2, after return()');

// 5 drops (i wish i had pipeline)
var iter3 = drop(
drop(
drop(
drop(
drop(
makeBadIterator(),
1
),
1
),
1
),
1
),
1
);
st.equal(returnCount, 2, 'iter3, before return()');
iter3['return']();
st.equal(returnCount, 3, 'iter3, after return()');

st.end();
});

t.test('262: test/built-ins/Iterator/prototype/drop/return-is-not-forwarded-after-exhaustion', { skip: !hasPropertyDescriptors }, function (st) {
var makeBadIterator = function makeBadIterator() {
return {
next: function next() {
return {
done: true,
value: undefined
};
},
'return': function () {
throw new SyntaxError();
}
};
};

var iter1 = drop(makeBadIterator(), 0);
st['throws'](
function () { iter1['return'](); },
SyntaxError,
'iter1, return() throws'
);
iter1.next();
iter1['return']();

var iter2 = drop(makeBadIterator(), 1);
st['throws'](
function () { iter2['return'](); },
SyntaxError,
'iter2, return() throws'
);
iter2.next();
iter2['return']();

// 5 drops (i wish i had pipeline)
var iter3 = drop(
drop(
drop(
drop(
drop(
makeBadIterator(),
1
),
1
),
1
),
1
),
1
);
st['throws'](
function () { iter3['return'](); },
SyntaxError,
'iter3, return() throws'
);
iter3.next();
iter3['return']();

var iter4 = drop(makeBadIterator(), 10);
st['throws'](
function () { iter4['return'](); },
SyntaxError,
'iter4, return() throws'
);
iter4.next();
iter4['return']();

st.end();
});
},
index: function () {
test('Iterator.prototype.' + fnName + ': index', function (t) {
Expand Down
70 changes: 70 additions & 0 deletions test/Iterator.prototype.filter.js
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,76 @@ module.exports = {

st.end();
});

t.test('262: test/built-ins/Iterator/prototype/drop/return-is-forwarded', function (st) {
var returnCount = 0;

var badIterator = {
next: function next() {
return {
done: false,
value: 1
};
},
'return': function () {
returnCount += 1;
return {};
}
};

var iter1 = filter(badIterator, function () { return false; });
st.equal(returnCount, 0, 'iter1, before return()');
iter1['return']();
st.equal(returnCount, 1, 'iter1, after return()');

st.end();
});

t.test('262: test/built-ins/Iterator/prototype/drop/return-is-not-forwarded-after-exhaustion', { skip: !hasPropertyDescriptors }, function (st) {
var makeBadIterator = function makeBadIterator() {
return {
next: function next() {
return {
done: true,
value: undefined
};
},
'return': function () {
throw new SyntaxError();
}
};
};

var iter1 = filter(makeBadIterator(), function () { return true; });
st['throws'](
function () { iter1['return'](); },
SyntaxError,
'iter1, return() throws'
);
iter1.next();
iter1['return']();

// 3 filters (i wish i had pipeline)
var iter2 = filter(
filter(
filter(
makeBadIterator(),
function () { return true; }
),
function () { return true; }
),
function () { return true; }
);
st['throws'](
function () { iter2['return'](); },
SyntaxError,
'iter2, return() throws'
);
iter2.next();
iter2['return']();

st.end();
});
},
index: function () {
test('Iterator.prototype.' + fnName + ': index', function (t) {
Expand Down

0 comments on commit f5372c7

Please sign in to comment.