Browse Source

Implement `WeakMap` (#2597)

This Pull Request changes the following:

- Implement `WeakMap` buildin object.
pull/2606/head
raskad 2 years ago
parent
commit
f538cb214c
  1. 4
      boa_engine/src/builtins/mod.rs
  2. 271
      boa_engine/src/builtins/weak_map/mod.rs
  3. 13
      boa_engine/src/context/intrinsics.rs
  4. 48
      boa_engine/src/object/mod.rs
  5. 8
      boa_gc/src/pointers/weak_map.rs

4
boa_engine/src/builtins/mod.rs

@ -32,6 +32,7 @@ pub mod symbol;
pub mod typed_array;
pub mod uri;
pub mod weak;
pub mod weak_map;
pub mod weak_set;
#[cfg(feature = "intl")]
@ -86,6 +87,7 @@ use crate::{
typed_array::TypedArray,
uri::{DecodeUri, DecodeUriComponent, EncodeUri, EncodeUriComponent},
weak::WeakRef,
weak_map::WeakMap,
weak_set::WeakSet,
},
context::intrinsics::{Intrinsics, StandardConstructor, StandardConstructors},
@ -248,6 +250,7 @@ impl Intrinsics {
DecodeUri::init(&intrinsics);
DecodeUriComponent::init(&intrinsics);
WeakRef::init(&intrinsics);
WeakMap::init(&intrinsics);
WeakSet::init(&intrinsics);
#[cfg(feature = "intl")]
{
@ -343,6 +346,7 @@ pub(crate) fn set_default_global_bindings(context: &mut Context<'_>) -> JsResult
global_binding::<DecodeUri>(context)?;
global_binding::<DecodeUriComponent>(context)?;
global_binding::<WeakRef>(context)?;
global_binding::<WeakMap>(context)?;
global_binding::<WeakSet>(context)?;
#[cfg(feature = "intl")]

271
boa_engine/src/builtins/weak_map/mod.rs

@ -0,0 +1,271 @@
//! Boa's implementation of ECMAScript's `WeakMap` builtin object.
//!
//! More information:
//! - [ECMAScript reference][spec]
//! - [MDN documentation][mdn]
//!
//! [spec]: https://tc39.es/ecma262/#sec-weakmap-objects
//! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WeakMap
use crate::{
builtins::{
map::add_entries_from_iterable, BuiltInBuilder, BuiltInConstructor, BuiltInObject,
IntrinsicObject,
},
context::intrinsics::{Intrinsics, StandardConstructor, StandardConstructors},
object::{internal_methods::get_prototype_from_constructor, JsObject, ObjectData},
property::Attribute,
symbol::JsSymbol,
Context, JsArgs, JsNativeError, JsResult, JsValue,
};
use boa_gc::{Finalize, Trace};
use boa_profiler::Profiler;
#[derive(Debug, Trace, Finalize)]
pub(crate) struct WeakMap;
impl IntrinsicObject for WeakMap {
fn get(intrinsics: &Intrinsics) -> JsObject {
Self::STANDARD_CONSTRUCTOR(intrinsics.constructors()).constructor()
}
fn init(intrinsics: &Intrinsics) {
let _timer = Profiler::global().start_event(Self::NAME, "init");
BuiltInBuilder::from_standard_constructor::<Self>(intrinsics)
.property(
JsSymbol::to_string_tag(),
Self::NAME,
Attribute::READONLY | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE,
)
.method(Self::delete, "delete", 1)
.method(Self::get, "get", 1)
.method(Self::has, "has", 1)
.method(Self::set, "set", 2)
.build();
}
}
impl BuiltInObject for WeakMap {
const NAME: &'static str = "WeakMap";
const ATTRIBUTE: Attribute = Attribute::WRITABLE.union(Attribute::CONFIGURABLE);
}
impl BuiltInConstructor for WeakMap {
/// The amount of arguments the `WeakMap` constructor takes.
const LENGTH: usize = 0;
const STANDARD_CONSTRUCTOR: fn(&StandardConstructors) -> &StandardConstructor =
StandardConstructors::weak_map;
/// `WeakMap ( [ iterable ] )`
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#sec-weakmap-iterable
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WeakMap/WeakMap
fn constructor(
new_target: &JsValue,
args: &[JsValue],
context: &mut Context<'_>,
) -> JsResult<JsValue> {
// 1. If NewTarget is undefined, throw a TypeError exception.
if new_target.is_undefined() {
return Err(JsNativeError::typ()
.with_message("WeakMap: cannot call constructor without `new`")
.into());
}
// 2. Let map be ? OrdinaryCreateFromConstructor(NewTarget, "%WeakMap.prototype%", « [[WeakMapData]] »).
// 3. Set map.[[WeakMapData]] to a new empty List.
let map = JsObject::from_proto_and_data(
get_prototype_from_constructor(new_target, StandardConstructors::weak_map, context)?,
ObjectData::weak_map(boa_gc::WeakMap::new()),
);
// 4. If iterable is either undefined or null, return map.
let iterable = args.get_or_undefined(0);
if iterable.is_null_or_undefined() {
return Ok(map.into());
}
// 5. Let adder be ? Get(map, "set").
let adder = map.get("set", context)?;
// 6. If IsCallable(adder) is false, throw a TypeError exception.
if !adder.is_callable() {
return Err(JsNativeError::typ()
.with_message("WeakMap: 'add' is not a function")
.into());
}
// 7. Return ? AddEntriesFromIterable(map, iterable, adder).
add_entries_from_iterable(&map, iterable, &adder, context)
}
}
impl WeakMap {
/// `WeakMap.prototype.delete ( key )`
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#sec-weakmap.prototype.delete
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WeakMap/delete
pub(crate) fn delete(
this: &JsValue,
args: &[JsValue],
_context: &mut Context<'_>,
) -> JsResult<JsValue> {
// 1. Let M be the this value.
// 2. Perform ? RequireInternalSlot(M, [[WeakMapData]]).
let Some(obj) = this.as_object() else {
return Err(JsNativeError::typ()
.with_message("WeakMap.delete: called with non-object value")
.into());
};
let mut obj_borrow = obj.borrow_mut();
let m = obj_borrow.as_weak_map_mut().ok_or_else(|| {
JsNativeError::typ().with_message("WeakMap.delete: called with non-object value")
})?;
// 3. Let entries be M.[[WeakMapData]].
// 4. If key is not an Object, return false.
let Some(key) = args.get_or_undefined(0).as_object() else {
return Ok(false.into());
};
// 5. For each Record { [[Key]], [[Value]] } p of entries, do
// a. If p.[[Key]] is not empty and SameValue(p.[[Key]], key) is true, then
// i. Set p.[[Key]] to empty.
// ii. Set p.[[Value]] to empty.
// iii. Return true.
// 6. Return false.
Ok(m.remove(key.inner()).is_some().into())
}
/// `WeakMap.prototype.get ( key )`
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#sec-weakmap.prototype.get
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WeakMap/get
pub(crate) fn get(
this: &JsValue,
args: &[JsValue],
_context: &mut Context<'_>,
) -> JsResult<JsValue> {
// 1. Let M be the this value.
// 2. Perform ? RequireInternalSlot(M, [[WeakMapData]]).
let Some(obj) = this.as_object() else {
return Err(JsNativeError::typ()
.with_message("WeakMap.get: called with non-object value")
.into());
};
let mut obj_borrow = obj.borrow_mut();
let m = obj_borrow.as_weak_map_mut().ok_or_else(|| {
JsNativeError::typ().with_message("WeakMap.get: called with non-object value")
})?;
// 3. Let entries be M.[[WeakMapData]].
// 4. If key is not an Object, return undefined.
let Some(key) = args.get_or_undefined(0).as_object() else {
return Ok(JsValue::undefined());
};
// 5. For each Record { [[Key]], [[Value]] } p of entries, do
// a. If p.[[Key]] is not empty and SameValue(p.[[Key]], key) is true, return p.[[Value]].
// 6. Return undefined.
Ok(m.get(key.inner()).unwrap_or_default())
}
/// `WeakMap.prototype.has ( key )`
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#sec-weakmap.prototype.has
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WeakMap/has
pub(crate) fn has(
this: &JsValue,
args: &[JsValue],
_context: &mut Context<'_>,
) -> JsResult<JsValue> {
// 1. Let M be the this value.
// 2. Perform ? RequireInternalSlot(M, [[WeakMapData]]).
let Some(obj) = this.as_object() else {
return Err(JsNativeError::typ()
.with_message("WeakMap.has: called with non-object value")
.into());
};
let mut obj_borrow = obj.borrow_mut();
let m = obj_borrow.as_weak_map_mut().ok_or_else(|| {
JsNativeError::typ().with_message("WeakMap.has: called with non-object value")
})?;
// 3. Let entries be M.[[WeakMapData]].
// 4. If key is not an Object, return false.
let Some(key) = args.get_or_undefined(0).as_object() else {
return Ok(false.into());
};
// 5. For each Record { [[Key]], [[Value]] } p of entries, do
// a. If p.[[Key]] is not empty and SameValue(p.[[Key]], key) is true, return true.
// 6. Return false.
Ok(m.contains_key(key.inner()).into())
}
/// `WeakMap.prototype.set ( key, value )`
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#sec-weakmap.prototype.set
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WeakMap/set
pub(crate) fn set(
this: &JsValue,
args: &[JsValue],
_context: &mut Context<'_>,
) -> JsResult<JsValue> {
// 1. Let M be the this value.
// 2. Perform ? RequireInternalSlot(M, [[WeakMapData]]).
let Some(obj) = this.as_object() else {
return Err(JsNativeError::typ()
.with_message("WeakMap.set: called with non-object value")
.into());
};
let mut obj_borrow = obj.borrow_mut();
let m = obj_borrow.as_weak_map_mut().ok_or_else(|| {
JsNativeError::typ().with_message("WeakMap.set: called with non-object value")
})?;
// 3. Let entries be M.[[WeakMapData]].
// 4. If key is not an Object, throw a TypeError exception.
let key = args.get_or_undefined(0);
let Some(key) = key.as_object() else {
return Err(JsNativeError::typ()
.with_message(format!(
"WeakMap.set: expected target argument of type `object`, got target of type `{}`",
key.type_of()
)).into());
};
// 5. For each Record { [[Key]], [[Value]] } p of entries, do
// a. If p.[[Key]] is not empty and SameValue(p.[[Key]], key) is true, then
// i. Set p.[[Value]] to value.
// ii. Return M.
// 6. Let p be the Record { [[Key]]: key, [[Value]]: value }.
// 7. Append p to entries.
m.insert(key.inner(), args.get_or_undefined(1).clone());
// 8. Return M.
Ok(this.clone())
}
}

13
boa_engine/src/context/intrinsics.rs

@ -114,6 +114,7 @@ pub struct StandardConstructors {
date_time_format: StandardConstructor,
promise: StandardConstructor,
weak_ref: StandardConstructor,
weak_map: StandardConstructor,
weak_set: StandardConstructor,
#[cfg(feature = "intl")]
collator: StandardConstructor,
@ -181,6 +182,7 @@ impl Default for StandardConstructors {
date_time_format: StandardConstructor::default(),
promise: StandardConstructor::default(),
weak_ref: StandardConstructor::default(),
weak_map: StandardConstructor::default(),
weak_set: StandardConstructor::default(),
#[cfg(feature = "intl")]
collator: StandardConstructor::default(),
@ -646,6 +648,17 @@ impl StandardConstructors {
&self.weak_ref
}
/// Returns the `WeakMap` constructor.
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-weakmap-constructor
#[inline]
pub const fn weak_map(&self) -> &StandardConstructor {
&self.weak_map
}
/// Returns the `WeakSet` constructor.
///
/// More information:

48
boa_engine/src/object/mod.rs

@ -54,7 +54,7 @@ use crate::{
Context, JsBigInt, JsString, JsSymbol, JsValue,
};
use boa_gc::{custom_trace, Finalize, GcRefCell, Trace, WeakGc, WeakMap};
use boa_gc::{custom_trace, Finalize, GcRefCell, Trace, WeakGc};
use std::{
any::Any,
fmt::{self, Debug},
@ -270,8 +270,11 @@ pub enum ObjectKind {
/// The `WeakRef` object kind.
WeakRef(WeakGc<GcRefCell<Object>>),
/// The `WeakMap` object kind.
WeakMap(boa_gc::WeakMap<GcRefCell<Object>, JsValue>),
/// The `WeakSet` object kind.
WeakSet(WeakMap<GcRefCell<Object>, ()>),
WeakSet(boa_gc::WeakMap<GcRefCell<Object>, ()>),
/// The `Intl.Collator` object kind.
#[cfg(feature = "intl")]
@ -314,6 +317,7 @@ unsafe impl Trace for ObjectKind {
Self::Promise(p) => mark(p),
Self::AsyncGenerator(g) => mark(g),
Self::WeakRef(wr) => mark(wr),
Self::WeakMap(wm) => mark(wm),
Self::WeakSet(ws) => mark(ws),
#[cfg(feature = "intl")]
Self::DateTimeFormat(f) => mark(f),
@ -631,9 +635,18 @@ impl ObjectData {
}
}
/// Create the `WeakMap` object data
#[must_use]
pub fn weak_map(weak_map: boa_gc::WeakMap<GcRefCell<Object>, JsValue>) -> Self {
Self {
kind: ObjectKind::WeakMap(weak_map),
internal_methods: &ORDINARY_INTERNAL_METHODS,
}
}
/// Create the `WeakSet` object data
#[must_use]
pub fn weak_set(weak_set: WeakMap<GcRefCell<Object>, ()>) -> Self {
pub fn weak_set(weak_set: boa_gc::WeakMap<GcRefCell<Object>, ()>) -> Self {
Self {
kind: ObjectKind::WeakSet(weak_set),
internal_methods: &ORDINARY_INTERNAL_METHODS,
@ -735,6 +748,7 @@ impl Debug for ObjectKind {
Self::DataView(_) => "DataView",
Self::Promise(_) => "Promise",
Self::WeakRef(_) => "WeakRef",
Self::WeakMap(_) => "WeakMap",
Self::WeakSet(_) => "WeakSet",
#[cfg(feature = "intl")]
Self::Collator(_) => "Collator",
@ -1553,9 +1567,33 @@ impl Object {
}
}
/// Gets the weak map data if the object is a `WeakMap`.
#[inline]
pub const fn as_weak_map(&self) -> Option<&boa_gc::WeakMap<GcRefCell<Object>, JsValue>> {
match self.data {
ObjectData {
kind: ObjectKind::WeakMap(ref weak_map),
..
} => Some(weak_map),
_ => None,
}
}
/// Gets the mutable weak map data if the object is a `WeakMap`.
#[inline]
pub fn as_weak_map_mut(&mut self) -> Option<&mut boa_gc::WeakMap<GcRefCell<Object>, JsValue>> {
match self.data {
ObjectData {
kind: ObjectKind::WeakMap(ref mut weak_map),
..
} => Some(weak_map),
_ => None,
}
}
/// Gets the weak set data if the object is a `WeakSet`.
#[inline]
pub const fn as_weak_set(&self) -> Option<&WeakMap<GcRefCell<Object>, ()>> {
pub const fn as_weak_set(&self) -> Option<&boa_gc::WeakMap<GcRefCell<Object>, ()>> {
match self.data {
ObjectData {
kind: ObjectKind::WeakSet(ref weak_set),
@ -1567,7 +1605,7 @@ impl Object {
/// Gets the mutable weak set data if the object is a `WeakSet`.
#[inline]
pub fn as_weak_set_mut(&mut self) -> Option<&mut WeakMap<GcRefCell<Object>, ()>> {
pub fn as_weak_set_mut(&mut self) -> Option<&mut boa_gc::WeakMap<GcRefCell<Object>, ()>> {
match self.data {
ObjectData {
kind: ObjectKind::WeakSet(ref mut weak_set),

8
boa_gc/src/pointers/weak_map.rs

@ -7,7 +7,7 @@ pub struct WeakMap<K: Trace + Sized + 'static, V: Trace + Sized + 'static> {
pub(crate) inner: Gc<GcRefCell<HashMap<WeakGc<K>, V>>>,
}
impl<K: Trace, V: Trace> WeakMap<K, V> {
impl<K: Trace, V: Trace + Clone> WeakMap<K, V> {
/// Creates a new [`WeakMap`].
#[must_use]
#[inline]
@ -32,4 +32,10 @@ impl<K: Trace, V: Trace> WeakMap<K, V> {
pub fn contains_key(&self, key: &Gc<K>) -> bool {
self.inner.borrow().contains_key(&WeakGc::new(key))
}
/// Returns a reference to the value corresponding to the key.
#[inline]
pub fn get(&self, key: &Gc<K>) -> Option<V> {
self.inner.borrow().get(&WeakGc::new(key)).cloned()
}
}

Loading…
Cancel
Save