Skip to main content

rustpython_vm/builtins/
mappingproxy.rs

1use super::{PyDict, PyDictRef, PyGenericAlias, PyList, PyTuple, PyType, PyTypeRef};
2use crate::{
3    AsObject, Context, Py, PyObject, PyObjectRef, PyPayload, PyRef, PyResult, VirtualMachine,
4    atomic_func,
5    class::PyClassImpl,
6    common::{hash, lock::LazyLock},
7    convert::ToPyObject,
8    function::{ArgMapping, OptionalArg, PyComparisonValue},
9    object::{Traverse, TraverseFn},
10    protocol::{PyMappingMethods, PyNumberMethods, PySequenceMethods},
11    types::{
12        AsMapping, AsNumber, AsSequence, Comparable, Constructor, Hashable, Iterable,
13        PyComparisonOp, Representable,
14    },
15};
16use rustpython_common::wtf8::{Wtf8Buf, wtf8_concat};
17
18#[pyclass(module = false, name = "mappingproxy", traverse)]
19#[derive(Debug)]
20pub struct PyMappingProxy {
21    mapping: MappingProxyInner,
22}
23
24#[derive(Debug)]
25enum MappingProxyInner {
26    Class(PyTypeRef),
27    Mapping(ArgMapping),
28}
29
30unsafe impl Traverse for MappingProxyInner {
31    fn traverse(&self, tracer_fn: &mut TraverseFn<'_>) {
32        match self {
33            Self::Class(r) => r.traverse(tracer_fn),
34            Self::Mapping(arg) => arg.traverse(tracer_fn),
35        }
36    }
37}
38
39impl PyPayload for PyMappingProxy {
40    #[inline]
41    fn class(ctx: &Context) -> &'static Py<PyType> {
42        ctx.types.mappingproxy_type
43    }
44}
45
46impl From<PyTypeRef> for PyMappingProxy {
47    fn from(dict: PyTypeRef) -> Self {
48        Self {
49            mapping: MappingProxyInner::Class(dict),
50        }
51    }
52}
53
54impl From<PyDictRef> for PyMappingProxy {
55    fn from(dict: PyDictRef) -> Self {
56        Self {
57            mapping: MappingProxyInner::Mapping(ArgMapping::from_dict_exact(dict)),
58        }
59    }
60}
61
62impl Constructor for PyMappingProxy {
63    type Args = PyObjectRef;
64
65    fn py_new(_cls: &Py<PyType>, mapping: Self::Args, vm: &VirtualMachine) -> PyResult<Self> {
66        if mapping.mapping_unchecked().check()
67            && !mapping.downcastable::<PyList>()
68            && !mapping.downcastable::<PyTuple>()
69        {
70            return Ok(Self {
71                mapping: MappingProxyInner::Mapping(ArgMapping::new(mapping)),
72            });
73        }
74        Err(vm.new_type_error(format!(
75            "mappingproxy() argument must be a mapping, not {}",
76            mapping.class()
77        )))
78    }
79}
80
81#[pyclass(with(
82    AsMapping,
83    Iterable,
84    Constructor,
85    AsSequence,
86    Comparable,
87    Hashable,
88    AsNumber,
89    Representable
90))]
91impl PyMappingProxy {
92    fn get_inner(&self, key: PyObjectRef, vm: &VirtualMachine) -> PyResult<Option<PyObjectRef>> {
93        match &self.mapping {
94            MappingProxyInner::Class(class) => Ok(key
95                .as_interned_str(vm)
96                .and_then(|key| class.attributes.read().get(key).cloned())),
97            MappingProxyInner::Mapping(mapping) => mapping.mapping().subscript(&*key, vm).map(Some),
98        }
99    }
100
101    #[pymethod]
102    fn get(
103        &self,
104        key: PyObjectRef,
105        default: OptionalArg,
106        vm: &VirtualMachine,
107    ) -> PyResult<Option<PyObjectRef>> {
108        let obj = self.to_object(vm)?;
109        Ok(Some(vm.call_method(
110            &obj,
111            "get",
112            (key, default.unwrap_or_none(vm)),
113        )?))
114    }
115
116    pub fn __getitem__(&self, key: PyObjectRef, vm: &VirtualMachine) -> PyResult {
117        self.get_inner(key.clone(), vm)?
118            .ok_or_else(|| vm.new_key_error(key))
119    }
120
121    fn _contains(&self, key: &PyObject, vm: &VirtualMachine) -> PyResult<bool> {
122        match &self.mapping {
123            MappingProxyInner::Class(class) => Ok(key
124                .as_interned_str(vm)
125                .is_some_and(|key| class.attributes.read().contains_key(key))),
126            MappingProxyInner::Mapping(mapping) => {
127                mapping.obj().sequence_unchecked().contains(key, vm)
128            }
129        }
130    }
131
132    pub fn __contains__(&self, key: PyObjectRef, vm: &VirtualMachine) -> PyResult<bool> {
133        self._contains(&key, vm)
134    }
135
136    fn to_object(&self, vm: &VirtualMachine) -> PyResult {
137        Ok(match &self.mapping {
138            MappingProxyInner::Mapping(d) => d.as_ref().to_owned(),
139            MappingProxyInner::Class(c) => {
140                PyDict::from_attributes(c.attributes.read().clone(), vm)?.to_pyobject(vm)
141            }
142        })
143    }
144
145    #[pymethod]
146    pub fn items(&self, vm: &VirtualMachine) -> PyResult {
147        let obj = self.to_object(vm)?;
148        vm.call_method(&obj, identifier!(vm, items).as_str(), ())
149    }
150
151    #[pymethod]
152    pub fn keys(&self, vm: &VirtualMachine) -> PyResult {
153        let obj = self.to_object(vm)?;
154        vm.call_method(&obj, identifier!(vm, keys).as_str(), ())
155    }
156
157    #[pymethod]
158    pub fn values(&self, vm: &VirtualMachine) -> PyResult {
159        let obj = self.to_object(vm)?;
160        vm.call_method(&obj, identifier!(vm, values).as_str(), ())
161    }
162
163    #[pymethod]
164    pub fn copy(&self, vm: &VirtualMachine) -> PyResult {
165        match &self.mapping {
166            MappingProxyInner::Mapping(d) => {
167                vm.call_method(d.obj(), identifier!(vm, copy).as_str(), ())
168            }
169            MappingProxyInner::Class(c) => {
170                Ok(PyDict::from_attributes(c.attributes.read().clone(), vm)?.to_pyobject(vm))
171            }
172        }
173    }
174
175    #[pyclassmethod]
176    fn __class_getitem__(cls: PyTypeRef, args: PyObjectRef, vm: &VirtualMachine) -> PyGenericAlias {
177        PyGenericAlias::from_args(cls, args, vm)
178    }
179
180    fn __len__(&self, vm: &VirtualMachine) -> PyResult<usize> {
181        let obj = self.to_object(vm)?;
182        obj.length(vm)
183    }
184
185    #[pymethod]
186    fn __reversed__(&self, vm: &VirtualMachine) -> PyResult {
187        vm.call_method(
188            self.to_object(vm)?.as_object(),
189            identifier!(vm, __reversed__).as_str(),
190            (),
191        )
192    }
193
194    fn __ior__(&self, _args: PyObjectRef, vm: &VirtualMachine) -> PyResult {
195        Err(vm.new_type_error(format!(
196            r#""'|=' is not supported by {}; use '|' instead""#,
197            Self::class(&vm.ctx)
198        )))
199    }
200
201    fn __or__(&self, args: PyObjectRef, vm: &VirtualMachine) -> PyResult {
202        vm._or(self.copy(vm)?.as_ref(), args.as_ref())
203    }
204}
205
206impl Comparable for PyMappingProxy {
207    fn cmp(
208        zelf: &Py<Self>,
209        other: &PyObject,
210        op: PyComparisonOp,
211        vm: &VirtualMachine,
212    ) -> PyResult<PyComparisonValue> {
213        let obj = zelf.to_object(vm)?;
214        Ok(PyComparisonValue::Implemented(
215            obj.rich_compare_bool(other, op, vm)?,
216        ))
217    }
218}
219
220impl Hashable for PyMappingProxy {
221    #[inline]
222    fn hash(zelf: &Py<Self>, vm: &VirtualMachine) -> PyResult<hash::PyHash> {
223        // Delegate hash to the underlying mapping
224        let obj = zelf.to_object(vm)?;
225        obj.hash(vm)
226    }
227}
228
229impl AsMapping for PyMappingProxy {
230    fn as_mapping() -> &'static PyMappingMethods {
231        static AS_MAPPING: LazyLock<PyMappingMethods> = LazyLock::new(|| PyMappingMethods {
232            length: atomic_func!(
233                |mapping, vm| PyMappingProxy::mapping_downcast(mapping).__len__(vm)
234            ),
235            subscript: atomic_func!(|mapping, needle, vm| {
236                PyMappingProxy::mapping_downcast(mapping).__getitem__(needle.to_owned(), vm)
237            }),
238            ..PyMappingMethods::NOT_IMPLEMENTED
239        });
240        &AS_MAPPING
241    }
242}
243
244impl AsSequence for PyMappingProxy {
245    fn as_sequence() -> &'static PySequenceMethods {
246        static AS_SEQUENCE: LazyLock<PySequenceMethods> = LazyLock::new(|| PySequenceMethods {
247            length: atomic_func!(|seq, vm| PyMappingProxy::sequence_downcast(seq).__len__(vm)),
248            contains: atomic_func!(
249                |seq, target, vm| PyMappingProxy::sequence_downcast(seq)._contains(target, vm)
250            ),
251            ..PySequenceMethods::NOT_IMPLEMENTED
252        });
253        &AS_SEQUENCE
254    }
255}
256
257impl AsNumber for PyMappingProxy {
258    fn as_number() -> &'static PyNumberMethods {
259        static AS_NUMBER: PyNumberMethods = PyNumberMethods {
260            or: Some(|a, b, vm| {
261                if let Some(a) = a.downcast_ref::<PyMappingProxy>() {
262                    a.__or__(b.to_pyobject(vm), vm)
263                } else {
264                    Ok(vm.ctx.not_implemented())
265                }
266            }),
267            inplace_or: Some(|a, b, vm| {
268                if let Some(a) = a.downcast_ref::<PyMappingProxy>() {
269                    a.__ior__(b.to_pyobject(vm), vm)
270                } else {
271                    Ok(vm.ctx.not_implemented())
272                }
273            }),
274            ..PyNumberMethods::NOT_IMPLEMENTED
275        };
276        &AS_NUMBER
277    }
278}
279
280impl Iterable for PyMappingProxy {
281    fn iter(zelf: PyRef<Self>, vm: &VirtualMachine) -> PyResult {
282        let obj = zelf.to_object(vm)?;
283        let iter = obj.get_iter(vm)?;
284        Ok(iter.into())
285    }
286}
287
288impl Representable for PyMappingProxy {
289    #[inline]
290    fn repr_wtf8(zelf: &Py<Self>, vm: &VirtualMachine) -> PyResult<Wtf8Buf> {
291        let obj = zelf.to_object(vm)?;
292        Ok(wtf8_concat!("mappingproxy(", obj.repr(vm)?.as_wtf8(), ')'))
293    }
294}
295
296pub fn init(context: &'static Context) {
297    PyMappingProxy::extend_class(context, context.types.mappingproxy_type)
298}