1use super::{PyStr, PyStrRef, PyType, PyTypeRef, PyWeak};
2use crate::{
3 atomic_func,
4 class::PyClassImpl,
5 common::hash::PyHash,
6 function::{OptionalArg, PyComparisonValue, PySetterValue},
7 protocol::{PyIter, PyIterReturn, PyMappingMethods, PySequenceMethods},
8 stdlib::builtins::reversed,
9 types::{
10 AsMapping, AsSequence, Comparable, Constructor, GetAttr, Hashable, IterNext, Iterable,
11 PyComparisonOp, Representable, SetAttr,
12 },
13 Context, Py, PyObject, PyObjectRef, PyPayload, PyRef, PyResult, VirtualMachine,
14};
15use once_cell::sync::Lazy;
16
17#[pyclass(module = false, name = "weakproxy", unhashable = true, traverse)]
18#[derive(Debug)]
19pub struct PyWeakProxy {
20 weak: PyRef<PyWeak>,
21}
22
23impl PyPayload for PyWeakProxy {
24 fn class(ctx: &Context) -> &'static Py<PyType> {
25 ctx.types.weakproxy_type
26 }
27}
28
29#[derive(FromArgs)]
30pub struct WeakProxyNewArgs {
31 #[pyarg(positional)]
32 referent: PyObjectRef,
33 #[pyarg(positional, optional)]
34 callback: OptionalArg<PyObjectRef>,
35}
36
37impl Constructor for PyWeakProxy {
38 type Args = WeakProxyNewArgs;
39
40 fn py_new(
41 cls: PyTypeRef,
42 Self::Args { referent, callback }: Self::Args,
43 vm: &VirtualMachine,
44 ) -> PyResult {
45 let weak_cls = WEAK_SUBCLASS.get_or_init(|| {
48 vm.ctx.new_class(
49 None,
50 "__weakproxy",
51 vm.ctx.types.weakref_type.to_owned(),
52 super::PyWeak::make_slots(),
53 )
54 });
55 PyWeakProxy {
57 weak: referent.downgrade_with_typ(callback.into_option(), weak_cls.clone(), vm)?,
58 }
59 .into_ref_with_type(vm, cls)
60 .map(Into::into)
61 }
62}
63
64crate::common::static_cell! {
65 static WEAK_SUBCLASS: PyTypeRef;
66}
67
68#[pyclass(with(
69 GetAttr,
70 SetAttr,
71 Constructor,
72 Comparable,
73 AsSequence,
74 AsMapping,
75 Representable,
76 IterNext
77))]
78impl PyWeakProxy {
79 fn try_upgrade(&self, vm: &VirtualMachine) -> PyResult {
80 self.weak.upgrade().ok_or_else(|| new_reference_error(vm))
81 }
82
83 #[pymethod(magic)]
84 fn str(&self, vm: &VirtualMachine) -> PyResult<PyStrRef> {
85 self.try_upgrade(vm)?.str(vm)
86 }
87
88 fn len(&self, vm: &VirtualMachine) -> PyResult<usize> {
89 self.try_upgrade(vm)?.length(vm)
90 }
91
92 #[pymethod(magic)]
93 fn bool(&self, vm: &VirtualMachine) -> PyResult<bool> {
94 self.try_upgrade(vm)?.is_true(vm)
95 }
96
97 #[pymethod(magic)]
98 fn bytes(&self, vm: &VirtualMachine) -> PyResult {
99 self.try_upgrade(vm)?.bytes(vm)
100 }
101
102 #[pymethod(magic)]
103 fn reversed(&self, vm: &VirtualMachine) -> PyResult {
104 let obj = self.try_upgrade(vm)?;
105 reversed(obj, vm)
106 }
107 #[pymethod(magic)]
108 fn contains(&self, needle: PyObjectRef, vm: &VirtualMachine) -> PyResult<bool> {
109 self.try_upgrade(vm)?.to_sequence().contains(&needle, vm)
110 }
111
112 fn getitem(&self, needle: PyObjectRef, vm: &VirtualMachine) -> PyResult {
113 let obj = self.try_upgrade(vm)?;
114 obj.get_item(&*needle, vm)
115 }
116
117 fn setitem(
118 &self,
119 needle: PyObjectRef,
120 value: PyObjectRef,
121 vm: &VirtualMachine,
122 ) -> PyResult<()> {
123 let obj = self.try_upgrade(vm)?;
124 obj.set_item(&*needle, value, vm)
125 }
126
127 fn delitem(&self, needle: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> {
128 let obj = self.try_upgrade(vm)?;
129 obj.del_item(&*needle, vm)
130 }
131}
132
133impl Iterable for PyWeakProxy {
134 fn iter(zelf: PyRef<Self>, vm: &VirtualMachine) -> PyResult {
135 let obj = zelf.try_upgrade(vm)?;
136 Ok(obj.get_iter(vm)?.into())
137 }
138}
139
140impl IterNext for PyWeakProxy {
141 fn next(zelf: &Py<Self>, vm: &VirtualMachine) -> PyResult<PyIterReturn> {
142 let obj = zelf.try_upgrade(vm)?;
143 PyIter::new(obj).next(vm)
144 }
145}
146
147fn new_reference_error(vm: &VirtualMachine) -> PyRef<super::PyBaseException> {
148 vm.new_exception_msg(
149 vm.ctx.exceptions.reference_error.to_owned(),
150 "weakly-referenced object no longer exists".to_owned(),
151 )
152}
153
154impl GetAttr for PyWeakProxy {
155 fn getattro(zelf: &Py<Self>, name: &Py<PyStr>, vm: &VirtualMachine) -> PyResult {
157 let obj = zelf.try_upgrade(vm)?;
158 obj.get_attr(name, vm)
159 }
160}
161
162impl SetAttr for PyWeakProxy {
163 fn setattro(
164 zelf: &Py<Self>,
165 attr_name: &Py<PyStr>,
166 value: PySetterValue,
167 vm: &VirtualMachine,
168 ) -> PyResult<()> {
169 let obj = zelf.try_upgrade(vm)?;
170 obj.call_set_attr(vm, attr_name, value)
171 }
172}
173
174impl Comparable for PyWeakProxy {
175 fn cmp(
176 zelf: &Py<Self>,
177 other: &PyObject,
178 op: PyComparisonOp,
179 vm: &VirtualMachine,
180 ) -> PyResult<PyComparisonValue> {
181 let obj = zelf.try_upgrade(vm)?;
182 Ok(PyComparisonValue::Implemented(
183 obj.rich_compare_bool(other, op, vm)?,
184 ))
185 }
186}
187
188impl AsSequence for PyWeakProxy {
189 fn as_sequence() -> &'static PySequenceMethods {
190 static AS_SEQUENCE: Lazy<PySequenceMethods> = Lazy::new(|| PySequenceMethods {
191 length: atomic_func!(|seq, vm| PyWeakProxy::sequence_downcast(seq).len(vm)),
192 contains: atomic_func!(|seq, needle, vm| {
193 PyWeakProxy::sequence_downcast(seq).contains(needle.to_owned(), vm)
194 }),
195 ..PySequenceMethods::NOT_IMPLEMENTED
196 });
197 &AS_SEQUENCE
198 }
199}
200
201impl AsMapping for PyWeakProxy {
202 fn as_mapping() -> &'static PyMappingMethods {
203 static AS_MAPPING: PyMappingMethods = PyMappingMethods {
204 length: atomic_func!(|mapping, vm| PyWeakProxy::mapping_downcast(mapping).len(vm)),
205 subscript: atomic_func!(|mapping, needle, vm| {
206 PyWeakProxy::mapping_downcast(mapping).getitem(needle.to_owned(), vm)
207 }),
208 ass_subscript: atomic_func!(|mapping, needle, value, vm| {
209 let zelf = PyWeakProxy::mapping_downcast(mapping);
210 if let Some(value) = value {
211 zelf.setitem(needle.to_owned(), value, vm)
212 } else {
213 zelf.delitem(needle.to_owned(), vm)
214 }
215 }),
216 };
217 &AS_MAPPING
218 }
219}
220
221impl Representable for PyWeakProxy {
222 #[inline]
223 fn repr(zelf: &Py<Self>, vm: &VirtualMachine) -> PyResult<PyStrRef> {
224 zelf.try_upgrade(vm)?.repr(vm)
225 }
226
227 #[cold]
228 fn repr_str(_zelf: &Py<Self>, _vm: &VirtualMachine) -> PyResult<String> {
229 unreachable!("use repr instead")
230 }
231}
232
233pub fn init(context: &Context) {
234 PyWeakProxy::extend_class(context, context.types.weakproxy_type);
235}
236
237impl Hashable for PyWeakProxy {
238 fn hash(zelf: &Py<Self>, vm: &VirtualMachine) -> PyResult<PyHash> {
239 zelf.try_upgrade(vm)?.hash(vm)
240 }
241}