From 6a1ea3514c766be50df4023edfbfb2a8156298e5 Mon Sep 17 00:00:00 2001 From: Stefan Penner Date: Wed, 26 Aug 2015 23:14:34 -0700 Subject: [PATCH 1/9] =?UTF-8?q?Add=20Weakmap=20=E2=80=93=20Weak=20from=20M?= =?UTF-8?q?ap=20->=20Obj,=20not=20Obj=20->=20Map?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/ember-metal/lib/meta.js | 14 ++++++ packages/ember-metal/lib/weak_map.js | 39 +++++++++++++++++ packages/ember-metal/tests/weak_map_test.js | 48 +++++++++++++++++++++ 3 files changed, 101 insertions(+) create mode 100644 packages/ember-metal/lib/weak_map.js create mode 100644 packages/ember-metal/tests/weak_map_test.js diff --git a/packages/ember-metal/lib/meta.js b/packages/ember-metal/lib/meta.js index 94a7e00b62c..0952d03965d 100644 --- a/packages/ember-metal/lib/meta.js +++ b/packages/ember-metal/lib/meta.js @@ -33,6 +33,7 @@ import EmptyObject from 'ember-metal/empty_object'; */ let members = { cache: ownMap, + weak: ownMap, watching: inheritedMap, mixins: inheritedMap, bindings: inheritedMap, @@ -53,6 +54,7 @@ function Meta(obj, parentMeta) { this._deps = undefined; this._chainWatchers = undefined; this._chains = undefined; + this._weak = undefined; // used only internally this.source = obj; @@ -148,6 +150,18 @@ Meta.prototype._getInherited = function(key) { } }; +Meta.prototype.getWeak = function(symbol) { + var weak = this.readableWeak(); + if (weak) { + return weak[symbol]; + } +}; + +Meta.prototype.setWeak = function(symbol, value) { + var weak = this.writableWeak(); + return weak[symbol] = value; +}; + Meta.prototype._findInherited = function(key, subkey) { let pointer = this; while (pointer !== undefined) { diff --git a/packages/ember-metal/lib/weak_map.js b/packages/ember-metal/lib/weak_map.js new file mode 100644 index 00000000000..3c190b88b9a --- /dev/null +++ b/packages/ember-metal/lib/weak_map.js @@ -0,0 +1,39 @@ +import { GUID_KEY } from 'ember-metal/utils'; +import { meta } from 'ember-metal/meta'; + +var id = 0; + +/* + * @private + * @class Ember.WeakMap + * + * Weak relationship from Map -> Key, but not Key to Map. + * + * Key must be a non null object + */ +export default function WeakMap() { + this._id = GUID_KEY + (id++); +} + +/* + * @method get + * @param key {Object} + * @return {*} stored value + */ +WeakMap.prototype.get = function(obj) { + var map = meta(obj).readableWeak(); + if (map) { + return map[this._id]; + } +}; + +/* + * @method set + * @param key {Object} + * @param value {Any} + * @return {Any} stored value + */ +WeakMap.prototype.set = function(obj, value) { + meta(obj).writableWeak()[this._id] = value; + return this; +}; diff --git a/packages/ember-metal/tests/weak_map_test.js b/packages/ember-metal/tests/weak_map_test.js new file mode 100644 index 00000000000..357d6bc7d3d --- /dev/null +++ b/packages/ember-metal/tests/weak_map_test.js @@ -0,0 +1,48 @@ +import WeakMap from 'ember-metal/weak_map'; + +QUnit.module('Ember.WeakMap'); + +QUnit.test('has weakMap like qualities', function(assert) { + var map = new WeakMap(); + var map2 = new WeakMap(); + + var a = {}; + var b = {}; + var c = {}; + + equal(map.get(a), undefined); + equal(map.get(b), undefined); + equal(map.get(c), undefined); + + equal(map2.get(a), undefined); + equal(map2.get(b), undefined); + equal(map2.get(c), undefined); + + equal(map.set(a, 1), map, 'map.set should return itself'); + equal(map.get(a), 1); + equal(map.set(b, undefined), map); + equal(map.set(a, 2), map); + equal(map.get(a), 2); + equal(map.set(b, undefined), map); + + equal(map2.get(a), undefined); + equal(map2.get(b), undefined); + equal(map2.get(c), undefined); + + equal(map.set(c, 1), map); + equal(map.get(c), 1); + equal(map.get(a), 2); + equal(map.get(b), undefined); + + equal(map2.set(a, 3), map2); + equal(map2.set(b, 4), map2); + equal(map2.set(c, 5), map2); + + equal(map2.get(a), 3); + equal(map2.get(b), 4); + equal(map2.get(c), 5); + + equal(map.get(c), 1); + equal(map.get(a), 2); + equal(map.get(b), undefined); +}); From 34854b6692759a08075cf8549150279a13153d69 Mon Sep 17 00:00:00 2001 From: Travis Hoover Date: Tue, 15 Sep 2015 15:12:31 -0700 Subject: [PATCH 2/9] Add has and delete methods to WeakMap --- packages/ember-metal/lib/weak_map.js | 21 +++++++++++++++++++++ packages/ember-metal/tests/weak_map_test.js | 15 +++++++++++++++ 2 files changed, 36 insertions(+) diff --git a/packages/ember-metal/lib/weak_map.js b/packages/ember-metal/lib/weak_map.js index 3c190b88b9a..de9c0a8482e 100644 --- a/packages/ember-metal/lib/weak_map.js +++ b/packages/ember-metal/lib/weak_map.js @@ -37,3 +37,24 @@ WeakMap.prototype.set = function(obj, value) { meta(obj).writableWeak()[this._id] = value; return this; }; + +/* + * @method has + * @param key {Object} + * @return {Boolean} if the key exists + */ +WeakMap.prototype.has = function(obj) { + return !!this.get(obj); +}; + +/* + * @method delete + * @param key {Object} + */ +WeakMap.prototype.delete = function(obj) { + if (this.has(obj)) { + delete meta(obj).writableWeak()[this._id]; + } + + return this; +}; diff --git a/packages/ember-metal/tests/weak_map_test.js b/packages/ember-metal/tests/weak_map_test.js index 357d6bc7d3d..cf67a8cc294 100644 --- a/packages/ember-metal/tests/weak_map_test.js +++ b/packages/ember-metal/tests/weak_map_test.js @@ -46,3 +46,18 @@ QUnit.test('has weakMap like qualities', function(assert) { equal(map.get(a), 2); equal(map.get(b), undefined); }); + +QUnit.test('that .has and .delete work as expected', function(assert) { + var map = new WeakMap(); + var a = {}; + var b = {}; + var foo = { id: 1, name: 'My file', progress: 0 }; + + deepEqual(map.set(a, foo), map); + deepEqual(map.get(a), foo); + ok(map.has(a)); + ok(!map.has(b)); + + deepEqual(map.delete(a), map); + ok(!map.has(a)); +}); From 2aa80751c5a9913f0b58fee3d457444a445562dc Mon Sep 17 00:00:00 2001 From: Travis Hoover Date: Tue, 15 Sep 2015 16:23:21 -0700 Subject: [PATCH 3/9] Assert that keys must be objects --- packages/ember-metal/lib/weak_map.js | 21 ++++++++++++++++++++- packages/ember-metal/tests/weak_map_test.js | 12 ++++++++++++ 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/packages/ember-metal/lib/weak_map.js b/packages/ember-metal/lib/weak_map.js index de9c0a8482e..16cc2d32251 100644 --- a/packages/ember-metal/lib/weak_map.js +++ b/packages/ember-metal/lib/weak_map.js @@ -1,7 +1,10 @@ +import { typeOf } from 'ember-runtime/utils'; +import { assert } from 'ember-metal/debug'; import { GUID_KEY } from 'ember-metal/utils'; import { meta } from 'ember-metal/meta'; var id = 0; +const UNDEFINED = function() {}; /* * @private @@ -23,6 +26,10 @@ export default function WeakMap() { WeakMap.prototype.get = function(obj) { var map = meta(obj).readableWeak(); if (map) { + if (map[this._id] === UNDEFINED) { + return undefined; + } + return map[this._id]; } }; @@ -34,6 +41,12 @@ WeakMap.prototype.get = function(obj) { * @return {Any} stored value */ WeakMap.prototype.set = function(obj, value) { + assert('Uncaught TypeError: Invalid value used as weak map key', typeOf(obj) === 'object'); + + if (typeOf(value) === 'undefined') { + value = UNDEFINED; + } + meta(obj).writableWeak()[this._id] = value; return this; }; @@ -44,7 +57,13 @@ WeakMap.prototype.set = function(obj, value) { * @return {Boolean} if the key exists */ WeakMap.prototype.has = function(obj) { - return !!this.get(obj); + var map = meta(obj).readableWeak(); + + if (map && typeOf(map[this._id]) !== 'undefined') { + return true; + } + + return false; }; /* diff --git a/packages/ember-metal/tests/weak_map_test.js b/packages/ember-metal/tests/weak_map_test.js index cf67a8cc294..cacdba4b7d8 100644 --- a/packages/ember-metal/tests/weak_map_test.js +++ b/packages/ember-metal/tests/weak_map_test.js @@ -47,6 +47,14 @@ QUnit.test('has weakMap like qualities', function(assert) { equal(map.get(b), undefined); }); +QUnit.test('that error is thrown when using a non object key', function(assert) { + var map = new WeakMap(); + + throws(function() { + map.set('a', 1); + }, /Uncaught TypeError: Invalid value used as weak map key/); +}); + QUnit.test('that .has and .delete work as expected', function(assert) { var map = new WeakMap(); var a = {}; @@ -60,4 +68,8 @@ QUnit.test('that .has and .delete work as expected', function(assert) { deepEqual(map.delete(a), map); ok(!map.has(a)); + + map.set(a, undefined); + ok(map.has(a)); }); + From 05fd432dc58e6afb96dbc7fab840a1dcdeffef62 Mon Sep 17 00:00:00 2001 From: Travis Hoover Date: Tue, 15 Sep 2015 16:53:02 -0700 Subject: [PATCH 4/9] Get rid of unneeded typeOf check --- packages/ember-metal/lib/weak_map.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/ember-metal/lib/weak_map.js b/packages/ember-metal/lib/weak_map.js index 16cc2d32251..9e4f0493deb 100644 --- a/packages/ember-metal/lib/weak_map.js +++ b/packages/ember-metal/lib/weak_map.js @@ -43,7 +43,7 @@ WeakMap.prototype.get = function(obj) { WeakMap.prototype.set = function(obj, value) { assert('Uncaught TypeError: Invalid value used as weak map key', typeOf(obj) === 'object'); - if (typeOf(value) === 'undefined') { + if (value === undefined) { value = UNDEFINED; } @@ -59,7 +59,7 @@ WeakMap.prototype.set = function(obj, value) { WeakMap.prototype.has = function(obj) { var map = meta(obj).readableWeak(); - if (map && typeOf(map[this._id]) !== 'undefined') { + if (map && map[this._id] !== undefined) { return true; } From 0ec6eee8f1b9cf51aad47c69957e86ff539a5d7d Mon Sep 17 00:00:00 2001 From: Travis Hoover Date: Tue, 15 Sep 2015 18:58:31 -0700 Subject: [PATCH 5/9] Make UNDEFINED a named function --- packages/ember-metal/lib/weak_map.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/ember-metal/lib/weak_map.js b/packages/ember-metal/lib/weak_map.js index 9e4f0493deb..636037c8c12 100644 --- a/packages/ember-metal/lib/weak_map.js +++ b/packages/ember-metal/lib/weak_map.js @@ -4,7 +4,7 @@ import { GUID_KEY } from 'ember-metal/utils'; import { meta } from 'ember-metal/meta'; var id = 0; -const UNDEFINED = function() {}; +function UNDEFINED() {} /* * @private From 45dfa32c5dd7298f05c67d1b1490418603924075 Mon Sep 17 00:00:00 2001 From: Travis Hoover Date: Tue, 22 Sep 2015 11:51:45 -0700 Subject: [PATCH 6/9] Change set typeof check to allow functions --- packages/ember-metal/lib/weak_map.js | 25 ++++++++++++++------- packages/ember-metal/tests/weak_map_test.js | 21 ++++++++++++++++- 2 files changed, 37 insertions(+), 9 deletions(-) diff --git a/packages/ember-metal/lib/weak_map.js b/packages/ember-metal/lib/weak_map.js index 636037c8c12..0d1dc394dc9 100644 --- a/packages/ember-metal/lib/weak_map.js +++ b/packages/ember-metal/lib/weak_map.js @@ -1,4 +1,3 @@ -import { typeOf } from 'ember-runtime/utils'; import { assert } from 'ember-metal/debug'; import { GUID_KEY } from 'ember-metal/utils'; import { meta } from 'ember-metal/meta'; @@ -6,8 +5,22 @@ import { meta } from 'ember-metal/meta'; var id = 0; function UNDEFINED() {} +function isPrimitiveType(thing) { + switch(typeof thing) { + case 'string': + case 'boolean': + case 'number': + case 'undefined': + case 'null': + case 'symbol': + return true; + default: + return false; + } +} + /* - * @private + * @public * @class Ember.WeakMap * * Weak relationship from Map -> Key, but not Key to Map. @@ -41,7 +54,7 @@ WeakMap.prototype.get = function(obj) { * @return {Any} stored value */ WeakMap.prototype.set = function(obj, value) { - assert('Uncaught TypeError: Invalid value used as weak map key', typeOf(obj) === 'object'); + assert('Uncaught TypeError: Invalid value used as weak map key', !isPrimitiveType(obj)); if (value === undefined) { value = UNDEFINED; @@ -59,11 +72,7 @@ WeakMap.prototype.set = function(obj, value) { WeakMap.prototype.has = function(obj) { var map = meta(obj).readableWeak(); - if (map && map[this._id] !== undefined) { - return true; - } - - return false; + return (map && map[this._id] !== undefined); }; /* diff --git a/packages/ember-metal/tests/weak_map_test.js b/packages/ember-metal/tests/weak_map_test.js index cacdba4b7d8..a9171b18f8a 100644 --- a/packages/ember-metal/tests/weak_map_test.js +++ b/packages/ember-metal/tests/weak_map_test.js @@ -53,6 +53,26 @@ QUnit.test('that error is thrown when using a non object key', function(assert) throws(function() { map.set('a', 1); }, /Uncaught TypeError: Invalid value used as weak map key/); + + throws(function() { + map.set(1, 1); + }, /Uncaught TypeError: Invalid value used as weak map key/); + + throws(function() { + map.set(Symbol(), 1); + }, /Uncaught TypeError: Invalid value used as weak map key/); + + throws(function() { + map.set(true, 1); + }, /Uncaught TypeError: Invalid value used as weak map key/); + + throws(function() { + map.set(null, 1); + }, /Uncaught TypeError: Invalid value used as weak map key/); + + throws(function() { + map.set(undefined, 1); + }, /Uncaught TypeError: Invalid value used as weak map key/); }); QUnit.test('that .has and .delete work as expected', function(assert) { @@ -72,4 +92,3 @@ QUnit.test('that .has and .delete work as expected', function(assert) { map.set(a, undefined); ok(map.has(a)); }); - From 1ce0e98266b2c9d4b2b6b9c0639f721e9f2198c3 Mon Sep 17 00:00:00 2001 From: Travis Hoover Date: Tue, 22 Sep 2015 12:02:51 -0700 Subject: [PATCH 7/9] Use expectAssertion instead of throws --- packages/ember-metal/tests/weak_map_test.js | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/packages/ember-metal/tests/weak_map_test.js b/packages/ember-metal/tests/weak_map_test.js index a9171b18f8a..9722521e22d 100644 --- a/packages/ember-metal/tests/weak_map_test.js +++ b/packages/ember-metal/tests/weak_map_test.js @@ -47,30 +47,30 @@ QUnit.test('has weakMap like qualities', function(assert) { equal(map.get(b), undefined); }); -QUnit.test('that error is thrown when using a non object key', function(assert) { +QUnit.test('that error is thrown when using a primitive key', function(assert) { var map = new WeakMap(); - throws(function() { + expectAssertion(function() { map.set('a', 1); }, /Uncaught TypeError: Invalid value used as weak map key/); - throws(function() { + expectAssertion(function() { map.set(1, 1); }, /Uncaught TypeError: Invalid value used as weak map key/); - throws(function() { + expectAssertion(function() { map.set(Symbol(), 1); }, /Uncaught TypeError: Invalid value used as weak map key/); - throws(function() { + expectAssertion(function() { map.set(true, 1); }, /Uncaught TypeError: Invalid value used as weak map key/); - throws(function() { + expectAssertion(function() { map.set(null, 1); }, /Uncaught TypeError: Invalid value used as weak map key/); - throws(function() { + expectAssertion(function() { map.set(undefined, 1); }, /Uncaught TypeError: Invalid value used as weak map key/); }); From 2211772ef505ad94e71da72e6d0722f12b99097b Mon Sep 17 00:00:00 2001 From: Travis Hoover Date: Tue, 22 Sep 2015 13:26:51 -0700 Subject: [PATCH 8/9] Move from blacklist to whitelist type check --- packages/ember-metal/lib/weak_map.js | 16 +--------------- 1 file changed, 1 insertion(+), 15 deletions(-) diff --git a/packages/ember-metal/lib/weak_map.js b/packages/ember-metal/lib/weak_map.js index 0d1dc394dc9..8beccda9301 100644 --- a/packages/ember-metal/lib/weak_map.js +++ b/packages/ember-metal/lib/weak_map.js @@ -5,20 +5,6 @@ import { meta } from 'ember-metal/meta'; var id = 0; function UNDEFINED() {} -function isPrimitiveType(thing) { - switch(typeof thing) { - case 'string': - case 'boolean': - case 'number': - case 'undefined': - case 'null': - case 'symbol': - return true; - default: - return false; - } -} - /* * @public * @class Ember.WeakMap @@ -54,7 +40,7 @@ WeakMap.prototype.get = function(obj) { * @return {Any} stored value */ WeakMap.prototype.set = function(obj, value) { - assert('Uncaught TypeError: Invalid value used as weak map key', !isPrimitiveType(obj)); + assert('Uncaught TypeError: Invalid value used as weak map key', typeof obj === 'object' || typeof obj === 'function'); if (value === undefined) { value = UNDEFINED; From cdebc452efa648f19cd80334484277bf17890131 Mon Sep 17 00:00:00 2001 From: Travis Hoover Date: Tue, 22 Sep 2015 15:47:16 -0700 Subject: [PATCH 9/9] Add check for null --- packages/ember-metal/lib/weak_map.js | 2 +- packages/ember-metal/tests/weak_map_test.js | 4 ---- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/packages/ember-metal/lib/weak_map.js b/packages/ember-metal/lib/weak_map.js index 8beccda9301..682686ff159 100644 --- a/packages/ember-metal/lib/weak_map.js +++ b/packages/ember-metal/lib/weak_map.js @@ -40,7 +40,7 @@ WeakMap.prototype.get = function(obj) { * @return {Any} stored value */ WeakMap.prototype.set = function(obj, value) { - assert('Uncaught TypeError: Invalid value used as weak map key', typeof obj === 'object' || typeof obj === 'function'); + assert('Uncaught TypeError: Invalid value used as weak map key', obj && (typeof obj === 'object' || typeof obj === 'function')); if (value === undefined) { value = UNDEFINED; diff --git a/packages/ember-metal/tests/weak_map_test.js b/packages/ember-metal/tests/weak_map_test.js index 9722521e22d..c55b015d285 100644 --- a/packages/ember-metal/tests/weak_map_test.js +++ b/packages/ember-metal/tests/weak_map_test.js @@ -58,10 +58,6 @@ QUnit.test('that error is thrown when using a primitive key', function(assert) { map.set(1, 1); }, /Uncaught TypeError: Invalid value used as weak map key/); - expectAssertion(function() { - map.set(Symbol(), 1); - }, /Uncaught TypeError: Invalid value used as weak map key/); - expectAssertion(function() { map.set(true, 1); }, /Uncaught TypeError: Invalid value used as weak map key/);