use std::cell::RefCell;
use std::rc::{Rc, Weak};
use crate::builtins::symbol::symbol_key_for;
use crate::error::{StatorError, StatorResult};
use crate::objects::heap_object::HeapObject;
use crate::objects::property_map::PropertyMap;
use crate::objects::value::JsValue;
#[derive(Debug, Clone)]
pub(crate) enum WeakTarget {
Plain(Weak<RefCell<PropertyMap>>),
Heap(Option<*mut HeapObject>),
Symbol(Option<u64>),
}
#[derive(Debug, Clone)]
pub struct JsWeakRef {
target: WeakTarget,
}
pub fn weak_ref_new(target: *mut HeapObject) -> StatorResult<JsWeakRef> {
if target.is_null() {
return Err(StatorError::TypeError(
"WeakRef target must be an object".into(),
));
}
Ok(JsWeakRef {
target: WeakTarget::Heap(Some(target)),
})
}
pub fn weak_ref_new_plain(rc: &Rc<RefCell<PropertyMap>>) -> JsWeakRef {
JsWeakRef {
target: WeakTarget::Plain(Rc::downgrade(rc)),
}
}
pub fn weak_ref_new_symbol(symbol_id: u64) -> StatorResult<JsWeakRef> {
if symbol_key_for(symbol_id).is_some() {
return Err(StatorError::TypeError(
"WeakRef target must be an object or non-registered symbol".into(),
));
}
Ok(JsWeakRef {
target: WeakTarget::Symbol(Some(symbol_id)),
})
}
pub fn weak_ref_deref(wr: &JsWeakRef) -> JsValue {
match &wr.target {
WeakTarget::Plain(weak) => match weak.upgrade() {
Some(rc) => JsValue::PlainObject(rc),
None => JsValue::Undefined,
},
WeakTarget::Heap(Some(ptr)) => JsValue::Object(*ptr),
WeakTarget::Heap(None) => JsValue::Undefined,
WeakTarget::Symbol(Some(symbol_id)) => JsValue::Symbol(*symbol_id),
WeakTarget::Symbol(None) => JsValue::Undefined,
}
}
pub fn weak_ref_clear(wr: &mut JsWeakRef) {
match &mut wr.target {
WeakTarget::Plain(_) => {
wr.target = WeakTarget::Plain(Weak::new());
}
WeakTarget::Heap(opt) => {
*opt = None;
}
WeakTarget::Symbol(symbol_id) => {
*symbol_id = None;
}
}
}
pub fn weak_ref_has_target(wr: &JsWeakRef) -> bool {
match &wr.target {
WeakTarget::Plain(weak) => weak.strong_count() > 0,
WeakTarget::Heap(opt) => opt.is_some(),
WeakTarget::Symbol(symbol_id) => symbol_id.is_some(),
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::builtins::symbol::{symbol_create, symbol_for};
#[test]
fn test_weak_ref_new_with_valid_target() {
let mut obj = HeapObject::new_null();
let wr = weak_ref_new(&raw mut obj);
assert!(wr.is_ok());
}
#[test]
fn test_weak_ref_new_null_target_returns_error() {
let result = weak_ref_new(std::ptr::null_mut());
assert!(result.is_err());
}
#[test]
fn test_weak_ref_new_null_error_is_type_error() {
let err = weak_ref_new(std::ptr::null_mut()).unwrap_err();
assert!(matches!(err, StatorError::TypeError(_)));
}
#[test]
fn test_weak_ref_deref_returns_object() {
let mut obj = HeapObject::new_null();
let ptr = &raw mut obj;
let wr = weak_ref_new(ptr).unwrap();
let val = weak_ref_deref(&wr);
assert!(matches!(val, JsValue::Object(p) if p == ptr));
}
#[test]
fn test_weak_ref_deref_after_clear_returns_undefined() {
let mut obj = HeapObject::new_null();
let mut wr = weak_ref_new(&raw mut obj).unwrap();
weak_ref_clear(&mut wr);
assert_eq!(weak_ref_deref(&wr), JsValue::Undefined);
}
#[test]
fn test_weak_ref_deref_preserves_pointer_identity() {
let mut obj = HeapObject::new_null();
let ptr = &raw mut obj;
let wr = weak_ref_new(ptr).unwrap();
if let JsValue::Object(p) = weak_ref_deref(&wr) {
assert_eq!(p, ptr);
} else {
panic!("deref should return Object");
}
}
#[test]
fn test_weak_ref_clear_makes_deref_undefined() {
let mut obj = HeapObject::new_null();
let mut wr = weak_ref_new(&raw mut obj).unwrap();
weak_ref_clear(&mut wr);
assert_eq!(weak_ref_deref(&wr), JsValue::Undefined);
}
#[test]
fn test_weak_ref_clear_idempotent() {
let mut obj = HeapObject::new_null();
let mut wr = weak_ref_new(&raw mut obj).unwrap();
weak_ref_clear(&mut wr);
weak_ref_clear(&mut wr); assert_eq!(weak_ref_deref(&wr), JsValue::Undefined);
}
#[test]
fn test_weak_ref_has_target_true_initially() {
let mut obj = HeapObject::new_null();
let wr = weak_ref_new(&raw mut obj).unwrap();
assert!(weak_ref_has_target(&wr));
}
#[test]
fn test_weak_ref_has_target_false_after_clear() {
let mut obj = HeapObject::new_null();
let mut wr = weak_ref_new(&raw mut obj).unwrap();
weak_ref_clear(&mut wr);
assert!(!weak_ref_has_target(&wr));
}
#[test]
fn test_weak_ref_distinct_targets() {
let mut a = HeapObject::new_null();
let mut b = HeapObject::new_null();
let pa = &raw mut a;
let pb = &raw mut b;
let wr_a = weak_ref_new(pa).unwrap();
let wr_b = weak_ref_new(pb).unwrap();
if let (JsValue::Object(ra), JsValue::Object(rb)) =
(weak_ref_deref(&wr_a), weak_ref_deref(&wr_b))
{
assert_eq!(ra, pa);
assert_eq!(rb, pb);
assert_ne!(ra, rb);
} else {
panic!("both derefs should return Object");
}
}
#[test]
fn test_weak_ref_clear_one_does_not_affect_other() {
let mut a = HeapObject::new_null();
let mut b = HeapObject::new_null();
let mut wr_a = weak_ref_new(&raw mut a).unwrap();
let wr_b = weak_ref_new(&raw mut b).unwrap();
weak_ref_clear(&mut wr_a);
assert!(!weak_ref_has_target(&wr_a));
assert!(weak_ref_has_target(&wr_b));
}
#[test]
fn test_weak_ref_deref_is_repeatable() {
let mut obj = HeapObject::new_null();
let ptr = &raw mut obj;
let wr = weak_ref_new(ptr).unwrap();
let v1 = weak_ref_deref(&wr);
let v2 = weak_ref_deref(&wr);
assert!(matches!(v1, JsValue::Object(p) if p == ptr));
assert!(matches!(v2, JsValue::Object(p) if p == ptr));
}
#[test]
fn test_weak_ref_new_plain_creates_weak_ref() {
let obj = Rc::new(RefCell::new(PropertyMap::new()));
let wr = weak_ref_new_plain(&obj);
assert!(weak_ref_has_target(&wr));
}
#[test]
fn test_weak_ref_plain_deref_returns_plain_object() {
let obj = Rc::new(RefCell::new(PropertyMap::new()));
let wr = weak_ref_new_plain(&obj);
assert!(matches!(weak_ref_deref(&wr), JsValue::PlainObject(_)));
}
#[test]
fn test_weak_ref_plain_deref_undefined_after_drop() {
let obj = Rc::new(RefCell::new(PropertyMap::new()));
let wr = weak_ref_new_plain(&obj);
drop(obj);
assert_eq!(weak_ref_deref(&wr), JsValue::Undefined);
}
#[test]
fn test_weak_ref_plain_has_target_false_after_drop() {
let obj = Rc::new(RefCell::new(PropertyMap::new()));
let wr = weak_ref_new_plain(&obj);
drop(obj);
assert!(!weak_ref_has_target(&wr));
}
#[test]
fn test_weak_ref_plain_clear() {
let obj = Rc::new(RefCell::new(PropertyMap::new()));
let mut wr = weak_ref_new_plain(&obj);
weak_ref_clear(&mut wr);
assert!(!weak_ref_has_target(&wr));
assert_eq!(weak_ref_deref(&wr), JsValue::Undefined);
}
#[test]
fn test_weak_ref_plain_alive_while_rc_exists() {
let obj = Rc::new(RefCell::new(PropertyMap::new()));
let obj2 = Rc::clone(&obj);
let wr = weak_ref_new_plain(&obj);
drop(obj);
assert!(weak_ref_has_target(&wr));
assert!(matches!(weak_ref_deref(&wr), JsValue::PlainObject(_)));
drop(obj2);
assert!(!weak_ref_has_target(&wr));
assert_eq!(weak_ref_deref(&wr), JsValue::Undefined);
}
#[test]
fn test_weak_ref_plain_deref_is_repeatable() {
let obj = Rc::new(RefCell::new(PropertyMap::new()));
let wr = weak_ref_new_plain(&obj);
let v1 = weak_ref_deref(&wr);
let v2 = weak_ref_deref(&wr);
assert!(matches!(v1, JsValue::PlainObject(_)));
assert!(matches!(v2, JsValue::PlainObject(_)));
}
#[test]
fn test_weak_ref_new_symbol_accepts_non_registered_symbol() {
let symbol_id = symbol_create(Some("local".into()));
let wr = weak_ref_new_symbol(symbol_id).unwrap();
assert_eq!(weak_ref_deref(&wr), JsValue::Symbol(symbol_id));
}
#[test]
fn test_weak_ref_new_symbol_rejects_registered_symbol() {
let symbol_id = symbol_for("shared");
let err = weak_ref_new_symbol(symbol_id).unwrap_err();
assert!(matches!(err, StatorError::TypeError(_)));
}
#[test]
fn test_weak_ref_clear_symbol_target_returns_undefined() {
let symbol_id = symbol_create(Some("clearable".into()));
let mut wr = weak_ref_new_symbol(symbol_id).unwrap();
weak_ref_clear(&mut wr);
assert_eq!(weak_ref_deref(&wr), JsValue::Undefined);
}
}