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 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}