Ignore non-abstract class attributes when enforcing B024 (#11210)

## Summary

I think the check included here does make sense, but I don't see why we
would allow it if a value is provided for the attribute -- since, in
that case, isn't it _not_ abstract?

Closes: https://github.com/astral-sh/ruff/issues/11208.
This commit is contained in:
Charlie Marsh 2024-04-30 12:01:08 -04:00 committed by GitHub
parent c6dcf3502b
commit c5adbf17da
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 27 additions and 9 deletions

View file

@ -115,25 +115,25 @@ class non_keyword_abcmeta_2(abc.ABCMeta): # safe
# very invalid code, but that's up to mypy et al to check # very invalid code, but that's up to mypy et al to check
class keyword_abc_1(metaclass=ABC): # safe class keyword_abc_1(metaclass=ABC): # incorrect but outside scope of this check
def method(self): def method(self):
foo() foo()
class keyword_abc_2(metaclass=abc.ABC): # safe class keyword_abc_2(metaclass=abc.ABC): # incorrect but outside scope of this check
def method(self): def method(self):
foo() foo()
class abc_set_class_variable_1(ABC): # safe class abc_set_class_variable_1(ABC): # safe (abstract attribute)
foo: int foo: int
class abc_set_class_variable_2(ABC): # safe class abc_set_class_variable_2(ABC): # error (not an abstract attribute)
foo = 2 foo = 2
class abc_set_class_variable_3(ABC): # safe class abc_set_class_variable_3(ABC): # error (not an abstract attribute)
foo: int = 2 foo: int = 2

View file

@ -56,6 +56,7 @@ impl Violation for AbstractBaseClassWithoutAbstractMethod {
format!("`{name}` is an abstract base class, but it has no abstract methods") format!("`{name}` is an abstract base class, but it has no abstract methods")
} }
} }
/// ## What it does /// ## What it does
/// Checks for empty methods in abstract base classes without an abstract /// Checks for empty methods in abstract base classes without an abstract
/// decorator. /// decorator.
@ -156,8 +157,13 @@ pub(crate) fn abstract_base_class(
let mut has_abstract_method = false; let mut has_abstract_method = false;
for stmt in body { for stmt in body {
// https://github.com/PyCQA/flake8-bugbear/issues/293 // https://github.com/PyCQA/flake8-bugbear/issues/293
// Ignore abc's that declares a class attribute that must be set // If an ABC declares an attribute by providing a type annotation
if let Stmt::AnnAssign(_) | Stmt::Assign(_) = stmt { // but does not actually assign a value for that attribute,
// assume it is intended to be an "abstract attribute"
if matches!(
stmt,
Stmt::AnnAssign(ast::StmtAnnAssign { value: None, .. })
) {
has_abstract_method = true; has_abstract_method = true;
continue; continue;
} }

View file

@ -41,6 +41,20 @@ B024.py:92:7: B024 `notabc_Base_1` is an abstract base class, but it has no abst
94 | foo() 94 | foo()
| |
B024.py:132:7: B024 `abc_set_class_variable_2` is an abstract base class, but it has no abstract methods
|
132 | class abc_set_class_variable_2(ABC): # error (not an abstract attribute)
| ^^^^^^^^^^^^^^^^^^^^^^^^ B024
133 | foo = 2
|
B024.py:136:7: B024 `abc_set_class_variable_3` is an abstract base class, but it has no abstract methods
|
136 | class abc_set_class_variable_3(ABC): # error (not an abstract attribute)
| ^^^^^^^^^^^^^^^^^^^^^^^^ B024
137 | foo: int = 2
|
B024.py:141:7: B024 `abc_set_class_variable_4` is an abstract base class, but it has no abstract methods B024.py:141:7: B024 `abc_set_class_variable_4` is an abstract base class, but it has no abstract methods
| |
140 | # this doesn't actually declare a class variable, it's just an expression 140 | # this doesn't actually declare a class variable, it's just an expression
@ -48,5 +62,3 @@ B024.py:141:7: B024 `abc_set_class_variable_4` is an abstract base class, but it
| ^^^^^^^^^^^^^^^^^^^^^^^^ B024 | ^^^^^^^^^^^^^^^^^^^^^^^^ B024
142 | foo 142 | foo
| |