v8/test/mjsunit/es6/iteration-semantics.js
Michael Achenbach 163b5d705e Revert "[esnext] load iterator.next only once at beginning of iteration"
This reverts commit bf4cc9ee15.

Reason for revert: Breaks windows with msvc and linux with gcc
https://build.chromium.org/p/client.v8/builders/V8%20Win64%20-%20msvc/builds/841
https://build.chromium.org/p/client.v8/builders/V8%20Linux%20gcc%204.8/builds/17265

Original change's description:
> [esnext] load `iterator.next` only once at beginning of iteration
> 
> https://github.com/tc39/ecma262/pull/988 gained concensus during the
> september 2017 TC39 meetings. This moves the load of the "next" method
> to the very beginning of the iteration protocol, rather than during
> each iteration step.
> 
> This impacts:
> 
> - yield*
> - for-of loops
> - spread arguments
> - array spreads
> 
> In the v8 implementation, this also affects async iteration versions of
> these things (the sole exception being the Async-From-Sync iterator,
> which requires a few more changes to work with this, likely done in a
> followup patch).
> 
> This change introduces a new AST node, ResolvedProperty, which can be used
> as a callee by Call nodes to produce the same bytecode as Property calls,
> without observably re-loading the property. This is used in several
> AST-desugarings involving the iteration protocol.
> 
> BUG=v8:6861, v8:5699
> R=​rmcilroy@chromium.org, neis@chromium.org, adamk@chromium.org
> 
> Cq-Include-Trybots: luci.v8.try:v8_linux_noi18n_rel_ng
> Change-Id: Ib81106a0182687fc5efea0bc32302ad06376773b
> Reviewed-on: https://chromium-review.googlesource.com/687997
> Commit-Queue: Caitlin Potter <caitp@igalia.com>
> Reviewed-by: Ross McIlroy <rmcilroy@chromium.org>
> Reviewed-by: Adam Klein <adamk@chromium.org>
> Reviewed-by: Georg Neis <neis@chromium.org>
> Cr-Commit-Position: refs/heads/master@{#50452}

TBR=rmcilroy@chromium.org,adamk@chromium.org,neis@chromium.org,caitp@igalia.com,caitp@chromium.org

Change-Id: I1797c0d596dfd6850d6f0f505f591a7a990dd1f1
No-Presubmit: true
No-Tree-Checks: true
No-Try: true
Bug: v8:6861, v8:5699
Cq-Include-Trybots: luci.v8.try:v8_linux_noi18n_rel_ng
Reviewed-on: https://chromium-review.googlesource.com/857616
Reviewed-by: Michael Achenbach <machenbach@chromium.org>
Commit-Queue: Michael Achenbach <machenbach@chromium.org>
Cr-Commit-Position: refs/heads/master@{#50454}
2018-01-09 16:50:33 +00:00

360 lines
10 KiB
JavaScript

// Copyright 2013 the V8 project authors. All rights reserved.
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following
// disclaimer in the documentation and/or other materials provided
// with the distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived
// from this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
// Test for-of semantics.
"use strict";
// First, some helpers.
function* values() {
for (var i = 0; i < arguments.length; i++) {
yield arguments[i];
}
}
function wrap_iterator(iterator) {
var iterable = {};
iterable[Symbol.iterator] = function() { return iterator; };
return iterable;
}
function integers_until(max) {
function next() {
var ret = { value: this.n, done: this.n == max };
this.n++;
return ret;
}
return wrap_iterator({ next: next, n: 0 });
}
function results(results) {
var i = 0;
function next() {
return results[i++];
}
return wrap_iterator({ next: next });
}
function* integers_from(n) {
while (1) yield n++;
}
// A destructive append.
function append(x, tail) {
tail[tail.length] = x;
return tail;
}
function sum(x, tail) {
return x + tail;
}
function fold(cons, seed, iterable) {
for (var x of iterable) {
seed = cons(x, seed);
}
return seed;
}
function* take(iterable, n) {
if (n == 0) return;
for (let x of iterable) {
yield x;
if (--n == 0) break;
}
}
function nth(iterable, n) {
for (let x of iterable) {
if (n-- == 0) return x;
}
throw "unreachable";
}
function* skip_every(iterable, n) {
var i = 0;
for (let x of iterable) {
if (++i % n == 0) continue;
yield x;
}
}
function* iter_map(iterable, f) {
for (var x of iterable) {
yield f(x);
}
}
function nested_fold(cons, seed, iterable) {
var visited = []
for (let x of iterable) {
for (let y of x) {
seed = cons(y, seed);
}
}
return seed;
}
function* unreachable(iterable) {
for (let x of iterable) {
throw "not reached";
}
}
function one_time_getter(o, prop, val) {
function set_never() { throw "unreachable"; }
var gotten = false;
function get_once() {
if (gotten) throw "got twice";
gotten = true;
return val;
}
Object.defineProperty(o, prop, {get: get_once, set: set_never})
return o;
}
function never_getter(o, prop) {
function never() { throw "unreachable"; }
Object.defineProperty(o, prop, {get: never, set: never})
return o;
}
function remove_next_after(iterable, n) {
var iterator = iterable[Symbol.iterator]();
function next() {
if (n-- == 0) delete this.next;
return iterator.next();
}
return wrap_iterator({ next: next });
}
function poison_next_after(iterable, n) {
var iterator = iterable[Symbol.iterator]();
function next() {
return iterator.next();
}
function next_getter() {
if (n-- < 0)
throw "poisoned";
return next;
}
var o = {};
Object.defineProperty(o, 'next', { get: next_getter });
return wrap_iterator(o);
}
// Now, the tests.
// Non-generator iterators.
assertEquals(45, fold(sum, 0, integers_until(10)));
// Generator iterators.
assertEquals([1, 2, 3], fold(append, [], values(1, 2, 3)));
// Break.
assertEquals(45, fold(sum, 0, take(integers_from(0), 10)));
// Continue.
assertEquals(90, fold(sum, 0, take(skip_every(integers_from(0), 2), 10)));
// Return.
assertEquals(10, nth(integers_from(0), 10));
// Nested for-of.
assertEquals([0, 0, 1, 0, 1, 2, 0, 1, 2, 3],
nested_fold(append,
[],
iter_map(integers_until(5), integers_until)));
// Result objects with sparse fields.
assertEquals([undefined, 1, 2, 3],
fold(append, [],
results([{ done: false },
{ value: 1, done: false },
// A missing "done" is the same as undefined, which
// is false.
{ value: 2 },
// Not done.
{ value: 3, done: 0 },
// Done.
{ value: 4, done: 42 }])));
// Results that are not objects.
assertThrows(function() {
assertEquals([undefined, undefined, undefined],
fold(append, [],
results([10, "foo", /qux/, { value: 37, done: true }])));
}, TypeError);
// Getters (shudder).
assertEquals([1, 2],
fold(append, [],
results([one_time_getter({ value: 1 }, 'done', false),
one_time_getter({ done: false }, 'value', 2),
{ value: 37, done: true },
never_getter(never_getter({}, 'done'), 'value')])));
// Unlike the case with for-in, null and undefined cause an error.
assertThrows('fold(sum, 0, unreachable(null))', TypeError);
assertThrows('fold(sum, 0, unreachable(undefined))', TypeError);
// Other non-iterators do cause an error.
assertThrows('fold(sum, 0, unreachable({}))', TypeError);
assertThrows('fold(sum, 0, unreachable(false))', TypeError);
assertThrows('fold(sum, 0, unreachable(37))', TypeError);
// "next" is looked up each time.
assertThrows('fold(sum, 0, remove_next_after(integers_until(10), 5))',
TypeError);
// It is not called at any other time.
assertEquals(45,
fold(sum, 0, remove_next_after(integers_until(10), 10)));
// It is not looked up too many times.
assertEquals(45,
fold(sum, 0, poison_next_after(integers_until(10), 10)));
function labelled_continue(iterable) {
var n = 0;
outer:
while (true) {
n++;
for (var x of iterable) continue outer;
break;
}
return n;
}
assertEquals(11, labelled_continue(integers_until(10)));
function labelled_break(iterable) {
var n = 0;
outer:
while (true) {
n++;
for (var x of iterable) break outer;
}
return n;
}
assertEquals(1, labelled_break(integers_until(10)));
// Test continue/break in catch.
function catch_control(iterable, k) {
var n = 0;
for (var x of iterable) {
try {
return k(x);
} catch (e) {
if (e == "continue") continue;
else if (e == "break") break;
else throw e;
}
} while (false);
return false;
}
assertEquals(false,
catch_control(integers_until(10),
function() { throw "break" }));
assertEquals(false,
catch_control(integers_until(10),
function() { throw "continue" }));
assertEquals(5,
catch_control(integers_until(10),
function(x) {
if (x == 5) return x;
throw "continue";
}));
// Test continue/break in try.
function try_control(iterable, k) {
var n = 0;
for (var x of iterable) {
try {
var e = k(x);
if (e == "continue") continue;
else if (e == "break") break;
return e;
} catch (e) {
throw e;
}
} while (false);
return false;
}
assertEquals(false,
try_control(integers_until(10),
function() { return "break" }));
assertEquals(false,
try_control(integers_until(10),
function() { return "continue" }));
assertEquals(5,
try_control(integers_until(10),
function(x) { return (x == 5) ? x : "continue" }));
// TODO(neis,cbruni): Enable once the corresponding traps work again.
// Proxy results, with getters.
// function transparent_proxy(x) {
// return new Proxy({}, {
// get: function(receiver, name) { return x[name]; }
// });
// }
// assertEquals([1, 2],
// fold(append, [],
// results([one_time_getter({ value: 1 }, 'done', false),
// one_time_getter({ done: false }, 'value', 2),
// { value: 37, done: true },
// never_getter(never_getter({}, 'done'), 'value')]
// .map(transparent_proxy))));
// Proxy iterators.
// function poison_proxy_after(iterable, n) {
// var iterator = iterable[Symbol.iterator]();
// return wrap_iterator(new Proxy({}, {
// get: function(receiver, name) {
// if (name == 'next' && n-- < 0) throw "unreachable";
// return iterator[name];
// },
// // Needed for integers_until(10)'s this.n++.
// set: function(receiver, name, val) {
// return iterator[name] = val;
// }
// }));
// }
// assertEquals(45, fold(sum, 0, poison_proxy_after(integers_until(10), 10)));
function test_iterator_result_object_non_object(value, descr) {
var arr = [];
var ex;
var message = 'Iterator result ' + (descr || value) + ' is not an object';
try {
fold(append, arr,
results([{value: 1}, {}, value, {value: 2}, {done: true}]));
} catch (e) {
ex = e;
}
assertInstanceof(ex, TypeError);
assertEquals(message, ex.message);
assertArrayEquals([1, undefined], arr);
}
test_iterator_result_object_non_object(null);
test_iterator_result_object_non_object(undefined);
test_iterator_result_object_non_object(42);
test_iterator_result_object_non_object('abc');
test_iterator_result_object_non_object(false);
test_iterator_result_object_non_object(Symbol('x'), 'Symbol(x)');