Skip to content

Commit 7b74e73

Browse files
committed
Integrate WeakSet into JS engine
1 parent 20dbfad commit 7b74e73

File tree

5 files changed

+107
-17
lines changed

5 files changed

+107
-17
lines changed

js-scripts/weakset_tests.js

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

src/core/eval.rs

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1722,6 +1722,33 @@ pub fn evaluate_expr<'gc>(mc: &MutationContext<'gc>, env: &JSObjectDataPtr<'gc>,
17221722
} else {
17231723
Err(EvalError::Js(raise_eval_error!(format!("Unknown Map function: {}", name))))
17241724
}
1725+
} else if name.starts_with("WeakSet.") {
1726+
if let Some(method) = name.strip_prefix("WeakSet.prototype.") {
1727+
let this_v = this_val.clone().unwrap_or(Value::Undefined);
1728+
if let Value::Object(obj) = this_v {
1729+
if let Some(ws_val) = obj_get_key_value(&obj, &"__weakset__".into())? {
1730+
if let Value::WeakSet(ws_ptr) = &*ws_val.borrow() {
1731+
Ok(crate::js_weakset::handle_weakset_instance_method(mc, ws_ptr, method, &eval_args)?)
1732+
} else {
1733+
Err(EvalError::Js(raise_eval_error!(
1734+
"TypeError: WeakSet.prototype method called on incompatible receiver"
1735+
)))
1736+
}
1737+
} else {
1738+
Err(EvalError::Js(raise_eval_error!(
1739+
"TypeError: WeakSet.prototype method called on incompatible receiver"
1740+
)))
1741+
}
1742+
} else if let Value::WeakSet(ws_ptr) = this_v {
1743+
Ok(crate::js_weakset::handle_weakset_instance_method(mc, &ws_ptr, method, &eval_args)?)
1744+
} else {
1745+
Err(EvalError::Js(raise_eval_error!(
1746+
"TypeError: WeakSet.prototype method called on non-object receiver"
1747+
)))
1748+
}
1749+
} else {
1750+
Err(EvalError::Js(raise_eval_error!(format!("Unknown Map function: {}", name))))
1751+
}
17251752
} else if name.starts_with("Set.") {
17261753
if let Some(method) = name.strip_prefix("Set.prototype.") {
17271754
let this_v = this_val.clone().unwrap_or(Value::Undefined);
@@ -2038,6 +2065,8 @@ pub fn evaluate_expr<'gc>(mc: &MutationContext<'gc>, env: &JSObjectDataPtr<'gc>,
20382065
return Ok(crate::js_map::handle_map_constructor(mc, &eval_args, env)?);
20392066
} else if name == &crate::unicode::utf8_to_utf16("WeakMap") {
20402067
return Ok(crate::js_weakmap::handle_weakmap_constructor(mc, &eval_args, env)?);
2068+
} else if name == &crate::unicode::utf8_to_utf16("WeakSet") {
2069+
return Ok(crate::js_weakset::handle_weakset_constructor(mc, &eval_args, env)?);
20412070
} else if name == &crate::unicode::utf8_to_utf16("Set") {
20422071
return Ok(crate::js_set::handle_set_constructor(mc, &eval_args, env)?);
20432072
}

src/core/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ use crate::js_set::initialize_set;
1212
use crate::js_string::initialize_string;
1313
use crate::js_symbol::initialize_symbol;
1414
use crate::js_weakmap::initialize_weakmap;
15+
use crate::js_weakset::initialize_weakset;
1516
use crate::raise_eval_error;
1617
use crate::unicode::utf8_to_utf16;
1718
pub(crate) use gc_arena::GcWeak;
@@ -91,6 +92,7 @@ pub fn initialize_global_constructors<'gc>(mc: &MutationContext<'gc>, env: &JSOb
9192
initialize_json(mc, env)?;
9293
initialize_map(mc, env)?;
9394
initialize_weakmap(mc, env)?;
95+
initialize_weakset(mc, env)?;
9496
initialize_symbol(mc, env)?;
9597
initialize_set(mc, env)?;
9698

src/js_weakset.rs

Lines changed: 69 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,40 +1,54 @@
1-
use crate::core::JSWeakSet;
2-
use crate::core::{Collect, Gc, GcCell, GcPtr, MutationContext, Trace};
1+
use crate::core::{Gc, GcCell, MutationContext};
2+
use crate::core::{JSWeakSet, 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
};
8-
use std::rc::Rc;
98

109
/// Handle WeakSet constructor calls
1110
pub(crate) fn handle_weakset_constructor<'gc>(
1211
mc: &MutationContext<'gc>,
13-
args: &[Expr],
12+
args: &[Value<'gc>],
1413
env: &JSObjectDataPtr<'gc>,
1514
) -> Result<Value<'gc>, JSError> {
1615
let weakset = Gc::new(mc, GcCell::new(JSWeakSet { values: Vec::new() }));
1716

1817
if !args.is_empty() {
1918
if args.len() == 1 {
20-
// WeakSet(iterable)
21-
initialize_weakset_from_iterable(mc, &weakset, args, env)?;
19+
// WeakSet(iterable) - args are already evaluated values
20+
initialize_weakset_from_iterable(mc, &weakset, &args[0])?;
2221
} else {
2322
return Err(raise_eval_error!("WeakSet constructor takes at most one argument"));
2423
}
2524
}
2625

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

3046
/// Initialize WeakSet from an iterable
3147
fn initialize_weakset_from_iterable<'gc>(
3248
mc: &MutationContext<'gc>,
3349
weakset: &Gc<'gc, GcCell<JSWeakSet<'gc>>>,
34-
args: &[Expr],
35-
env: &JSObjectDataPtr<'gc>,
50+
iterable: &Value<'gc>,
3651
) -> Result<(), JSError> {
37-
let iterable = evaluate_expr(mc, env, &args[0])?;
3852
match iterable {
3953
Value::Object(obj) => {
4054
let mut i = 0;
@@ -63,6 +77,46 @@ fn initialize_weakset_from_iterable<'gc>(
6377
Ok(())
6478
}
6579

80+
/// Initialize WeakSet constructor and prototype
81+
pub fn initialize_weakset<'gc>(mc: &MutationContext<'gc>, env: &JSObjectDataPtr<'gc>) -> Result<(), JSError> {
82+
let weakset_ctor = new_js_object_data(mc);
83+
obj_set_key_value(mc, &weakset_ctor, &"__is_constructor".into(), Value::Boolean(true))?;
84+
obj_set_key_value(mc, &weakset_ctor, &"__native_ctor".into(), Value::String(utf8_to_utf16("WeakSet")))?;
85+
86+
// Get Object.prototype
87+
let object_proto = if let Some(obj_val) = obj_get_key_value(env, &"Object".into())?
88+
&& let Value::Object(obj_ctor) = &*obj_val.borrow()
89+
&& let Some(proto_val) = obj_get_key_value(obj_ctor, &"prototype".into())?
90+
&& let Value::Object(proto) = &*proto_val.borrow()
91+
{
92+
Some(*proto)
93+
} else {
94+
None
95+
};
96+
97+
let weakset_proto = new_js_object_data(mc);
98+
if let Some(proto) = object_proto {
99+
weakset_proto.borrow_mut(mc).prototype = Some(proto);
100+
}
101+
102+
obj_set_key_value(mc, &weakset_ctor, &"prototype".into(), Value::Object(weakset_proto.clone()))?;
103+
obj_set_key_value(mc, &weakset_proto, &"constructor".into(), Value::Object(weakset_ctor.clone()))?;
104+
105+
// Register instance methods
106+
let methods = vec!["add", "has", "delete", "toString"];
107+
108+
for method in methods {
109+
let val = Value::Function(format!("WeakSet.prototype.{}", method));
110+
obj_set_key_value(mc, &weakset_proto, &method.into(), val)?;
111+
weakset_proto.borrow_mut(mc).set_non_enumerable(PropertyKey::from(method));
112+
}
113+
// Mark constructor non-enumerable
114+
weakset_proto.borrow_mut(mc).set_non_enumerable(PropertyKey::from("constructor"));
115+
116+
env_set(mc, env, "WeakSet", Value::Object(weakset_ctor))?;
117+
Ok(())
118+
}
119+
66120
/// Check if WeakSet has a value and clean up dead entries
67121
fn weakset_has_value<'gc>(
68122
mc: &MutationContext<'gc>,
@@ -110,15 +164,14 @@ pub(crate) fn handle_weakset_instance_method<'gc>(
110164
mc: &MutationContext<'gc>,
111165
weakset: &Gc<'gc, GcCell<JSWeakSet<'gc>>>,
112166
method: &str,
113-
args: &[Expr],
114-
env: &JSObjectDataPtr<'gc>,
167+
args: &[Value<'gc>],
115168
) -> Result<Value<'gc>, JSError> {
116169
match method {
117170
"add" => {
118171
if args.len() != 1 {
119172
return Err(raise_eval_error!("WeakSet.prototype.add requires exactly one argument"));
120173
}
121-
let value = evaluate_expr(mc, env, &args[0])?;
174+
let value = args[0].clone();
122175

123176
// Check if value is an object
124177
let value_obj_rc = match value {
@@ -146,7 +199,7 @@ pub(crate) fn handle_weakset_instance_method<'gc>(
146199
if args.len() != 1 {
147200
return Err(raise_eval_error!("WeakSet.prototype.has requires exactly one argument"));
148201
}
149-
let value = evaluate_expr(mc, env, &args[0])?;
202+
let value = args[0].clone();
150203

151204
let value_obj_rc = match value {
152205
Value::Object(ref obj) => obj,
@@ -159,7 +212,7 @@ pub(crate) fn handle_weakset_instance_method<'gc>(
159212
if args.len() != 1 {
160213
return Err(raise_eval_error!("WeakSet.prototype.delete requires exactly one argument"));
161214
}
162-
let value = evaluate_expr(mc, env, &args[0])?;
215+
let value = args[0].clone();
163216

164217
let value_obj_rc = match value {
165218
Value::Object(ref obj) => obj,

src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ pub(crate) mod js_symbol;
3131
// pub(crate) mod js_testintl;
3232
// pub(crate) mod js_typedarray;
3333
pub(crate) mod js_weakmap;
34-
// pub(crate) mod js_weakset;
34+
pub(crate) mod js_weakset;
3535
pub(crate) mod repl;
3636
pub(crate) mod unicode;
3737

0 commit comments

Comments
 (0)