From 257adcf0eda7520de6eab180917c16e27a984c3a Mon Sep 17 00:00:00 2001 From: "adamk@chromium.org" Date: Mon, 23 Jun 2014 18:05:57 +0000 Subject: [PATCH] Map/Set: Implement constructor parameter handling 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 . git-svn-id: https://v8.googlecode.com/svn/branches/bleeding_edge@21950 ce2b1a6d-e550-0410-aec6-3dcde31c8c00 --- src/collection.js | 89 ++++++++++-- src/messages.js | 4 + test/mjsunit/harmony/collections.js | 260 +++++++++++++++++++++++++++++++++++- tools/generate-runtime-tests.py | 2 +- 4 files changed, 345 insertions(+), 10 deletions(-) diff --git a/src/collection.js b/src/collection.js index a1342a3..8ddf8e1 100644 --- a/src/collection.js +++ b/src/collection.js @@ -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); + } } diff --git a/src/messages.js b/src/messages.js index 859bc0d..fd94cc0 100644 --- a/src/messages.js +++ b/src/messages.js @@ -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"], diff --git a/test/mjsunit/harmony/collections.js b/test/mjsunit/harmony/collections.js index edbdd41..1dd7f0d 100644 --- a/test/mjsunit/harmony/collections.js +++ b/test/mjsunit/harmony/collections.js @@ -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); +})(); diff --git a/tools/generate-runtime-tests.py b/tools/generate-runtime-tests.py index a604cd3..e8d4c64 100755 --- a/tools/generate-runtime-tests.py +++ b/tools/generate-runtime-tests.py @@ -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. -- 2.7.4