Skip to content

Commit

Permalink
Implement WeakMap (#2597)
Browse files Browse the repository at this point in the history
This Pull Request changes the following:

- Implement `WeakMap` buildin object.
  • Loading branch information
raskad committed Feb 17, 2023
1 parent e93b927 commit f538cb2
Show file tree
Hide file tree
Showing 5 changed files with 338 additions and 6 deletions.
4 changes: 4 additions & 0 deletions boa_engine/src/builtins/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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")]
Expand Down Expand Up @@ -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},
Expand Down Expand Up @@ -248,6 +250,7 @@ impl Intrinsics {
DecodeUri::init(&intrinsics);
DecodeUriComponent::init(&intrinsics);
WeakRef::init(&intrinsics);
WeakMap::init(&intrinsics);
WeakSet::init(&intrinsics);
#[cfg(feature = "intl")]
{
Expand Down Expand Up @@ -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")]
Expand Down
271 changes: 271 additions & 0 deletions boa_engine/src/builtins/weak_map/mod.rs
Original file line number Diff line number Diff line change
@@ -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 changes: 13 additions & 0 deletions boa_engine/src/context/intrinsics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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(),
Expand Down Expand Up @@ -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:
Expand Down

0 comments on commit f538cb2

Please sign in to comment.