Home | History | Annotate | Download | only in es6
      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