Skip to content

feat(ecmascript): WeakMap Constructor #561

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 11 commits into
base: main
Choose a base branch
from
Original file line number Diff line number Diff line change
Expand Up @@ -406,13 +406,14 @@ pub fn add_entries_from_iterable_map_constructor<'a>(
}
}

add_entries_from_iterable(
Ok(Map::try_from(add_entries_from_iterable(
agent,
target.unbind(),
target.into_object().unbind(),
iterable.unbind(),
adder.unbind(),
gc,
)
)?)
.unwrap())
}

/// ### [24.1.1.2 AddEntriesFromIterable ( target, iterable, adder )](https://tc39.es/ecma262/#sec-add-entries-from-iterable)
Expand All @@ -430,15 +431,13 @@ pub fn add_entries_from_iterable_map_constructor<'a>(
/// > key.
pub(crate) fn add_entries_from_iterable<'a>(
agent: &mut Agent,
target: Map,
target: Object,
iterable: Value,
adder: Function,
mut gc: GcScope<'a, '_>,
) -> JsResult<Map<'a>> {
let nogc = gc.nogc();
let target = target.bind(nogc).scope(agent, nogc);
let iterable = iterable.bind(nogc);
let adder = adder.bind(nogc).scope(agent, nogc);
) -> JsResult<Object<'a>> {
let target = target.bind(gc.nogc()).scope(agent, gc.nogc());
let adder = adder.bind(gc.nogc()).scope(agent, gc.nogc());
// 1. Let iteratorRecord be ? GetIterator(iterable, SYNC).
let Some(IteratorRecord {
iterator,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,43 @@
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at https://mozilla.org/MPL/2.0/.

use crate::engine::context::GcScope;
use core::hash::Hasher;

use ahash::AHasher;

use crate::{
ecmascript::{
abstract_operations::{
operations_on_objects::{get, get_method, try_get},
testing_and_comparison::is_callable,
},
builders::builtin_function_builder::BuiltinFunctionBuilder,
builtins::{ArgumentsList, Behaviour, Builtin, BuiltinIntrinsicConstructor},
execution::{Agent, JsResult, RealmIdentifier},
types::{BUILTIN_STRING_MEMORY, IntoObject, Object, String, Value},
builtins::{
ArgumentsList, Behaviour, Builtin, BuiltinIntrinsicConstructor,
array::ArrayHeap,
keyed_collections::map_objects::{
map_constructor::add_entries_from_iterable,
map_prototype::canonicalize_keyed_collection_key,
},
ordinary::ordinary_create_from_constructor,
weak_map::{WeakMap, data::WeakMapData},
},
execution::{Agent, JsResult, ProtoIntrinsics, RealmIdentifier, agent::ExceptionType},
types::{
BUILTIN_STRING_MEMORY, Function, IntoFunction, IntoObject, IntoValue, Object, String,
Value,
},
},
heap::IntrinsicConstructorIndexes,
engine::{
TryResult,
context::{Bindable, GcScope},
rootable::Scopable,
},
heap::{Heap, IntrinsicConstructorIndexes, PrimitiveHeap, WellKnownSymbolIndexes},
};

use super::weak_map_prototype::WeakMapPrototypeSet;

pub(crate) struct WeakMapConstructor;
impl Builtin for WeakMapConstructor {
const NAME: String<'static> = BUILTIN_STRING_MEMORY.WeakMap;
Expand All @@ -21,19 +47,100 @@ impl Builtin for WeakMapConstructor {

const BEHAVIOUR: Behaviour = Behaviour::Constructor(Self::constructor);
}

impl BuiltinIntrinsicConstructor for WeakMapConstructor {
const INDEX: IntrinsicConstructorIndexes = IntrinsicConstructorIndexes::WeakMap;
}

impl WeakMapConstructor {
fn constructor<'gc>(
_agent: &mut Agent,
_this_value: Value,
_arguments: ArgumentsList,
_new_target: Option<Object>,
_gc: GcScope<'gc, '_>,
agent: &mut Agent,
_: Value,
arguments: ArgumentsList,
new_target: Option<Object>,
mut gc: GcScope<'gc, '_>,
) -> JsResult<Value<'gc>> {
todo!()
let nogc = gc.nogc();
let iterable = arguments.get(0).bind(nogc);
let no_iterable = iterable.is_undefined() || iterable.is_null();
let new_target = new_target.bind(nogc);

// If NewTarget is undefined, throw a TypeError exception.
let Some(new_target) = new_target else {
return Err(agent.throw_exception_with_static_message(
ExceptionType::TypeError,
"Constructor WeakMap requires 'new'",
gc.nogc(),
));
};
let new_target = Function::try_from(new_target).unwrap();
// 2. Let map be ? OrdinaryCreateFromConstructor(NewTarget, "%WeakMap.prototype%", « [[WeakMapData]] »).
// 4. If iterable is either undefined or null, return map.
if no_iterable {
return Ok(ordinary_create_from_constructor(
agent,
new_target.unbind(),
ProtoIntrinsics::WeakMap,
gc,
)?
.into_value());
}
let iterable = iterable.scope(agent, nogc);
let mut map = WeakMap::try_from(ordinary_create_from_constructor(
agent,
new_target.unbind(),
ProtoIntrinsics::WeakMap,
gc.reborrow(),
)?)
.unwrap()
.unbind()
.bind(gc.nogc());
// Note
// If the parameter iterable is present, it is expected to be an
// object that implements an @@iterator method that returns an
// iterator object that produces a two element array-like object
// whose first element is a value that will be used as a WeakMap key
// and whose second element is the value to associate with that
// key.

// 5. Let adder be ? Get(map, "set").
let adder = if let TryResult::Continue(adder) = try_get(
agent,
map.into_object().unbind(),
BUILTIN_STRING_MEMORY.set.to_property_key(),
gc.nogc(),
) {
adder
} else {
let scoped_map = map.scope(agent, gc.nogc());
let adder = get(
agent,
map.into_object().unbind(),
BUILTIN_STRING_MEMORY.set.to_property_key(),
gc.reborrow(),
)?
.unbind();
let gc = gc.nogc();
map = scoped_map.get(agent).bind(gc);
adder
};
// 6. If IsCallable(adder) is false, throw a TypeError exception.
let Some(adder) = is_callable(adder, gc.nogc()) else {
return Err(agent.throw_exception_with_static_message(
ExceptionType::TypeError,
"WeakMap.prototype.set is not callable",
gc.nogc(),
));
};
// 7. Return ? AddEntriesFromIterable(map, iterable, adder).
add_entries_from_iterable_weak_map_constructor(
agent,
map.unbind(),
iterable.get(agent),
adder.unbind(),
gc,
)
.map(|result| result.into_value())
}

pub(crate) fn create_intrinsic(agent: &mut Agent, realm: RealmIdentifier) {
Expand All @@ -46,3 +153,149 @@ impl WeakMapConstructor {
.build();
}
}

/// ### [24.1.1.2 AddEntriesFromIterable ( target, iterable, adder )](https://tc39.es/ecma262/#sec-add-entries-from-iterable)
///
/// #### Unspecified specialization
///
/// This is a specialization for the `new WeakMap()` use case.
pub fn add_entries_from_iterable_weak_map_constructor<'a>(
agent: &mut Agent,
target: WeakMap,
iterable: Value,
adder: Function,
mut gc: GcScope<'a, '_>,
) -> JsResult<WeakMap<'a>> {
let mut target = target.bind(gc.nogc());
let mut iterable = iterable.bind(gc.nogc());
let mut adder = adder.bind(gc.nogc());
if let Function::BuiltinFunction(bf) = adder {
if agent[bf].behaviour == WeakMapPrototypeSet::BEHAVIOUR {
// Normal WeakMap.prototype.set
if let Value::Array(arr_iterable) = iterable {
let scoped_target = target.scope(agent, gc.nogc());
let scoped_iterable = arr_iterable.scope(agent, gc.nogc());
let scoped_adder = bf.scope(agent, gc.nogc());
let using_iterator = get_method(
agent,
arr_iterable.into_value().unbind(),
WellKnownSymbolIndexes::Iterator.into(),
gc.reborrow(),
)?
.map(|f| f.unbind())
.map(|f| f.bind(gc.nogc()));
target = scoped_target.get(agent).bind(gc.nogc());
if using_iterator
== Some(
agent
.current_realm()
.intrinsics()
.array_prototype_values()
.into_function(),
)
{
let arr_iterable = scoped_iterable.get(agent).bind(gc.nogc());
let Heap {
elements,
arrays,
bigints,
numbers,
strings,
weak_maps,
..
} = &mut agent.heap;
let array_heap = ArrayHeap::new(elements, arrays);
let primitive_heap = PrimitiveHeap::new(bigints, numbers, strings);

// Iterable uses the normal Array iterator of this realm.
if arr_iterable.len(&array_heap) == 0 {
// Array iterator does not iterate empty arrays.
return Ok(scoped_target.get(agent).bind(gc.into_nogc()));
}
if arr_iterable.is_trivial(&array_heap)
&& arr_iterable.as_slice(&array_heap).iter().all(|entry| {
if let Some(Value::Array(entry)) = *entry {
entry.len(&array_heap) == 2
&& entry.is_trivial(&array_heap)
&& entry.is_dense(&array_heap)
} else {
false
}
})
{
// Trivial, dense array of trivial, dense arrays of two elements.
let target = target.unbind();
let arr_iterable = arr_iterable.unbind();
let gc = gc.into_nogc();
let target = target.bind(gc);
let arr_iterable = arr_iterable.bind(gc);
let length = arr_iterable.len(&array_heap);
let WeakMapData {
keys,
values,
weak_map_data,
..
} = weak_maps[target].borrow_mut(&primitive_heap);
let map_data = weak_map_data.get_mut();

let length = length as usize;
keys.reserve(length);
values.reserve(length);
// Note: The WeakMap is empty at this point, we don't need the hasher function.
assert!(map_data.is_empty());
map_data.reserve(length, |_| 0);
let hasher = |value: Value| {
let mut hasher = AHasher::default();
value.hash(&primitive_heap, &mut hasher);
hasher.finish()
};
for entry in arr_iterable.as_slice(&array_heap).iter() {
let Some(Value::Array(entry)) = *entry else {
unreachable!()
};
let slice = entry.as_slice(&array_heap);
let key = canonicalize_keyed_collection_key(
numbers,
slice[0].unwrap().bind(gc),
);
let key_hash = hasher(key);
let value = slice[1].unwrap().bind(gc);
let next_index = keys.len() as u32;
let entry = map_data.entry(
key_hash,
|hash_equal_index| keys[*hash_equal_index as usize].unwrap() == key,
|index_to_hash| hasher(keys[*index_to_hash as usize].unwrap()),
);
match entry {
hashbrown::hash_table::Entry::Occupied(occupied) => {
// We have duplicates in the array. Latter
// ones overwrite earlier ones.
let index = *occupied.get();
values[index as usize] = Some(value.unbind());
}
hashbrown::hash_table::Entry::Vacant(vacant) => {
vacant.insert(next_index);
keys.push(Some(key.unbind()));
values.push(Some(value.unbind()));
}
}
}
return Ok(scoped_target.get(agent).bind(gc));
}
}
let gc = gc.nogc();
iterable = scoped_iterable.get(agent).bind(gc).into_value();
adder = scoped_adder.get(agent).bind(gc).into_function();
}
}
}

Ok(WeakMap::try_from(add_entries_from_iterable(
agent,
target.into_object().unbind(),
iterable.unbind(),
adder.unbind(),
gc,
)?)
.unwrap())
}
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ impl Builtin for WeakMapPrototypeHas {
const LENGTH: u8 = 1;
const BEHAVIOUR: Behaviour = Behaviour::Regular(WeakMapPrototype::has);
}
struct WeakMapPrototypeSet;
pub(super) struct WeakMapPrototypeSet;
impl Builtin for WeakMapPrototypeSet {
const NAME: String<'static> = BUILTIN_STRING_MEMORY.set;
const LENGTH: u8 = 2;
Expand Down
11 changes: 11 additions & 0 deletions nova_vm/src/ecmascript/builtins/weak_map.rs
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,17 @@ impl<'a> From<WeakMap<'a>> for Object<'a> {
}
}

impl<'a> TryFrom<Object<'a>> for WeakMap<'a> {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

praise: Nice; it's a bit awkward to be missing these all over the place :D

type Error = ();

fn try_from(value: Object<'a>) -> Result<Self, Self::Error> {
match value {
Object::WeakMap(data) => Ok(data),
_ => Err(()),
}
}
}

impl<'a> InternalSlots<'a> for WeakMap<'a> {
const DEFAULT_PROTOTYPE: ProtoIntrinsics = ProtoIntrinsics::WeakMap;

Expand Down
Loading
Loading