Skip to content
51 changes: 50 additions & 1 deletion tests/snippets/weakrefs.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,62 @@
import sys
from _weakref import ref

from testutils import assert_raises

data_holder = {}


class X:
pass
def __init__(self, param=0):
self.param = param

def __str__(self):
return f"param: {self.param}"


a = X()
b = ref(a)


def callback(weak_ref):
assert weak_ref is c
assert b() is None, 'reference to same object is dead'
assert c() is None, 'reference is dead'
data_holder['first'] = True


c = ref(a, callback)


def never_callback(_weak_ref):
data_holder['never'] = True


# weakref should be cleaned up before object, so callback is never called
ref(a, never_callback)

assert callable(b)
assert b() is a

assert 'first' not in data_holder
del a
assert b() is None
assert 'first' in data_holder
assert 'never' not in data_holder

# TODO proper detection of RustPython if sys.implementation.name == 'RustPython':
if not hasattr(sys, 'implementation'):
# implementation detail that the object isn't dropped straight away
# this tests that when an object is resurrected it still acts as normal
delayed_drop = X(5)
delayed_drop_ref = ref(delayed_drop)

delayed_drop = None

assert delayed_drop_ref() is not None
value = delayed_drop_ref()
del delayed_drop # triggers process_deletes

assert str(value) == "param: 5"

assert_raises(TypeError, lambda: ref(1), "can't create weak reference to an int")
15 changes: 2 additions & 13 deletions vm/src/builtins.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,7 @@ use super::obj::objstr;
use super::obj::objtype;

use super::pyobject::{
AttributeProtocol, IdProtocol, PyContext, PyFuncArgs, PyObject, PyObjectPayload, PyObjectRef,
PyResult, Scope, TypeProtocol,
AttributeProtocol, IdProtocol, PyContext, PyFuncArgs, PyObjectRef, PyResult, TypeProtocol,
};
use super::stdlib::io::io_open;

Expand Down Expand Up @@ -265,17 +264,7 @@ fn make_scope(vm: &mut VirtualMachine, locals: Option<&PyObjectRef>) -> PyObject
};

// TODO: handle optional globals
// Construct new scope:
let scope_inner = Scope {
locals,
parent: None,
};

PyObject {
payload: PyObjectPayload::Scope { scope: scope_inner },
typ: None,
}
.into_ref()
vm.ctx.new_scope_with_locals(None, locals)
}

fn builtin_format(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult {
Expand Down
6 changes: 6 additions & 0 deletions vm/src/frame.rs
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,8 @@ impl Frame {
}
};

PyObjectRef::process_deletes(vm);

vm.current_frame = prev_frame;
value
}
Expand Down Expand Up @@ -844,6 +846,10 @@ impl Frame {
// Assume here that locals is a dict
let name = vm.ctx.new_str(name.to_string());
vm.call_method(&locals, "__delitem__", vec![name])?;

// process possible delete
PyObjectRef::process_deletes(vm);

Ok(None)
}

Expand Down
1 change: 1 addition & 0 deletions vm/src/obj/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,4 +28,5 @@ pub mod objstr;
pub mod objsuper;
pub mod objtuple;
pub mod objtype;
pub mod objweakref;
pub mod objzip;
6 changes: 3 additions & 3 deletions vm/src/obj/objstr.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use super::super::format::{FormatParseError, FormatPart, FormatString};
use super::super::pyobject::{
PyContext, PyFuncArgs, PyObject, PyObjectPayload, PyObjectRef, PyResult, TypeProtocol,
PyContext, PyFuncArgs, PyObjectPayload, PyObjectRef, PyResult, TypeProtocol,
};
use super::super::vm::VirtualMachine;
use super::objint;
Expand Down Expand Up @@ -1126,8 +1126,8 @@ pub fn subscript(vm: &mut VirtualMachine, value: &str, b: PyObjectRef) -> PyResu

// help get optional string indices
fn get_slice(
start: Option<&std::rc::Rc<std::cell::RefCell<PyObject>>>,
end: Option<&std::rc::Rc<std::cell::RefCell<PyObject>>>,
start: Option<&PyObjectRef>,
end: Option<&PyObjectRef>,
len: usize,
) -> Result<(usize, usize), String> {
let start_idx = match start {
Expand Down
2 changes: 1 addition & 1 deletion vm/src/obj/objtype.rs
Original file line number Diff line number Diff line change
Expand Up @@ -232,7 +232,7 @@ pub fn get_attributes(obj: &PyObjectRef) -> PyAttributes {
}

// Get instance attributes:
if let PyObjectPayload::Instance { dict } = &obj.borrow().payload {
if let PyObjectPayload::Instance { dict, .. } = &obj.borrow().payload {
for (name, value) in dict.borrow().iter() {
attributes.insert(name.to_string(), value.clone());
}
Expand Down
86 changes: 86 additions & 0 deletions vm/src/obj/objweakref.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
use super::super::pyobject::{
PyContext, PyFuncArgs, PyObject, PyObjectPayload, PyObjectRef, PyObjectWeakRef, PyResult,
TypeProtocol,
};
use super::super::vm::VirtualMachine;
use super::objtype; // Required for arg_check! to use isinstance

fn ref_new(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult {
// TODO: check first argument for subclass of `ref`.
arg_check!(
vm,
args,
required = [(cls, Some(vm.ctx.type_type())), (referent, None)],
optional = [(callback, None)]
);
let weak_referent = PyObjectRef::downgrade(referent);
let weakref = PyObject::new(
PyObjectPayload::WeakRef {
referent: weak_referent,
callback: callback.cloned(),
},
cls.clone(),
);
if referent.borrow_mut().add_weakref(&weakref) {
Ok(weakref)
} else {
let referent_repr = vm.to_pystr(&referent.typ())?;
Err(vm.new_type_error(format!(
"cannot create weak reference to '{}' object",
referent_repr
)))
}
}

/// Dereference the weakref, and check if we still refer something.
fn ref_call(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult {
arg_check!(vm, args, required = [(zelf, Some(vm.ctx.weakref_type()))]);
let referent = get_value(zelf);
let py_obj = if let Some(obj) = referent.upgrade() {
obj
} else {
vm.get_none()
};
Ok(py_obj)
}

fn get_value(obj: &PyObjectRef) -> PyObjectWeakRef {
if let PyObjectPayload::WeakRef { referent, .. } = &obj.borrow().payload {
referent.clone()
} else {
panic!("Inner error getting weak ref {:?}", obj);
}
}

fn get_callback(obj: &PyObjectRef) -> Option<PyObjectRef> {
if let PyObjectPayload::WeakRef { callback, .. } = &obj.borrow().payload {
callback.as_ref().cloned()
} else {
panic!("Inner error getting weak ref callback {:?}", obj);
}
}

pub fn clear_weak_ref(obj: &PyObjectRef) {
if let PyObjectPayload::WeakRef {
ref mut referent, ..
} = &mut obj.borrow_mut().payload
{
referent.clear();
} else {
panic!("Inner error getting weak ref {:?}", obj);
}
}

pub fn notify_weak_ref(vm: &mut VirtualMachine, obj: PyObjectRef) -> PyResult {
if let Some(callback) = get_callback(&obj) {
vm.invoke(callback.clone(), PyFuncArgs::new(vec![obj], vec![]))
} else {
Ok(vm.get_none())
}
}

pub fn init(context: &PyContext) {
let weakref_type = &context.weakref_type;
context.set_attr(weakref_type, "__new__", context.new_rustfunc(ref_new));
context.set_attr(weakref_type, "__call__", context.new_rustfunc(ref_call));
}
Loading