rustpython_vm/builtins/
object.rs

1use super::{PyDictRef, PyList, PyStr, PyStrRef, PyType, PyTypeRef};
2use crate::common::hash::PyHash;
3use crate::types::PyTypeFlags;
4use crate::{
5    class::PyClassImpl,
6    convert::ToPyResult,
7    function::{Either, FuncArgs, PyArithmeticValue, PyComparisonValue, PySetterValue},
8    types::{Constructor, PyComparisonOp},
9    AsObject, Context, Py, PyObject, PyObjectRef, PyPayload, PyResult, VirtualMachine,
10};
11use itertools::Itertools;
12
13/// object()
14/// --
15///
16/// The base class of the class hierarchy.
17///
18/// When called, it accepts no arguments and returns a new featureless
19/// instance that has no instance attributes and cannot be given any.
20#[pyclass(module = false, name = "object")]
21#[derive(Debug)]
22pub struct PyBaseObject;
23
24impl PyPayload for PyBaseObject {
25    fn class(ctx: &Context) -> &'static Py<PyType> {
26        ctx.types.object_type
27    }
28}
29
30impl Constructor for PyBaseObject {
31    type Args = FuncArgs;
32
33    fn py_new(cls: PyTypeRef, _args: Self::Args, vm: &VirtualMachine) -> PyResult {
34        // more or less __new__ operator
35        let dict = if cls.is(vm.ctx.types.object_type) {
36            None
37        } else {
38            Some(vm.ctx.new_dict())
39        };
40
41        // Ensure that all abstract methods are implemented before instantiating instance.
42        if let Some(abs_methods) = cls.get_attr(identifier!(vm, __abstractmethods__)) {
43            if let Some(unimplemented_abstract_method_count) = abs_methods.length_opt(vm) {
44                let methods: Vec<PyStrRef> = abs_methods.try_to_value(vm)?;
45                let methods: String =
46                    Itertools::intersperse(methods.iter().map(|name| name.as_str()), "', '")
47                        .collect();
48
49                let unimplemented_abstract_method_count = unimplemented_abstract_method_count?;
50                let name = cls.name().to_string();
51
52                match unimplemented_abstract_method_count {
53                    0 => {}
54                    1 => {
55                        return Err(vm.new_type_error(format!(
56                            "class {} without an implementation for abstract method '{}'",
57                            name, methods
58                        )));
59                    }
60                    2.. => {
61                        return Err(vm.new_type_error(format!(
62                            "class {} without an implementation for abstract methods '{}'",
63                            name, methods
64                        )));
65                    }
66                    // TODO: remove `allow` when redox build doesn't complain about it
67                    #[allow(unreachable_patterns)]
68                    _ => unreachable!(),
69                }
70            }
71        }
72
73        Ok(crate::PyRef::new_ref(PyBaseObject, cls, dict).into())
74    }
75}
76
77// TODO: implement _PyType_GetSlotNames properly
78fn type_slot_names(typ: &Py<PyType>, vm: &VirtualMachine) -> PyResult<Option<super::PyListRef>> {
79    // let attributes = typ.attributes.read();
80    // if let Some(slot_names) = attributes.get(identifier!(vm.ctx, __slotnames__)) {
81    //     return match_class!(match slot_names.clone() {
82    //         l @ super::PyList => Ok(Some(l)),
83    //         _n @ super::PyNone => Ok(None),
84    //         _ => Err(vm.new_type_error(format!(
85    //             "{:.200}.__slotnames__ should be a list or None, not {:.200}",
86    //             typ.name(),
87    //             slot_names.class().name()
88    //         ))),
89    //     });
90    // }
91
92    let copyreg = vm.import("copyreg", 0)?;
93    let copyreg_slotnames = copyreg.get_attr("_slotnames", vm)?;
94    let slot_names = copyreg_slotnames.call((typ.to_owned(),), vm)?;
95    let result = match_class!(match slot_names {
96        l @ super::PyList => Some(l),
97        _n @ super::PyNone => None,
98        _ =>
99            return Err(
100                vm.new_type_error("copyreg._slotnames didn't return a list or None".to_owned())
101            ),
102    });
103    Ok(result)
104}
105
106// object_getstate_default in CPython
107fn object_getstate_default(obj: &PyObject, required: bool, vm: &VirtualMachine) -> PyResult {
108    // TODO: itemsize
109    // if required && obj.class().slots.itemsize > 0 {
110    //     return vm.new_type_error(format!(
111    //         "cannot pickle {:.200} objects",
112    //         obj.class().name()
113    //     ));
114    // }
115
116    let state = if obj.dict().map_or(true, |d| d.is_empty()) {
117        vm.ctx.none()
118    } else {
119        // let state = object_get_dict(obj.clone(), obj.ctx()).unwrap();
120        let Some(state) = obj.dict() else {
121            return Ok(vm.ctx.none());
122        };
123        state.into()
124    };
125
126    let slot_names = type_slot_names(obj.class(), vm)
127        .map_err(|_| vm.new_type_error("cannot pickle object".to_owned()))?;
128
129    if required {
130        let mut basicsize = obj.class().slots.basicsize;
131        // if obj.class().slots.dictoffset > 0
132        //     && !obj.class().slots.flags.has_feature(PyTypeFlags::MANAGED_DICT)
133        // {
134        //     basicsize += std::mem::size_of::<PyObjectRef>();
135        // }
136        // if obj.class().slots.weaklistoffset > 0 {
137        //     basicsize += std::mem::size_of::<PyObjectRef>();
138        // }
139        if let Some(ref slot_names) = slot_names {
140            basicsize += std::mem::size_of::<PyObjectRef>() * slot_names.len();
141        }
142        if obj.class().slots.basicsize > basicsize {
143            return Err(
144                vm.new_type_error(format!("cannot pickle {:.200} object", obj.class().name()))
145            );
146        }
147    }
148
149    if let Some(slot_names) = slot_names {
150        let slot_names_len = slot_names.len();
151        if slot_names_len > 0 {
152            let slots = vm.ctx.new_dict();
153            for i in 0..slot_names_len {
154                let borrowed_names = slot_names.borrow_vec();
155                let name = borrowed_names[i].downcast_ref::<PyStr>().unwrap();
156                let Ok(value) = obj.get_attr(name, vm) else {
157                    continue;
158                };
159                slots.set_item(name.as_str(), value, vm).unwrap();
160            }
161
162            if slots.len() > 0 {
163                return (state, slots).to_pyresult(vm);
164            }
165        }
166    }
167
168    Ok(state)
169}
170
171// object_getstate in CPython
172// fn object_getstate(
173//     obj: &PyObject,
174//     required: bool,
175//     vm: &VirtualMachine,
176// ) -> PyResult {
177//     let getstate = obj.get_attr(identifier!(vm, __getstate__), vm)?;
178//     if vm.is_none(&getstate) {
179//         return Ok(None);
180//     }
181
182//     let getstate = match getstate.downcast_exact::<PyNativeFunction>(vm) {
183//         Ok(getstate)
184//             if getstate
185//                 .get_self()
186//                 .map_or(false, |self_obj| self_obj.is(obj))
187//                 && std::ptr::addr_eq(
188//                     getstate.as_func() as *const _,
189//                     &PyBaseObject::__getstate__ as &dyn crate::function::PyNativeFn as *const _,
190//                 ) =>
191//         {
192//             return object_getstate_default(obj, required, vm);
193//         }
194//         Ok(getstate) => getstate.into_pyref().into(),
195//         Err(getstate) => getstate,
196//     };
197//     getstate.call((), vm)
198// }
199
200#[pyclass(with(Constructor), flags(BASETYPE))]
201impl PyBaseObject {
202    #[pymethod(raw)]
203    fn __getstate__(vm: &VirtualMachine, args: FuncArgs) -> PyResult {
204        let (zelf,): (PyObjectRef,) = args.bind(vm)?;
205        object_getstate_default(&zelf, false, vm)
206    }
207
208    #[pyslot]
209    fn slot_richcompare(
210        zelf: &PyObject,
211        other: &PyObject,
212        op: PyComparisonOp,
213        vm: &VirtualMachine,
214    ) -> PyResult<Either<PyObjectRef, PyComparisonValue>> {
215        Self::cmp(zelf, other, op, vm).map(Either::B)
216    }
217
218    #[inline(always)]
219    fn cmp(
220        zelf: &PyObject,
221        other: &PyObject,
222        op: PyComparisonOp,
223        vm: &VirtualMachine,
224    ) -> PyResult<PyComparisonValue> {
225        let res = match op {
226            PyComparisonOp::Eq => {
227                if zelf.is(other) {
228                    PyComparisonValue::Implemented(true)
229                } else {
230                    PyComparisonValue::NotImplemented
231                }
232            }
233            PyComparisonOp::Ne => {
234                let cmp = zelf
235                    .class()
236                    .mro_find_map(|cls| cls.slots.richcompare.load())
237                    .unwrap();
238                let value = match cmp(zelf, other, PyComparisonOp::Eq, vm)? {
239                    Either::A(obj) => PyArithmeticValue::from_object(vm, obj)
240                        .map(|obj| obj.try_to_bool(vm))
241                        .transpose()?,
242                    Either::B(value) => value,
243                };
244                value.map(|v| !v)
245            }
246            _ => PyComparisonValue::NotImplemented,
247        };
248        Ok(res)
249    }
250
251    /// Return self==value.
252    #[pymethod(magic)]
253    fn eq(
254        zelf: PyObjectRef,
255        value: PyObjectRef,
256        vm: &VirtualMachine,
257    ) -> PyResult<PyComparisonValue> {
258        Self::cmp(&zelf, &value, PyComparisonOp::Eq, vm)
259    }
260
261    /// Return self!=value.
262    #[pymethod(magic)]
263    fn ne(
264        zelf: PyObjectRef,
265        value: PyObjectRef,
266        vm: &VirtualMachine,
267    ) -> PyResult<PyComparisonValue> {
268        Self::cmp(&zelf, &value, PyComparisonOp::Ne, vm)
269    }
270
271    /// Return self<value.
272    #[pymethod(magic)]
273    fn lt(
274        zelf: PyObjectRef,
275        value: PyObjectRef,
276        vm: &VirtualMachine,
277    ) -> PyResult<PyComparisonValue> {
278        Self::cmp(&zelf, &value, PyComparisonOp::Lt, vm)
279    }
280
281    /// Return self<=value.
282    #[pymethod(magic)]
283    fn le(
284        zelf: PyObjectRef,
285        value: PyObjectRef,
286        vm: &VirtualMachine,
287    ) -> PyResult<PyComparisonValue> {
288        Self::cmp(&zelf, &value, PyComparisonOp::Le, vm)
289    }
290
291    /// Return self>=value.
292    #[pymethod(magic)]
293    fn ge(
294        zelf: PyObjectRef,
295        value: PyObjectRef,
296        vm: &VirtualMachine,
297    ) -> PyResult<PyComparisonValue> {
298        Self::cmp(&zelf, &value, PyComparisonOp::Ge, vm)
299    }
300
301    /// Return self>value.
302    #[pymethod(magic)]
303    fn gt(
304        zelf: PyObjectRef,
305        value: PyObjectRef,
306        vm: &VirtualMachine,
307    ) -> PyResult<PyComparisonValue> {
308        Self::cmp(&zelf, &value, PyComparisonOp::Gt, vm)
309    }
310
311    /// Implement setattr(self, name, value).
312    #[pymethod]
313    fn __setattr__(
314        obj: PyObjectRef,
315        name: PyStrRef,
316        value: PyObjectRef,
317        vm: &VirtualMachine,
318    ) -> PyResult<()> {
319        obj.generic_setattr(&name, PySetterValue::Assign(value), vm)
320    }
321
322    /// Implement delattr(self, name).
323    #[pymethod]
324    fn __delattr__(obj: PyObjectRef, name: PyStrRef, vm: &VirtualMachine) -> PyResult<()> {
325        obj.generic_setattr(&name, PySetterValue::Delete, vm)
326    }
327
328    #[pyslot]
329    fn slot_setattro(
330        obj: &PyObject,
331        attr_name: &Py<PyStr>,
332        value: PySetterValue,
333        vm: &VirtualMachine,
334    ) -> PyResult<()> {
335        obj.generic_setattr(attr_name, value, vm)
336    }
337
338    /// Return str(self).
339    #[pymethod(magic)]
340    fn str(zelf: PyObjectRef, vm: &VirtualMachine) -> PyResult<PyStrRef> {
341        // FIXME: try tp_repr first and fallback to object.__repr__
342        zelf.repr(vm)
343    }
344
345    #[pyslot]
346    fn slot_repr(zelf: &PyObject, vm: &VirtualMachine) -> PyResult<PyStrRef> {
347        let class = zelf.class();
348        match (
349            class
350                .qualname(vm)
351                .downcast_ref::<PyStr>()
352                .map(|n| n.as_str()),
353            class.module(vm).downcast_ref::<PyStr>().map(|m| m.as_str()),
354        ) {
355            (None, _) => Err(vm.new_type_error("Unknown qualified name".into())),
356            (Some(qualname), Some(module)) if module != "builtins" => Ok(PyStr::from(format!(
357                "<{}.{} object at {:#x}>",
358                module,
359                qualname,
360                zelf.get_id()
361            ))
362            .into_ref(&vm.ctx)),
363            _ => Ok(PyStr::from(format!(
364                "<{} object at {:#x}>",
365                class.slot_name(),
366                zelf.get_id()
367            ))
368            .into_ref(&vm.ctx)),
369        }
370    }
371
372    /// Return repr(self).
373    #[pymethod(magic)]
374    fn repr(zelf: PyObjectRef, vm: &VirtualMachine) -> PyResult<PyStrRef> {
375        Self::slot_repr(&zelf, vm)
376    }
377
378    #[pyclassmethod(magic)]
379    fn subclasshook(_args: FuncArgs, vm: &VirtualMachine) -> PyObjectRef {
380        vm.ctx.not_implemented()
381    }
382
383    #[pyclassmethod(magic)]
384    fn init_subclass(_cls: PyTypeRef) {}
385
386    #[pymethod(magic)]
387    pub fn dir(obj: PyObjectRef, vm: &VirtualMachine) -> PyResult<PyList> {
388        obj.dir(vm)
389    }
390
391    #[pymethod(magic)]
392    fn format(obj: PyObjectRef, format_spec: PyStrRef, vm: &VirtualMachine) -> PyResult<PyStrRef> {
393        if !format_spec.is_empty() {
394            return Err(vm.new_type_error(format!(
395                "unsupported format string passed to {}.__format__",
396                obj.class().name()
397            )));
398        }
399        obj.str(vm)
400    }
401
402    #[pyslot]
403    #[pymethod(magic)]
404    fn init(_zelf: PyObjectRef, _args: FuncArgs, _vm: &VirtualMachine) -> PyResult<()> {
405        Ok(())
406    }
407
408    #[pygetset(name = "__class__")]
409    fn get_class(obj: PyObjectRef) -> PyTypeRef {
410        obj.class().to_owned()
411    }
412
413    #[pygetset(name = "__class__", setter)]
414    fn set_class(instance: PyObjectRef, value: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> {
415        match value.downcast::<PyType>() {
416            Ok(cls) => {
417                let both_module = instance.class().fast_issubclass(vm.ctx.types.module_type)
418                    && cls.fast_issubclass(vm.ctx.types.module_type);
419                let both_mutable = !instance
420                    .class()
421                    .slots
422                    .flags
423                    .has_feature(PyTypeFlags::IMMUTABLETYPE)
424                    && !cls.slots.flags.has_feature(PyTypeFlags::IMMUTABLETYPE);
425                // FIXME(#1979) cls instances might have a payload
426                if both_mutable || both_module {
427                    instance.set_class(cls, vm);
428                    Ok(())
429                } else {
430                    Err(vm.new_type_error(
431                        "__class__ assignment only supported for mutable types or ModuleType subclasses"
432                            .to_owned(),
433                    ))
434                }
435            }
436            Err(value) => {
437                let value_class = value.class();
438                let type_repr = &value_class.name();
439                Err(vm.new_type_error(format!(
440                    "__class__ must be set to a class, not '{type_repr}' object"
441                )))
442            }
443        }
444    }
445
446    /// Return getattr(self, name).
447    #[pyslot]
448    pub(crate) fn getattro(obj: &PyObject, name: &Py<PyStr>, vm: &VirtualMachine) -> PyResult {
449        vm_trace!("object.__getattribute__({:?}, {:?})", obj, name);
450        obj.as_object().generic_getattr(name, vm)
451    }
452
453    #[pymethod(magic)]
454    fn getattribute(obj: PyObjectRef, name: PyStrRef, vm: &VirtualMachine) -> PyResult {
455        Self::getattro(&obj, &name, vm)
456    }
457
458    #[pymethod(magic)]
459    fn reduce(obj: PyObjectRef, vm: &VirtualMachine) -> PyResult {
460        common_reduce(obj, 0, vm)
461    }
462
463    #[pymethod(magic)]
464    fn reduce_ex(obj: PyObjectRef, proto: usize, vm: &VirtualMachine) -> PyResult {
465        let __reduce__ = identifier!(vm, __reduce__);
466        if let Some(reduce) = vm.get_attribute_opt(obj.clone(), __reduce__)? {
467            let object_reduce = vm.ctx.types.object_type.get_attr(__reduce__).unwrap();
468            let typ_obj: PyObjectRef = obj.class().to_owned().into();
469            let class_reduce = typ_obj.get_attr(__reduce__, vm)?;
470            if !class_reduce.is(&object_reduce) {
471                return reduce.call((), vm);
472            }
473        }
474        common_reduce(obj, proto, vm)
475    }
476
477    #[pyslot]
478    fn slot_hash(zelf: &PyObject, _vm: &VirtualMachine) -> PyResult<PyHash> {
479        Ok(zelf.get_id() as _)
480    }
481
482    /// Return hash(self).
483    #[pymethod(magic)]
484    fn hash(zelf: PyObjectRef, vm: &VirtualMachine) -> PyResult<PyHash> {
485        Self::slot_hash(&zelf, vm)
486    }
487
488    #[pymethod(magic)]
489    fn sizeof(zelf: PyObjectRef) -> usize {
490        zelf.class().slots.basicsize
491    }
492}
493
494pub fn object_get_dict(obj: PyObjectRef, vm: &VirtualMachine) -> PyResult<PyDictRef> {
495    obj.dict()
496        .ok_or_else(|| vm.new_attribute_error("This object has no __dict__".to_owned()))
497}
498pub fn object_set_dict(obj: PyObjectRef, dict: PyDictRef, vm: &VirtualMachine) -> PyResult<()> {
499    obj.set_dict(dict)
500        .map_err(|_| vm.new_attribute_error("This object has no __dict__".to_owned()))
501}
502
503pub fn init(ctx: &Context) {
504    PyBaseObject::extend_class(ctx, ctx.types.object_type);
505}
506
507fn common_reduce(obj: PyObjectRef, proto: usize, vm: &VirtualMachine) -> PyResult {
508    if proto >= 2 {
509        let reducelib = vm.import("__reducelib", 0)?;
510        let reduce_2 = reducelib.get_attr("reduce_2", vm)?;
511        reduce_2.call((obj,), vm)
512    } else {
513        let copyreg = vm.import("copyreg", 0)?;
514        let reduce_ex = copyreg.get_attr("_reduce_ex", vm)?;
515        reduce_ex.call((obj, proto), vm)
516    }
517}
Morty Proxy This is a proxified and sanitized view of the page, visit original site.