Fix __isabstractmethod__ for class/static methods and properties

Includes a minor departure from CPython implementation of inspect.isabstract
because TPFLAGS_IS_ABSTRACT is not set in obj.__flags__.
This commit is contained in:
Chris Moradi
2021-11-28 15:00:32 -08:00
parent 2ceeea8c37
commit 4409460cb3
5 changed files with 58 additions and 16 deletions

6
Lib/inspect.py vendored
View File

@@ -300,16 +300,16 @@ def isroutine(object):
def isabstract(object):
"""Return true if the object is an abstract base class (ABC)."""
# TODO: RUSTPYTHON
# TPFLAGS_IS_ABSTRACT is not being set for abstract classes, so this implementation differs from CPython.
if not isinstance(object, type):
return False
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 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():

12
Lib/test/test_abc.py vendored
View File

@@ -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

View File

@@ -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) {

View File

@@ -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(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(vm.ctx.new_bool(false).into()),
_ => vm.ctx.new_bool(false).into(),
};
vm._or(&setter_abstract, &getter_abstract)
.unwrap_or(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) {

View File

@@ -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);