Skip to main content

pyo3/types/weakref/
proxy.rs

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