pyo3/types/weakref/
proxy.rs

1use crate::err::PyResult;
2use crate::ffi_ptr_ext::FfiPtrExt;
3use crate::py_result_ext::PyResultExt;
4use crate::type_object::PyTypeCheck;
5use crate::types::any::PyAny;
6use crate::{ffi, Borrowed, Bound, BoundObject, IntoPyObject, IntoPyObjectExt};
7
8use super::PyWeakrefMethods;
9
10/// Represents any Python `weakref` Proxy type.
11///
12/// In Python this is created by calling `weakref.proxy`.
13/// This is either a `weakref.ProxyType` or a `weakref.CallableProxyType` (`weakref.ProxyTypes`).
14#[repr(transparent)]
15pub struct PyWeakrefProxy(PyAny);
16
17pyobject_native_type_named!(PyWeakrefProxy);
18
19// TODO: We known the layout but this cannot be implemented, due to the lack of public typeobject pointers. And it is 2 distinct types
20// #[cfg(not(Py_LIMITED_API))]
21// pyobject_native_type_sized!(PyWeakrefProxy, ffi::PyWeakReference);
22
23impl PyTypeCheck for PyWeakrefProxy {
24    const NAME: &'static str = "weakref.ProxyTypes";
25
26    fn type_check(object: &Bound<'_, PyAny>) -> bool {
27        unsafe { ffi::PyWeakref_CheckProxy(object.as_ptr()) > 0 }
28    }
29}
30
31/// TODO: UPDATE DOCS
32impl PyWeakrefProxy {
33    /// Constructs a new Weak Reference (`weakref.proxy`/`weakref.ProxyType`/`weakref.CallableProxyType`) for the given object.
34    ///
35    /// Returns a `TypeError` if `object` is not weak referenceable (Most native types and PyClasses without `weakref` flag).
36    ///
37    /// # Examples
38    #[cfg_attr(
39        not(all(feature = "macros", not(all(Py_LIMITED_API, not(Py_3_9))))),
40        doc = "```rust,ignore"
41    )]
42    #[cfg_attr(
43        all(feature = "macros", not(all(Py_LIMITED_API, not(Py_3_9)))),
44        doc = "```rust"
45    )]
46    /// use pyo3::prelude::*;
47    /// use pyo3::types::PyWeakrefProxy;
48    ///
49    /// #[pyclass(weakref)]
50    /// struct Foo { /* fields omitted */ }
51    ///
52    /// # fn main() -> PyResult<()> {
53    /// Python::with_gil(|py| {
54    ///     let foo = Bound::new(py, Foo {})?;
55    ///     let weakref = PyWeakrefProxy::new(&foo)?;
56    ///     assert!(
57    ///         // In normal situations where a direct `Bound<'py, Foo>` is required use `upgrade::<Foo>`
58    ///         weakref.upgrade()
59    ///             .map_or(false, |obj| obj.is(&foo))
60    ///     );
61    ///
62    ///     let weakref2 = PyWeakrefProxy::new(&foo)?;
63    ///     assert!(weakref.is(&weakref2));
64    ///
65    ///     drop(foo);
66    ///
67    ///     assert!(weakref.upgrade().is_none());
68    ///     Ok(())
69    /// })
70    /// # }
71    /// ```
72    #[inline]
73    pub fn new<'py>(object: &Bound<'py, PyAny>) -> PyResult<Bound<'py, PyWeakrefProxy>> {
74        unsafe {
75            Bound::from_owned_ptr_or_err(
76                object.py(),
77                ffi::PyWeakref_NewProxy(object.as_ptr(), ffi::Py_None()),
78            )
79            .downcast_into_unchecked()
80        }
81    }
82
83    /// Constructs a new Weak Reference (`weakref.proxy`/`weakref.ProxyType`/`weakref.CallableProxyType`) for the given object with a callback.
84    ///
85    /// Returns a `TypeError` if `object` is not weak referenceable (Most native types and PyClasses without `weakref` flag) or if the `callback` is not callable or None.
86    ///
87    /// # Examples
88    #[cfg_attr(
89        not(all(feature = "macros", not(all(Py_LIMITED_API, not(Py_3_9))))),
90        doc = "```rust,ignore"
91    )]
92    #[cfg_attr(
93        all(feature = "macros", not(all(Py_LIMITED_API, not(Py_3_9)))),
94        doc = "```rust"
95    )]
96    /// use pyo3::prelude::*;
97    /// use pyo3::types::PyWeakrefProxy;
98    /// use pyo3::ffi::c_str;
99    ///
100    /// #[pyclass(weakref)]
101    /// struct Foo { /* fields omitted */ }
102    ///
103    /// #[pyfunction]
104    /// fn callback(wref: Bound<'_, PyWeakrefProxy>) -> PyResult<()> {
105    ///         let py = wref.py();
106    ///         assert!(wref.upgrade_as::<Foo>()?.is_none());
107    ///         py.run(c_str!("counter = 1"), None, None)
108    /// }
109    ///
110    /// # fn main() -> PyResult<()> {
111    /// Python::with_gil(|py| {
112    ///     py.run(c_str!("counter = 0"), None, None)?;
113    ///     assert_eq!(py.eval(c_str!("counter"), None, None)?.extract::<u32>()?, 0);
114    ///     let foo = Bound::new(py, Foo{})?;
115    ///
116    ///     // This is fine.
117    ///     let weakref = PyWeakrefProxy::new_with(&foo, py.None())?;
118    ///     assert!(weakref.upgrade_as::<Foo>()?.is_some());
119    ///     assert!(
120    ///         // In normal situations where a direct `Bound<'py, Foo>` is required use `upgrade::<Foo>`
121    ///         weakref.upgrade()
122    ///             .map_or(false, |obj| obj.is(&foo))
123    ///     );
124    ///     assert_eq!(py.eval(c_str!("counter"), None, None)?.extract::<u32>()?, 0);
125    ///
126    ///     let weakref2 = PyWeakrefProxy::new_with(&foo, wrap_pyfunction!(callback, py)?)?;
127    ///     assert!(!weakref.is(&weakref2)); // Not the same weakref
128    ///     assert!(weakref.eq(&weakref2)?);  // But Equal, since they point to the same object
129    ///
130    ///     drop(foo);
131    ///
132    ///     assert!(weakref.upgrade_as::<Foo>()?.is_none());
133    ///     assert_eq!(py.eval(c_str!("counter"), None, None)?.extract::<u32>()?, 1);
134    ///     Ok(())
135    /// })
136    /// # }
137    /// ```
138    #[inline]
139    pub fn new_with<'py, C>(
140        object: &Bound<'py, PyAny>,
141        callback: C,
142    ) -> PyResult<Bound<'py, PyWeakrefProxy>>
143    where
144        C: IntoPyObject<'py>,
145    {
146        fn inner<'py>(
147            object: &Bound<'py, PyAny>,
148            callback: Borrowed<'_, 'py, PyAny>,
149        ) -> PyResult<Bound<'py, PyWeakrefProxy>> {
150            unsafe {
151                Bound::from_owned_ptr_or_err(
152                    object.py(),
153                    ffi::PyWeakref_NewProxy(object.as_ptr(), callback.as_ptr()),
154                )
155                .downcast_into_unchecked()
156            }
157        }
158
159        let py = object.py();
160        inner(
161            object,
162            callback
163                .into_pyobject_or_pyerr(py)?
164                .into_any()
165                .as_borrowed(),
166        )
167    }
168}
169
170impl<'py> PyWeakrefMethods<'py> for Bound<'py, PyWeakrefProxy> {
171    fn upgrade(&self) -> Option<Bound<'py, PyAny>> {
172        let mut obj: *mut ffi::PyObject = std::ptr::null_mut();
173        match unsafe { ffi::compat::PyWeakref_GetRef(self.as_ptr(), &mut obj) } {
174            std::os::raw::c_int::MIN..=-1 => panic!("The 'weakref.ProxyType' (or `weakref.CallableProxyType`) instance should be valid (non-null and actually a weakref reference)"),
175            0 => None,
176            1..=std::os::raw::c_int::MAX => Some(unsafe { obj.assume_owned_unchecked(self.py()) }),
177        }
178    }
179}
180
181#[cfg(test)]
182mod tests {
183    use crate::exceptions::{PyAttributeError, PyReferenceError, PyTypeError};
184    use crate::types::any::{PyAny, PyAnyMethods};
185    use crate::types::weakref::{PyWeakrefMethods, PyWeakrefProxy};
186    use crate::{Bound, PyResult, Python};
187
188    #[cfg(all(Py_3_13, not(Py_LIMITED_API)))]
189    const DEADREF_FIX: Option<&str> = None;
190    #[cfg(all(not(Py_3_13), not(Py_LIMITED_API)))]
191    const DEADREF_FIX: Option<&str> = Some("NoneType");
192
193    #[cfg(not(Py_LIMITED_API))]
194    fn check_repr(
195        reference: &Bound<'_, PyWeakrefProxy>,
196        object: &Bound<'_, PyAny>,
197        class: Option<&str>,
198    ) -> PyResult<()> {
199        let repr = reference.repr()?.to_string();
200
201        #[cfg(Py_3_13)]
202        let (first_part, second_part) = repr.split_once(';').unwrap();
203        #[cfg(not(Py_3_13))]
204        let (first_part, second_part) = repr.split_once(" to ").unwrap();
205
206        {
207            let (msg, addr) = first_part.split_once("0x").unwrap();
208
209            assert_eq!(msg, "<weakproxy at ");
210            assert!(addr
211                .to_lowercase()
212                .contains(format!("{:x?}", reference.as_ptr()).split_at(2).1));
213        }
214
215        if let Some(class) = class.or(DEADREF_FIX) {
216            let (msg, addr) = second_part.split_once("0x").unwrap();
217
218            // Avoids not succeeding at unreliable quotation (Python 3.13-dev adds ' around classname without documenting)
219            #[cfg(Py_3_13)]
220            assert!(msg.starts_with(" to '"));
221            assert!(msg.contains(class));
222            assert!(msg.ends_with(" at "));
223
224            assert!(addr
225                .to_lowercase()
226                .contains(format!("{:x?}", object.as_ptr()).split_at(2).1));
227        } else {
228            assert!(second_part.contains("dead"));
229        }
230
231        Ok(())
232    }
233
234    mod proxy {
235        use super::*;
236
237        #[cfg(all(not(Py_LIMITED_API), Py_3_10))]
238        const CLASS_NAME: &str = "'weakref.ProxyType'";
239        #[cfg(all(not(Py_LIMITED_API), not(Py_3_10)))]
240        const CLASS_NAME: &str = "'weakproxy'";
241
242        mod python_class {
243            use super::*;
244            use crate::ffi;
245            use crate::{py_result_ext::PyResultExt, types::PyDict, types::PyType};
246            use std::ptr;
247
248            fn get_type(py: Python<'_>) -> PyResult<Bound<'_, PyType>> {
249                let globals = PyDict::new(py);
250                py.run(ffi::c_str!("class A:\n    pass\n"), Some(&globals), None)?;
251                py.eval(ffi::c_str!("A"), Some(&globals), None)
252                    .downcast_into::<PyType>()
253            }
254
255            #[test]
256            fn test_weakref_proxy_behavior() -> PyResult<()> {
257                Python::with_gil(|py| {
258                    let class = get_type(py)?;
259                    let object = class.call0()?;
260                    let reference = PyWeakrefProxy::new(&object)?;
261
262                    assert!(!reference.is(&object));
263                    assert!(reference.upgrade().unwrap().is(&object));
264
265                    #[cfg(not(Py_LIMITED_API))]
266                    assert_eq!(
267                        reference.get_type().to_string(),
268                        format!("<class {}>", CLASS_NAME)
269                    );
270
271                    assert_eq!(reference.getattr("__class__")?.to_string(), "<class 'A'>");
272                    #[cfg(not(Py_LIMITED_API))]
273                    check_repr(&reference, &object, Some("A"))?;
274
275                    assert!(reference
276                        .getattr("__callback__")
277                        .err()
278                        .map_or(false, |err| err.is_instance_of::<PyAttributeError>(py)));
279
280                    assert!(reference.call0().err().map_or(false, |err| {
281                        let result = err.is_instance_of::<PyTypeError>(py);
282                        #[cfg(not(Py_LIMITED_API))]
283                        let result = result
284                            & (err.value(py).to_string()
285                                == format!("{} object is not callable", CLASS_NAME));
286                        result
287                    }));
288
289                    drop(object);
290
291                    assert!(reference.upgrade().is_none());
292                    assert!(reference
293                        .getattr("__class__")
294                        .err()
295                        .map_or(false, |err| err.is_instance_of::<PyReferenceError>(py)));
296                    #[cfg(not(Py_LIMITED_API))]
297                    check_repr(&reference, py.None().bind(py), None)?;
298
299                    assert!(reference
300                        .getattr("__callback__")
301                        .err()
302                        .map_or(false, |err| err.is_instance_of::<PyReferenceError>(py)));
303
304                    assert!(reference.call0().err().map_or(false, |err| {
305                        let result = err.is_instance_of::<PyTypeError>(py);
306                        #[cfg(not(Py_LIMITED_API))]
307                        let result = result
308                            & (err.value(py).to_string()
309                                == format!("{} object is not callable", CLASS_NAME));
310                        result
311                    }));
312
313                    Ok(())
314                })
315            }
316
317            #[test]
318            fn test_weakref_upgrade_as() -> PyResult<()> {
319                Python::with_gil(|py| {
320                    let class = get_type(py)?;
321                    let object = class.call0()?;
322                    let reference = PyWeakrefProxy::new(&object)?;
323
324                    {
325                        // This test is a bit weird but ok.
326                        let obj = reference.upgrade_as::<PyAny>();
327
328                        assert!(obj.is_ok());
329                        let obj = obj.unwrap();
330
331                        assert!(obj.is_some());
332                        assert!(
333                            obj.map_or(false, |obj| ptr::eq(obj.as_ptr(), object.as_ptr())
334                                && obj.is_exact_instance(&class))
335                        );
336                    }
337
338                    drop(object);
339
340                    {
341                        // This test is a bit weird but ok.
342                        let obj = reference.upgrade_as::<PyAny>();
343
344                        assert!(obj.is_ok());
345                        let obj = obj.unwrap();
346
347                        assert!(obj.is_none());
348                    }
349
350                    Ok(())
351                })
352            }
353
354            #[test]
355            fn test_weakref_upgrade_as_unchecked() -> PyResult<()> {
356                Python::with_gil(|py| {
357                    let class = get_type(py)?;
358                    let object = class.call0()?;
359                    let reference = PyWeakrefProxy::new(&object)?;
360
361                    {
362                        // This test is a bit weird but ok.
363                        let obj = unsafe { reference.upgrade_as_unchecked::<PyAny>() };
364
365                        assert!(obj.is_some());
366                        assert!(
367                            obj.map_or(false, |obj| ptr::eq(obj.as_ptr(), object.as_ptr())
368                                && obj.is_exact_instance(&class))
369                        );
370                    }
371
372                    drop(object);
373
374                    {
375                        // This test is a bit weird but ok.
376                        let obj = unsafe { reference.upgrade_as_unchecked::<PyAny>() };
377
378                        assert!(obj.is_none());
379                    }
380
381                    Ok(())
382                })
383            }
384
385            #[test]
386            fn test_weakref_upgrade() -> PyResult<()> {
387                Python::with_gil(|py| {
388                    let class = get_type(py)?;
389                    let object = class.call0()?;
390                    let reference = PyWeakrefProxy::new(&object)?;
391
392                    assert!(reference.upgrade().is_some());
393                    assert!(reference.upgrade().map_or(false, |obj| obj.is(&object)));
394
395                    drop(object);
396
397                    assert!(reference.upgrade().is_none());
398
399                    Ok(())
400                })
401            }
402
403            #[test]
404            fn test_weakref_get_object() -> PyResult<()> {
405                Python::with_gil(|py| {
406                    let class = get_type(py)?;
407                    let object = class.call0()?;
408                    let reference = PyWeakrefProxy::new(&object)?;
409
410                    assert!(reference.upgrade().unwrap().is(&object));
411
412                    drop(object);
413
414                    assert!(reference.upgrade().is_none());
415
416                    Ok(())
417                })
418            }
419        }
420
421        // under 'abi3-py37' and 'abi3-py38' PyClass cannot be weakreferencable.
422        #[cfg(all(feature = "macros", not(all(Py_LIMITED_API, not(Py_3_9)))))]
423        mod pyo3_pyclass {
424            use super::*;
425            use crate::{pyclass, Py};
426            use std::ptr;
427
428            #[pyclass(weakref, crate = "crate")]
429            struct WeakrefablePyClass {}
430
431            #[test]
432            fn test_weakref_proxy_behavior() -> PyResult<()> {
433                Python::with_gil(|py| {
434                    let object: Bound<'_, WeakrefablePyClass> =
435                        Bound::new(py, WeakrefablePyClass {})?;
436                    let reference = PyWeakrefProxy::new(&object)?;
437
438                    assert!(!reference.is(&object));
439                    assert!(reference.upgrade().unwrap().is(&object));
440                    #[cfg(not(Py_LIMITED_API))]
441                    assert_eq!(
442                        reference.get_type().to_string(),
443                        format!("<class {}>", CLASS_NAME)
444                    );
445
446                    assert_eq!(
447                        reference.getattr("__class__")?.to_string(),
448                        "<class 'builtins.WeakrefablePyClass'>"
449                    );
450                    #[cfg(not(Py_LIMITED_API))]
451                    check_repr(&reference, object.as_any(), Some("WeakrefablePyClass"))?;
452
453                    assert!(reference
454                        .getattr("__callback__")
455                        .err()
456                        .map_or(false, |err| err.is_instance_of::<PyAttributeError>(py)));
457
458                    assert!(reference.call0().err().map_or(false, |err| {
459                        let result = err.is_instance_of::<PyTypeError>(py);
460                        #[cfg(not(Py_LIMITED_API))]
461                        let result = result
462                            & (err.value(py).to_string()
463                                == format!("{} object is not callable", CLASS_NAME));
464                        result
465                    }));
466
467                    drop(object);
468
469                    assert!(reference.upgrade().is_none());
470                    assert!(reference
471                        .getattr("__class__")
472                        .err()
473                        .map_or(false, |err| err.is_instance_of::<PyReferenceError>(py)));
474                    #[cfg(not(Py_LIMITED_API))]
475                    check_repr(&reference, py.None().bind(py), None)?;
476
477                    assert!(reference
478                        .getattr("__callback__")
479                        .err()
480                        .map_or(false, |err| err.is_instance_of::<PyReferenceError>(py)));
481
482                    assert!(reference.call0().err().map_or(false, |err| {
483                        let result = err.is_instance_of::<PyTypeError>(py);
484                        #[cfg(not(Py_LIMITED_API))]
485                        let result = result
486                            & (err.value(py).to_string()
487                                == format!("{} object is not callable", CLASS_NAME));
488                        result
489                    }));
490
491                    Ok(())
492                })
493            }
494
495            #[test]
496            fn test_weakref_upgrade_as() -> PyResult<()> {
497                Python::with_gil(|py| {
498                    let object = Py::new(py, WeakrefablePyClass {})?;
499                    let reference = PyWeakrefProxy::new(object.bind(py))?;
500
501                    {
502                        let obj = reference.upgrade_as::<WeakrefablePyClass>();
503
504                        assert!(obj.is_ok());
505                        let obj = obj.unwrap();
506
507                        assert!(obj.is_some());
508                        assert!(obj.map_or(false, |obj| ptr::eq(obj.as_ptr(), object.as_ptr())));
509                    }
510
511                    drop(object);
512
513                    {
514                        let obj = reference.upgrade_as::<WeakrefablePyClass>();
515
516                        assert!(obj.is_ok());
517                        let obj = obj.unwrap();
518
519                        assert!(obj.is_none());
520                    }
521
522                    Ok(())
523                })
524            }
525
526            #[test]
527            fn test_weakref_upgrade_as_unchecked() -> PyResult<()> {
528                Python::with_gil(|py| {
529                    let object = Py::new(py, WeakrefablePyClass {})?;
530                    let reference = PyWeakrefProxy::new(object.bind(py))?;
531
532                    {
533                        let obj = unsafe { reference.upgrade_as_unchecked::<WeakrefablePyClass>() };
534
535                        assert!(obj.is_some());
536                        assert!(obj.map_or(false, |obj| ptr::eq(obj.as_ptr(), object.as_ptr())));
537                    }
538
539                    drop(object);
540
541                    {
542                        let obj = unsafe { reference.upgrade_as_unchecked::<WeakrefablePyClass>() };
543
544                        assert!(obj.is_none());
545                    }
546
547                    Ok(())
548                })
549            }
550
551            #[test]
552            fn test_weakref_upgrade() -> PyResult<()> {
553                Python::with_gil(|py| {
554                    let object = Py::new(py, WeakrefablePyClass {})?;
555                    let reference = PyWeakrefProxy::new(object.bind(py))?;
556
557                    assert!(reference.upgrade().is_some());
558                    assert!(reference.upgrade().map_or(false, |obj| obj.is(&object)));
559
560                    drop(object);
561
562                    assert!(reference.upgrade().is_none());
563
564                    Ok(())
565                })
566            }
567        }
568    }
569
570    mod callable_proxy {
571        use super::*;
572
573        #[cfg(all(not(Py_LIMITED_API), Py_3_10))]
574        const CLASS_NAME: &str = "<class 'weakref.CallableProxyType'>";
575        #[cfg(all(not(Py_LIMITED_API), not(Py_3_10)))]
576        const CLASS_NAME: &str = "<class 'weakcallableproxy'>";
577
578        mod python_class {
579            use super::*;
580            use crate::ffi;
581            use crate::{py_result_ext::PyResultExt, types::PyDict, types::PyType};
582            use std::ptr;
583
584            fn get_type(py: Python<'_>) -> PyResult<Bound<'_, PyType>> {
585                let globals = PyDict::new(py);
586                py.run(
587                    ffi::c_str!("class A:\n    def __call__(self):\n        return 'This class is callable!'\n"),
588                    Some(&globals),
589                    None,
590                )?;
591                py.eval(ffi::c_str!("A"), Some(&globals), None)
592                    .downcast_into::<PyType>()
593            }
594
595            #[test]
596            fn test_weakref_proxy_behavior() -> PyResult<()> {
597                Python::with_gil(|py| {
598                    let class = get_type(py)?;
599                    let object = class.call0()?;
600                    let reference = PyWeakrefProxy::new(&object)?;
601
602                    assert!(!reference.is(&object));
603                    assert!(reference.upgrade().unwrap().is(&object));
604                    #[cfg(not(Py_LIMITED_API))]
605                    assert_eq!(reference.get_type().to_string(), CLASS_NAME);
606
607                    assert_eq!(reference.getattr("__class__")?.to_string(), "<class 'A'>");
608                    #[cfg(not(Py_LIMITED_API))]
609                    check_repr(&reference, &object, Some("A"))?;
610
611                    assert!(reference
612                        .getattr("__callback__")
613                        .err()
614                        .map_or(false, |err| err.is_instance_of::<PyAttributeError>(py)));
615
616                    assert_eq!(reference.call0()?.to_string(), "This class is callable!");
617
618                    drop(object);
619
620                    assert!(reference.upgrade().is_none());
621                    assert!(reference
622                        .getattr("__class__")
623                        .err()
624                        .map_or(false, |err| err.is_instance_of::<PyReferenceError>(py)));
625                    #[cfg(not(Py_LIMITED_API))]
626                    check_repr(&reference, py.None().bind(py), None)?;
627
628                    assert!(reference
629                        .getattr("__callback__")
630                        .err()
631                        .map_or(false, |err| err.is_instance_of::<PyReferenceError>(py)));
632
633                    assert!(reference
634                        .call0()
635                        .err()
636                        .map_or(false, |err| err.is_instance_of::<PyReferenceError>(py)
637                            & (err.value(py).to_string()
638                                == "weakly-referenced object no longer exists")));
639
640                    Ok(())
641                })
642            }
643
644            #[test]
645            fn test_weakref_upgrade_as() -> PyResult<()> {
646                Python::with_gil(|py| {
647                    let class = get_type(py)?;
648                    let object = class.call0()?;
649                    let reference = PyWeakrefProxy::new(&object)?;
650
651                    {
652                        // This test is a bit weird but ok.
653                        let obj = reference.upgrade_as::<PyAny>();
654
655                        assert!(obj.is_ok());
656                        let obj = obj.unwrap();
657
658                        assert!(obj.is_some());
659                        assert!(
660                            obj.map_or(false, |obj| ptr::eq(obj.as_ptr(), object.as_ptr())
661                                && obj.is_exact_instance(&class))
662                        );
663                    }
664
665                    drop(object);
666
667                    {
668                        // This test is a bit weird but ok.
669                        let obj = reference.upgrade_as::<PyAny>();
670
671                        assert!(obj.is_ok());
672                        let obj = obj.unwrap();
673
674                        assert!(obj.is_none());
675                    }
676
677                    Ok(())
678                })
679            }
680
681            #[test]
682            fn test_weakref_upgrade_as_unchecked() -> PyResult<()> {
683                Python::with_gil(|py| {
684                    let class = get_type(py)?;
685                    let object = class.call0()?;
686                    let reference = PyWeakrefProxy::new(&object)?;
687
688                    {
689                        // This test is a bit weird but ok.
690                        let obj = unsafe { reference.upgrade_as_unchecked::<PyAny>() };
691
692                        assert!(obj.is_some());
693                        assert!(
694                            obj.map_or(false, |obj| ptr::eq(obj.as_ptr(), object.as_ptr())
695                                && obj.is_exact_instance(&class))
696                        );
697                    }
698
699                    drop(object);
700
701                    {
702                        // This test is a bit weird but ok.
703                        let obj = unsafe { reference.upgrade_as_unchecked::<PyAny>() };
704
705                        assert!(obj.is_none());
706                    }
707
708                    Ok(())
709                })
710            }
711
712            #[test]
713            fn test_weakref_upgrade() -> PyResult<()> {
714                Python::with_gil(|py| {
715                    let class = get_type(py)?;
716                    let object = class.call0()?;
717                    let reference = PyWeakrefProxy::new(&object)?;
718
719                    assert!(reference.upgrade().is_some());
720                    assert!(reference.upgrade().map_or(false, |obj| obj.is(&object)));
721
722                    drop(object);
723
724                    assert!(reference.upgrade().is_none());
725
726                    Ok(())
727                })
728            }
729        }
730
731        // under 'abi3-py37' and 'abi3-py38' PyClass cannot be weakreferencable.
732        #[cfg(all(feature = "macros", not(all(Py_LIMITED_API, not(Py_3_9)))))]
733        mod pyo3_pyclass {
734            use super::*;
735            use crate::{pyclass, pymethods, Py};
736            use std::ptr;
737
738            #[pyclass(weakref, crate = "crate")]
739            struct WeakrefablePyClass {}
740
741            #[pymethods(crate = "crate")]
742            impl WeakrefablePyClass {
743                fn __call__(&self) -> &str {
744                    "This class is callable!"
745                }
746            }
747
748            #[test]
749            fn test_weakref_proxy_behavior() -> PyResult<()> {
750                Python::with_gil(|py| {
751                    let object: Bound<'_, WeakrefablePyClass> =
752                        Bound::new(py, WeakrefablePyClass {})?;
753                    let reference = PyWeakrefProxy::new(&object)?;
754
755                    assert!(!reference.is(&object));
756                    assert!(reference.upgrade().unwrap().is(&object));
757                    #[cfg(not(Py_LIMITED_API))]
758                    assert_eq!(reference.get_type().to_string(), CLASS_NAME);
759
760                    assert_eq!(
761                        reference.getattr("__class__")?.to_string(),
762                        "<class 'builtins.WeakrefablePyClass'>"
763                    );
764                    #[cfg(not(Py_LIMITED_API))]
765                    check_repr(&reference, object.as_any(), Some("WeakrefablePyClass"))?;
766
767                    assert!(reference
768                        .getattr("__callback__")
769                        .err()
770                        .map_or(false, |err| err.is_instance_of::<PyAttributeError>(py)));
771
772                    assert_eq!(reference.call0()?.to_string(), "This class is callable!");
773
774                    drop(object);
775
776                    assert!(reference.upgrade().is_none());
777                    assert!(reference
778                        .getattr("__class__")
779                        .err()
780                        .map_or(false, |err| err.is_instance_of::<PyReferenceError>(py)));
781                    #[cfg(not(Py_LIMITED_API))]
782                    check_repr(&reference, py.None().bind(py), None)?;
783
784                    assert!(reference
785                        .getattr("__callback__")
786                        .err()
787                        .map_or(false, |err| err.is_instance_of::<PyReferenceError>(py)));
788
789                    assert!(reference
790                        .call0()
791                        .err()
792                        .map_or(false, |err| err.is_instance_of::<PyReferenceError>(py)
793                            & (err.value(py).to_string()
794                                == "weakly-referenced object no longer exists")));
795
796                    Ok(())
797                })
798            }
799
800            #[test]
801            fn test_weakref_upgrade_as() -> PyResult<()> {
802                Python::with_gil(|py| {
803                    let object = Py::new(py, WeakrefablePyClass {})?;
804                    let reference = PyWeakrefProxy::new(object.bind(py))?;
805
806                    {
807                        let obj = reference.upgrade_as::<WeakrefablePyClass>();
808
809                        assert!(obj.is_ok());
810                        let obj = obj.unwrap();
811
812                        assert!(obj.is_some());
813                        assert!(obj.map_or(false, |obj| ptr::eq(obj.as_ptr(), object.as_ptr())));
814                    }
815
816                    drop(object);
817
818                    {
819                        let obj = reference.upgrade_as::<WeakrefablePyClass>();
820
821                        assert!(obj.is_ok());
822                        let obj = obj.unwrap();
823
824                        assert!(obj.is_none());
825                    }
826
827                    Ok(())
828                })
829            }
830
831            #[test]
832            fn test_weakref_upgrade_as_unchecked() -> PyResult<()> {
833                Python::with_gil(|py| {
834                    let object = Py::new(py, WeakrefablePyClass {})?;
835                    let reference = PyWeakrefProxy::new(object.bind(py))?;
836
837                    {
838                        let obj = unsafe { reference.upgrade_as_unchecked::<WeakrefablePyClass>() };
839
840                        assert!(obj.is_some());
841                        assert!(obj.map_or(false, |obj| ptr::eq(obj.as_ptr(), object.as_ptr())));
842                    }
843
844                    drop(object);
845
846                    {
847                        let obj = unsafe { reference.upgrade_as_unchecked::<WeakrefablePyClass>() };
848
849                        assert!(obj.is_none());
850                    }
851
852                    Ok(())
853                })
854            }
855
856            #[test]
857            fn test_weakref_upgrade() -> PyResult<()> {
858                Python::with_gil(|py| {
859                    let object = Py::new(py, WeakrefablePyClass {})?;
860                    let reference = PyWeakrefProxy::new(object.bind(py))?;
861
862                    assert!(reference.upgrade().is_some());
863                    assert!(reference.upgrade().map_or(false, |obj| obj.is(&object)));
864
865                    drop(object);
866
867                    assert!(reference.upgrade().is_none());
868
869                    Ok(())
870                })
871            }
872        }
873    }
874}