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#[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 let dict = if cls.is(vm.ctx.types.object_type) {
36 None
37 } else {
38 Some(vm.ctx.new_dict())
39 };
40
41 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 #[allow(unreachable_patterns)]
68 _ => unreachable!(),
69 }
70 }
71 }
72
73 Ok(crate::PyRef::new_ref(PyBaseObject, cls, dict).into())
74 }
75}
76
77fn type_slot_names(typ: &Py<PyType>, vm: &VirtualMachine) -> PyResult<Option<super::PyListRef>> {
79 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
106fn object_getstate_default(obj: &PyObject, required: bool, vm: &VirtualMachine) -> PyResult {
108 let state = if obj.dict().map_or(true, |d| d.is_empty()) {
117 vm.ctx.none()
118 } else {
119 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 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#[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[pymethod(magic)]
340 fn str(zelf: PyObjectRef, vm: &VirtualMachine) -> PyResult<PyStrRef> {
341 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 #[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 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 #[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 #[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}