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 a1342a3..8ddf8e1 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 859bc0d..fd94cc0 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 edbdd41..1dd7f0d 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 a604cd3..e8d4c64 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.