LibJS: Implement WeakMap.prototype.getOrInsert[Computed]
This is part of the Upsert proposal: https://github.com/tc39/proposal-upsert
This commit is contained in:
parent
b6924309a0
commit
979761ad82
4 changed files with 191 additions and 0 deletions
|
|
@ -26,6 +26,8 @@ void WeakMapPrototype::initialize(Realm& realm)
|
|||
|
||||
define_native_function(realm, vm.names.delete_, delete_, 1, attr);
|
||||
define_native_function(realm, vm.names.get, get, 1, attr);
|
||||
define_native_function(realm, vm.names.getOrInsert, get_or_insert, 2, attr);
|
||||
define_native_function(realm, vm.names.getOrInsertComputed, get_or_insert_computed, 2, attr);
|
||||
define_native_function(realm, vm.names.has, has, 1, attr);
|
||||
define_native_function(realm, vm.names.set, set, 2, attr);
|
||||
|
||||
|
|
@ -79,6 +81,79 @@ JS_DEFINE_NATIVE_FUNCTION(WeakMapPrototype::get)
|
|||
return js_undefined();
|
||||
}
|
||||
|
||||
// 3 WeakMap.prototype.getOrInsert ( key, value ), https://tc39.es/proposal-upsert/#sec-weakmap.prototype.getOrInsert
|
||||
JS_DEFINE_NATIVE_FUNCTION(WeakMapPrototype::get_or_insert)
|
||||
{
|
||||
auto key = vm.argument(0);
|
||||
auto value = vm.argument(1);
|
||||
|
||||
// 1. Let M be the this value.
|
||||
// 2. Perform ? RequireInternalSlot(M, [[WeakMapData]]).
|
||||
auto weak_map = TRY(typed_this_object(vm));
|
||||
|
||||
// 3. If CanBeHeldWeakly(key) is false, throw a TypeError exception.
|
||||
if (!can_be_held_weakly(key))
|
||||
return vm.throw_completion<TypeError>(ErrorType::CannotBeHeldWeakly, key);
|
||||
|
||||
auto& values = weak_map->values();
|
||||
|
||||
// 4. For each Record { [[Key]], [[Value]] } p of M.[[WeakMapData]], do
|
||||
if (auto result = values.find(&key.as_cell()); result != values.end()) {
|
||||
// a. If p.[[Key]] is not empty and SameValue(p.[[Key]], key) is true, return p.[[Value]].
|
||||
return result->value;
|
||||
}
|
||||
|
||||
// 5. Let p be the Record { [[Key]]: key, [[Value]]: value }.
|
||||
// 6. Append p to M.[[WeakMapData]].
|
||||
values.set(&key.as_cell(), value);
|
||||
|
||||
// 7. Return value.
|
||||
return value;
|
||||
}
|
||||
|
||||
// 4 WeakMap.prototype.getOrInsertComputed ( key, callback ), https://tc39.es/proposal-upsert/#sec-weakmap.prototype.getOrInsertComputed
|
||||
JS_DEFINE_NATIVE_FUNCTION(WeakMapPrototype::get_or_insert_computed)
|
||||
{
|
||||
auto key = vm.argument(0);
|
||||
auto callback = vm.argument(1);
|
||||
|
||||
// 1. Let M be the this value.
|
||||
// 2. Perform ? RequireInternalSlot(M, [[WeakMapData]]).
|
||||
auto weak_map = TRY(typed_this_object(vm));
|
||||
|
||||
// 3. If CanBeHeldWeakly(key) is false, throw a TypeError exception.
|
||||
if (!can_be_held_weakly(key))
|
||||
return vm.throw_completion<TypeError>(ErrorType::CannotBeHeldWeakly, key);
|
||||
|
||||
// 4. If IsCallable(callback) is false, throw a TypeError exception.
|
||||
if (!callback.is_function())
|
||||
return vm.throw_completion<TypeError>(ErrorType::NotAFunction, callback);
|
||||
|
||||
auto& values = weak_map->values();
|
||||
|
||||
// 5. For each Record { [[Key]], [[Value]] } p of M.[[WeakMapData]], do
|
||||
if (auto result = values.find(&key.as_cell()); result != values.end()) {
|
||||
// a. If p.[[Key]] is not empty and SameValue(p.[[Key]], key) is true, return p.[[Value]].
|
||||
return result->value;
|
||||
}
|
||||
|
||||
// 6. Let value be ? Call(callback, undefined, « key »).
|
||||
auto value = TRY(call(vm, callback.as_function(), js_undefined(), key));
|
||||
|
||||
// 7. NOTE: The WeakMap may have been modified during execution of callback.
|
||||
|
||||
// 8. For each Record { [[Key]], [[Value]] } p of M.[[WeakMapData]], do
|
||||
// a. If p.[[Key]] is not empty and SameValue(p.[[Key]], key) is true, then
|
||||
// i. Set p.[[Value]] to value.
|
||||
// ii. Return value.
|
||||
// 9. Let p be the Record { [[Key]]: key, [[Value]]: value }.
|
||||
// 10. Append p to M.[[WeakMapData]].
|
||||
values.set(&key.as_cell(), value);
|
||||
|
||||
// 11. Return value.
|
||||
return value;
|
||||
}
|
||||
|
||||
// 24.3.3.4 WeakMap.prototype.has ( key ), https://tc39.es/ecma262/#sec-weakmap.prototype.has
|
||||
JS_DEFINE_NATIVE_FUNCTION(WeakMapPrototype::has)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -24,6 +24,8 @@ private:
|
|||
|
||||
JS_DECLARE_NATIVE_FUNCTION(delete_);
|
||||
JS_DECLARE_NATIVE_FUNCTION(get);
|
||||
JS_DECLARE_NATIVE_FUNCTION(get_or_insert);
|
||||
JS_DECLARE_NATIVE_FUNCTION(get_or_insert_computed);
|
||||
JS_DECLARE_NATIVE_FUNCTION(has);
|
||||
JS_DECLARE_NATIVE_FUNCTION(set);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -0,0 +1,45 @@
|
|||
const a = new String("a");
|
||||
const b = new String("b");
|
||||
|
||||
describe("errors", () => {
|
||||
test("called on non-WeakMap object", () => {
|
||||
expect(() => {
|
||||
WeakMap.prototype.getOrInsert(a, 1);
|
||||
}).toThrowWithMessage(TypeError, "Not an object of type WeakMap");
|
||||
});
|
||||
|
||||
test("key cannot be held weakly", () => {
|
||||
const map = new WeakMap();
|
||||
|
||||
[-100, Infinity, NaN, "hello", 152n].forEach(key => {
|
||||
const suffix = typeof key === "bigint" ? "n" : "";
|
||||
|
||||
expect(() => {
|
||||
map.getOrInsert(key, 1);
|
||||
}).toThrowWithMessage(TypeError, `${key}${suffix} cannot be held weakly`);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("correct behavior", () => {
|
||||
test("length is 2", () => {
|
||||
expect(WeakMap.prototype.getOrInsert).toHaveLength(2);
|
||||
});
|
||||
|
||||
test("inserts new value", () => {
|
||||
const map = new WeakMap();
|
||||
|
||||
let result = map.getOrInsert(a, 2);
|
||||
expect(result).toBe(2);
|
||||
|
||||
result = map.getOrInsert(b, 4);
|
||||
expect(result).toBe(4);
|
||||
});
|
||||
|
||||
test("does not overwrite existing value", () => {
|
||||
const map = new WeakMap([[a, 2]]);
|
||||
|
||||
let result = map.getOrInsert(a, 3);
|
||||
expect(result).toBe(2);
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,69 @@
|
|||
const a = new String("a");
|
||||
const b = new String("b");
|
||||
|
||||
describe("errors", () => {
|
||||
test("called on non-WeakMap object", () => {
|
||||
expect(() => {
|
||||
WeakMap.prototype.getOrInsertComputed(a, () => 0);
|
||||
}).toThrowWithMessage(TypeError, "Not an object of type WeakMap");
|
||||
});
|
||||
|
||||
test("key cannot be held weakly", () => {
|
||||
const map = new WeakMap();
|
||||
|
||||
[-100, Infinity, NaN, "hello", 152n].forEach(key => {
|
||||
const suffix = typeof key === "bigint" ? "n" : "";
|
||||
|
||||
expect(() => {
|
||||
map.getOrInsertComputed(key, 1);
|
||||
}).toThrowWithMessage(TypeError, `${key}${suffix} cannot be held weakly`);
|
||||
});
|
||||
});
|
||||
|
||||
test("called with non-function", () => {
|
||||
expect(() => {
|
||||
new WeakMap().getOrInsertComputed(a, 1);
|
||||
}).toThrowWithMessage(TypeError, "1 is not a function");
|
||||
});
|
||||
|
||||
test("callback function throws", () => {
|
||||
expect(() => {
|
||||
new WeakMap().getOrInsertComputed(a, () => {
|
||||
throw Error(":^)");
|
||||
});
|
||||
}).toThrowWithMessage(Error, ":^)");
|
||||
});
|
||||
});
|
||||
|
||||
describe("correct behavior", () => {
|
||||
test("length is 2", () => {
|
||||
expect(WeakMap.prototype.getOrInsertComputed).toHaveLength(2);
|
||||
});
|
||||
|
||||
test("inserts new value", () => {
|
||||
const map = new WeakMap();
|
||||
|
||||
let result = map.getOrInsertComputed(a, () => 2);
|
||||
expect(result).toBe(2);
|
||||
|
||||
result = map.getOrInsertComputed(b, () => 4);
|
||||
expect(result).toBe(4);
|
||||
});
|
||||
|
||||
test("does not overwrite existing value", () => {
|
||||
const map = new WeakMap([[a, 2]]);
|
||||
|
||||
let result = map.getOrInsertComputed(a, () => 3);
|
||||
expect(result).toBe(2);
|
||||
});
|
||||
|
||||
test("does not invoke callback if already existing", () => {
|
||||
const map = new WeakMap([[a, 2]]);
|
||||
let invoked = false;
|
||||
|
||||
let result = map.getOrInsertComputed(a, () => {
|
||||
invoked = true;
|
||||
});
|
||||
expect(invoked).toBeFalse();
|
||||
});
|
||||
});
|
||||
Loading…
Reference in a new issue