1use crate::{
4 AsObject, Py, PyObject, PyObjectRef, PyRef, PyResult, TryFromObject, VirtualMachine,
5 builtins::{
6 PyBaseObject, PyBytes, PyDict, PyDictRef, PyGenericAlias, PyInt, PyList, PyStr, PyTuple,
7 PyTupleRef, PyType, PyTypeRef, PyUtf8Str, int::check_int_to_str_digits, pystr::AsPyStr,
8 },
9 common::{hash::PyHash, str::to_ascii},
10 convert::ToPyObject,
11 dict_inner::DictKey,
12 function::{Either, FuncArgs, PyArithmeticValue, PySetterValue},
13 object::PyPayload,
14 protocol::PyIter,
15 types::{Constructor, PyComparisonOp},
16};
17
18impl PyObjectRef {
23 #[inline(always)]
29 pub fn rich_compare(self, other: Self, op_id: PyComparisonOp, vm: &VirtualMachine) -> PyResult {
30 self._cmp(&other, op_id, vm).map(|res| res.to_pyobject(vm))
31 }
32
33 pub fn bytes(self, vm: &VirtualMachine) -> PyResult {
34 let bytes_type = vm.ctx.types.bytes_type;
35 self.downcast_exact::<PyInt>(vm).map_or_else(
36 |obj| {
37 let args = FuncArgs::from(vec![obj]);
38 <PyBytes as Constructor>::slot_new(bytes_type.to_owned(), args, vm)
39 },
40 |int| Err(vm.new_downcast_type_error(bytes_type, &int)),
41 )
42 }
43
44 pub fn is_true(self, vm: &VirtualMachine) -> PyResult<bool> {
47 self.try_to_bool(vm)
48 }
49
50 pub fn not(self, vm: &VirtualMachine) -> PyResult<bool> {
51 self.is_true(vm).map(|x| !x)
52 }
53
54 pub fn length_hint(self, defaultvalue: usize, vm: &VirtualMachine) -> PyResult<usize> {
55 Ok(vm.length_hint_opt(self)?.unwrap_or(defaultvalue))
56 }
57
58 pub fn dir(self, vm: &VirtualMachine) -> PyResult<PyList> {
60 let attributes = self.class().get_attributes(&vm.ctx);
61
62 let dict = PyDict::from_attributes(attributes, vm)?.into_ref(&vm.ctx);
63
64 if let Some(object_dict) = self.dict() {
65 vm.call_method(
66 dict.as_object(),
67 identifier!(vm, update).as_str(),
68 (object_dict,),
69 )?;
70 }
71
72 let attributes = dict.into_iter().map(|(k, _v)| k).collect::<Vec<_>>();
73
74 Ok(PyList::from(attributes))
75 }
76}
77
78impl PyObject {
79 pub fn get_iter(&self, vm: &VirtualMachine) -> PyResult<PyIter> {
83 PyIter::try_from_object(vm, self.to_owned())
85 }
86
87 pub fn get_aiter(&self, vm: &VirtualMachine) -> PyResult {
89 use crate::builtins::PyCoroutine;
90
91 let aiter_method = self.class().get_attr(identifier!(vm, __aiter__));
93 let Some(_aiter_method) = aiter_method else {
94 return Err(vm.new_type_error(format!(
95 "'{}' object is not an async iterable",
96 self.class().slot_name()
97 )));
98 };
99
100 let iterator = vm.call_special_method(self, identifier!(vm, __aiter__), ())?;
102
103 if iterator.downcast_ref::<PyCoroutine>().is_some() {
105 const MSG: &str = "'async_iterator' object cannot be interpreted as an async iterable; perhaps you forgot to call aiter()?";
106 return Err(vm.new_type_error(MSG));
107 }
108
109 if !iterator.class().has_attr(identifier!(vm, __anext__)) {
111 return Err(vm.new_type_error(format!(
112 "'{}' object is not an async iterator",
113 iterator.class().slot_name()
114 )));
115 }
116
117 Ok(iterator)
118 }
119
120 pub fn has_attr<'a>(&self, attr_name: impl AsPyStr<'a>, vm: &VirtualMachine) -> PyResult<bool> {
122 Ok(vm.get_attribute_opt(self, attr_name)?.is_some())
123 }
124
125 pub fn get_attr<'a>(&self, attr_name: impl AsPyStr<'a>, vm: &VirtualMachine) -> PyResult {
128 let attr_name = attr_name.as_pystr(&vm.ctx);
129 self.get_attr_inner(attr_name, vm)
130 }
131
132 #[cfg_attr(feature = "flame-it", flame("PyObjectRef"))]
134 #[inline]
135 pub(crate) fn get_attr_inner(&self, attr_name: &Py<PyStr>, vm: &VirtualMachine) -> PyResult {
136 vm_trace!("object.__getattribute__: {:?} {:?}", self, attr_name);
137 let getattro = self.class().slots().getattro.load().unwrap();
138 getattro(self, attr_name, vm).inspect_err(|exc| {
139 vm.set_attribute_error_context(exc, self.to_owned(), attr_name.to_owned());
140 })
141 }
142
143 pub fn call_set_attr(
144 &self,
145 vm: &VirtualMachine,
146 attr_name: &Py<PyStr>,
147 attr_value: PySetterValue,
148 ) -> PyResult<()> {
149 let setattro = {
150 let cls = self.class();
151 cls.slots.setattro.load().ok_or_else(|| {
152 let has_getattr = cls.slots.getattro.load().is_some();
153 vm.new_type_error(format!(
154 "'{}' object has {} attributes ({} {})",
155 cls.slot_name(),
156 if has_getattr { "only read-only" } else { "no" },
157 if attr_value.is_assign() {
158 "assign to"
159 } else {
160 "del"
161 },
162 attr_name
163 ))
164 })?
165 };
166 setattro(self, attr_name, attr_value, vm)
167 }
168
169 pub fn set_attr<'a>(
170 &self,
171 attr_name: impl AsPyStr<'a>,
172 attr_value: impl Into<PyObjectRef>,
173 vm: &VirtualMachine,
174 ) -> PyResult<()> {
175 let attr_name = attr_name.as_pystr(&vm.ctx);
176 let attr_value = attr_value.into();
177 self.call_set_attr(vm, attr_name, PySetterValue::Assign(attr_value))
178 }
179
180 #[cfg_attr(feature = "flame-it", flame)]
182 pub fn generic_setattr(
183 &self,
184 attr_name: &Py<PyStr>,
185 value: PySetterValue,
186 vm: &VirtualMachine,
187 ) -> PyResult<()> {
188 vm_trace!("object.__setattr__({:?}, {}, {:?})", self, attr_name, value);
189 let descr = vm
190 .ctx
191 .interned_str(attr_name)
192 .and_then(|attr_name| self.get_class_attr(attr_name));
193 if let Some(attr) = &descr
194 && let Some(descriptor) = attr.class().slots().descr_set.load()
195 {
196 return descriptor(attr, self.to_owned(), value, vm);
197 }
198
199 let Some(instance_dict) = self.instance_dict() else {
200 let name = self.class().slot_name();
201 if descr.is_some() {
202 return Err(vm.new_attribute_error(format!(
203 "'{name}' object attribute '{attr_name}' is read-only"
204 )));
205 }
206 let generic_setattro = self.class().slots().setattro.load().is_some_and(|f| {
209 crate::types::fn_addr(f)
210 == crate::types::fn_addr(
211 PyBaseObject::slot_setattro as crate::types::SetattroFunc,
212 )
213 });
214 let msg = if generic_setattro {
215 format!(
216 "'{name}' object has no attribute '{attr_name}' and no \
217 __dict__ for setting new attributes"
218 )
219 } else {
220 format!("'{name}' object has no attribute '{attr_name}'")
221 };
222 let exc = vm.new_attribute_error(msg);
223 vm.set_attribute_error_context(&exc, self.to_owned(), attr_name.to_owned());
224 return Err(exc);
225 };
226
227 if let PySetterValue::Assign(value) = value {
228 instance_dict
229 .get_or_insert(vm)
230 .set_item(attr_name, value, vm)?;
231 instance_dict.maybe_materialize_inline_values();
232 } else if let Some(dict) = instance_dict.get() {
233 dict.del_item(attr_name, vm).map_err(|e| {
234 if e.fast_isinstance(vm.ctx.exceptions.key_error) {
235 vm.new_no_attribute_error(self.to_owned(), attr_name.to_owned())
236 } else {
237 e
238 }
239 })?;
240 } else {
241 return Err(vm.new_no_attribute_error(self.to_owned(), attr_name.to_owned()));
242 }
243 Ok(())
244 }
245
246 pub fn generic_getattr(&self, name: &Py<PyStr>, vm: &VirtualMachine) -> PyResult {
247 self.generic_getattr_opt(name, None, vm)?
248 .ok_or_else(|| vm.new_no_attribute_error(self.to_owned(), name.to_owned()))
249 }
250
251 pub fn generic_getattr_opt(
253 &self,
254 name_str: &Py<PyStr>,
255 dict: Option<PyDictRef>,
256 vm: &VirtualMachine,
257 ) -> PyResult<Option<PyObjectRef>> {
258 let obj_cls = self.class();
259 let cls_attr_name = vm.ctx.interned_str(name_str);
260 let cls_attr = match cls_attr_name.and_then(|name| obj_cls.get_attr(name)) {
261 Some(descr) => {
262 let descr_cls = descr.class();
263 let descr_get = descr_cls.slots.descr_get.load();
264 if let Some(descr_get) = descr_get
265 && descr_cls.slots.descr_set.load().is_some()
266 {
267 return descr_get(descr.as_object(), Some(self), Some(obj_cls.as_object()), vm)
268 .map(Some);
269 }
270 Some((descr, descr_get))
271 }
272 None => None,
273 };
274
275 let dict = dict.or_else(|| self.dict());
276
277 let attr = if let Some(dict) = dict {
278 dict.get_item_opt(name_str, vm)?
281 } else {
282 None
283 };
284
285 if let Some(obj_attr) = attr {
286 Ok(Some(obj_attr))
287 } else if let Some((attr, descr_get)) = cls_attr {
288 match descr_get {
289 Some(descr_get) => {
290 descr_get(attr.as_object(), Some(self), Some(obj_cls.as_object()), vm).map(Some)
291 }
292 None => Ok(Some(attr)),
293 }
294 } else {
295 Ok(None)
296 }
297 }
298
299 pub fn del_attr<'a>(&self, attr_name: impl AsPyStr<'a>, vm: &VirtualMachine) -> PyResult<()> {
300 let attr_name = attr_name.as_pystr(&vm.ctx);
301 self.call_set_attr(vm, attr_name, PySetterValue::Delete)
302 }
303
304 #[inline(always)]
309 pub fn rich_compare(
310 &self,
311 other: &Self,
312 op_id: PyComparisonOp,
313 vm: &VirtualMachine,
314 ) -> PyResult {
315 self._cmp(other, op_id, vm).map(|res| res.to_pyobject(vm))
316 }
317
318 #[inline] fn _cmp(
323 &self,
324 other: &Self,
325 op: PyComparisonOp,
326 vm: &VirtualMachine,
327 ) -> PyResult<Either<PyObjectRef, bool>> {
328 vm.with_recursion("in comparison", || self._cmp_inner(other, op, vm))
331 }
332
333 fn _cmp_inner(
334 &self,
335 other: &Self,
336 op: PyComparisonOp,
337 vm: &VirtualMachine,
338 ) -> PyResult<Either<PyObjectRef, bool>> {
339 let swapped = op.swapped();
340 let call_cmp = |obj: &Self, other: &Self, op| {
341 let Some(cmp) = obj.class().slots().richcompare.load() else {
342 return Ok(PyArithmeticValue::NotImplemented);
343 };
344 let r = match cmp(obj, other, op, vm)? {
345 Either::A(obj) => PyArithmeticValue::from_object(vm, obj).map(Either::A),
346 Either::B(arithmetic) => arithmetic.map(Either::B),
347 };
348 Ok(r)
349 };
350
351 let mut checked_reverse_op = false;
352 let is_strict_subclass = {
353 let self_class = self.class();
354 let other_class = other.class();
355 !self_class.is(other_class) && other_class.fast_issubclass(self_class)
356 };
357
358 if is_strict_subclass {
359 let res = call_cmp(other, self, swapped)?;
360 checked_reverse_op = true;
361 if let PyArithmeticValue::Implemented(x) = res {
362 return Ok(x);
363 }
364 }
365
366 if let PyArithmeticValue::Implemented(x) = call_cmp(self, other, op)? {
367 return Ok(x);
368 }
369
370 if !checked_reverse_op {
371 let res = call_cmp(other, self, swapped)?;
372 if let PyArithmeticValue::Implemented(x) = res {
373 return Ok(x);
374 }
375 }
376
377 match op {
378 PyComparisonOp::Eq => Ok(Either::B(self.is(&other))),
379 PyComparisonOp::Ne => Ok(Either::B(!self.is(&other))),
380 _ => Err(vm.new_type_error(format!(
381 "'{}' not supported between instances of '{}' and '{}'",
382 op.operator_token(),
383 self.class().slot_name(),
384 other.class().slot_name()
385 ))),
386 }
387 }
388
389 #[inline(always)]
390 pub fn rich_compare_bool(
391 &self,
392 other: &Self,
393 op_id: PyComparisonOp,
394 vm: &VirtualMachine,
395 ) -> PyResult<bool> {
396 if self.is(other) {
404 match op_id {
405 PyComparisonOp::Eq => return Ok(true),
406 PyComparisonOp::Ne => return Ok(false),
407 _ => {}
408 }
409 }
410
411 match self._cmp(other, op_id, vm)? {
412 Either::A(obj) => obj.try_to_bool(vm),
413 Either::B(other) => Ok(other),
414 }
415 }
416
417 pub fn repr_utf8(&self, vm: &VirtualMachine) -> PyResult<PyRef<PyUtf8Str>> {
418 self.repr(vm)?.try_into_utf8(vm)
419 }
420
421 pub fn repr(&self, vm: &VirtualMachine) -> PyResult<PyRef<PyStr>> {
422 vm.with_recursion("while getting the repr of an object", || {
423 self.class().slots().repr.load().map_or_else(
424 || {
425 Err(vm.new_runtime_error(format!(
426 "BUG: object of type '{}' has no __repr__ method. This is a bug in RustPython.",
427 self.class().name()
428 )))
429 },
430 |repr| repr(self, vm),
431 )
432 })
433 }
434
435 pub fn ascii(&self, vm: &VirtualMachine) -> PyResult<PyRef<PyStr>> {
436 let repr = self.repr(vm)?;
437 Ok(if repr.as_wtf8().is_ascii() {
438 repr
439 } else {
440 vm.ctx.new_str(to_ascii(repr.as_wtf8()))
441 })
442 }
443
444 pub fn str_utf8(&self, vm: &VirtualMachine) -> PyResult<PyRef<PyUtf8Str>> {
445 self.str(vm)?.try_into_utf8(vm)
446 }
447
448 pub fn str(&self, vm: &VirtualMachine) -> PyResult<PyRef<PyStr>> {
449 let obj = match self.to_owned().downcast_exact::<PyStr>(vm) {
450 Ok(s) => return Ok(s.into_pyref()),
451 Err(obj) => obj,
452 };
453
454 let obj = match obj.downcast_exact::<PyInt>(vm) {
456 Ok(int) => {
457 check_int_to_str_digits(int.as_bigint(), vm)?;
458 return Ok(vm.ctx.new_str(int.to_str_radix_10()));
459 }
460 Err(obj) => obj,
461 };
462
463 let Some(str_method) = vm.get_special_method(&obj, identifier!(vm, __str__))? else {
465 return obj.repr(vm);
466 };
467
468 let s = str_method.invoke((), vm)?;
469 s.downcast::<PyStr>().map_err(|obj| {
470 vm.new_type_error(format!(
471 "__str__ returned non-string (type {})",
472 obj.class().slot_name()
473 ))
474 })
475 }
476
477 fn check_class(&self, vm: &VirtualMachine, msg: &'static str) -> PyResult<()> {
480 if self.abstract_get_bases(vm)?.is_some() {
481 Ok(())
483 } else {
484 Err(vm.new_type_error(msg))
486 }
487 }
488
489 fn abstract_get_bases(&self, vm: &VirtualMachine) -> PyResult<Option<PyTupleRef>> {
499 Ok(vm
500 .get_attribute_opt(self, identifier!(vm, __bases__))?
501 .and_then(|bases| {
503 PyTupleRef::try_from_object(vm, bases).ok()
505 }))
506 }
507
508 fn abstract_issubclass(&self, cls: &Self, vm: &VirtualMachine) -> PyResult<bool> {
509 let mut bases: PyTupleRef;
511 let mut derived = self;
512
513 let bases = loop {
515 if derived.is(cls) {
516 return Ok(true);
517 }
518
519 let Some(derived_bases) = derived.abstract_get_bases(vm)? else {
520 return Ok(false);
521 };
522
523 let n = derived_bases.as_slice().len();
524 match n {
525 0 => return Ok(false),
526 1 => {
527 bases = derived_bases;
530 derived = &bases.as_slice()[0];
531 continue;
532 }
533 _ => {
534 break derived_bases;
536 }
537 }
538 };
539
540 let n = bases.as_slice().len();
541 debug_assert!(n >= 2);
543
544 for i in 0..n {
545 let result = vm.with_recursion("in __issubclass__", || {
546 bases.as_slice()[i].abstract_issubclass(cls, vm)
547 })?;
548
549 if result {
550 return Ok(true);
551 }
552 }
553
554 Ok(false)
555 }
556
557 fn recursive_issubclass(&self, cls: &Self, vm: &VirtualMachine) -> PyResult<bool> {
558 if let Some(cls) = PyType::check(cls)
560 && let Some(derived) = PyType::check(self)
561 {
562 return Ok(derived.is_subtype(cls));
564 }
565
566 self.check_class(vm, "issubclass() arg 1 must be a class")?;
568
569 if !cls.class().is(vm.ctx.types.union_type) {
571 cls.check_class(
572 vm,
573 "issubclass() arg 2 must be a class, a tuple of classes, or a union",
574 )?;
575 }
576
577 self.abstract_issubclass(cls, vm)
578 }
579
580 pub fn real_is_subclass(&self, cls: &Self, vm: &VirtualMachine) -> PyResult<bool> {
583 self.recursive_issubclass(cls, vm)
584 }
585
586 pub fn is_subclass(&self, cls: &Self, vm: &VirtualMachine) -> PyResult<bool> {
590 let derived = self;
591 if cls.class().is(vm.ctx.types.type_type) {
593 if derived.is(cls) {
594 return Ok(true);
595 }
596 return derived.recursive_issubclass(cls, vm);
597 }
598
599 let cls = if cls.class().is(vm.ctx.types.union_type) {
601 let union = cls
604 .downcast_ref::<crate::builtins::PyUnion>()
605 .expect("union is already checked");
606 union.args().as_object()
607 } else {
608 cls
609 };
610
611 if let Some(tuple) = cls.downcast_ref::<PyTuple>() {
613 for item in tuple {
614 if vm.with_recursion("in __subclasscheck__", || derived.is_subclass(item, vm))? {
615 return Ok(true);
616 }
617 }
618 return Ok(false);
619 }
620
621 if let Some(checker) = cls.lookup_special(identifier!(vm, __subclasscheck__), vm)? {
623 let res = vm.with_recursion("in __subclasscheck__", || {
624 checker.call((derived.to_owned(),), vm)
625 })?;
626 return res.try_to_bool(vm);
627 }
628
629 derived.recursive_issubclass(cls, vm)
630 }
631
632 pub(crate) fn real_is_instance(&self, cls: &Self, vm: &VirtualMachine) -> PyResult<bool> {
634 self.object_isinstance(cls, vm)
635 }
636
637 fn object_isinstance(&self, cls: &Self, vm: &VirtualMachine) -> PyResult<bool> {
640 if let Ok(cls) = cls.try_to_ref::<PyType>(vm) {
641 let mut retval = self.class().is_subtype(cls);
643 if !retval
644 && let Some(i_cls) = vm.get_attribute_opt(self, identifier!(vm, __class__))?
645 && let Ok(i_cls_type) = PyTypeRef::try_from_object(vm, i_cls)
646 && !i_cls_type.is(self.class())
647 {
648 retval = i_cls_type.is_subtype(cls);
649 }
650 Ok(retval)
651 } else {
652 cls.check_class(
654 vm,
655 "isinstance() arg 2 must be a type, a tuple of types, or a union",
656 )?;
657
658 if let Some(i_cls) = vm.get_attribute_opt(self, identifier!(vm, __class__))? {
659 i_cls.abstract_issubclass(cls, vm)
660 } else {
661 Ok(false)
662 }
663 }
664 }
665
666 pub fn is_instance(&self, cls: &Self, vm: &VirtualMachine) -> PyResult<bool> {
669 self.object_recursive_isinstance(cls, vm)
670 }
671
672 fn object_recursive_isinstance(&self, cls: &Self, vm: &VirtualMachine) -> PyResult<bool> {
674 if self.class().is(cls) {
677 return Ok(true);
678 }
679
680 if cls.class().is(vm.ctx.types.type_type) {
682 return self.object_isinstance(cls, vm);
685 }
686
687 let cls = if cls.class().is(vm.ctx.types.union_type) {
689 let union = cls
691 .try_to_ref::<crate::builtins::PyUnion>(vm)
692 .expect("checked by is");
693 union.args().as_object()
694 } else {
695 cls
696 };
697
698 if let Some(tuple) = cls.downcast_ref::<PyTuple>() {
700 for item in tuple {
701 if vm.with_recursion("in __instancecheck__", || {
702 self.object_recursive_isinstance(item, vm)
703 })? {
704 return Ok(true);
705 }
706 }
707 return Ok(false);
708 }
709
710 if let Some(checker) = cls.lookup_special(identifier!(vm, __instancecheck__), vm)? {
712 let res = vm.with_recursion("in __instancecheck__", || {
713 checker.call((self.to_owned(),), vm)
714 })?;
715 return res.try_to_bool(vm);
716 }
717
718 self.object_isinstance(cls, vm)
720 }
721
722 pub fn hash(&self, vm: &VirtualMachine) -> PyResult<PyHash> {
723 if let Some(hash) = self.class().slots().hash.load() {
724 return vm.with_recursion("while hashing", || hash(self, vm));
725 }
726
727 Err(vm.new_type_error(format!("unhashable type: '{}'", self.class().slot_name())))
728 }
729
730 pub fn obj_type(&self) -> PyObjectRef {
733 self.class().to_owned().into()
734 }
735
736 pub fn type_check(&self, typ: &Py<PyType>) -> bool {
738 self.fast_isinstance(typ)
739 }
740
741 pub fn length_opt(&self, vm: &VirtualMachine) -> Option<PyResult<usize>> {
742 self.sequence_unchecked()
743 .length_opt(vm)
744 .or_else(|| self.mapping_unchecked().length_opt(vm))
745 }
746
747 pub fn length(&self, vm: &VirtualMachine) -> PyResult<usize> {
748 self.length_opt(vm).ok_or_else(|| {
749 vm.new_type_error(format!(
750 "object of type '{}' has no len()",
751 self.class().slot_name()
752 ))
753 })?
754 }
755
756 pub fn get_item<K: DictKey + ?Sized>(&self, needle: &K, vm: &VirtualMachine) -> PyResult {
757 if let Some(dict) = self.downcast_ref_if_exact::<PyDict>(vm) {
758 return dict.get_item(needle, vm);
759 }
760
761 let needle = needle.to_pyobject(vm);
762
763 if let Ok(mapping) = self.try_mapping(vm) {
764 mapping.subscript(&needle, vm)
765 } else if let Ok(seq) = self.try_sequence(vm) {
766 let i = needle.key_as_isize(vm)?;
767 seq.get_item(i, vm)
768 } else {
769 if self.class().fast_issubclass(vm.ctx.types.type_type) {
770 if self.is(vm.ctx.types.type_type) {
771 let alias = PyGenericAlias::from_args(self.class().to_owned(), needle, vm)?;
772 return Ok(alias.to_pyobject(vm));
773 }
774
775 if let Some(class_getitem) =
776 vm.get_attribute_opt(self, identifier!(vm, __class_getitem__))?
777 && !vm.is_none(&class_getitem)
778 {
779 return class_getitem.call((needle,), vm);
780 }
781 return Err(vm.new_type_error(format!(
782 "type '{}' is not subscriptable",
783 self.downcast_ref::<PyType>().unwrap().slot_name()
784 )));
785 }
786 Err(vm.new_type_error(format!(
787 "'{}' object is not subscriptable",
788 self.class().slot_name()
789 )))
790 }
791 }
792
793 pub fn set_item<K: DictKey + ?Sized>(
794 &self,
795 needle: &K,
796 value: PyObjectRef,
797 vm: &VirtualMachine,
798 ) -> PyResult<()> {
799 if let Some(dict) = self.downcast_ref_if_exact::<PyDict>(vm) {
800 return dict.set_item(needle, value, vm);
801 }
802
803 let mapping = self.mapping_unchecked();
804 if let Some(f) = mapping.slots().ass_subscript.load() {
805 let needle = needle.to_pyobject(vm);
806 return f(mapping, &needle, Some(value), vm);
807 }
808
809 let seq = self.sequence_unchecked();
810 if let Some(f) = seq.slots().ass_item.load() {
811 let i = needle.key_as_isize(vm)?;
812 return f(seq, i, Some(value), vm);
813 }
814
815 Err(vm.new_type_error(format!(
816 "'{}' object does not support item assignment",
817 self.class().slot_name()
818 )))
819 }
820
821 pub fn del_item<K: DictKey + ?Sized>(&self, needle: &K, vm: &VirtualMachine) -> PyResult<()> {
822 if let Some(dict) = self.downcast_ref_if_exact::<PyDict>(vm) {
823 return dict.del_item(needle, vm);
824 }
825
826 let mapping = self.mapping_unchecked();
827 if let Some(f) = mapping.slots().ass_subscript.load() {
828 let needle = needle.to_pyobject(vm);
829 return f(mapping, &needle, None, vm);
830 }
831 let seq = self.sequence_unchecked();
832 if let Some(f) = seq.slots().ass_item.load() {
833 let i = needle.key_as_isize(vm)?;
834 return f(seq, i, None, vm);
835 }
836
837 let name = self.class().slot_name();
840 let msg = if seq.slots().has_any() || self.class().heaptype_ext().is_some() {
841 format!("'{name}' object doesn't support item deletion")
842 } else {
843 format!("'{name}' object does not support item deletion")
844 };
845 Err(vm.new_type_error(msg))
846 }
847
848 pub fn lookup_special(
852 &self,
853 attr: &Py<PyStr>,
854 vm: &VirtualMachine,
855 ) -> PyResult<Option<PyObjectRef>> {
856 let obj_cls = self.class();
857
858 let Some(res) = obj_cls.lookup_ref(attr, vm) else {
859 return Ok(None);
860 };
861
862 let descr_get = res.class().slots().descr_get.load();
863 if let Some(descr_get) = descr_get {
864 descr_get(res.as_object(), Some(self), Some(obj_cls.as_object()), vm).map(Some)
865 } else {
866 Ok(Some(res))
867 }
868 }
869}
870
871#[macro_export]
873macro_rules! set_attrs {
874 ($obj:expr, $vm:expr, unwrap, $($key:expr => $val:expr),+ $(,)?) => {
875 $(
876 $obj.set_attr($key, $val, $vm).unwrap();
877 )+
878 };
879 ($obj:expr, $vm:expr, $($key:expr => $val:expr),+ $(,)?) => {
880 $(
881 $obj.set_attr($key, $val, $vm)?;
882 )+
883 };
884}