var BUGNUMBER = 1180306; var summary = 'Array.from should close iterator on error'; print(BUGNUMBER + ": " + summary); function test(ctor, { mapVal=undefined, nextVal=undefined, nextThrowVal=undefined, modifier=undefined, exceptionVal=undefined, exceptionType=undefined, closed=true }) { let iterable = { closed: false, [Symbol.iterator]() { let iterator = { first: true, next() { if (this.first) { this.first = false; if (nextThrowVal) throw nextThrowVal; return nextVal; } return { value: undefined, done: true }; }, return() { iterable.closed = true; return {}; } }; if (modifier) modifier(iterator, iterable); return iterator; } }; if (exceptionVal) { let caught = false; try { ctor.from(iterable, mapVal); } catch (e) { assertEq(e, exceptionVal); caught = true; } assertEq(caught, true); } else if (exceptionType) { assertThrowsInstanceOf(() => ctor.from(iterable, mapVal), exceptionType); } else { ctor.from(iterable, mapVal); } assertEq(iterable.closed, closed); } // == Error cases with close == // ES 2017 draft 22.1.2.1 step 5.e.i.1. // Cannot test. // ES 2017 draft 22.1.2.1 step 5.e.vi.2. test(Array, { mapVal: () => { throw "map throws"; }, nextVal: { value: 1, done: false }, exceptionVal: "map throws", closed: true, }); // ES 2017 draft 22.1.2.1 step 5.e.ix. class MyArray extends Array { constructor() { return new Proxy({}, { defineProperty() { throw "defineProperty throws"; } }); } } test(MyArray, { nextVal: { value: 1, done: false }, exceptionVal: "defineProperty throws", closed: true, }); // ES 2017 draft 7.4.6 step 3. // if GetMethod fails, the thrown value should be used. test(MyArray, { nextVal: { value: 1, done: false }, modifier: (iterator, iterable) => { Object.defineProperty(iterator, "return", { get: function() { iterable.closed = true; throw "return getter throws"; } }); }, exceptionVal: "return getter throws", closed: true, }); test(MyArray, { nextVal: { value: 1, done: false }, modifier: (iterator, iterable) => { Object.defineProperty(iterator, "return", { get: function() { iterable.closed = true; return "non object"; } }); }, exceptionType: TypeError, closed: true, }); test(MyArray, { nextVal: { value: 1, done: false }, modifier: (iterator, iterable) => { Object.defineProperty(iterator, "return", { get: function() { iterable.closed = true; // Non callable. return {}; } }); }, exceptionType: TypeError, closed: true, }); // ES 2017 draft 7.4.6 steps 6. // if return method throws, the thrown value should be ignored. test(MyArray, { nextVal: { value: 1, done: false }, modifier: (iterator, iterable) => { iterator.return = function() { iterable.closed = true; throw "return throws"; }; }, exceptionVal: "defineProperty throws", closed: true, }); test(MyArray, { nextVal: { value: 1, done: false }, modifier: (iterator, iterable) => { iterator.return = function() { iterable.closed = true; return "non object"; }; }, exceptionVal: "defineProperty throws", closed: true, }); // == Error cases without close == // ES 2017 draft 22.1.2.1 step 5.e.iii. test(Array, { nextThrowVal: "next throws", exceptionVal: "next throws", closed: false, }); test(Array, { nextVal: { value: {}, get done() { throw "done getter throws"; } }, exceptionVal: "done getter throws", closed: false, }); // ES 2017 draft 22.1.2.1 step 5.e.v. test(Array, { nextVal: { get value() { throw "value getter throws"; }, done: false }, exceptionVal: "value getter throws", closed: false, }); // == Successful cases == test(Array, { nextVal: { value: 1, done: false }, closed: false, }); if (typeof reportCompare === 'function') reportCompare(true, true);