Map/Set: Implement constructor parameter handling
authoradamk@chromium.org <adamk@chromium.org@ce2b1a6d-e550-0410-aec6-3dcde31c8c00>
Mon, 23 Jun 2014 18:05:57 +0000 (18:05 +0000)
committeradamk@chromium.org <adamk@chromium.org@ce2b1a6d-e550-0410-aec6-3dcde31c8c00>
Mon, 23 Jun 2014 18:05:57 +0000 (18:05 +0000)
When an iterable object is passed in as the argument to the Map and Set
constructor the elements of the iterable object are used to populate the
Map and Set.

http://people.mozilla.org/~jorendorff/es6-draft.html#sec-map-iterable
http://people.mozilla.org/~jorendorff/es6-draft.html#sec-set-iterable

BUG=v8:3398
LOG=Y
R=rossberg@chromium.org

Review URL: https://codereview.chromium.org/345613003

Patch from Erik Arvidsson <arv@chromium.org>.

git-svn-id: https://v8.googlecode.com/svn/branches/bleeding_edge@21950 ce2b1a6d-e550-0410-aec6-3dcde31c8c00

src/collection.js
src/messages.js
test/mjsunit/harmony/collections.js
tools/generate-runtime-tests.py

index a1342a373322b79e85a811fd72aee463161bd99c..8ddf8e1001712e97b9a231d33889a5aa14a9c309 100644 (file)
@@ -12,15 +12,64 @@ var $Set = global.Set;
 var $Map = global.Map;
 
 
+// TODO(arv): Move these general functions to v8natives.js when Map and Set are
+// no longer experimental.
+
+
+// 7.4.1 CheckIterable ( obj )
+function ToIterable(obj) {
+  if (!IS_SPEC_OBJECT(obj)) {
+    return UNDEFINED;
+  }
+  return obj[symbolIterator];
+}
+
+
+// 7.4.2 GetIterator ( obj, method )
+function GetIterator(obj, method) {
+  if (IS_UNDEFINED(method)) {
+    method = ToIterable(obj);
+  }
+  if (!IS_SPEC_FUNCTION(method)) {
+    throw MakeTypeError('not_iterable', [obj]);
+  }
+  var iterator = %_CallFunction(obj, method);
+  if (!IS_SPEC_OBJECT(iterator)) {
+    throw MakeTypeError('not_an_iterator', [iterator]);
+  }
+  return iterator;
+}
+
+
 // -------------------------------------------------------------------
 // Harmony Set
 
-function SetConstructor() {
-  if (%_IsConstructCall()) {
-    %SetInitialize(this);
-  } else {
+function SetConstructor(iterable) {
+  if (!%_IsConstructCall()) {
     throw MakeTypeError('constructor_not_function', ['Set']);
   }
+
+  var iter, adder;
+
+  if (!IS_NULL_OR_UNDEFINED(iterable)) {
+    iter = GetIterator(iterable);
+    adder = this.add;
+    if (!IS_SPEC_FUNCTION(adder)) {
+      throw MakeTypeError('property_not_function', ['add', this]);
+    }
+  }
+
+  %SetInitialize(this);
+
+  if (IS_UNDEFINED(iter)) return;
+
+  var next, done;
+  while (!(next = iter.next()).done) {
+    if (!IS_SPEC_OBJECT(next)) {
+      throw MakeTypeError('iterator_result_not_an_object', [next]);
+    }
+    %_CallFunction(this, next.value, adder);
+  }
 }
 
 
@@ -117,12 +166,36 @@ SetUpSet();
 // -------------------------------------------------------------------
 // Harmony Map
 
-function MapConstructor() {
-  if (%_IsConstructCall()) {
-    %MapInitialize(this);
-  } else {
+function MapConstructor(iterable) {
+  if (!%_IsConstructCall()) {
     throw MakeTypeError('constructor_not_function', ['Map']);
   }
+
+  var iter, adder;
+
+  if (!IS_NULL_OR_UNDEFINED(iterable)) {
+    iter = GetIterator(iterable);
+    adder = this.set;
+    if (!IS_SPEC_FUNCTION(adder)) {
+      throw MakeTypeError('property_not_function', ['set', this]);
+    }
+  }
+
+  %MapInitialize(this);
+
+  if (IS_UNDEFINED(iter)) return;
+
+  var next, done, nextItem;
+  while (!(next = iter.next()).done) {
+    if (!IS_SPEC_OBJECT(next)) {
+      throw MakeTypeError('iterator_result_not_an_object', [next]);
+    }
+    nextItem = next.value;
+    if (!IS_SPEC_OBJECT(nextItem)) {
+      throw MakeTypeError('iterator_value_not_an_object', [nextItem]);
+    }
+    %_CallFunction(this, nextItem[0], nextItem[1], adder);
+  }
 }
 
 
index 859bc0d721cd84bd9ba5bdd86b011b06d59dff8b..fd94cc0ede9ed26c49f96e1171c9273d2b98545a 100644 (file)
@@ -89,6 +89,10 @@ var kMessages = {
   array_functions_on_frozen:     ["Cannot modify frozen array elements"],
   array_functions_change_sealed: ["Cannot add/remove sealed array elements"],
   first_argument_not_regexp:     ["First argument to ", "%0", " must not be a regular expression"],
+  not_iterable:                  ["%0", " is not iterable"],
+  not_an_iterator:               ["%0", " is not an iterator"],
+  iterator_result_not_an_object: ["Iterator result ", "%0", " is not an object"],
+  iterator_value_not_an_object:  ["Iterator value ", "%0", " is not an entry object"],
   // RangeError
   invalid_array_length:          ["Invalid array length"],
   invalid_array_buffer_length:   ["Invalid array buffer length"],
index edbdd41340c3249f178be6f19ae0c41a38600579..1dd7f0d13ef178c13820a9912e297c6721136a4b 100644 (file)
@@ -25,7 +25,7 @@
 // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
-// Flags: --harmony-collections
+// Flags: --harmony-collections --harmony-iteration
 // Flags: --expose-gc --allow-natives-syntax
 
 
@@ -987,3 +987,261 @@ for (var i = 9; i >= 0; i--) {
 
   assertArrayEquals([0, 1, 2, 3, 4], buffer);
 })();
+
+
+(function TestSetConstructor() {
+  var s = new Set(null);
+  assertEquals(s.size, 0);
+
+  s = new Set(undefined);
+  assertEquals(s.size, 0);
+
+  // No @@iterator
+  assertThrows(function() {
+    new Set({});
+  }, TypeError);
+
+  // @@iterator not callable
+  assertThrows(function() {
+    var object = {};
+    object[Symbol.iterator] = 42;
+    new Set(object);
+  }, TypeError);
+
+  // @@iterator result not object
+  assertThrows(function() {
+    var object = {};
+    object[Symbol.iterator] = function() {
+      return 42;
+    };
+    new Set(object);
+  }, TypeError);
+
+  var s2 = new Set();
+  s2.add('a');
+  s2.add('b');
+  s2.add('c');
+  s = new Set(s2.values());
+  assertEquals(s.size, 3);
+  assertTrue(s.has('a'));
+  assertTrue(s.has('b'));
+  assertTrue(s.has('c'));
+})();
+
+
+(function TestSetConstructorAddNotCallable() {
+  var originalSetPrototypeAdd = Set.prototype.add;
+  assertThrows(function() {
+    Set.prototype.add = 42;
+    new Set([1, 2].values());
+  }, TypeError);
+  Set.prototype.add = originalSetPrototypeAdd;
+})();
+
+
+(function TestSetConstructorGetAddOnce() {
+  var originalSetPrototypeAdd = Set.prototype.add;
+  var getAddCount = 0;
+  Object.defineProperty(Set.prototype, 'add', {
+    get: function() {
+      getAddCount++;
+      return function() {};
+    }
+  });
+  var s = new Set([1, 2].values());
+  assertEquals(getAddCount, 1);
+  assertEquals(s.size, 0);
+  Object.defineProperty(Set.prototype, 'add', {
+    value: originalSetPrototypeAdd,
+    writable: true
+  });
+})();
+
+
+(function TestSetConstructorAddReplaced() {
+  var originalSetPrototypeAdd = Set.prototype.add;
+  var addCount = 0;
+  Set.prototype.add = function(value) {
+    addCount++;
+    originalSetPrototypeAdd.call(this, value);
+    Set.prototype.add = null;
+  };
+  var s = new Set([1, 2].values());
+  assertEquals(addCount, 2);
+  assertEquals(s.size, 2);
+  Set.prototype.add = originalSetPrototypeAdd;
+})();
+
+
+(function TestSetConstructorOrderOfDoneValue() {
+  var valueCount = 0, doneCount = 0;
+  var iterator = {
+    next: function() {
+      return {
+        get value() {
+          valueCount++;
+        },
+        get done() {
+          doneCount++;
+          throw new Error();
+        }
+      };
+    }
+  };
+  iterator[Symbol.iterator] = function() {
+    return this;
+  };
+  assertThrows(function() {
+    new Set(iterator);
+  });
+  assertEquals(doneCount, 1);
+  assertEquals(valueCount, 0);
+})();
+
+
+(function TestSetConstructorNextNotAnObject() {
+  var iterator = {
+    next: function() {
+      return 'abc';
+    }
+  };
+  iterator[Symbol.iterator] = function() {
+    return this;
+  };
+  assertThrows(function() {
+    new Set(iterator);
+  }, TypeError);
+})();
+
+
+(function TestMapConstructor() {
+  var m = new Map(null);
+  assertEquals(m.size, 0);
+
+  m = new Map(undefined);
+  assertEquals(m.size, 0);
+
+  // No @@iterator
+  assertThrows(function() {
+    new Map({});
+  }, TypeError);
+
+  // @@iterator not callable
+  assertThrows(function() {
+    var object = {};
+    object[Symbol.iterator] = 42;
+    new Map(object);
+  }, TypeError);
+
+  // @@iterator result not object
+  assertThrows(function() {
+    var object = {};
+    object[Symbol.iterator] = function() {
+      return 42;
+    };
+    new Map(object);
+  }, TypeError);
+
+  var m2 = new Map();
+  m2.set(0, 'a');
+  m2.set(1, 'b');
+  m2.set(2, 'c');
+  m = new Map(m2.entries());
+  assertEquals(m.size, 3);
+  assertEquals(m.get(0), 'a');
+  assertEquals(m.get(1), 'b');
+  assertEquals(m.get(2), 'c');
+})();
+
+
+(function TestMapConstructorSetNotCallable() {
+  var originalMapPrototypeSet = Map.prototype.set;
+  assertThrows(function() {
+    Map.prototype.set = 42;
+    new Map([1, 2].entries());
+  }, TypeError);
+  Map.prototype.set = originalMapPrototypeSet;
+})();
+
+
+(function TestMapConstructorGetAddOnce() {
+  var originalMapPrototypeSet = Map.prototype.set;
+  var getSetCount = 0;
+  Object.defineProperty(Map.prototype, 'set', {
+    get: function() {
+      getSetCount++;
+      return function() {};
+    }
+  });
+  var m = new Map([1, 2].entries());
+  assertEquals(getSetCount, 1);
+  assertEquals(m.size, 0);
+  Object.defineProperty(Map.prototype, 'set', {
+    value: originalMapPrototypeSet,
+    writable: true
+  });
+})();
+
+
+(function TestMapConstructorSetReplaced() {
+  var originalMapPrototypeSet = Map.prototype.set;
+  var setCount = 0;
+  Map.prototype.set = function(key, value) {
+    setCount++;
+    originalMapPrototypeSet.call(this, key, value);
+    Map.prototype.set = null;
+  };
+  var m = new Map([1, 2].entries());
+  assertEquals(setCount, 2);
+  assertEquals(m.size, 2);
+  Map.prototype.set = originalMapPrototypeSet;
+})();
+
+
+(function TestMapConstructorOrderOfDoneValue() {
+  var valueCount = 0, doneCount = 0;
+  function FakeError() {}
+  var iterator = {
+    next: function() {
+      return {
+        get value() {
+          valueCount++;
+        },
+        get done() {
+          doneCount++;
+          throw new FakeError();
+        }
+      };
+    }
+  };
+  iterator[Symbol.iterator] = function() {
+    return this;
+  };
+  assertThrows(function() {
+    new Map(iterator);
+  }, FakeError);
+  assertEquals(doneCount, 1);
+  assertEquals(valueCount, 0);
+})();
+
+
+(function TestMapConstructorNextNotAnObject() {
+  var iterator = {
+    next: function() {
+      return 'abc';
+    }
+  };
+  iterator[Symbol.iterator] = function() {
+    return this;
+  };
+  assertThrows(function() {
+    new Map(iterator);
+  }, TypeError);
+})();
+
+
+(function TestMapConstructorIteratorNotObjectValues() {
+  assertThrows(function() {
+    new Map([1, 2].values());
+  }, TypeError);
+})();
index a604cd355556b2766c1c1d51af36955bd89683a0..e8d4c64c0f0a91c9cd09c1d47cabafd198a2ad73 100755 (executable)
@@ -51,7 +51,7 @@ EXPECTED_FUNCTION_COUNT = 358
 EXPECTED_FUZZABLE_COUNT = 326
 EXPECTED_CCTEST_COUNT = 6
 EXPECTED_UNKNOWN_COUNT = 4
-EXPECTED_BUILTINS_COUNT = 798
+EXPECTED_BUILTINS_COUNT = 800
 
 
 # Don't call these at all.