1// Copyright 2013 the V8 project authors. All rights reserved. 2// Redistribution and use in source and binary forms, with or without 3// modification, are permitted provided that the following conditions are 4// met: 5// 6// * Redistributions of source code must retain the above copyright 7// notice, this list of conditions and the following disclaimer. 8// * Redistributions in binary form must reproduce the above 9// copyright notice, this list of conditions and the following 10// disclaimer in the documentation and/or other materials provided 11// with the distribution. 12// * Neither the name of Google Inc. nor the names of its 13// contributors may be used to endorse or promote products derived 14// from this software without specific prior written permission. 15// 16// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 17// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 18// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 19// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 20// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 21// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 22// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 23// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 24// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 25// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 26// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 27 28// Flags: --harmony-proxies 29 30// Test for-of semantics. 31 32"use strict"; 33 34 35// First, some helpers. 36 37function* values() { 38 for (var i = 0; i < arguments.length; i++) { 39 yield arguments[i]; 40 } 41} 42 43function wrap_iterator(iterator) { 44 var iterable = {}; 45 iterable[Symbol.iterator] = function() { return iterator; }; 46 return iterable; 47} 48 49function integers_until(max) { 50 function next() { 51 var ret = { value: this.n, done: this.n == max }; 52 this.n++; 53 return ret; 54 } 55 return wrap_iterator({ next: next, n: 0 }); 56} 57 58function results(results) { 59 var i = 0; 60 function next() { 61 return results[i++]; 62 } 63 return wrap_iterator({ next: next }); 64} 65 66function* integers_from(n) { 67 while (1) yield n++; 68} 69 70// A destructive append. 71function append(x, tail) { 72 tail[tail.length] = x; 73 return tail; 74} 75 76function sum(x, tail) { 77 return x + tail; 78} 79 80function fold(cons, seed, iterable) { 81 for (var x of iterable) { 82 seed = cons(x, seed); 83 } 84 return seed; 85} 86 87function* take(iterable, n) { 88 if (n == 0) return; 89 for (let x of iterable) { 90 yield x; 91 if (--n == 0) break; 92 } 93} 94 95function nth(iterable, n) { 96 for (let x of iterable) { 97 if (n-- == 0) return x; 98 } 99 throw "unreachable"; 100} 101 102function* skip_every(iterable, n) { 103 var i = 0; 104 for (let x of iterable) { 105 if (++i % n == 0) continue; 106 yield x; 107 } 108} 109 110function* iter_map(iterable, f) { 111 for (var x of iterable) { 112 yield f(x); 113 } 114} 115function nested_fold(cons, seed, iterable) { 116 var visited = [] 117 for (let x of iterable) { 118 for (let y of x) { 119 seed = cons(y, seed); 120 } 121 } 122 return seed; 123} 124 125function* unreachable(iterable) { 126 for (let x of iterable) { 127 throw "not reached"; 128 } 129} 130 131function one_time_getter(o, prop, val) { 132 function set_never() { throw "unreachable"; } 133 var gotten = false; 134 function get_once() { 135 if (gotten) throw "got twice"; 136 gotten = true; 137 return val; 138 } 139 Object.defineProperty(o, prop, {get: get_once, set: set_never}) 140 return o; 141} 142 143function never_getter(o, prop) { 144 function never() { throw "unreachable"; } 145 Object.defineProperty(o, prop, {get: never, set: never}) 146 return o; 147} 148 149function remove_next_after(iterable, n) { 150 var iterator = iterable[Symbol.iterator](); 151 function next() { 152 if (n-- == 0) delete this.next; 153 return iterator.next(); 154 } 155 return wrap_iterator({ next: next }); 156} 157 158function poison_next_after(iterable, n) { 159 var iterator = iterable[Symbol.iterator](); 160 function next() { 161 return iterator.next(); 162 } 163 function next_getter() { 164 if (n-- < 0) 165 throw "poisoned"; 166 return next; 167 } 168 var o = {}; 169 Object.defineProperty(o, 'next', { get: next_getter }); 170 return wrap_iterator(o); 171} 172 173// Now, the tests. 174 175// Non-generator iterators. 176assertEquals(45, fold(sum, 0, integers_until(10))); 177// Generator iterators. 178assertEquals([1, 2, 3], fold(append, [], values(1, 2, 3))); 179// Break. 180assertEquals(45, fold(sum, 0, take(integers_from(0), 10))); 181// Continue. 182assertEquals(90, fold(sum, 0, take(skip_every(integers_from(0), 2), 10))); 183// Return. 184assertEquals(10, nth(integers_from(0), 10)); 185// Nested for-of. 186assertEquals([0, 0, 1, 0, 1, 2, 0, 1, 2, 3], 187 nested_fold(append, 188 [], 189 iter_map(integers_until(5), integers_until))); 190// Result objects with sparse fields. 191assertEquals([undefined, 1, 2, 3], 192 fold(append, [], 193 results([{ done: false }, 194 { value: 1, done: false }, 195 // A missing "done" is the same as undefined, which 196 // is false. 197 { value: 2 }, 198 // Not done. 199 { value: 3, done: 0 }, 200 // Done. 201 { value: 4, done: 42 }]))); 202// Results that are not objects. 203assertThrows(function() { 204 assertEquals([undefined, undefined, undefined], 205 fold(append, [], 206 results([10, "foo", /qux/, { value: 37, done: true }]))); 207}, TypeError); 208// Getters (shudder). 209assertEquals([1, 2], 210 fold(append, [], 211 results([one_time_getter({ value: 1 }, 'done', false), 212 one_time_getter({ done: false }, 'value', 2), 213 { value: 37, done: true }, 214 never_getter(never_getter({}, 'done'), 'value')]))); 215 216// Unlike the case with for-in, null and undefined cause an error. 217assertThrows('fold(sum, 0, unreachable(null))', TypeError); 218assertThrows('fold(sum, 0, unreachable(undefined))', TypeError); 219 220// Other non-iterators do cause an error. 221assertThrows('fold(sum, 0, unreachable({}))', TypeError); 222assertThrows('fold(sum, 0, unreachable(false))', TypeError); 223assertThrows('fold(sum, 0, unreachable(37))', TypeError); 224 225// "next" is looked up each time. 226assertThrows('fold(sum, 0, remove_next_after(integers_until(10), 5))', 227 TypeError); 228// It is not called at any other time. 229assertEquals(45, 230 fold(sum, 0, remove_next_after(integers_until(10), 10))); 231// It is not looked up too many times. 232assertEquals(45, 233 fold(sum, 0, poison_next_after(integers_until(10), 10))); 234 235function labelled_continue(iterable) { 236 var n = 0; 237outer: 238 while (true) { 239 n++; 240 for (var x of iterable) continue outer; 241 break; 242 } 243 return n; 244} 245assertEquals(11, labelled_continue(integers_until(10))); 246 247function labelled_break(iterable) { 248 var n = 0; 249outer: 250 while (true) { 251 n++; 252 for (var x of iterable) break outer; 253 } 254 return n; 255} 256assertEquals(1, labelled_break(integers_until(10))); 257 258// Test continue/break in catch. 259function catch_control(iterable, k) { 260 var n = 0; 261 for (var x of iterable) { 262 try { 263 return k(x); 264 } catch (e) { 265 if (e == "continue") continue; 266 else if (e == "break") break; 267 else throw e; 268 } 269 } while (false); 270 return false; 271} 272assertEquals(false, 273 catch_control(integers_until(10), 274 function() { throw "break" })); 275assertEquals(false, 276 catch_control(integers_until(10), 277 function() { throw "continue" })); 278assertEquals(5, 279 catch_control(integers_until(10), 280 function(x) { 281 if (x == 5) return x; 282 throw "continue"; 283 })); 284 285// Test continue/break in try. 286function try_control(iterable, k) { 287 var n = 0; 288 for (var x of iterable) { 289 try { 290 var e = k(x); 291 if (e == "continue") continue; 292 else if (e == "break") break; 293 return e; 294 } catch (e) { 295 throw e; 296 } 297 } while (false); 298 return false; 299} 300assertEquals(false, 301 try_control(integers_until(10), 302 function() { return "break" })); 303assertEquals(false, 304 try_control(integers_until(10), 305 function() { return "continue" })); 306assertEquals(5, 307 try_control(integers_until(10), 308 function(x) { return (x == 5) ? x : "continue" })); 309 310// TODO(neis,cbruni): Enable once the corresponding traps work again. 311// Proxy results, with getters. 312// function transparent_proxy(x) { 313// return new Proxy({}, { 314// get: function(receiver, name) { return x[name]; } 315// }); 316// } 317// assertEquals([1, 2], 318// fold(append, [], 319// results([one_time_getter({ value: 1 }, 'done', false), 320// one_time_getter({ done: false }, 'value', 2), 321// { value: 37, done: true }, 322// never_getter(never_getter({}, 'done'), 'value')] 323// .map(transparent_proxy)))); 324 325// Proxy iterators. 326// function poison_proxy_after(iterable, n) { 327// var iterator = iterable[Symbol.iterator](); 328// return wrap_iterator(new Proxy({}, { 329// get: function(receiver, name) { 330// if (name == 'next' && n-- < 0) throw "unreachable"; 331// return iterator[name]; 332// }, 333// // Needed for integers_until(10)'s this.n++. 334// set: function(receiver, name, val) { 335// return iterator[name] = val; 336// } 337// })); 338// } 339// assertEquals(45, fold(sum, 0, poison_proxy_after(integers_until(10), 10))); 340 341 342function test_iterator_result_object_non_object(value, descr) { 343 var arr = []; 344 var ex; 345 var message = 'Iterator result ' + (descr || value) + ' is not an object'; 346 try { 347 fold(append, arr, 348 results([{value: 1}, {}, value, {value: 2}, {done: true}])); 349 } catch (e) { 350 ex = e; 351 } 352 assertInstanceof(ex, TypeError); 353 assertEquals(message, ex.message); 354 assertArrayEquals([1, undefined], arr); 355} 356test_iterator_result_object_non_object(null); 357test_iterator_result_object_non_object(undefined); 358test_iterator_result_object_non_object(42); 359test_iterator_result_object_non_object('abc'); 360test_iterator_result_object_non_object(false); 361test_iterator_result_object_non_object(Symbol('x'), 'Symbol(x)'); 362