Skip to content

Commit

Permalink
Map and Set methods are not generic, and must only be called on v…
Browse files Browse the repository at this point in the history
…alid `Map` and `Set` objects.
  • Loading branch information
ljharb committed Apr 27, 2015
1 parent 0fa4c96 commit d5b4031
Show file tree
Hide file tree
Showing 2 changed files with 54 additions and 4 deletions.
42 changes: 38 additions & 4 deletions es6-shim.js
Expand Up @@ -1915,7 +1915,18 @@
return this.key === empty;
};

var isMap = function isMap(map) {
return !!map._es6map;
};

var requireMapSlot = function requireMapSlot(map, method) {
if (!ES.TypeIsObject(map) || !isMap(map)) {
throw new TypeError('Method Map.prototype.' + method + ' called on incompatible receiver ' + String(map));
}
};

function MapIterator(map, kind) {
requireMapSlot(map, '[[MapIterator]]');
this.head = map._head;
this.i = this.head;
this.kind = kind;
Expand Down Expand Up @@ -2008,6 +2019,7 @@

defineProperties(Map.prototype, {
get: function (key) {
requireMapSlot(this, 'get');
var fkey = fastkey(key);
if (fkey !== null) {
// fast O(1) path
Expand All @@ -2027,6 +2039,7 @@
},

has: function (key) {
requireMapSlot(this, 'has');
var fkey = fastkey(key);
if (fkey !== null) {
// fast O(1) path
Expand All @@ -2042,6 +2055,7 @@
},

set: function (key, value) {
requireMapSlot(this, 'set');
var head = this._head, i = head, entry;
var fkey = fastkey(key);
if (fkey !== null) {
Expand Down Expand Up @@ -2074,6 +2088,7 @@
},

'delete': function (key) {
requireMapSlot(this, 'delete');
var head = this._head, i = head;
var fkey = fastkey(key);
if (fkey !== null) {
Expand All @@ -2098,6 +2113,7 @@
},

clear: function clear() {
requireMapSlot(this, 'clear');
this._size = 0;
this._storage = emptyObject();
var head = this._head, i = head, p = i.next;
Expand All @@ -2110,18 +2126,22 @@
},

keys: function keys() {
requireMapSlot(this, 'keys');
return new MapIterator(this, 'key');
},

values: function values() {
requireMapSlot(this, 'values');
return new MapIterator(this, 'value');
},

entries: function entries() {
requireMapSlot(this, 'entries');
return new MapIterator(this, 'key+value');
},

forEach: function forEach(callback) {
requireMapSlot(this, 'forEach');
var context = arguments.length > 1 ? arguments[1] : null;
var it = this.entries();
for (var entry = it.next(); !entry.done; entry = it.next()) {
Expand All @@ -2139,6 +2159,16 @@
}()),

Set: (function () {
var isSet = function isSet(set) {
return set._es6set && typeof set._storage !== 'undefined';
};
var requireSetSlot = function requireSetSlot(set, method) {
if (!ES.TypeIsObject(set) || !isSet(set)) {
// https://github.com/paulmillr/es6-shim/issues/176
throw new TypeError('Set.prototype.' + method + ' called on incompatible receiver ' + String(set));
}
};

// Creating a Map is expensive. To speed up the common case of
// Sets containing only string or numeric keys, we use an object
// as backing storage and lazily create a full Map only when
Expand Down Expand Up @@ -2202,16 +2232,14 @@
};

Value.getter(SetShim.prototype, 'size', function () {
if (typeof this._storage === 'undefined') {
// https://github.com/paulmillr/es6-shim/issues/176
throw new TypeError('size method called on incompatible Set');
}
requireSetSlot(this, 'size');
ensureMap(this);
return this['[[SetData]]'].size;
});

defineProperties(SetShim.prototype, {
has: function (key) {
requireSetSlot(this, 'has');
var fkey;
if (this._storage && (fkey = fastkey(key)) !== null) {
return !!this._storage[fkey];
Expand All @@ -2221,6 +2249,7 @@
},

add: function (key) {
requireSetSlot(this, 'add');
var fkey;
if (this._storage && (fkey = fastkey(key)) !== null) {
this._storage[fkey] = true;
Expand All @@ -2232,6 +2261,7 @@
},

'delete': function (key) {
requireSetSlot(this, 'delete');
var fkey;
if (this._storage && (fkey = fastkey(key)) !== null) {
var hasFKey = _hasOwnProperty(this._storage, fkey);
Expand All @@ -2242,6 +2272,7 @@
},

clear: function clear() {
requireSetSlot(this, 'clear');
if (this._storage) {
this._storage = emptyObject();
} else {
Expand All @@ -2250,16 +2281,19 @@
},

values: function values() {
requireSetSlot(this, 'values');
ensureMap(this);
return this['[[SetData]]'].values();
},

entries: function entries() {
requireSetSlot(this, 'entries');
ensureMap(this);
return this['[[SetData]]'].entries();
},

forEach: function forEach(callback) {
requireSetSlot(this, 'forEach');
var context = arguments.length > 1 ? arguments[1] : null;
var entireSet = this;
ensureMap(entireSet);
Expand Down
16 changes: 16 additions & 0 deletions test/collections.js
Expand Up @@ -292,6 +292,14 @@ describe('Collections', function () {
it('has the right arity', function () {
expect(Map.prototype.entries).to.have.property('length', 0);
});

it('throws when called on a non-Map', function () {
var expectedMessage = /^(Method )?Map.prototype.entries called on incompatible receiver |^entries method called on incompatible /;
var nonMaps = [true, false, 'abc', NaN, new Set([1, 2]), { a: true }, [1], Object('abc'), Object(NaN)];
nonMaps.forEach(function (nonMap) {
expect(function () { return Map.prototype.entries.call(nonMap); }).to['throw'](TypeError, expectedMessage);
});
});
});

it('should has unique constructor', function () {
Expand Down Expand Up @@ -748,6 +756,14 @@ describe('Collections', function () {
it('has the right arity', function () {
expect(Set.prototype.values).to.have.property('length', 0);
});

it('throws when called on a non-Set', function () {
var expectedMessage = /^(Method )?Set.prototype.values called on incompatible receiver |^values method called on incompatible /;
var nonSets = [true, false, 'abc', NaN, new Map([[1, 2]]), { a: true }, [1], Object('abc'), Object(NaN)];
nonSets.forEach(function (nonSet) {
expect(function () { return Set.prototype.values.call(nonSet); }).to['throw'](TypeError, expectedMessage);
});
});
});

describe('#entries()', function () {
Expand Down

0 comments on commit d5b4031

Please sign in to comment.