Skip to content

Commit 2c46b69

Browse files
Add weakref support to c-api (RustPython#8006)
1 parent 74b4707 commit 2c46b69

3 files changed

Lines changed: 137 additions & 7 deletions

File tree

crates/capi/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ pub mod traceback;
3030
pub mod tupleobject;
3131
pub mod unicodeobject;
3232
mod util;
33+
pub mod weakrefobject;
3334

3435
/// Get main interpreter of this process. Will be None if it has not been initialized yet.
3536
pub fn get_main_interpreter() -> MutexGuard<'static, Option<Interpreter>> {

crates/capi/src/weakrefobject.rs

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
use crate::PyObject;
2+
use crate::object::define_py_check;
3+
use crate::pystate::with_vm;
4+
use core::ffi::c_int;
5+
use rustpython_vm::builtins::{PyWeak, PyWeakProxy};
6+
7+
define_py_check!(fn PyWeakref_CheckProxy, types.weakproxy_type);
8+
define_py_check!(fn PyWeakref_CheckRef, types.weakref_type);
9+
10+
#[unsafe(no_mangle)]
11+
pub unsafe extern "C" fn PyWeakref_GetRef(
12+
reference: *mut PyObject,
13+
result: *mut *mut PyObject,
14+
) -> c_int {
15+
with_vm(|vm| {
16+
unsafe {
17+
*result = core::ptr::null_mut();
18+
}
19+
20+
let reference = unsafe { &*reference };
21+
let upgraded = if let Some(weak) = reference.downcast_ref::<PyWeak>() {
22+
weak.upgrade()
23+
} else if let Some(proxy) = reference.downcast_ref::<PyWeakProxy>() {
24+
proxy.get_weak().upgrade()
25+
} else {
26+
return Err(vm.new_type_error("expected a weakref"));
27+
};
28+
29+
if let Some(obj) = upgraded {
30+
unsafe {
31+
*result = obj.into_raw().as_ptr();
32+
}
33+
Ok(true)
34+
} else {
35+
Ok(false)
36+
}
37+
})
38+
}
39+
40+
#[unsafe(no_mangle)]
41+
pub unsafe extern "C" fn PyWeakref_NewProxy(
42+
ob: *mut PyObject,
43+
callback: *mut PyObject,
44+
) -> *mut PyObject {
45+
with_vm(|vm| {
46+
let ob = unsafe { &*ob };
47+
let callback = unsafe { callback.as_ref() }
48+
.filter(|callback| !vm.is_none(callback))
49+
.map(ToOwned::to_owned);
50+
PyWeakProxy::new_weakproxy(ob, callback, vm)
51+
})
52+
}
53+
54+
#[unsafe(no_mangle)]
55+
pub unsafe extern "C" fn PyWeakref_NewRef(
56+
ob: *mut PyObject,
57+
callback: *mut PyObject,
58+
) -> *mut PyObject {
59+
with_vm(|vm| {
60+
let ob = unsafe { &*ob };
61+
let callback = unsafe { callback.as_ref() }
62+
.filter(|callback| !vm.is_none(callback))
63+
.map(ToOwned::to_owned);
64+
ob.downgrade(callback, vm)
65+
})
66+
}
67+
68+
#[cfg(false)]
69+
mod tests {
70+
use pyo3::prelude::*;
71+
use pyo3::types::PyAnyMethods;
72+
use pyo3::types::{PyInt, PyWeakrefMethods, PyWeakrefProxy, PyWeakrefReference};
73+
74+
#[test]
75+
fn check_ref_and_proxy() {
76+
Python::attach(|py| {
77+
let object_ty = py.get_type::<PyInt>();
78+
79+
let weak_ref = PyWeakrefReference::new(&object_ty).unwrap();
80+
let weak_proxy = PyWeakrefProxy::new(&object_ty).unwrap();
81+
82+
assert!(weak_ref.is_instance_of::<PyWeakrefReference>());
83+
assert!(weak_proxy.is_instance_of::<PyWeakrefProxy>());
84+
});
85+
}
86+
87+
#[test]
88+
fn new_ref_and_get_ref() {
89+
Python::attach(|py| {
90+
let object_ty = py.get_type::<PyInt>();
91+
let weak_ref = PyWeakrefReference::new(&object_ty).unwrap();
92+
93+
assert!(weak_ref.upgrade().is_some_and(|obj| obj.is(&object_ty)));
94+
});
95+
}
96+
97+
#[test]
98+
fn new_proxy() {
99+
Python::attach(|py| {
100+
let object_ty = py.get_type::<PyInt>();
101+
let weak_proxy = PyWeakrefProxy::new(&object_ty).unwrap();
102+
103+
assert!(weak_proxy.upgrade().is_some_and(|obj| obj.is(&object_ty)));
104+
});
105+
}
106+
}

crates/vm/src/builtins/weakproxy.rs

Lines changed: 30 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,22 @@ impl Constructor for PyWeakProxy {
4242
Self::Args { referent, callback }: Self::Args,
4343
vm: &VirtualMachine,
4444
) -> PyResult<Self> {
45+
let weak = Self::new_weak(referent.as_ref(), callback.into_option(), vm)?;
46+
// TODO: PyWeakProxy should use the same payload as PyWeak
47+
Ok(Self { weak })
48+
}
49+
}
50+
51+
crate::common::static_cell! {
52+
static WEAK_SUBCLASS: PyTypeRef;
53+
}
54+
55+
impl PyWeakProxy {
56+
fn new_weak(
57+
referent: &PyObject,
58+
callback: Option<PyObjectRef>,
59+
vm: &VirtualMachine,
60+
) -> PyResult<PyRef<PyWeak>> {
4561
// using an internal subclass as the class prevents us from getting the generic weakref,
4662
// which would mess up the weakref count
4763
let weak_cls = WEAK_SUBCLASS.get_or_init(|| {
@@ -52,15 +68,22 @@ impl Constructor for PyWeakProxy {
5268
super::PyWeak::make_slots(),
5369
)
5470
});
55-
// TODO: PyWeakProxy should use the same payload as PyWeak
56-
Ok(Self {
57-
weak: referent.downgrade_with_typ(callback.into_option(), weak_cls.clone(), vm)?,
58-
})
71+
referent.downgrade_with_typ(callback, weak_cls.clone(), vm)
5972
}
60-
}
6173

62-
crate::common::static_cell! {
63-
static WEAK_SUBCLASS: PyTypeRef;
74+
pub fn new_weakproxy(
75+
referent: &PyObject,
76+
callback: Option<PyObjectRef>,
77+
vm: &VirtualMachine,
78+
) -> PyResult<PyRef<Self>> {
79+
let weak = Self::new_weak(referent, callback, vm)?;
80+
Ok(Self { weak }.into_ref(&vm.ctx))
81+
}
82+
83+
#[must_use]
84+
pub fn get_weak(&self) -> &PyRef<PyWeak> {
85+
&self.weak
86+
}
6487
}
6588

6689
#[pyclass(with(

0 commit comments

Comments
 (0)