1use crate::{
5 builtins::{
6 pystr::AsPyStr, PyAsyncGen, PyBytes, PyDict, PyDictRef, PyGenericAlias, PyInt, PyList,
7 PyStr, PyStrRef, PyTuple, PyTupleRef, PyType, PyTypeRef,
8 },
9 bytesinner::ByteInnerNewOptions,
10 common::{hash::PyHash, str::to_ascii},
11 convert::{ToPyObject, ToPyResult},
12 dictdatatype::DictKey,
13 function::{Either, OptionalArg, PyArithmeticValue, PySetterValue},
14 object::PyPayload,
15 protocol::{PyIter, PyMapping, PySequence},
16 types::{Constructor, PyComparisonOp},
17 AsObject, Py, PyObject, PyObjectRef, PyResult, TryFromObject, VirtualMachine,
18};
19
20impl PyObjectRef {
25 #[inline(always)]
31 pub fn rich_compare(self, other: Self, opid: PyComparisonOp, vm: &VirtualMachine) -> PyResult {
32 self._cmp(&other, opid, vm).map(|res| res.to_pyobject(vm))
33 }
34
35 pub fn bytes(self, vm: &VirtualMachine) -> PyResult {
36 let bytes_type = vm.ctx.types.bytes_type;
37 match self.downcast_exact::<PyInt>(vm) {
38 Ok(int) => Err(vm.new_downcast_type_error(bytes_type, &int)),
39 Err(obj) => PyBytes::py_new(
40 bytes_type.to_owned(),
41 ByteInnerNewOptions {
42 source: OptionalArg::Present(obj),
43 encoding: OptionalArg::Missing,
44 errors: OptionalArg::Missing,
45 },
46 vm,
47 ),
48 }
49 }
50
51 pub fn is_true(self, vm: &VirtualMachine) -> PyResult<bool> {
54 self.try_to_bool(vm)
55 }
56
57 pub fn not(self, vm: &VirtualMachine) -> PyResult<bool> {
58 self.is_true(vm).map(|x| !x)
59 }
60
61 pub fn length_hint(self, defaultvalue: usize, vm: &VirtualMachine) -> PyResult<usize> {
62 Ok(vm.length_hint_opt(self)?.unwrap_or(defaultvalue))
63 }
64
65 pub fn dir(self, vm: &VirtualMachine) -> PyResult<PyList> {
67 let attributes = self.class().get_attributes();
68
69 let dict = PyDict::from_attributes(attributes, vm)?.into_ref(&vm.ctx);
70
71 if let Some(object_dict) = self.dict() {
72 vm.call_method(
73 dict.as_object(),
74 identifier!(vm, update).as_str(),
75 (object_dict,),
76 )?;
77 }
78
79 let attributes: Vec<_> = dict.into_iter().map(|(k, _v)| k).collect();
80
81 Ok(PyList::from(attributes))
82 }
83}
84
85impl PyObject {
86 pub fn get_iter(&self, vm: &VirtualMachine) -> PyResult<PyIter> {
90 PyIter::try_from_object(vm, self.to_owned())
92 }
93
94 pub fn get_aiter(&self, vm: &VirtualMachine) -> PyResult {
96 if self.payload_is::<PyAsyncGen>() {
97 vm.call_special_method(self, identifier!(vm, __aiter__), ())
98 } else {
99 Err(vm.new_type_error("wrong argument type".to_owned()))
100 }
101 }
102
103 pub fn has_attr<'a>(&self, attr_name: impl AsPyStr<'a>, vm: &VirtualMachine) -> PyResult<bool> {
104 self.get_attr(attr_name, vm).map(|o| !vm.is_none(&o))
105 }
106
107 pub fn get_attr<'a>(&self, attr_name: impl AsPyStr<'a>, vm: &VirtualMachine) -> PyResult {
108 let attr_name = attr_name.as_pystr(&vm.ctx);
109 self.get_attr_inner(attr_name, vm)
110 }
111
112 #[cfg_attr(feature = "flame-it", flame("PyObjectRef"))]
114 #[inline]
115 pub(crate) fn get_attr_inner(&self, attr_name: &Py<PyStr>, vm: &VirtualMachine) -> PyResult {
116 vm_trace!("object.__getattribute__: {:?} {:?}", self, attr_name);
117 let getattro = self
118 .class()
119 .mro_find_map(|cls| cls.slots.getattro.load())
120 .unwrap();
121 getattro(self, attr_name, vm).inspect_err(|exc| {
122 vm.set_attribute_error_context(exc, self.to_owned(), attr_name.to_owned());
123 })
124 }
125
126 pub fn call_set_attr(
127 &self,
128 vm: &VirtualMachine,
129 attr_name: &Py<PyStr>,
130 attr_value: PySetterValue,
131 ) -> PyResult<()> {
132 let setattro = {
133 let cls = self.class();
134 cls.mro_find_map(|cls| cls.slots.setattro.load())
135 .ok_or_else(|| {
136 let has_getattr = cls.mro_find_map(|cls| cls.slots.getattro.load()).is_some();
137 vm.new_type_error(format!(
138 "'{}' object has {} attributes ({} {})",
139 cls.name(),
140 if has_getattr { "only read-only" } else { "no" },
141 if attr_value.is_assign() {
142 "assign to"
143 } else {
144 "del"
145 },
146 attr_name
147 ))
148 })?
149 };
150 setattro(self, attr_name, attr_value, vm)
151 }
152
153 pub fn set_attr<'a>(
154 &self,
155 attr_name: impl AsPyStr<'a>,
156 attr_value: impl Into<PyObjectRef>,
157 vm: &VirtualMachine,
158 ) -> PyResult<()> {
159 let attr_name = attr_name.as_pystr(&vm.ctx);
160 let attr_value = attr_value.into();
161 self.call_set_attr(vm, attr_name, PySetterValue::Assign(attr_value))
162 }
163
164 #[cfg_attr(feature = "flame-it", flame)]
166 pub fn generic_setattr(
167 &self,
168 attr_name: &Py<PyStr>,
169 value: PySetterValue,
170 vm: &VirtualMachine,
171 ) -> PyResult<()> {
172 vm_trace!("object.__setattr__({:?}, {}, {:?})", self, attr_name, value);
173 if let Some(attr) = vm
174 .ctx
175 .interned_str(attr_name)
176 .and_then(|attr_name| self.get_class_attr(attr_name))
177 {
178 let descr_set = attr.class().mro_find_map(|cls| cls.slots.descr_set.load());
179 if let Some(descriptor) = descr_set {
180 return descriptor(&attr, self.to_owned(), value, vm);
181 }
182 }
183
184 if let Some(dict) = self.dict() {
185 if let PySetterValue::Assign(value) = value {
186 dict.set_item(attr_name, value, vm)?;
187 } else {
188 dict.del_item(attr_name, vm).map_err(|e| {
189 if e.fast_isinstance(vm.ctx.exceptions.key_error) {
190 vm.new_attribute_error(format!(
191 "'{}' object has no attribute '{}'",
192 self.class().name(),
193 attr_name.as_str(),
194 ))
195 } else {
196 e
197 }
198 })?;
199 }
200 Ok(())
201 } else {
202 Err(vm.new_attribute_error(format!(
203 "'{}' object has no attribute '{}'",
204 self.class().name(),
205 attr_name.as_str(),
206 )))
207 }
208 }
209
210 pub fn generic_getattr(&self, name: &Py<PyStr>, vm: &VirtualMachine) -> PyResult {
211 self.generic_getattr_opt(name, None, vm)?.ok_or_else(|| {
212 vm.new_attribute_error(format!(
213 "'{}' object has no attribute '{}'",
214 self.class().name(),
215 name
216 ))
217 })
218 }
219
220 pub fn generic_getattr_opt(
222 &self,
223 name_str: &Py<PyStr>,
224 dict: Option<PyDictRef>,
225 vm: &VirtualMachine,
226 ) -> PyResult<Option<PyObjectRef>> {
227 let name = name_str.as_str();
228 let obj_cls = self.class();
229 let cls_attr_name = vm.ctx.interned_str(name_str);
230 let cls_attr = match cls_attr_name.and_then(|name| obj_cls.get_attr(name)) {
231 Some(descr) => {
232 let descr_cls = descr.class();
233 let descr_get = descr_cls.mro_find_map(|cls| cls.slots.descr_get.load());
234 if let Some(descr_get) = descr_get {
235 if descr_cls
236 .mro_find_map(|cls| cls.slots.descr_set.load())
237 .is_some()
238 {
239 let cls = obj_cls.to_owned().into();
240 return descr_get(descr, Some(self.to_owned()), Some(cls), vm).map(Some);
241 }
242 }
243 Some((descr, descr_get))
244 }
245 None => None,
246 };
247
248 let dict = dict.or_else(|| self.dict());
249
250 let attr = if let Some(dict) = dict {
251 dict.get_item_opt(name, vm)?
252 } else {
253 None
254 };
255
256 if let Some(obj_attr) = attr {
257 Ok(Some(obj_attr))
258 } else if let Some((attr, descr_get)) = cls_attr {
259 match descr_get {
260 Some(descr_get) => {
261 let cls = obj_cls.to_owned().into();
262 descr_get(attr, Some(self.to_owned()), Some(cls), vm).map(Some)
263 }
264 None => Ok(Some(attr)),
265 }
266 } else {
267 Ok(None)
268 }
269 }
270
271 pub fn del_attr<'a>(&self, attr_name: impl AsPyStr<'a>, vm: &VirtualMachine) -> PyResult<()> {
272 let attr_name = attr_name.as_pystr(&vm.ctx);
273 self.call_set_attr(vm, attr_name, PySetterValue::Delete)
274 }
275
276 #[inline] fn _cmp(
281 &self,
282 other: &Self,
283 op: PyComparisonOp,
284 vm: &VirtualMachine,
285 ) -> PyResult<Either<PyObjectRef, bool>> {
286 let swapped = op.swapped();
287 let call_cmp = |obj: &PyObject, other: &PyObject, op| {
288 let cmp = obj
289 .class()
290 .mro_find_map(|cls| cls.slots.richcompare.load())
291 .unwrap();
292 let r = match cmp(obj, other, op, vm)? {
293 Either::A(obj) => PyArithmeticValue::from_object(vm, obj).map(Either::A),
294 Either::B(arithmetic) => arithmetic.map(Either::B),
295 };
296 Ok(r)
297 };
298
299 let mut checked_reverse_op = false;
300 let is_strict_subclass = {
301 let self_class = self.class();
302 let other_class = other.class();
303 !self_class.is(other_class) && other_class.fast_issubclass(self_class)
304 };
305 if is_strict_subclass {
306 let res = vm.with_recursion("in comparison", || call_cmp(other, self, swapped))?;
307 checked_reverse_op = true;
308 if let PyArithmeticValue::Implemented(x) = res {
309 return Ok(x);
310 }
311 }
312 if let PyArithmeticValue::Implemented(x) =
313 vm.with_recursion("in comparison", || call_cmp(self, other, op))?
314 {
315 return Ok(x);
316 }
317 if !checked_reverse_op {
318 let res = vm.with_recursion("in comparison", || call_cmp(other, self, swapped))?;
319 if let PyArithmeticValue::Implemented(x) = res {
320 return Ok(x);
321 }
322 }
323 match op {
324 PyComparisonOp::Eq => Ok(Either::B(self.is(&other))),
325 PyComparisonOp::Ne => Ok(Either::B(!self.is(&other))),
326 _ => Err(vm.new_unsupported_binop_error(self, other, op.operator_token())),
327 }
328 }
329 #[inline(always)]
330 pub fn rich_compare_bool(
331 &self,
332 other: &Self,
333 opid: PyComparisonOp,
334 vm: &VirtualMachine,
335 ) -> PyResult<bool> {
336 match self._cmp(other, opid, vm)? {
337 Either::A(obj) => obj.try_to_bool(vm),
338 Either::B(other) => Ok(other),
339 }
340 }
341
342 pub fn repr(&self, vm: &VirtualMachine) -> PyResult<PyStrRef> {
343 vm.with_recursion("while getting the repr of an object", || {
344 match self.class().slots.repr.load() {
345 Some(slot) => slot(self, vm),
346 None => vm
347 .call_special_method(self, identifier!(vm, __repr__), ())?
348 .try_into_value(vm), }
350 })
351 }
352
353 pub fn ascii(&self, vm: &VirtualMachine) -> PyResult<ascii::AsciiString> {
354 let repr = self.repr(vm)?;
355 let ascii = to_ascii(repr.as_str());
356 Ok(ascii)
357 }
358
359 pub fn str(&self, vm: &VirtualMachine) -> PyResult<PyStrRef> {
361 let obj = match self.to_owned().downcast_exact::<PyStr>(vm) {
362 Ok(s) => return Ok(s.into_pyref()),
363 Err(obj) => obj,
364 };
365 let str_method = match vm.get_special_method(&obj, identifier!(vm, __str__))? {
367 Some(str_method) => str_method,
368 None => return obj.repr(vm),
369 };
370 let s = str_method.invoke((), vm)?;
371 s.downcast::<PyStr>().map_err(|obj| {
372 vm.new_type_error(format!(
373 "__str__ returned non-string (type {})",
374 obj.class().name()
375 ))
376 })
377 }
378
379 fn check_cls<F>(&self, cls: &PyObject, vm: &VirtualMachine, msg: F) -> PyResult
382 where
383 F: Fn() -> String,
384 {
385 cls.get_attr(identifier!(vm, __bases__), vm).map_err(|e| {
386 if e.class().is(vm.ctx.exceptions.attribute_error) {
388 vm.new_type_error(msg())
389 } else {
390 e
391 }
392 })
393 }
394
395 fn abstract_issubclass(&self, cls: &PyObject, vm: &VirtualMachine) -> PyResult<bool> {
396 let mut derived = self;
397 let mut first_item: PyObjectRef;
398 loop {
399 if derived.is(cls) {
400 return Ok(true);
401 }
402
403 let bases = derived.get_attr(identifier!(vm, __bases__), vm)?;
404 let tuple = PyTupleRef::try_from_object(vm, bases)?;
405
406 let n = tuple.len();
407 match n {
408 0 => {
409 return Ok(false);
410 }
411 1 => {
412 first_item = tuple.fast_getitem(0).clone();
413 derived = &first_item;
414 continue;
415 }
416 _ => {
417 if let Some(i) = (0..n).next() {
418 let check = vm.with_recursion("in abstract_issubclass", || {
419 tuple.fast_getitem(i).abstract_issubclass(cls, vm)
420 })?;
421 if check {
422 return Ok(true);
423 }
424 }
425 }
426 }
427
428 return Ok(false);
429 }
430 }
431
432 fn recursive_issubclass(&self, cls: &PyObject, vm: &VirtualMachine) -> PyResult<bool> {
433 if let (Ok(obj), Ok(cls)) = (self.try_to_ref::<PyType>(vm), cls.try_to_ref::<PyType>(vm)) {
434 Ok(obj.fast_issubclass(cls))
435 } else {
436 self.check_cls(self, vm, || {
437 format!("issubclass() arg 1 must be a class, not {}", self.class())
438 })
439 .and(self.check_cls(cls, vm, || {
440 format!(
441 "issubclass() arg 2 must be a class, a tuple of classes, or a union, not {}",
442 cls.class()
443 )
444 }))
445 .and(self.abstract_issubclass(cls, vm))
446 }
447 }
448
449 pub fn is_subclass(&self, cls: &PyObject, vm: &VirtualMachine) -> PyResult<bool> {
452 if cls.class().is(vm.ctx.types.type_type) {
453 if self.is(cls) {
454 return Ok(true);
455 }
456 return self.recursive_issubclass(cls, vm);
457 }
458
459 if let Ok(tuple) = cls.try_to_value::<&Py<PyTuple>>(vm) {
460 for typ in tuple {
461 if vm.with_recursion("in __subclasscheck__", || self.is_subclass(typ, vm))? {
462 return Ok(true);
463 }
464 }
465 return Ok(false);
466 }
467
468 if let Some(meth) = vm.get_special_method(cls, identifier!(vm, __subclasscheck__))? {
469 let ret = vm.with_recursion("in __subclasscheck__", || {
470 meth.invoke((self.to_owned(),), vm)
471 })?;
472 return ret.try_to_bool(vm);
473 }
474
475 self.recursive_issubclass(cls, vm)
476 }
477
478 fn abstract_isinstance(&self, cls: &PyObject, vm: &VirtualMachine) -> PyResult<bool> {
479 let r = if let Ok(typ) = cls.try_to_ref::<PyType>(vm) {
480 if self.class().fast_issubclass(typ) {
481 true
482 } else if let Ok(icls) =
483 PyTypeRef::try_from_object(vm, self.get_attr(identifier!(vm, __class__), vm)?)
484 {
485 if icls.is(self.class()) {
486 false
487 } else {
488 icls.fast_issubclass(typ)
489 }
490 } else {
491 false
492 }
493 } else {
494 self.check_cls(cls, vm, || {
495 format!(
496 "isinstance() arg 2 must be a type or tuple of types, not {}",
497 cls.class()
498 )
499 })?;
500 let icls: PyObjectRef = self.get_attr(identifier!(vm, __class__), vm)?;
501 if vm.is_none(&icls) {
502 false
503 } else {
504 icls.abstract_issubclass(cls, vm)?
505 }
506 };
507 Ok(r)
508 }
509
510 pub fn is_instance(&self, cls: &PyObject, vm: &VirtualMachine) -> PyResult<bool> {
513 if self.class().is(cls) {
516 return Ok(true);
517 }
518
519 if cls.class().is(vm.ctx.types.type_type) {
520 return self.abstract_isinstance(cls, vm);
521 }
522
523 if let Ok(tuple) = cls.try_to_ref::<PyTuple>(vm) {
524 for typ in tuple {
525 if vm.with_recursion("in __instancecheck__", || self.is_instance(typ, vm))? {
526 return Ok(true);
527 }
528 }
529 return Ok(false);
530 }
531
532 if let Some(meth) = vm.get_special_method(cls, identifier!(vm, __instancecheck__))? {
533 let ret = vm.with_recursion("in __instancecheck__", || {
534 meth.invoke((self.to_owned(),), vm)
535 })?;
536 return ret.try_to_bool(vm);
537 }
538
539 self.abstract_isinstance(cls, vm)
540 }
541
542 pub fn hash(&self, vm: &VirtualMachine) -> PyResult<PyHash> {
543 let hash = self.get_class_attr(identifier!(vm, __hash__)).unwrap();
544 if vm.is_none(&hash) {
545 return Err(vm.new_exception_msg(
546 vm.ctx.exceptions.type_error.to_owned(),
547 format!("unhashable type: '{}'", self.class().name()),
548 ));
549 }
550
551 let hash = self
552 .class()
553 .mro_find_map(|cls| cls.slots.hash.load())
554 .unwrap();
555
556 hash(self, vm)
557 }
558
559 pub fn obj_type(&self) -> PyObjectRef {
562 self.class().to_owned().into()
563 }
564
565 pub fn type_check(&self, typ: &Py<PyType>) -> bool {
567 self.fast_isinstance(typ)
568 }
569
570 pub fn length_opt(&self, vm: &VirtualMachine) -> Option<PyResult<usize>> {
571 self.to_sequence()
572 .length_opt(vm)
573 .or_else(|| self.to_mapping().length_opt(vm))
574 }
575
576 pub fn length(&self, vm: &VirtualMachine) -> PyResult<usize> {
577 self.length_opt(vm).ok_or_else(|| {
578 vm.new_type_error(format!(
579 "object of type '{}' has no len()",
580 self.class().name()
581 ))
582 })?
583 }
584
585 pub fn get_item<K: DictKey + ?Sized>(&self, needle: &K, vm: &VirtualMachine) -> PyResult {
586 if let Some(dict) = self.downcast_ref_if_exact::<PyDict>(vm) {
587 return dict.get_item(needle, vm);
588 }
589
590 let needle = needle.to_pyobject(vm);
591
592 if let Ok(mapping) = PyMapping::try_protocol(self, vm) {
593 mapping.subscript(&needle, vm)
594 } else if let Ok(seq) = PySequence::try_protocol(self, vm) {
595 let i = needle.key_as_isize(vm)?;
596 seq.get_item(i, vm)
597 } else {
598 if self.class().fast_issubclass(vm.ctx.types.type_type) {
599 if self.is(vm.ctx.types.type_type) {
600 return PyGenericAlias::new(self.class().to_owned(), needle, vm)
601 .to_pyresult(vm);
602 }
603
604 if let Some(class_getitem) =
605 vm.get_attribute_opt(self.to_owned(), identifier!(vm, __class_getitem__))?
606 {
607 return class_getitem.call((needle,), vm);
608 }
609 }
610 Err(vm.new_type_error(format!("'{}' object is not subscriptable", self.class())))
611 }
612 }
613
614 pub fn set_item<K: DictKey + ?Sized>(
615 &self,
616 needle: &K,
617 value: PyObjectRef,
618 vm: &VirtualMachine,
619 ) -> PyResult<()> {
620 if let Some(dict) = self.downcast_ref_if_exact::<PyDict>(vm) {
621 return dict.set_item(needle, value, vm);
622 }
623
624 let mapping = self.to_mapping();
625 if let Some(f) = mapping.methods.ass_subscript.load() {
626 let needle = needle.to_pyobject(vm);
627 return f(mapping, &needle, Some(value), vm);
628 }
629
630 let seq = self.to_sequence();
631 if let Some(f) = seq.methods.ass_item.load() {
632 let i = needle.key_as_isize(vm)?;
633 return f(seq, i, Some(value), vm);
634 }
635
636 Err(vm.new_type_error(format!(
637 "'{}' does not support item assignment",
638 self.class()
639 )))
640 }
641
642 pub fn del_item<K: DictKey + ?Sized>(&self, needle: &K, vm: &VirtualMachine) -> PyResult<()> {
643 if let Some(dict) = self.downcast_ref_if_exact::<PyDict>(vm) {
644 return dict.del_item(needle, vm);
645 }
646
647 let mapping = self.to_mapping();
648 if let Some(f) = mapping.methods.ass_subscript.load() {
649 let needle = needle.to_pyobject(vm);
650 return f(mapping, &needle, None, vm);
651 }
652 let seq = self.to_sequence();
653 if let Some(f) = seq.methods.ass_item.load() {
654 let i = needle.key_as_isize(vm)?;
655 return f(seq, i, None, vm);
656 }
657
658 Err(vm.new_type_error(format!("'{}' does not support item deletion", self.class())))
659 }
660}