Skip to content

Commit 83cfb06

Browse files
committed
Implement WeakMap and WeakSet constructors with full API
- Add JSWeakMap and JSWeakSet structs with weak reference semantics - Implement set/get/has/delete methods for WeakMap, add/has/delete for WeakSet - Update Value enum, typeof evaluation, and method dispatch - Add global constructors and integrate with class system - Update console, string conversion, and FFI support - Add comprehensive tests covering all functionality and error cases
1 parent c7b9771 commit 83cfb06

File tree

13 files changed

+614
-3
lines changed

13 files changed

+614
-3
lines changed

examples/js.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,8 @@ fn print_eval_result(result: &Value) {
6969
Value::BigInt(s) => println!("{s}"),
7070
Value::Map(_) => println!("[object Map]"),
7171
Value::Set(_) => println!("[object Set]"),
72+
Value::WeakMap(_) => println!("[object WeakMap]"),
73+
Value::WeakSet(_) => println!("[object WeakSet]"),
7274
}
7375
}
7476

src/core.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -501,6 +501,18 @@ pub(crate) fn initialize_global_constructors(env: &JSObjectDataPtr) -> Result<()
501501
Rc::new(RefCell::new(Value::Function("Set".to_string()))),
502502
);
503503

504+
// WeakMap constructor
505+
env_borrow.insert(
506+
PropertyKey::String("WeakMap".to_string()),
507+
Rc::new(RefCell::new(Value::Function("WeakMap".to_string()))),
508+
);
509+
510+
// WeakSet constructor
511+
env_borrow.insert(
512+
PropertyKey::String("WeakSet".to_string()),
513+
Rc::new(RefCell::new(Value::Function("WeakSet".to_string()))),
514+
);
515+
504516
// Create a few well-known symbols and store them in the well-known symbol registry
505517
WELL_KNOWN_SYMBOLS.with(|wk| {
506518
let mut map = wk.borrow_mut();

src/core/eval.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2733,6 +2733,8 @@ fn evaluate_typeof(env: &JSObjectDataPtr, expr: &Expr) -> Result<Value, JSError>
27332733
Value::Symbol(_) => "symbol",
27342734
Value::Map(_) => "object",
27352735
Value::Set(_) => "object",
2736+
Value::WeakMap(_) => "object",
2737+
Value::WeakSet(_) => "object",
27362738
};
27372739
Ok(Value::String(utf8_to_utf16(type_str)))
27382740
}
@@ -3784,6 +3786,8 @@ fn evaluate_call(env: &JSObjectDataPtr, func_expr: &Expr, args: &[Expr]) -> Resu
37843786
(Value::Symbol(sd), "valueOf") => crate::js_object::handle_value_of_method(&Value::Symbol(sd.clone()), args),
37853787
(Value::Map(map), method) => crate::js_map::handle_map_instance_method(&map, method, args, env),
37863788
(Value::Set(set), method) => crate::js_set::handle_set_instance_method(&set, method, args, env),
3789+
(Value::WeakMap(weakmap), method) => crate::js_weakmap::handle_weakmap_instance_method(&weakmap, method, args, env),
3790+
(Value::WeakSet(weakset), method) => crate::js_weakset::handle_weakset_instance_method(&weakset, method, args, env),
37873791
(Value::Object(obj_map), method) => {
37883792
// Object prototype methods are supplied on `Object.prototype`.
37893793
// Lookups will find user-defined (own) methods before inherited

src/core/ffi.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -943,8 +943,10 @@ pub unsafe fn JS_Eval(_ctx: *mut JSContext, input: *const i8, input_len: usize,
943943
Ok(Value::Promise(_)) => JS_UNDEFINED, // For now
944944
Ok(Value::Symbol(_)) => JS_UNDEFINED, // For now
945945
Ok(Value::BigInt(_)) => JS_UNDEFINED,
946-
Ok(Value::Map(_)) => JS_UNDEFINED, // For now
947-
Ok(Value::Set(_)) => JS_UNDEFINED, // For now
946+
Ok(Value::Map(_)) => JS_UNDEFINED, // For now
947+
Ok(Value::Set(_)) => JS_UNDEFINED, // For now
948+
Ok(Value::WeakMap(_)) => JS_UNDEFINED, // For now
949+
Ok(Value::WeakSet(_)) => JS_UNDEFINED, // For now
948950
Err(_) => JS_UNDEFINED,
949951
}
950952
}

src/core/value.rs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,16 @@ pub struct JSSet {
1919
pub values: Vec<Value>,
2020
}
2121

22+
#[derive(Clone, Debug)]
23+
pub struct JSWeakMap {
24+
pub entries: Vec<(std::rc::Weak<RefCell<JSObjectData>>, Value)>, // weak key-value pairs
25+
}
26+
27+
#[derive(Clone, Debug)]
28+
pub struct JSWeakSet {
29+
pub values: Vec<std::rc::Weak<RefCell<JSObjectData>>>, // weak values
30+
}
31+
2232
pub type JSObjectDataPtr = Rc<RefCell<JSObjectData>>;
2333

2434
#[derive(Clone, Default)]
@@ -106,6 +116,8 @@ pub enum Value {
106116
Symbol(Rc<SymbolData>), // Symbol primitive with description
107117
Map(Rc<RefCell<JSMap>>), // Map object
108118
Set(Rc<RefCell<JSSet>>), // Set object
119+
WeakMap(Rc<RefCell<JSWeakMap>>), // WeakMap object
120+
WeakSet(Rc<RefCell<JSWeakSet>>), // WeakSet object
109121
}
110122

111123
impl std::fmt::Debug for Value {
@@ -128,6 +140,8 @@ impl std::fmt::Debug for Value {
128140
Value::Symbol(_) => write!(f, "Symbol"),
129141
Value::Map(m) => write!(f, "Map({:p})", Rc::as_ptr(m)),
130142
Value::Set(s) => write!(f, "Set({:p})", Rc::as_ptr(s)),
143+
Value::WeakMap(wm) => write!(f, "WeakMap({:p})", Rc::as_ptr(wm)),
144+
Value::WeakSet(ws) => write!(f, "WeakSet({:p})", Rc::as_ptr(ws)),
131145
}
132146
}
133147
}
@@ -165,6 +179,8 @@ pub fn is_truthy(val: &Value) -> bool {
165179
Value::Symbol(_) => true,
166180
Value::Map(_) => true,
167181
Value::Set(_) => true,
182+
Value::WeakMap(_) => true,
183+
Value::WeakSet(_) => true,
168184
}
169185
}
170186

@@ -205,6 +221,8 @@ pub fn value_to_string(val: &Value) -> String {
205221
},
206222
Value::Map(_) => "[object Map]".to_string(),
207223
Value::Set(_) => "[object Set]".to_string(),
224+
Value::WeakMap(_) => "[object WeakMap]".to_string(),
225+
Value::WeakSet(_) => "[object WeakSet]".to_string(),
208226
}
209227
}
210228

@@ -301,6 +319,8 @@ pub fn value_to_sort_string(val: &Value) -> String {
301319
Value::Symbol(_) => "[object Symbol]".to_string(),
302320
Value::Map(_) => "[object Map]".to_string(),
303321
Value::Set(_) => "[object Set]".to_string(),
322+
Value::WeakMap(_) => "[object WeakMap]".to_string(),
323+
Value::WeakSet(_) => "[object WeakSet]".to_string(),
304324
}
305325
}
306326

src/js_class.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,8 @@ pub(crate) fn evaluate_new(env: &JSObjectDataPtr, constructor: &Expr, args: &[Ex
170170
}
171171
"Map" => return crate::js_map::handle_map_constructor(args, env),
172172
"Set" => return crate::js_set::handle_set_constructor(args, env),
173+
"WeakMap" => return crate::js_weakmap::handle_weakmap_constructor(args, env),
174+
"WeakSet" => return crate::js_weakset::handle_weakset_constructor(args, env),
173175
"MockIntlConstructor" => {
174176
// Handle mock Intl constructor for testing
175177
let locale_arg = if !args.is_empty() {
@@ -668,6 +670,8 @@ pub(crate) fn handle_string_constructor(args: &[Expr], env: &JSObjectDataPtr) ->
668670
Value::BigInt(s) => utf8_to_utf16(&s),
669671
Value::Map(_) => utf8_to_utf16("[object Map]"),
670672
Value::Set(_) => utf8_to_utf16("[object Set]"),
673+
Value::WeakMap(_) => utf8_to_utf16("[object WeakMap]"),
674+
Value::WeakSet(_) => utf8_to_utf16("[object WeakSet]"),
671675
}
672676
};
673677

src/js_console.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,8 @@ pub fn handle_console_method(method: &str, args: &[Expr], env: &JSObjectDataPtr)
107107
Value::Symbol(_) => print!("[object Symbol]"),
108108
Value::Map(_) => print!("[object Map]"),
109109
Value::Set(_) => print!("[object Set]"),
110+
Value::WeakMap(_) => print!("[object WeakMap]"),
111+
Value::WeakSet(_) => print!("[object WeakSet]"),
110112
}
111113
if i < count - 1 {
112114
print!(" ");

src/js_function.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,8 @@ pub fn handle_global_function(func_name: &str, args: &[Expr], env: &JSObjectData
7979
Value::BigInt(s) => Ok(Value::String(utf8_to_utf16(&s))),
8080
Value::Map(_) => Ok(Value::String(utf8_to_utf16("[object Map]"))),
8181
Value::Set(_) => Ok(Value::String(utf8_to_utf16("[object Set]"))),
82+
Value::WeakMap(_) => Ok(Value::String(utf8_to_utf16("[object WeakMap]"))),
83+
Value::WeakSet(_) => Ok(Value::String(utf8_to_utf16("[object WeakSet]"))),
8284
}
8385
} else {
8486
Ok(Value::String(Vec::new())) // String() with no args returns empty string

src/js_object.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -295,6 +295,8 @@ pub(crate) fn handle_to_string_method(obj_val: &Value, args: &[Expr]) -> Result<
295295
Value::Symbol(_) => "Symbol",
296296
Value::Map(_) => "Map",
297297
Value::Set(_) => "Set",
298+
Value::WeakMap(_) => "WeakMap",
299+
Value::WeakSet(_) => "WeakSet",
298300
},
299301
args.len()
300302
)));
@@ -367,6 +369,8 @@ pub(crate) fn handle_to_string_method(obj_val: &Value, args: &[Expr]) -> Result<
367369
}
368370
Value::Map(_) => Ok(Value::String(utf8_to_utf16("[object Map]"))),
369371
Value::Set(_) => Ok(Value::String(utf8_to_utf16("[object Set]"))),
372+
Value::WeakMap(_) => Ok(Value::String(utf8_to_utf16("[object WeakMap]"))),
373+
Value::WeakSet(_) => Ok(Value::String(utf8_to_utf16("[object WeakSet]"))),
370374
}
371375
}
372376

@@ -391,6 +395,8 @@ pub(crate) fn handle_value_of_method(obj_val: &Value, args: &[Expr]) -> Result<V
391395
Value::Symbol(_) => "Symbol",
392396
Value::Map(_) => "Map",
393397
Value::Set(_) => "Set",
398+
Value::WeakMap(_) => "WeakMap",
399+
Value::WeakSet(_) => "WeakSet",
394400
},
395401
args.len()
396402
)));
@@ -429,5 +435,7 @@ pub(crate) fn handle_value_of_method(obj_val: &Value, args: &[Expr]) -> Result<V
429435
Value::Symbol(symbol_data) => Ok(Value::Symbol(symbol_data.clone())),
430436
Value::Map(map) => Ok(Value::Map(map.clone())),
431437
Value::Set(set) => Ok(Value::Set(set.clone())),
438+
Value::WeakMap(weakmap) => Ok(Value::WeakMap(weakmap.clone())),
439+
Value::WeakSet(weakset) => Ok(Value::WeakSet(weakset.clone())),
432440
}
433441
}

src/js_weakmap.rs

Lines changed: 198 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,198 @@
1+
use crate::{
2+
core::{Expr, JSObjectDataPtr, PropertyKey, Value, evaluate_expr},
3+
error::JSError,
4+
raise_eval_error,
5+
unicode::utf8_to_utf16,
6+
};
7+
use std::cell::RefCell;
8+
use std::rc::Rc;
9+
10+
use crate::core::JSWeakMap;
11+
12+
/// Handle WeakMap constructor calls
13+
pub(crate) fn handle_weakmap_constructor(args: &[Expr], env: &JSObjectDataPtr) -> Result<Value, JSError> {
14+
let weakmap = Rc::new(RefCell::new(JSWeakMap { entries: Vec::new() }));
15+
16+
if !args.is_empty() {
17+
if args.len() == 1 {
18+
// WeakMap(iterable)
19+
let iterable = evaluate_expr(env, &args[0])?;
20+
match iterable {
21+
Value::Object(obj) => {
22+
// Try to iterate over the object
23+
let mut i = 0;
24+
loop {
25+
let key = format!("{}", i);
26+
if let Some(entry_val) = obj_get_value(&obj, &key.into())? {
27+
let entry = entry_val.borrow().clone();
28+
if let Value::Object(entry_obj) = entry
29+
&& let (Some(key_val), Some(value_val)) =
30+
(obj_get_value(&entry_obj, &"0".into())?, obj_get_value(&entry_obj, &"1".into())?)
31+
{
32+
let key_obj = key_val.borrow().clone();
33+
let value_obj = value_val.borrow().clone();
34+
35+
// Check if key is an object
36+
if let Value::Object(ref obj) = key_obj {
37+
let weak_key = Rc::downgrade(obj);
38+
weakmap.borrow_mut().entries.push((weak_key, value_obj));
39+
} else {
40+
return Err(raise_eval_error!("WeakMap keys must be objects"));
41+
}
42+
}
43+
} else {
44+
break;
45+
}
46+
i += 1;
47+
}
48+
}
49+
_ => {
50+
return Err(raise_eval_error!("WeakMap constructor requires an iterable"));
51+
}
52+
}
53+
} else {
54+
return Err(raise_eval_error!("WeakMap constructor takes at most one argument"));
55+
}
56+
}
57+
58+
Ok(Value::WeakMap(weakmap))
59+
}
60+
61+
/// Handle WeakMap instance method calls
62+
pub(crate) fn handle_weakmap_instance_method(
63+
weakmap: &Rc<RefCell<JSWeakMap>>,
64+
method: &str,
65+
args: &[Expr],
66+
env: &JSObjectDataPtr,
67+
) -> Result<Value, JSError> {
68+
match method {
69+
"set" => {
70+
if args.len() != 2 {
71+
return Err(raise_eval_error!("WeakMap.prototype.set requires exactly two arguments"));
72+
}
73+
let key = evaluate_expr(env, &args[0])?;
74+
let value = evaluate_expr(env, &args[1])?;
75+
76+
// Check if key is an object
77+
let key_obj_rc = match key {
78+
Value::Object(ref obj) => obj.clone(),
79+
_ => return Err(raise_eval_error!("WeakMap keys must be objects")),
80+
};
81+
82+
let weak_key = Rc::downgrade(&key_obj_rc);
83+
84+
// Remove existing entry with same key (if still alive)
85+
weakmap.borrow_mut().entries.retain(|(k, _)| {
86+
if let Some(strong_k) = k.upgrade() {
87+
!Rc::ptr_eq(&key_obj_rc, &strong_k)
88+
} else {
89+
false // Remove dead entries
90+
}
91+
});
92+
93+
// Add new entry
94+
weakmap.borrow_mut().entries.push((weak_key, value));
95+
96+
Ok(Value::WeakMap(weakmap.clone()))
97+
}
98+
"get" => {
99+
if args.len() != 1 {
100+
return Err(raise_eval_error!("WeakMap.prototype.get requires exactly one argument"));
101+
}
102+
let key = evaluate_expr(env, &args[0])?;
103+
104+
let key_obj_rc = match key {
105+
Value::Object(ref obj) => obj,
106+
_ => return Ok(Value::Undefined),
107+
};
108+
109+
// Clean up dead entries and find the key
110+
let mut result = None;
111+
weakmap.borrow_mut().entries.retain(|(k, v)| {
112+
if let Some(strong_k) = k.upgrade() {
113+
if Rc::ptr_eq(key_obj_rc, &strong_k) {
114+
result = Some(v.clone());
115+
}
116+
true // Keep alive entries
117+
} else {
118+
false // Remove dead entries
119+
}
120+
});
121+
122+
Ok(result.unwrap_or(Value::Undefined))
123+
}
124+
"has" => {
125+
if args.len() != 1 {
126+
return Err(raise_eval_error!("WeakMap.prototype.has requires exactly one argument"));
127+
}
128+
let key = evaluate_expr(env, &args[0])?;
129+
130+
let key_obj_rc = match key {
131+
Value::Object(ref obj) => obj,
132+
_ => return Ok(Value::Boolean(false)),
133+
};
134+
135+
// Clean up dead entries and check if key exists
136+
let mut found = false;
137+
weakmap.borrow_mut().entries.retain(|(k, _)| {
138+
if let Some(strong_k) = k.upgrade() {
139+
if Rc::ptr_eq(key_obj_rc, &strong_k) {
140+
found = true;
141+
}
142+
true // Keep alive entries
143+
} else {
144+
false // Remove dead entries
145+
}
146+
});
147+
148+
Ok(Value::Boolean(found))
149+
}
150+
"delete" => {
151+
if args.len() != 1 {
152+
return Err(raise_eval_error!("WeakMap.prototype.delete requires exactly one argument"));
153+
}
154+
let key = evaluate_expr(env, &args[0])?;
155+
156+
let key_obj_rc = match key {
157+
Value::Object(ref obj) => obj,
158+
_ => return Ok(Value::Boolean(false)),
159+
};
160+
161+
// Clean up dead entries and remove the key
162+
let mut deleted = false;
163+
weakmap.borrow_mut().entries.retain(|(k, _)| {
164+
if let Some(strong_k) = k.upgrade() {
165+
if Rc::ptr_eq(key_obj_rc, &strong_k) {
166+
deleted = true;
167+
false // Remove this entry
168+
} else {
169+
true // Keep other alive entries
170+
}
171+
} else {
172+
false // Remove dead entries
173+
}
174+
});
175+
176+
Ok(Value::Boolean(deleted))
177+
}
178+
"toString" => {
179+
if !args.is_empty() {
180+
return Err(raise_eval_error!("WeakMap.prototype.toString takes no arguments"));
181+
}
182+
Ok(Value::String(utf8_to_utf16("[object WeakMap]")))
183+
}
184+
_ => Err(raise_eval_error!(format!("WeakMap.prototype.{} is not implemented", method))),
185+
}
186+
}
187+
188+
// Helper function to get object property value
189+
fn obj_get_value(js_obj: &JSObjectDataPtr, key: &PropertyKey) -> Result<Option<Rc<RefCell<Value>>>, JSError> {
190+
let mut current: Option<JSObjectDataPtr> = Some(js_obj.clone());
191+
while let Some(cur) = current {
192+
if let Some(val) = cur.borrow().properties.get(key) {
193+
return Ok(Some(val.clone()));
194+
}
195+
current = cur.borrow().prototype.clone();
196+
}
197+
Ok(None)
198+
}

0 commit comments

Comments
 (0)