diff --git a/Lib/test/test_isinstance.py b/Lib/test/test_isinstance.py index 5834e0884..a2e95793c 100644 --- a/Lib/test/test_isinstance.py +++ b/Lib/test/test_isinstance.py @@ -224,8 +224,6 @@ class TestIsInstanceIsSubclass(unittest.TestCase): self.assertEqual(False, isinstance(AbstractChild(), Super)) self.assertEqual(False, isinstance(AbstractChild(), Child)) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_subclass_normal(self): # normal classes self.assertEqual(True, issubclass(Super, Super)) @@ -299,6 +297,7 @@ class TestIsInstanceIsSubclass(unittest.TestCase): # TODO: RUSTPYTHON @unittest.expectedFailure + @unittest.skipIf(sys.platform == "win32", "thread 'main' has overflowed its stack") def test_infinite_recursion_in_bases(self): class X: @property diff --git a/extra_tests/snippets/issubclass.py b/extra_tests/snippets/issubclass.py index 62577f213..7f1d87abb 100644 --- a/extra_tests/snippets/issubclass.py +++ b/extra_tests/snippets/issubclass.py @@ -49,6 +49,13 @@ assert issubclass(AlwaysSubClass, AlwaysSubClass) assert issubclass(InheritedAlwaysSubClass, AlwaysSubClass) assert issubclass(AlwaysSubClass, InheritedAlwaysSubClass) +class GenericInstance: + def __subclasscheck__(self, _): + return True + +assert issubclass(A, GenericInstance()) +assert issubclass(list, GenericInstance()) +assert issubclass([], GenericInstance()) class MCAVirtualSubClass(type): def __subclasscheck__(self, subclass): diff --git a/vm/src/builtins/make_module.rs b/vm/src/builtins/make_module.rs index 5bb858b05..73c87bf69 100644 --- a/vm/src/builtins/make_module.rs +++ b/vm/src/builtins/make_module.rs @@ -25,9 +25,7 @@ mod decl { use crate::common::{hash::PyHash, str::to_ascii}; #[cfg(feature = "rustpython-compiler")] use crate::compile; - use crate::function::{ - single_or_tuple_any, Args, FuncArgs, KwArgs, OptionalArg, OptionalOption, - }; + use crate::function::{Args, FuncArgs, KwArgs, OptionalArg, OptionalOption}; use crate::iterator; use crate::readline::{Readline, ReadlineResult}; use crate::scope::Scope; @@ -412,18 +410,8 @@ mod decl { } #[pyfunction] - fn issubclass(subclass: PyTypeRef, typ: PyObjectRef, vm: &VirtualMachine) -> PyResult { - single_or_tuple_any( - typ, - &|cls: &PyTypeRef| vm.issubclass(&subclass, cls), - &|o| { - format!( - "issubclass() arg 2 must be a class or tuple of classes, not {}", - o.class() - ) - }, - vm, - ) + fn issubclass(subclass: PyObjectRef, typ: PyObjectRef, vm: &VirtualMachine) -> PyResult { + vm.issubclass(&subclass, &typ) } #[pyfunction] diff --git a/vm/src/vm.rs b/vm/src/vm.rs index d8067d24f..78d1192ef 100644 --- a/vm/src/vm.rs +++ b/vm/src/vm.rs @@ -985,15 +985,86 @@ impl VirtualMachine { self._isinstance(obj, cls) } + fn abstract_issubclass(&self, subclass: PyObjectRef, cls: &PyObjectRef) -> PyResult { + let mut derived = subclass; + loop { + if derived.class().is(cls) { + return Ok(true); + } + + let bases = self.get_attribute(derived, "__bases__")?; + let tuple = PyTupleRef::try_from_object(self, bases)?; + + let n = tuple.len(); + match n { + 0 => { + return Ok(false); + } + 1 => { + derived = tuple.fast_getitem(0); + continue; + } + _ => { + for i in 0..n { + if let Ok(true) = self.abstract_issubclass(tuple.fast_getitem(i), cls) { + return Ok(true); + } + } + } + } + + return Ok(false); + } + } + + fn recursive_issubclass(&self, subclass: &PyObjectRef, cls: &PyObjectRef) -> PyResult { + if let (Ok(subclass), Ok(cls)) = ( + PyTypeRef::try_from_object(self, subclass.clone()), + PyTypeRef::try_from_object(self, cls.clone()), + ) { + Ok(subclass.issubclass(cls)) + } else { + if self.get_attribute(subclass.clone(), "__bases__").is_err() { + return Err(self.new_type_error(format!( + "issubclass() arg 1 must be a class, not {}", + subclass.class() + ))); + } + if self.get_attribute(cls.clone(), "__bases__").is_err() { + return Err(self.new_type_error(format!( + "issubclass() arg 2 must be a class or tuple of classes, not {}", + cls.class() + ))); + } + self.abstract_issubclass(subclass.clone(), cls) + } + } + /// Determines if `subclass` is a subclass of `cls`, either directly, indirectly or virtually /// via the __subclasscheck__ magic method. - pub fn issubclass(&self, subclass: &PyTypeRef, cls: &PyTypeRef) -> PyResult { - let ret = self.call_special_method( - cls.as_object().clone(), - "__subclasscheck__", - (subclass.clone(),), - )?; - pybool::boolval(self, ret) + pub fn issubclass(&self, subclass: &PyObjectRef, cls: &PyObjectRef) -> PyResult { + if cls.class().is(&self.ctx.types.type_type) { + if subclass.is(cls) { + return Ok(true); + } + return self.recursive_issubclass(subclass, cls); + } + + if let Ok(tuple) = PyTupleRef::try_from_object(self, cls.clone()) { + for typ in tuple.as_slice().iter() { + if self.issubclass(subclass, typ)? { + return Ok(true); + } + } + return Ok(false); + } + + if let Ok(meth) = self.get_special_method(cls.clone(), "__subclasscheck__")? { + let ret = meth.invoke((subclass.clone(),), self)?; + return pybool::boolval(self, ret); + } + + self.recursive_issubclass(subclass, cls) } pub fn call_get_descriptor_specific(