load(libdir + 'asserts.js'); load(libdir + 'iteration.js'); load(libdir + 'eqArrayHelper.js'); // throw on non-iterables assertThrowsInstanceOf(() => { [a, b, c] = {0: 0, 1: 1, 2: 2} }, TypeError); var nextcalls = 0, donecalls = 0, valuecalls = 0; var doneafter = 0; var iterable = {}; iterable[Symbol.iterator] = function () { return { next: function () { assertEq(arguments.length, 0, 'iterator.next() should be called with no arguments'); nextcalls++; return { get done() { donecalls++; return --doneafter < 0; }, get value() { valuecalls++; return valuecalls; } }; } } }; function assertIterable(expectCalls, fn, expectResult) { [nextcalls, donecalls, valuecalls, doneafter] = [0,0,0, expectCalls[3]]; assertEqArray(fn(iterable), expectResult); assertEq(nextcalls, expectCalls[0], 'calls to iterator.next()'); assertEq(donecalls, expectCalls[1], 'getting iterator.next().done'); assertEq(valuecalls, expectCalls[2], 'getting iterator.next().value'); } assertIterable([1,1,1,1], it => { var [a] = it; return [a]; }, [1]); assertIterable([2,2,1,1], it => { var [a,b,c] = it; return [a,b,c]; }, [1,undefined,undefined]); assertIterable([2,2,1,1], it => { var [a,b,...rest] = it; return [a,b,...rest]; }, [1,undefined]); assertIterable([5,5,4,4], it => { var [,,...rest] = it; return rest; }, [3,4]); // the iterator should be exhausted before any error is thrown assertIterable([5,5,4,4], it => { assertThrowsInstanceOf(function () { "use strict"; [...{0: "".x}] = it; }, TypeError); return []; }, []); var arraycalls = 0; var ArrayIterator = Array.prototype[Symbol.iterator]; Array.prototype[Symbol.iterator] = function () { arraycalls++; return ArrayIterator.apply(this, arguments); }; // [...rest] should not call Array#@@iterator for the LHS var [...rest] = iterable; assertEq(arraycalls, 0, 'calls to Array#@@iterator'); // [...[...rest]] should do so, since it creates an implicit array for the // first rest pattern, then destructures that again using @@iterator() for the // second rest pattern. var [...[...rest]] = iterable; assertEq(arraycalls, 1, 'calls to Array#@@iterator'); // loop `fn` a few times, to get it JIT-compiled function loop(fn) { var i = 1e4; while (i--) fn(); } loop(() => { doneafter = 4; var [a] = iterable; return a; }); loop(() => { doneafter = 4; var [a,b,...[...rest]] = iterable; return rest; }); // destructuring assignment should always use iterators and not optimize // to a "group assignment" delete Array.prototype[Symbol.iterator]; assertThrowsInstanceOf(() => { var [a,b] = [1,2]; }, TypeError); Array.prototype[Symbol.iterator] = ArrayIterator; // observe the binding order a = undefined, b = undefined, c = undefined; var obj = {}; obj[Symbol.iterator] = function* () { // normal fields should be initialized right after |.next()| yield 1; assertEq(a, 1); yield 2; yield 3; assertEq(b, 3); yield 4; yield 5; // rest should be initialized after the iterator is exhausted assertEq(c, undefined); }; [a, , b, ...c] = obj; assertEqArray(c, [4,5]); // a throw inside the destructuring of the "catch" value should not enter // the "catch" block assertThrowsValue(function () { try { Array.prototype[Symbol.iterator] = function () { throw 'from iterator'; }; throw [1, 2]; } catch ([x, y]) { throw 'not reached'; } }, 'from iterator'); Array.prototype[Symbol.iterator] = ArrayIterator;