Skip to content

Commit

Permalink
Include static and class methods in in abstract decorator list (#4298)
Browse files Browse the repository at this point in the history
  • Loading branch information
charliermarsh authored May 9, 2023
1 parent f238511 commit d365dab
Show file tree
Hide file tree
Showing 3 changed files with 93 additions and 64 deletions.
23 changes: 22 additions & 1 deletion crates/ruff/resources/test/fixtures/flake8_bugbear/B027.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,12 @@
"""
import abc
from abc import ABC
from abc import abstractmethod, abstractproperty
from abc import (
abstractmethod,
abstractproperty,
abstractclassmethod,
abstractstaticmethod,
)
from abc import abstractmethod as notabstract
from abc import abstractproperty as notabstract_property

Expand Down Expand Up @@ -55,6 +60,22 @@ def abstract_5(self):
def abstract_6(self):
...

@abstractclassmethod
def abstract_7(self):
pass

@abc.abstractclassmethod
def abstract_8(self):
...

@abstractstaticmethod
def abstract_9(self):
pass

@abc.abstractstaticmethod
def abstract_10(self):
...

def body_1(self):
print("foo")
...
Expand Down
Original file line number Diff line number Diff line change
@@ -1,96 +1,96 @@
---
source: crates/ruff/src/rules/flake8_bugbear/mod.rs
---
B027.py:13:5: B027 [*] `AbstractClass.empty_1` is an empty method in an abstract base class, but has no abstract decorator
B027.py:18:5: B027 [*] `AbstractClass.empty_1` is an empty method in an abstract base class, but has no abstract decorator
|
13 | class AbstractClass(ABC):
14 | def empty_1(self): # error
18 | class AbstractClass(ABC):
19 | def empty_1(self): # error
| _____^
15 | | ...
20 | | ...
| |___________^ B027
16 |
17 | def empty_2(self): # error
21 |
22 | def empty_2(self): # error
|
= help: Add the `@abstractmethod` decorator

Suggested fix
10 10 |
11 11 |
12 12 | class AbstractClass(ABC):
13 |+ @notabstract
13 14 | def empty_1(self): # error
14 15 | ...
15 16 |
15 15 |
16 16 |
17 17 | class AbstractClass(ABC):
18 |+ @notabstract
18 19 | def empty_1(self): # error
19 20 | ...
20 21 |

B027.py:16:5: B027 [*] `AbstractClass.empty_2` is an empty method in an abstract base class, but has no abstract decorator
B027.py:21:5: B027 [*] `AbstractClass.empty_2` is an empty method in an abstract base class, but has no abstract decorator
|
16 | ...
17 |
18 | def empty_2(self): # error
21 | ...
22 |
23 | def empty_2(self): # error
| _____^
19 | | pass
24 | | pass
| |____________^ B027
20 |
21 | def empty_3(self): # error
25 |
26 | def empty_3(self): # error
|
= help: Add the `@abstractmethod` decorator

Suggested fix
13 13 | def empty_1(self): # error
14 14 | ...
15 15 |
16 |+ @notabstract
16 17 | def empty_2(self): # error
17 18 | pass
18 19 |
18 18 | def empty_1(self): # error
19 19 | ...
20 20 |
21 |+ @notabstract
21 22 | def empty_2(self): # error
22 23 | pass
23 24 |

B027.py:19:5: B027 [*] `AbstractClass.empty_3` is an empty method in an abstract base class, but has no abstract decorator
B027.py:24:5: B027 [*] `AbstractClass.empty_3` is an empty method in an abstract base class, but has no abstract decorator
|
19 | pass
20 |
21 | def empty_3(self): # error
24 | pass
25 |
26 | def empty_3(self): # error
| _____^
22 | | """docstring"""
23 | | ...
27 | | """docstring"""
28 | | ...
| |___________^ B027
24 |
25 | def empty_4(self): # error
29 |
30 | def empty_4(self): # error
|
= help: Add the `@abstractmethod` decorator

Suggested fix
16 16 | def empty_2(self): # error
17 17 | pass
18 18 |
19 |+ @notabstract
19 20 | def empty_3(self): # error
20 21 | """docstring"""
21 22 | ...
21 21 | def empty_2(self): # error
22 22 | pass
23 23 |
24 |+ @notabstract
24 25 | def empty_3(self): # error
25 26 | """docstring"""
26 27 | ...

B027.py:23:5: B027 [*] `AbstractClass.empty_4` is an empty method in an abstract base class, but has no abstract decorator
B027.py:28:5: B027 [*] `AbstractClass.empty_4` is an empty method in an abstract base class, but has no abstract decorator
|
23 | ...
24 |
25 | def empty_4(self): # error
28 | ...
29 |
30 | def empty_4(self): # error
| _____^
26 | | """multiple ellipsis/pass"""
27 | | ...
28 | | pass
29 | | ...
30 | | pass
31 | | """multiple ellipsis/pass"""
32 | | ...
33 | | pass
34 | | ...
35 | | pass
| |____________^ B027
31 |
32 | @notabstract
36 |
37 | @notabstract
|
= help: Add the `@abstractmethod` decorator

Suggested fix
20 20 | """docstring"""
21 21 | ...
22 22 |
23 |+ @notabstract
23 24 | def empty_4(self): # error
24 25 | """multiple ellipsis/pass"""
25 26 | ...
25 25 | """docstring"""
26 26 | ...
27 27 |
28 |+ @notabstract
28 29 | def empty_4(self): # error
29 30 | """multiple ellipsis/pass"""
30 31 | ...


14 changes: 11 additions & 3 deletions crates/ruff_python_semantic/src/analyze/visibility.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,13 +60,21 @@ pub fn is_override(ctx: &Context, decorator_list: &[Expr]) -> bool {
.any(|expr| ctx.match_typing_expr(map_callable(expr), "override"))
}

/// Returns `true` if a function definition is an `@abstractmethod`.
/// Returns `true` if a function definition is an abstract method based on its decorators.
pub fn is_abstract(ctx: &Context, decorator_list: &[Expr]) -> bool {
decorator_list.iter().any(|expr| {
ctx.resolve_call_path(map_callable(expr))
.map_or(false, |call_path| {
call_path.as_slice() == ["abc", "abstractmethod"]
|| call_path.as_slice() == ["abc", "abstractproperty"]
matches!(
call_path.as_slice(),
[
"abc",
"abstractmethod"
| "abstractclassmethod"
| "abstractstaticmethod"
| "abstractproperty"
]
)
})
})
}
Expand Down

0 comments on commit d365dab

Please sign in to comment.