1use crate::err::PyResult;
2use crate::ffi_ptr_ext::FfiPtrExt;
3use crate::py_result_ext::PyResultExt;
4use crate::types::any::PyAny;
5use crate::{ffi, Borrowed, Bound, BoundObject, IntoPyObject, IntoPyObjectExt};
6
7#[cfg(any(PyPy, GraalPy, Py_LIMITED_API))]
8use crate::type_object::PyTypeCheck;
9
10use super::PyWeakrefMethods;
11
12#[repr(transparent)]
16pub struct PyWeakrefReference(PyAny);
17
18#[cfg(not(any(PyPy, GraalPy, Py_LIMITED_API)))]
19pyobject_subclassable_native_type!(PyWeakrefReference, crate::ffi::PyWeakReference);
20
21#[cfg(not(any(PyPy, GraalPy, Py_LIMITED_API)))]
22pyobject_native_type!(
23 PyWeakrefReference,
24 ffi::PyWeakReference,
25 pyobject_native_static_type_object!(ffi::_PyWeakref_RefType),
27 #module=Some("weakref"),
28 #checkfunction=ffi::PyWeakref_CheckRefExact
29);
30
31#[cfg(any(PyPy, GraalPy, Py_LIMITED_API))]
33pyobject_native_type_named!(PyWeakrefReference);
34
35#[cfg(any(PyPy, GraalPy, Py_LIMITED_API))]
36impl PyTypeCheck for PyWeakrefReference {
37 const NAME: &'static str = "weakref.ReferenceType";
38 #[cfg(feature = "experimental-inspect")]
39 const PYTHON_TYPE: &'static str = "weakref.ReferenceType";
40
41 fn type_check(object: &Bound<'_, PyAny>) -> bool {
42 unsafe { ffi::PyWeakref_CheckRef(object.as_ptr()) > 0 }
43 }
44}
45
46impl PyWeakrefReference {
47 #[cfg_attr(
53 not(all(feature = "macros", not(all(Py_LIMITED_API, not(Py_3_9))))),
54 doc = "```rust,ignore"
55 )]
56 #[cfg_attr(
57 all(feature = "macros", not(all(Py_LIMITED_API, not(Py_3_9)))),
58 doc = "```rust"
59 )]
60 pub fn new<'py>(object: &Bound<'py, PyAny>) -> PyResult<Bound<'py, PyWeakrefReference>> {
86 unsafe {
87 Bound::from_owned_ptr_or_err(
88 object.py(),
89 ffi::PyWeakref_NewRef(object.as_ptr(), ffi::Py_None()),
90 )
91 .cast_into_unchecked()
92 }
93 }
94
95 #[cfg_attr(
101 not(all(feature = "macros", not(all(Py_LIMITED_API, not(Py_3_9))))),
102 doc = "```rust,ignore"
103 )]
104 #[cfg_attr(
105 all(feature = "macros", not(all(Py_LIMITED_API, not(Py_3_9)))),
106 doc = "```rust"
107 )]
108 pub fn new_with<'py, C>(
150 object: &Bound<'py, PyAny>,
151 callback: C,
152 ) -> PyResult<Bound<'py, PyWeakrefReference>>
153 where
154 C: IntoPyObject<'py>,
155 {
156 fn inner<'py>(
157 object: &Bound<'py, PyAny>,
158 callback: Borrowed<'_, 'py, PyAny>,
159 ) -> PyResult<Bound<'py, PyWeakrefReference>> {
160 unsafe {
161 Bound::from_owned_ptr_or_err(
162 object.py(),
163 ffi::PyWeakref_NewRef(object.as_ptr(), callback.as_ptr()),
164 )
165 .cast_into_unchecked()
166 }
167 }
168
169 let py = object.py();
170 inner(
171 object,
172 callback
173 .into_pyobject_or_pyerr(py)?
174 .into_any()
175 .as_borrowed(),
176 )
177 }
178}
179
180impl<'py> PyWeakrefMethods<'py> for Bound<'py, PyWeakrefReference> {
181 fn upgrade(&self) -> Option<Bound<'py, PyAny>> {
182 let mut obj: *mut ffi::PyObject = std::ptr::null_mut();
183 match unsafe { ffi::compat::PyWeakref_GetRef(self.as_ptr(), &mut obj) } {
184 std::ffi::c_int::MIN..=-1 => panic!("The 'weakref.ReferenceType' instance should be valid (non-null and actually a weakref reference)"),
185 0 => None,
186 1..=std::ffi::c_int::MAX => Some(unsafe { obj.assume_owned_unchecked(self.py()) }),
187 }
188 }
189}
190
191#[cfg(test)]
192mod tests {
193 use crate::types::any::{PyAny, PyAnyMethods};
194 use crate::types::weakref::{PyWeakrefMethods, PyWeakrefReference};
195 use crate::{Bound, PyResult, Python};
196
197 #[cfg(all(not(Py_LIMITED_API), Py_3_10))]
198 const CLASS_NAME: &str = "<class 'weakref.ReferenceType'>";
199 #[cfg(all(not(Py_LIMITED_API), not(Py_3_10)))]
200 const CLASS_NAME: &str = "<class 'weakref'>";
201
202 fn check_repr(
203 reference: &Bound<'_, PyWeakrefReference>,
204 object: Option<(&Bound<'_, PyAny>, &str)>,
205 ) -> PyResult<()> {
206 let repr = reference.repr()?.to_string();
207 let (first_part, second_part) = repr.split_once("; ").unwrap();
208
209 {
210 let (msg, addr) = first_part.split_once("0x").unwrap();
211
212 assert_eq!(msg, "<weakref at ");
213 assert!(addr
214 .to_lowercase()
215 .contains(format!("{:x?}", reference.as_ptr()).split_at(2).1));
216 }
217
218 match object {
219 Some((object, class)) => {
220 let (msg, addr) = second_part.split_once("0x").unwrap();
221
222 assert!(msg.starts_with("to '"));
224 assert!(msg.contains(class));
225 assert!(msg.ends_with("' at "));
226
227 assert!(addr
228 .to_lowercase()
229 .contains(format!("{:x?}", object.as_ptr()).split_at(2).1));
230 }
231 None => {
232 assert_eq!(second_part, "dead>")
233 }
234 }
235
236 Ok(())
237 }
238
239 mod python_class {
240 use super::*;
241 use crate::ffi;
242 use crate::{py_result_ext::PyResultExt, types::PyType};
243 use std::ptr;
244
245 fn get_type(py: Python<'_>) -> PyResult<Bound<'_, PyType>> {
246 py.run(ffi::c_str!("class A:\n pass\n"), None, None)?;
247 py.eval(ffi::c_str!("A"), None, None).cast_into::<PyType>()
248 }
249
250 #[test]
251 fn test_weakref_reference_behavior() -> PyResult<()> {
252 Python::attach(|py| {
253 let class = get_type(py)?;
254 let object = class.call0()?;
255 let reference = PyWeakrefReference::new(&object)?;
256
257 assert!(!reference.is(&object));
258 assert!(reference.upgrade().unwrap().is(&object));
259
260 #[cfg(not(Py_LIMITED_API))]
261 assert_eq!(reference.get_type().to_string(), CLASS_NAME);
262
263 #[cfg(not(Py_LIMITED_API))]
264 assert_eq!(reference.getattr("__class__")?.to_string(), CLASS_NAME);
265
266 #[cfg(not(Py_LIMITED_API))]
267 check_repr(&reference, Some((object.as_any(), "A")))?;
268
269 assert!(reference
270 .getattr("__callback__")
271 .is_ok_and(|result| result.is_none()));
272
273 assert!(reference.call0()?.is(&object));
274
275 drop(object);
276
277 assert!(reference.upgrade().is_none());
278 #[cfg(not(Py_LIMITED_API))]
279 assert_eq!(reference.getattr("__class__")?.to_string(), CLASS_NAME);
280 check_repr(&reference, None)?;
281
282 assert!(reference
283 .getattr("__callback__")
284 .is_ok_and(|result| result.is_none()));
285
286 assert!(reference.call0()?.is_none());
287
288 Ok(())
289 })
290 }
291
292 #[test]
293 fn test_weakref_upgrade_as() -> PyResult<()> {
294 Python::attach(|py| {
295 let class = get_type(py)?;
296 let object = class.call0()?;
297 let reference = PyWeakrefReference::new(&object)?;
298
299 {
300 let obj = reference.upgrade_as::<PyAny>();
302
303 assert!(obj.is_ok());
304 let obj = obj.unwrap();
305
306 assert!(obj.is_some());
307 assert!(obj.is_some_and(|obj| ptr::eq(obj.as_ptr(), object.as_ptr())
308 && obj.is_exact_instance(&class)));
309 }
310
311 drop(object);
312
313 {
314 let obj = reference.upgrade_as::<PyAny>();
316
317 assert!(obj.is_ok());
318 let obj = obj.unwrap();
319
320 assert!(obj.is_none());
321 }
322
323 Ok(())
324 })
325 }
326
327 #[test]
328 fn test_weakref_upgrade_as_unchecked() -> PyResult<()> {
329 Python::attach(|py| {
330 let class = get_type(py)?;
331 let object = class.call0()?;
332 let reference = PyWeakrefReference::new(&object)?;
333
334 {
335 let obj = unsafe { reference.upgrade_as_unchecked::<PyAny>() };
337
338 assert!(obj.is_some());
339 assert!(obj.is_some_and(|obj| ptr::eq(obj.as_ptr(), object.as_ptr())
340 && obj.is_exact_instance(&class)));
341 }
342
343 drop(object);
344
345 {
346 let obj = unsafe { reference.upgrade_as_unchecked::<PyAny>() };
348
349 assert!(obj.is_none());
350 }
351
352 Ok(())
353 })
354 }
355
356 #[test]
357 fn test_weakref_upgrade() -> PyResult<()> {
358 Python::attach(|py| {
359 let class = get_type(py)?;
360 let object = class.call0()?;
361 let reference = PyWeakrefReference::new(&object)?;
362
363 assert!(reference.call0()?.is(&object));
364 assert!(reference.upgrade().is_some());
365 assert!(reference.upgrade().is_some_and(|obj| obj.is(&object)));
366
367 drop(object);
368
369 assert!(reference.call0()?.is_none());
370 assert!(reference.upgrade().is_none());
371
372 Ok(())
373 })
374 }
375 }
376
377 #[cfg(all(feature = "macros", not(all(Py_LIMITED_API, not(Py_3_9)))))]
379 mod pyo3_pyclass {
380 use super::*;
381 use crate::{pyclass, Py};
382 use std::ptr;
383
384 #[pyclass(weakref, crate = "crate")]
385 struct WeakrefablePyClass {}
386
387 #[test]
388 fn test_weakref_reference_behavior() -> PyResult<()> {
389 Python::attach(|py| {
390 let object: Bound<'_, WeakrefablePyClass> = Bound::new(py, WeakrefablePyClass {})?;
391 let reference = PyWeakrefReference::new(&object)?;
392
393 assert!(!reference.is(&object));
394 assert!(reference.upgrade().unwrap().is(&object));
395 #[cfg(not(Py_LIMITED_API))]
396 assert_eq!(reference.get_type().to_string(), CLASS_NAME);
397
398 #[cfg(not(Py_LIMITED_API))]
399 assert_eq!(reference.getattr("__class__")?.to_string(), CLASS_NAME);
400 #[cfg(not(Py_LIMITED_API))]
401 check_repr(&reference, Some((object.as_any(), "WeakrefablePyClass")))?;
402
403 assert!(reference
404 .getattr("__callback__")
405 .is_ok_and(|result| result.is_none()));
406
407 assert!(reference.call0()?.is(&object));
408
409 drop(object);
410
411 assert!(reference.upgrade().is_none());
412 #[cfg(not(Py_LIMITED_API))]
413 assert_eq!(reference.getattr("__class__")?.to_string(), CLASS_NAME);
414 check_repr(&reference, None)?;
415
416 assert!(reference
417 .getattr("__callback__")
418 .is_ok_and(|result| result.is_none()));
419
420 assert!(reference.call0()?.is_none());
421
422 Ok(())
423 })
424 }
425
426 #[test]
427 fn test_weakref_upgrade_as() -> PyResult<()> {
428 Python::attach(|py| {
429 let object = Py::new(py, WeakrefablePyClass {})?;
430 let reference = PyWeakrefReference::new(object.bind(py))?;
431
432 {
433 let obj = reference.upgrade_as::<WeakrefablePyClass>();
434
435 assert!(obj.is_ok());
436 let obj = obj.unwrap();
437
438 assert!(obj.is_some());
439 assert!(obj.is_some_and(|obj| ptr::eq(obj.as_ptr(), object.as_ptr())));
440 }
441
442 drop(object);
443
444 {
445 let obj = reference.upgrade_as::<WeakrefablePyClass>();
446
447 assert!(obj.is_ok());
448 let obj = obj.unwrap();
449
450 assert!(obj.is_none());
451 }
452
453 Ok(())
454 })
455 }
456
457 #[test]
458 fn test_weakref_upgrade_as_unchecked() -> PyResult<()> {
459 Python::attach(|py| {
460 let object = Py::new(py, WeakrefablePyClass {})?;
461 let reference = PyWeakrefReference::new(object.bind(py))?;
462
463 {
464 let obj = unsafe { reference.upgrade_as_unchecked::<WeakrefablePyClass>() };
465
466 assert!(obj.is_some());
467 assert!(obj.is_some_and(|obj| ptr::eq(obj.as_ptr(), object.as_ptr())));
468 }
469
470 drop(object);
471
472 {
473 let obj = unsafe { reference.upgrade_as_unchecked::<WeakrefablePyClass>() };
474
475 assert!(obj.is_none());
476 }
477
478 Ok(())
479 })
480 }
481
482 #[test]
483 fn test_weakref_upgrade() -> PyResult<()> {
484 Python::attach(|py| {
485 let object = Py::new(py, WeakrefablePyClass {})?;
486 let reference = PyWeakrefReference::new(object.bind(py))?;
487
488 assert!(reference.call0()?.is(&object));
489 assert!(reference.upgrade().is_some());
490 assert!(reference.upgrade().is_some_and(|obj| obj.is(&object)));
491
492 drop(object);
493
494 assert!(reference.call0()?.is_none());
495 assert!(reference.upgrade().is_none());
496
497 Ok(())
498 })
499 }
500 }
501}