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 // Test for-of semantics. 29 30 "use strict"; 31 32 33 // First, some helpers. 34 35 function* values() { 36 for (var i = 0; i < arguments.length; i++) { 37 yield arguments[i]; 38 } 39 } 40 41 function wrap_iterator(iterator) { 42 var iterable = {}; 43 iterable[Symbol.iterator] = function() { return iterator; }; 44 return iterable; 45 } 46 47 function integers_until(max) { 48 function next() { 49 var ret = { value: this.n, done: this.n == max }; 50 this.n++; 51 return ret; 52 } 53 return wrap_iterator({ next: next, n: 0 }); 54 } 55 56 function results(results) { 57 var i = 0; 58 function next() { 59 return results[i++]; 60 } 61 return wrap_iterator({ next: next }); 62 } 63 64 function* integers_from(n) { 65 while (1) yield n++; 66 } 67 68 // A destructive append. 69 function append(x, tail) { 70 tail[tail.length] = x; 71 return tail; 72 } 73 74 function sum(x, tail) { 75 return x + tail; 76 } 77 78 function fold(cons, seed, iterable) { 79 for (var x of iterable) { 80 seed = cons(x, seed); 81 } 82 return seed; 83 } 84 85 function* take(iterable, n) { 86 if (n == 0) return; 87 for (let x of iterable) { 88 yield x; 89 if (--n == 0) break; 90 } 91 } 92 93 function nth(iterable, n) { 94 for (let x of iterable) { 95 if (n-- == 0) return x; 96 } 97 throw "unreachable"; 98 } 99 100 function* skip_every(iterable, n) { 101 var i = 0; 102 for (let x of iterable) { 103 if (++i % n == 0) continue; 104 yield x; 105 } 106 } 107 108 function* iter_map(iterable, f) { 109 for (var x of iterable) { 110 yield f(x); 111 } 112 } 113 function nested_fold(cons, seed, iterable) { 114 var visited = [] 115 for (let x of iterable) { 116 for (let y of x) { 117 seed = cons(y, seed); 118 } 119 } 120 return seed; 121 } 122 123 function* unreachable(iterable) { 124 for (let x of iterable) { 125 throw "not reached"; 126 } 127 } 128 129 function one_time_getter(o, prop, val) { 130 function set_never() { throw "unreachable"; } 131 var gotten = false; 132 function get_once() { 133 if (gotten) throw "got twice"; 134 gotten = true; 135 return val; 136 } 137 Object.defineProperty(o, prop, {get: get_once, set: set_never}) 138 return o; 139 } 140 141 function never_getter(o, prop) { 142 function never() { throw "unreachable"; } 143 Object.defineProperty(o, prop, {get: never, set: never}) 144 return o; 145 } 146 147 function remove_next_after(iterable, n) { 148 var iterator = iterable[Symbol.iterator](); 149 function next() { 150 if (n-- == 0) delete this.next; 151 return iterator.next(); 152 } 153 return wrap_iterator({ next: next }); 154 } 155 156 function poison_next_after(iterable, n) { 157 var iterator = iterable[Symbol.iterator](); 158 function next() { 159 return iterator.next(); 160 } 161 function next_getter() { 162 if (n-- < 0) 163 throw "poisoned"; 164 return next; 165 } 166 var o = {}; 167 Object.defineProperty(o, 'next', { get: next_getter }); 168 return wrap_iterator(o); 169 } 170 171 // Now, the tests. 172 173 // Non-generator iterators. 174 assertEquals(45, fold(sum, 0, integers_until(10))); 175 // Generator iterators. 176 assertEquals([1, 2, 3], fold(append, [], values(1, 2, 3))); 177 // Break. 178 assertEquals(45, fold(sum, 0, take(integers_from(0), 10))); 179 // Continue. 180 assertEquals(90, fold(sum, 0, take(skip_every(integers_from(0), 2), 10))); 181 // Return. 182 assertEquals(10, nth(integers_from(0), 10)); 183 // Nested for-of. 184 assertEquals([0, 0, 1, 0, 1, 2, 0, 1, 2, 3], 185 nested_fold(append, 186 [], 187 iter_map(integers_until(5), integers_until))); 188 // Result objects with sparse fields. 189 assertEquals([undefined, 1, 2, 3], 190 fold(append, [], 191 results([{ done: false }, 192 { value: 1, done: false }, 193 // A missing "done" is the same as undefined, which 194 // is false. 195 { value: 2 }, 196 // Not done. 197 { value: 3, done: 0 }, 198 // Done. 199 { value: 4, done: 42 }]))); 200 // Results that are not objects. 201 assertThrows(function() { 202 assertEquals([undefined, undefined, undefined], 203 fold(append, [], 204 results([10, "foo", /qux/, { value: 37, done: true }]))); 205 }, TypeError); 206 // Getters (shudder). 207 assertEquals([1, 2], 208 fold(append, [], 209 results([one_time_getter({ value: 1 }, 'done', false), 210 one_time_getter({ done: false }, 'value', 2), 211 { value: 37, done: true }, 212 never_getter(never_getter({}, 'done'), 'value')]))); 213 214 // Unlike the case with for-in, null and undefined cause an error. 215 assertThrows('fold(sum, 0, unreachable(null))', TypeError); 216 assertThrows('fold(sum, 0, unreachable(undefined))', TypeError); 217 218 // Other non-iterators do cause an error. 219 assertThrows('fold(sum, 0, unreachable({}))', TypeError); 220 assertThrows('fold(sum, 0, unreachable(false))', TypeError); 221 assertThrows('fold(sum, 0, unreachable(37))', TypeError); 222 223 // "next" is looked up each time. 224 assertThrows('fold(sum, 0, remove_next_after(integers_until(10), 5))', 225 TypeError); 226 // It is not called at any other time. 227 assertEquals(45, 228 fold(sum, 0, remove_next_after(integers_until(10), 10))); 229 // It is not looked up too many times. 230 assertEquals(45, 231 fold(sum, 0, poison_next_after(integers_until(10), 10))); 232 233 function labelled_continue(iterable) { 234 var n = 0; 235 outer: 236 while (true) { 237 n++; 238 for (var x of iterable) continue outer; 239 break; 240 } 241 return n; 242 } 243 assertEquals(11, labelled_continue(integers_until(10))); 244 245 function labelled_break(iterable) { 246 var n = 0; 247 outer: 248 while (true) { 249 n++; 250 for (var x of iterable) break outer; 251 } 252 return n; 253 } 254 assertEquals(1, labelled_break(integers_until(10))); 255 256 // Test continue/break in catch. 257 function catch_control(iterable, k) { 258 var n = 0; 259 for (var x of iterable) { 260 try { 261 return k(x); 262 } catch (e) { 263 if (e == "continue") continue; 264 else if (e == "break") break; 265 else throw e; 266 } 267 } while (false); 268 return false; 269 } 270 assertEquals(false, 271 catch_control(integers_until(10), 272 function() { throw "break" })); 273 assertEquals(false, 274 catch_control(integers_until(10), 275 function() { throw "continue" })); 276 assertEquals(5, 277 catch_control(integers_until(10), 278 function(x) { 279 if (x == 5) return x; 280 throw "continue"; 281 })); 282 283 // Test continue/break in try. 284 function try_control(iterable, k) { 285 var n = 0; 286 for (var x of iterable) { 287 try { 288 var e = k(x); 289 if (e == "continue") continue; 290 else if (e == "break") break; 291 return e; 292 } catch (e) { 293 throw e; 294 } 295 } while (false); 296 return false; 297 } 298 assertEquals(false, 299 try_control(integers_until(10), 300 function() { return "break" })); 301 assertEquals(false, 302 try_control(integers_until(10), 303 function() { return "continue" })); 304 assertEquals(5, 305 try_control(integers_until(10), 306 function(x) { return (x == 5) ? x : "continue" })); 307 308 // TODO(neis,cbruni): Enable once the corresponding traps work again. 309 // Proxy results, with getters. 310 // function transparent_proxy(x) { 311 // return new Proxy({}, { 312 // get: function(receiver, name) { return x[name]; } 313 // }); 314 // } 315 // assertEquals([1, 2], 316 // fold(append, [], 317 // results([one_time_getter({ value: 1 }, 'done', false), 318 // one_time_getter({ done: false }, 'value', 2), 319 // { value: 37, done: true }, 320 // never_getter(never_getter({}, 'done'), 'value')] 321 // .map(transparent_proxy)))); 322 323 // Proxy iterators. 324 // function poison_proxy_after(iterable, n) { 325 // var iterator = iterable[Symbol.iterator](); 326 // return wrap_iterator(new Proxy({}, { 327 // get: function(receiver, name) { 328 // if (name == 'next' && n-- < 0) throw "unreachable"; 329 // return iterator[name]; 330 // }, 331 // // Needed for integers_until(10)'s this.n++. 332 // set: function(receiver, name, val) { 333 // return iterator[name] = val; 334 // } 335 // })); 336 // } 337 // assertEquals(45, fold(sum, 0, poison_proxy_after(integers_until(10), 10))); 338 339 340 function test_iterator_result_object_non_object(value, descr) { 341 var arr = []; 342 var ex; 343 var message = 'Iterator result ' + (descr || value) + ' is not an object'; 344 try { 345 fold(append, arr, 346 results([{value: 1}, {}, value, {value: 2}, {done: true}])); 347 } catch (e) { 348 ex = e; 349 } 350 assertInstanceof(ex, TypeError); 351 assertEquals(message, ex.message); 352 assertArrayEquals([1, undefined], arr); 353 } 354 test_iterator_result_object_non_object(null); 355 test_iterator_result_object_non_object(undefined); 356 test_iterator_result_object_non_object(42); 357 test_iterator_result_object_non_object('abc'); 358 test_iterator_result_object_non_object(false); 359 test_iterator_result_object_non_object(Symbol('x'), 'Symbol(x)'); 360