Skip to content

Commit 20dbfad

Browse files
committed
Integrate WeakMap into JS engine
1 parent 026d651 commit 20dbfad

File tree

5 files changed

+117
-20
lines changed

5 files changed

+117
-20
lines changed

js-scripts/weakmap_tests.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
let wm = new WeakMap();
2+
let o = {};
3+
wm.set(o, 42);
4+
console.log(wm.get(o));
5+
console.log(wm.has(o));
6+
wm.delete(o);
7+
console.log(wm.has(o));

src/core/eval.rs

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1691,6 +1691,37 @@ pub fn evaluate_expr<'gc>(mc: &MutationContext<'gc>, env: &JSObjectDataPtr<'gc>,
16911691
} else {
16921692
Err(EvalError::Js(raise_eval_error!(format!("Unknown Map function: {}", name))))
16931693
}
1694+
} else if name.starts_with("WeakMap.") {
1695+
if let Some(method) = name.strip_prefix("WeakMap.prototype.") {
1696+
let this_v = this_val.clone().unwrap_or(Value::Undefined);
1697+
if let Value::Object(obj) = this_v {
1698+
if let Some(wm_val) = obj_get_key_value(&obj, &"__weakmap__".into())? {
1699+
if let Value::WeakMap(wm_ptr) = &*wm_val.borrow() {
1700+
Ok(crate::js_weakmap::handle_weakmap_instance_method(
1701+
mc, wm_ptr, method, &eval_args, env,
1702+
)?)
1703+
} else {
1704+
Err(EvalError::Js(raise_eval_error!(
1705+
"TypeError: WeakMap.prototype method called on incompatible receiver"
1706+
)))
1707+
}
1708+
} else {
1709+
Err(EvalError::Js(raise_eval_error!(
1710+
"TypeError: WeakMap.prototype method called on incompatible receiver"
1711+
)))
1712+
}
1713+
} else if let Value::WeakMap(wm_ptr) = this_v {
1714+
Ok(crate::js_weakmap::handle_weakmap_instance_method(
1715+
mc, &wm_ptr, method, &eval_args, env,
1716+
)?)
1717+
} else {
1718+
Err(EvalError::Js(raise_eval_error!(
1719+
"TypeError: WeakMap.prototype method called on non-object receiver"
1720+
)))
1721+
}
1722+
} else {
1723+
Err(EvalError::Js(raise_eval_error!(format!("Unknown Map function: {}", name))))
1724+
}
16941725
} else if name.starts_with("Set.") {
16951726
if let Some(method) = name.strip_prefix("Set.prototype.") {
16961727
let this_v = this_val.clone().unwrap_or(Value::Undefined);
@@ -2005,6 +2036,8 @@ pub fn evaluate_expr<'gc>(mc: &MutationContext<'gc>, env: &JSObjectDataPtr<'gc>,
20052036
return Ok(crate::js_date::handle_date_constructor(mc, &eval_args, env)?);
20062037
} else if name == &crate::unicode::utf8_to_utf16("Map") {
20072038
return Ok(crate::js_map::handle_map_constructor(mc, &eval_args, env)?);
2039+
} else if name == &crate::unicode::utf8_to_utf16("WeakMap") {
2040+
return Ok(crate::js_weakmap::handle_weakmap_constructor(mc, &eval_args, env)?);
20082041
} else if name == &crate::unicode::utf8_to_utf16("Set") {
20092042
return Ok(crate::js_set::handle_set_constructor(mc, &eval_args, env)?);
20102043
}

src/core/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ use crate::js_regexp::initialize_regexp;
1111
use crate::js_set::initialize_set;
1212
use crate::js_string::initialize_string;
1313
use crate::js_symbol::initialize_symbol;
14+
use crate::js_weakmap::initialize_weakmap;
1415
use crate::raise_eval_error;
1516
use crate::unicode::utf8_to_utf16;
1617
pub(crate) use gc_arena::GcWeak;
@@ -89,6 +90,7 @@ pub fn initialize_global_constructors<'gc>(mc: &MutationContext<'gc>, env: &JSOb
8990
initialize_bigint(mc, env)?;
9091
initialize_json(mc, env)?;
9192
initialize_map(mc, env)?;
93+
initialize_weakmap(mc, env)?;
9294
initialize_symbol(mc, env)?;
9395
initialize_set(mc, env)?;
9496

src/js_weakmap.rs

Lines changed: 74 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,39 +1,54 @@
1-
use crate::core::JSWeakMap;
2-
use crate::core::{Collect, Gc, GcCell, GcPtr, MutationContext, Trace};
1+
use crate::core::{Gc, GcCell, MutationContext};
2+
use crate::core::{JSWeakMap, PropertyKey};
33
use crate::{
4-
core::{Expr, JSObjectDataPtr, Value, evaluate_expr, obj_get_key_value},
4+
core::{JSObjectDataPtr, Value, env_set, new_js_object_data, obj_get_key_value, obj_set_key_value},
55
error::JSError,
66
unicode::utf8_to_utf16,
77
};
88

99
/// Handle WeakMap constructor calls
1010
pub(crate) fn handle_weakmap_constructor<'gc>(
1111
mc: &MutationContext<'gc>,
12-
args: &[Expr],
12+
args: &[Value<'gc>],
1313
env: &JSObjectDataPtr<'gc>,
1414
) -> Result<Value<'gc>, JSError> {
1515
let weakmap = Gc::new(mc, GcCell::new(JSWeakMap { entries: Vec::new() }));
1616

1717
if !args.is_empty() {
1818
if args.len() == 1 {
19-
// WeakMap(iterable)
20-
initialize_weakmap_from_iterable(mc, &weakmap, args, env)?;
19+
// WeakMap(iterable) - args are already evaluated values
20+
initialize_weakmap_from_iterable(mc, &weakmap, &args[0])?;
2121
} else {
2222
return Err(raise_eval_error!("WeakMap constructor takes at most one argument"));
2323
}
2424
}
2525

26-
Ok(Value::WeakMap(weakmap))
26+
// Create a wrapper object for the WeakMap
27+
let weakmap_obj = new_js_object_data(mc);
28+
// Store the actual weakmap data
29+
weakmap_obj.borrow_mut(mc).insert(
30+
PropertyKey::String("__weakmap__".to_string()),
31+
Gc::new(mc, GcCell::new(Value::WeakMap(weakmap))),
32+
);
33+
34+
// Set prototype to WeakMap.prototype if available
35+
if let Some(weakmap_ctor) = obj_get_key_value(env, &"WeakMap".into())?
36+
&& let Value::Object(ctor) = &*weakmap_ctor.borrow()
37+
&& let Some(proto) = obj_get_key_value(ctor, &"prototype".into())?
38+
&& let Value::Object(proto_obj) = &*proto.borrow()
39+
{
40+
weakmap_obj.borrow_mut(mc).prototype = Some(proto_obj.clone());
41+
}
42+
43+
Ok(Value::Object(weakmap_obj))
2744
}
2845

2946
/// Initialize WeakMap from an iterable
3047
fn initialize_weakmap_from_iterable<'gc>(
3148
mc: &MutationContext<'gc>,
3249
weakmap: &Gc<'gc, GcCell<JSWeakMap<'gc>>>,
33-
args: &[Expr],
34-
env: &JSObjectDataPtr<'gc>,
50+
iterable: &Value<'gc>,
3551
) -> Result<(), JSError> {
36-
let iterable = evaluate_expr(mc, env, &args[0])?;
3752
match iterable {
3853
Value::Object(obj) => {
3954
let mut i = 0;
@@ -72,8 +87,48 @@ fn initialize_weakmap_from_iterable<'gc>(
7287
Ok(())
7388
}
7489

90+
/// Initialize WeakMap constructor and prototype
91+
pub fn initialize_weakmap<'gc>(mc: &MutationContext<'gc>, env: &JSObjectDataPtr<'gc>) -> Result<(), JSError> {
92+
let weakmap_ctor = new_js_object_data(mc);
93+
obj_set_key_value(mc, &weakmap_ctor, &"__is_constructor".into(), Value::Boolean(true))?;
94+
obj_set_key_value(mc, &weakmap_ctor, &"__native_ctor".into(), Value::String(utf8_to_utf16("WeakMap")))?;
95+
96+
// Get Object.prototype
97+
let object_proto = if let Some(obj_val) = obj_get_key_value(env, &"Object".into())?
98+
&& let Value::Object(obj_ctor) = &*obj_val.borrow()
99+
&& let Some(proto_val) = obj_get_key_value(obj_ctor, &"prototype".into())?
100+
&& let Value::Object(proto) = &*proto_val.borrow()
101+
{
102+
Some(*proto)
103+
} else {
104+
None
105+
};
106+
107+
let weakmap_proto = new_js_object_data(mc);
108+
if let Some(proto) = object_proto {
109+
weakmap_proto.borrow_mut(mc).prototype = Some(proto);
110+
}
111+
112+
obj_set_key_value(mc, &weakmap_ctor, &"prototype".into(), Value::Object(weakmap_proto.clone()))?;
113+
obj_set_key_value(mc, &weakmap_proto, &"constructor".into(), Value::Object(weakmap_ctor.clone()))?;
114+
115+
// Register instance methods
116+
let methods = vec!["set", "get", "has", "delete", "toString"];
117+
118+
for method in methods {
119+
let val = Value::Function(format!("WeakMap.prototype.{method}"));
120+
obj_set_key_value(mc, &weakmap_proto, &method.into(), val)?;
121+
weakmap_proto.borrow_mut(mc).set_non_enumerable(PropertyKey::from(method));
122+
}
123+
// Mark constructor non-enumerable
124+
weakmap_proto.borrow_mut(mc).set_non_enumerable(PropertyKey::from("constructor"));
125+
126+
env_set(mc, env, "WeakMap", Value::Object(weakmap_ctor))?;
127+
Ok(())
128+
}
129+
75130
/// Check if WeakMap has a key
76-
fn weakmap_has_key<'gc>(weakmap: &Gc<'gc, GcCell<JSWeakMap<'gc>>>, key_obj_rc: &JSObjectDataPtr<'gc>) -> bool {
131+
fn weakmap_has_key<'gc>(mc: &MutationContext<'gc>, weakmap: &Gc<'gc, GcCell<JSWeakMap<'gc>>>, key_obj_rc: &JSObjectDataPtr<'gc>) -> bool {
77132
let weakmap = weakmap.borrow();
78133
for (k, _) in &weakmap.entries {
79134
if k.upgrade(mc).map_or(false, |p| Gc::ptr_eq(p, *key_obj_rc)) {
@@ -102,16 +157,16 @@ pub(crate) fn handle_weakmap_instance_method<'gc>(
102157
mc: &MutationContext<'gc>,
103158
weakmap: &Gc<'gc, GcCell<JSWeakMap<'gc>>>,
104159
method: &str,
105-
args: &[Expr],
106-
env: &JSObjectDataPtr<'gc>,
160+
args: &[Value<'gc>],
161+
_env: &JSObjectDataPtr<'gc>,
107162
) -> Result<Value<'gc>, JSError> {
108163
match method {
109164
"set" => {
110165
if args.len() != 2 {
111166
return Err(raise_eval_error!("WeakMap.prototype.set requires exactly two arguments"));
112167
}
113-
let key = evaluate_expr(mc, env, &args[0])?;
114-
let value = evaluate_expr(mc, env, &args[1])?;
168+
let key = args[0].clone();
169+
let value = args[1].clone();
115170

116171
// Check if key is an object
117172
let key_obj_rc = match key {
@@ -134,7 +189,7 @@ pub(crate) fn handle_weakmap_instance_method<'gc>(
134189
if args.len() != 1 {
135190
return Err(raise_eval_error!("WeakMap.prototype.get requires exactly one argument"));
136191
}
137-
let key = evaluate_expr(mc, env, &args[0])?;
192+
let key = args[0].clone();
138193

139194
let key_obj_rc = match key {
140195
Value::Object(ref obj) => obj,
@@ -154,20 +209,20 @@ pub(crate) fn handle_weakmap_instance_method<'gc>(
154209
if args.len() != 1 {
155210
return Err(raise_eval_error!("WeakMap.prototype.has requires exactly one argument"));
156211
}
157-
let key = evaluate_expr(mc, env, &args[0])?;
212+
let key = args[0].clone();
158213

159214
let key_obj_rc = match key {
160215
Value::Object(ref obj) => obj,
161216
_ => return Ok(Value::Boolean(false)),
162217
};
163218

164-
Ok(Value::Boolean(weakmap_has_key(weakmap, key_obj_rc)))
219+
Ok(Value::Boolean(weakmap_has_key(mc, weakmap, key_obj_rc)))
165220
}
166221
"delete" => {
167222
if args.len() != 1 {
168223
return Err(raise_eval_error!("WeakMap.prototype.delete requires exactly one argument"));
169224
}
170-
let key = evaluate_expr(mc, env, &args[0])?;
225+
let key = args[0].clone();
171226

172227
let key_obj_rc = match key {
173228
Value::Object(ref obj) => obj,

src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ pub(crate) mod js_string;
3030
pub(crate) mod js_symbol;
3131
// pub(crate) mod js_testintl;
3232
// pub(crate) mod js_typedarray;
33-
// pub(crate) mod js_weakmap;
33+
pub(crate) mod js_weakmap;
3434
// pub(crate) mod js_weakset;
3535
pub(crate) mod repl;
3636
pub(crate) mod unicode;

0 commit comments

Comments
 (0)