Revert class lookup (#7306)

* Revert __class__ lookup skip in object_isinstance

The optimization to skip __class__ lookup based on getattro check
was incorrect: a class can override __class__ as a property while
still using standard __getattribute__. Revert to always performing
the lookup, matching CPython's object_isinstance behavior.

* Collapse nested if in object_isinstance for clippy
This commit is contained in:
Jeong, YunWon
2026-03-02 19:41:43 +09:00
committed by GitHub
parent baba1f9447
commit 3865fdbf5b

View File

@@ -4,8 +4,8 @@
use crate::{
AsObject, Py, PyObject, PyObjectRef, PyRef, PyResult, TryFromObject, VirtualMachine,
builtins::{
PyBaseObject, PyBytes, PyDict, PyDictRef, PyGenericAlias, PyInt, PyList, PyStr, PyTuple,
PyTupleRef, PyType, PyTypeRef, PyUtf8Str, pystr::AsPyStr,
PyBytes, PyDict, PyDictRef, PyGenericAlias, PyInt, PyList, PyStr, PyTuple, PyTupleRef,
PyType, PyTypeRef, PyUtf8Str, pystr::AsPyStr,
},
common::{hash::PyHash, str::to_ascii},
convert::{ToPyObject, ToPyResult},
@@ -580,18 +580,13 @@ impl PyObject {
if let Ok(cls) = cls.try_to_ref::<PyType>(vm) {
// PyType_Check(cls) - cls is a type object
let mut retval = self.class().is_subtype(cls);
if !retval {
// __class__ is a data descriptor on object that always returns
// obj.class() under standard __getattribute__. Only do the
// expensive attribute lookup when getattro is overridden.
if !self.has_standard_getattro()
&& let Some(i_cls) =
vm.get_attribute_opt(self.to_owned(), identifier!(vm, __class__))?
&& let Ok(i_cls_type) = PyTypeRef::try_from_object(vm, i_cls)
&& !i_cls_type.is(self.class())
{
retval = i_cls_type.is_subtype(cls);
}
if !retval
&& let Some(i_cls) =
vm.get_attribute_opt(self.to_owned(), identifier!(vm, __class__))?
&& let Ok(i_cls_type) = PyTypeRef::try_from_object(vm, i_cls)
&& !i_cls_type.is(self.class())
{
retval = i_cls_type.is_subtype(cls);
}
Ok(retval)
} else {
@@ -603,15 +598,13 @@ impl PyObject {
)
})?;
let i_cls: PyObjectRef = if self.has_standard_getattro() {
self.class().to_owned().into()
if let Some(i_cls) =
vm.get_attribute_opt(self.to_owned(), identifier!(vm, __class__))?
{
i_cls.abstract_issubclass(cls, vm)
} else {
match vm.get_attribute_opt(self.to_owned(), identifier!(vm, __class__))? {
Some(cls) => cls,
None => return Ok(false),
}
};
i_cls.abstract_issubclass(cls, vm)
Ok(false)
}
}
}
@@ -789,15 +782,6 @@ impl PyObject {
Err(vm.new_type_error(format!("'{}' does not support item deletion", self.class())))
}
/// Returns true if the object uses the standard `__getattribute__`
/// (i.e. `object.__getattribute__`), meaning attribute access follows
/// the normal descriptor protocol without custom interception.
#[inline]
fn has_standard_getattro(&self) -> bool {
let getattro = self.class().slots.getattro.load().unwrap();
getattro as usize == PyBaseObject::getattro as *const () as usize
}
/// Equivalent to CPython's _PyObject_LookupSpecial
/// Looks up a special method in the type's MRO without checking instance dict.
/// Returns None if not found (masking AttributeError like CPython).