mirror of
https://github.com/RustPython/RustPython.git
synced 2026-10-28 10:44:36 +08:00
Add __callback__ property to weakref type (#7590)
* Initial plan * Add __callback__ property to PyWeak for weakref compatibility Add get_callback() method to PyWeak in core.rs that safely reads the callback field under the stripe lock. Expose it as a read-only __callback__ property on the weakref type, matching CPython behavior: - Returns the callback function when one was provided and referent is alive - Returns None when no callback was provided - Returns None after the referent has been collected - Raises AttributeError on assignment (read-only) Remove @unittest.expectedFailure from test_callback_attribute and test_callback_attribute_after_deletion in test_weakref.py. Add regression tests to extra_tests/snippets/stdlib_weakref.py. Agent-Logs-Url: https://github.com/RustPython/RustPython/sessions/a8689daa-4476-4645-a935-0e13c7f7bb42 Co-authored-by: youknowone <69878+youknowone@users.noreply.github.com> * Fix ruff formatting in stdlib_weakref.py snippet Agent-Logs-Url: https://github.com/RustPython/RustPython/sessions/4995198f-e083-4dac-823a-166fcf54adc4 Co-authored-by: youknowone <69878+youknowone@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: youknowone <69878+youknowone@users.noreply.github.com>
This commit is contained in:
1 parent
030da76d4e
commit
76755524df
4 files changed
+52
-2
No files matched your search
Vendored
-2
@@ -993,7 +993,6 @@ class ReferencesTestCase(TestBase):
|
||||
del root
|
||||
gc.collect()
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON
|
||||
def test_callback_attribute(self):
|
||||
x = Object(1)
|
||||
callback = lambda ref: None
|
||||
@@ -1003,7 +1002,6 @@ class ReferencesTestCase(TestBase):
|
||||
ref2 = weakref.ref(x)
|
||||
self.assertIsNone(ref2.__callback__)
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON
|
||||
def test_callback_attribute_after_deletion(self):
|
||||
x = Object(1)
|
||||
ref = weakref.ref(x, self.callback)
|
||||
|
||||
@@ -85,6 +85,11 @@ impl Initializer for PyWeak {
|
||||
flags(BASETYPE)
|
||||
)]
|
||||
impl PyWeak {
|
||||
#[pygetset]
|
||||
fn __callback__(&self, vm: &VirtualMachine) -> PyObjectRef {
|
||||
vm.unwrap_or_none(self.get_callback())
|
||||
}
|
||||
|
||||
#[pyclassmethod]
|
||||
fn __class_getitem__(cls: PyTypeRef, args: PyObjectRef, vm: &VirtualMachine) -> PyGenericAlias {
|
||||
PyGenericAlias::from_args(cls, args, vm)
|
||||
|
||||
@@ -852,6 +852,29 @@ impl PyWeak {
|
||||
self.wr_object.load(Ordering::Acquire).is_null()
|
||||
}
|
||||
|
||||
/// Get the callback associated with this weak reference.
|
||||
/// Returns `None` if there is no callback or if the referent has been
|
||||
/// collected (at which point the callback was already consumed).
|
||||
pub(crate) fn get_callback(&self) -> Option<PyObjectRef> {
|
||||
let obj_ptr = self.wr_object.load(Ordering::Acquire);
|
||||
if obj_ptr.is_null() {
|
||||
// Dead weakref: callback was consumed during clear
|
||||
return None;
|
||||
}
|
||||
|
||||
let _lock = weakref_lock::lock(obj_ptr as usize);
|
||||
|
||||
// Double-check under lock (clear may have run between our check and lock)
|
||||
let obj_ptr = self.wr_object.load(Ordering::Relaxed);
|
||||
if obj_ptr.is_null() {
|
||||
return None;
|
||||
}
|
||||
|
||||
// Safety: we hold the stripe lock that protects the callback field
|
||||
let callback = unsafe { &*self.callback.get() };
|
||||
callback.clone()
|
||||
}
|
||||
|
||||
/// weakref_dealloc: remove from list if still linked.
|
||||
fn drop_inner(&self) {
|
||||
let obj_ptr = self.wr_object.load(Ordering::Acquire);
|
||||
|
||||
@@ -13,6 +13,30 @@ b = ref(a)
|
||||
assert callable(b)
|
||||
assert b() is a
|
||||
|
||||
# Test __callback__ property
|
||||
assert b.__callback__ is None, (
|
||||
"weakref without callback should have __callback__ == None"
|
||||
)
|
||||
|
||||
callback = lambda r: None
|
||||
c = ref(a, callback)
|
||||
assert c.__callback__ is callback, "weakref with callback should return the callback"
|
||||
|
||||
# Test __callback__ is read-only
|
||||
try:
|
||||
c.__callback__ = lambda r: None
|
||||
assert False, "Setting __callback__ should raise AttributeError"
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
# Test __callback__ after referent deletion
|
||||
x = X()
|
||||
cb = lambda r: None
|
||||
w = ref(x, cb)
|
||||
assert w.__callback__ is cb
|
||||
del x
|
||||
assert w.__callback__ is None, "__callback__ should be None after referent is collected"
|
||||
|
||||
|
||||
class G:
|
||||
def __init__(self, h):
|
||||
|
||||
Reference in new issue
Block a user