diff --git a/Lib/inspect.py b/Lib/inspect.py index 3ff395ca3..a84f3346b 100644 --- a/Lib/inspect.py +++ b/Lib/inspect.py @@ -302,14 +302,17 @@ def isabstract(object): """Return true if the object is an abstract base class (ABC).""" if not isinstance(object, type): return False - if object.__flags__ & TPFLAGS_IS_ABSTRACT: - return True + # TODO: RUSTPYTHON + # TPFLAGS_IS_ABSTRACT is not being set for abstract classes, so this implementation differs from CPython. + # if object.__flags__ & TPFLAGS_IS_ABSTRACT: + # return True if not issubclass(type(object), abc.ABCMeta): return False if hasattr(object, '__abstractmethods__'): # It looks like ABCMeta.__new__ has finished running; # TPFLAGS_IS_ABSTRACT should have been accurate. - return False + # return False + return bool(getattr(object, '__abstractmethods__')) # It looks like ABCMeta.__new__ has not finished running yet; we're # probably in __init_subclass__. We'll look for abstractmethods manually. for name, value in object.__dict__.items(): diff --git a/Lib/test/test_abc.py b/Lib/test/test_abc.py index 5b897ba8c..ea553b6c5 100644 --- a/Lib/test/test_abc.py +++ b/Lib/test/test_abc.py @@ -71,8 +71,6 @@ def test_factory(abc_ABCMeta, abc_get_cache_token): class TestABC(unittest.TestCase): - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_ABC_helper(self): # create an ABC using the helper class and perform basic checks class C(abc.ABC): @@ -93,8 +91,6 @@ def test_factory(abc_ABCMeta, abc_get_cache_token): def bar(self): pass self.assertFalse(hasattr(bar, "__isabstractmethod__")) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_abstractproperty_basics(self): @property @abc.abstractmethod @@ -113,8 +109,6 @@ def test_factory(abc_ABCMeta, abc_get_cache_token): def foo(self): return super().foo self.assertEqual(D().foo, 3) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_abstractclassmethod_basics(self): @classmethod @abc.abstractmethod @@ -135,8 +129,6 @@ def test_factory(abc_ABCMeta, abc_get_cache_token): self.assertEqual(D.foo(), 'D') self.assertEqual(D().foo(), 'D') - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_abstractstaticmethod_basics(self): @staticmethod @abc.abstractmethod @@ -157,8 +149,6 @@ def test_factory(abc_ABCMeta, abc_get_cache_token): self.assertEqual(D.foo(), 4) self.assertEqual(D().foo(), 4) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_abstractmethod_integration(self): for abstractthing in [abc.abstractmethod, abc.abstractproperty, abc.abstractclassmethod, @@ -187,8 +177,6 @@ def test_factory(abc_ABCMeta, abc_get_cache_token): self.assertRaises(TypeError, F) # because bar is abstract now self.assertTrue(isabstract(F)) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_descriptors_with_abstractmethod(self): class C(metaclass=abc_ABCMeta): @property diff --git a/vm/src/builtins/classmethod.rs b/vm/src/builtins/classmethod.rs index ae115b1e4..553f6f49c 100644 --- a/vm/src/builtins/classmethod.rs +++ b/vm/src/builtins/classmethod.rs @@ -76,6 +76,20 @@ impl PyClassMethod { fn func(&self) -> PyObjectRef { self.callable.clone() } + + #[pyproperty(magic)] + fn isabstractmethod(&self, vm: &VirtualMachine) -> PyObjectRef { + match vm.get_attribute_opt(self.callable.clone(), "__isabstractmethod__") { + Ok(Some(is_abstract)) => is_abstract, + _ => vm.ctx.new_bool(false).into(), + } + } + + #[pyproperty(magic, setter)] + fn set_isabstractmethod(&self, value: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> { + self.callable.set_attr("__isabstractmethod__", value, vm)?; + Ok(()) + } } pub(crate) fn init(context: &PyContext) { diff --git a/vm/src/builtins/property.rs b/vm/src/builtins/property.rs index ee7f51dcd..585c02e15 100644 --- a/vm/src/builtins/property.rs +++ b/vm/src/builtins/property.rs @@ -217,6 +217,32 @@ impl PyProperty { } .into_ref_with_type(vm, TypeProtocol::clone_class(&zelf)) } + + #[pyproperty(magic)] + fn isabstractmethod(&self, vm: &VirtualMachine) -> PyObjectRef { + let getter_abstract = match self.getter.read().to_owned() { + Some(getter) => getter + .get_attr("__isabstractmethod__", vm) + .unwrap_or_else(|_| vm.ctx.new_bool(false).into()), + _ => vm.ctx.new_bool(false).into(), + }; + let setter_abstract = match self.setter.read().to_owned() { + Some(setter) => setter + .get_attr("__isabstractmethod__", vm) + .unwrap_or_else(|_| vm.ctx.new_bool(false).into()), + _ => vm.ctx.new_bool(false).into(), + }; + vm._or(&setter_abstract, &getter_abstract) + .unwrap_or_else(|_| vm.ctx.new_bool(false).into()) + } + + #[pyproperty(magic, setter)] + fn set_isabstractmethod(&self, value: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> { + if let Some(getter) = self.getter.read().to_owned() { + getter.set_attr("__isabstractmethod__", value, vm)?; + } + Ok(()) + } } pub(crate) fn init(context: &PyContext) { diff --git a/vm/src/builtins/staticmethod.rs b/vm/src/builtins/staticmethod.rs index a6e7b8938..038b93b16 100644 --- a/vm/src/builtins/staticmethod.rs +++ b/vm/src/builtins/staticmethod.rs @@ -60,7 +60,21 @@ impl PyStaticMethod { } #[pyimpl(with(GetDescriptor, Constructor), flags(BASETYPE, HAS_DICT))] -impl PyStaticMethod {} +impl PyStaticMethod { + #[pyproperty(magic)] + fn isabstractmethod(&self, vm: &VirtualMachine) -> PyObjectRef { + match vm.get_attribute_opt(self.callable.clone(), "__isabstractmethod__") { + Ok(Some(is_abstract)) => is_abstract, + _ => vm.ctx.new_bool(false).into(), + } + } + + #[pyproperty(magic, setter)] + fn set_isabstractmethod(&self, value: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> { + self.callable.set_attr("__isabstractmethod__", value, vm)?; + Ok(()) + } +} pub fn init(context: &PyContext) { PyStaticMethod::extend_class(context, &context.types.staticmethod_type);