Skip to content

Commit 461831f

Browse files
Fix a false positive with singledispatchmethod-function (#9599) (#9605)
* Fix a false positive with ``singledispatchmethod-function`` when a method is decorated with both ``functools.singledispatchmethod`` and ``staticmethod``. Closes #9531 (cherry picked from commit 6df4e1d) Co-authored-by: Mark Byrne <[email protected]>
1 parent cf102ff commit 461831f

16 files changed

+196
-164
lines changed

doc/data/messages/s/singledispatch-method/bad.py

+3-6
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,14 @@
33

44
class Board:
55
@singledispatch # [singledispatch-method]
6-
@classmethod
7-
def convert_position(cls, position):
6+
def convert_position(self, position):
87
pass
98

109
@convert_position.register # [singledispatch-method]
11-
@classmethod
12-
def _(cls, position: str) -> tuple:
10+
def _(self, position: str) -> tuple:
1311
position_a, position_b = position.split(",")
1412
return (int(position_a), int(position_b))
1513

1614
@convert_position.register # [singledispatch-method]
17-
@classmethod
18-
def _(cls, position: tuple) -> str:
15+
def _(self, position: tuple) -> str:
1916
return f"{position[0]},{position[1]}"
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,17 @@
11
from functools import singledispatch
22

33

4-
class Board:
5-
@singledispatch
6-
@staticmethod
7-
def convert_position(position):
8-
pass
4+
@singledispatch
5+
def convert_position(position):
6+
print(position)
97

10-
@convert_position.register
11-
@staticmethod
12-
def _(position: str) -> tuple:
13-
position_a, position_b = position.split(",")
14-
return (int(position_a), int(position_b))
158

16-
@convert_position.register
17-
@staticmethod
18-
def _(position: tuple) -> str:
19-
return f"{position[0]},{position[1]}"
9+
@convert_position.register
10+
def _(position: str) -> tuple:
11+
position_a, position_b = position.split(",")
12+
return (int(position_a), int(position_b))
13+
14+
15+
@convert_position.register
16+
def _(position: tuple) -> str:
17+
return f"{position[0]},{position[1]}"
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,17 @@
11
from functools import singledispatchmethod
22

33

4-
class Board:
5-
@singledispatchmethod # [singledispatchmethod-function]
6-
@staticmethod
7-
def convert_position(position):
8-
pass
4+
@singledispatchmethod # [singledispatchmethod-function]
5+
def convert_position(position):
6+
print(position)
97

10-
@convert_position.register # [singledispatchmethod-function]
11-
@staticmethod
12-
def _(position: str) -> tuple:
13-
position_a, position_b = position.split(",")
14-
return (int(position_a), int(position_b))
158

16-
@convert_position.register # [singledispatchmethod-function]
17-
@staticmethod
18-
def _(position: tuple) -> str:
19-
return f"{position[0]},{position[1]}"
9+
@convert_position.register # [singledispatchmethod-function]
10+
def _(position: str) -> tuple:
11+
position_a, position_b = position.split(",")
12+
return (int(position_a), int(position_b))
13+
14+
15+
@convert_position.register # [singledispatchmethod-function]
16+
def _(position: tuple) -> str:
17+
return f"{position[0]},{position[1]}"
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Fix a false positive with ``singledispatchmethod-function`` when a method is decorated with both ``functools.singledispatchmethod`` and ``staticmethod``.
2+
3+
Closes #9531

pylint/checkers/stdlib.py

+11-12
Original file line numberDiff line numberDiff line change
@@ -673,8 +673,9 @@ def visit_boolop(self, node: nodes.BoolOp) -> None:
673673
"singledispatchmethod-function",
674674
)
675675
def visit_functiondef(self, node: nodes.FunctionDef) -> None:
676-
if node.decorators and isinstance(node.parent, nodes.ClassDef):
677-
self._check_lru_cache_decorators(node)
676+
if node.decorators:
677+
if isinstance(node.parent, nodes.ClassDef):
678+
self._check_lru_cache_decorators(node)
678679
self._check_dispatch_decorators(node)
679680

680681
def _check_lru_cache_decorators(self, node: nodes.FunctionDef) -> None:
@@ -733,16 +734,14 @@ def _check_dispatch_decorators(self, node: nodes.FunctionDef) -> None:
733734
interfaces.INFERENCE,
734735
)
735736

736-
if "singledispatch" in decorators_map and "classmethod" in decorators_map:
737-
self.add_message(
738-
"singledispatch-method",
739-
node=decorators_map["singledispatch"][0],
740-
confidence=decorators_map["singledispatch"][1],
741-
)
742-
elif (
743-
"singledispatchmethod" in decorators_map
744-
and "staticmethod" in decorators_map
745-
):
737+
if node.is_method():
738+
if "singledispatch" in decorators_map:
739+
self.add_message(
740+
"singledispatch-method",
741+
node=decorators_map["singledispatch"][0],
742+
confidence=decorators_map["singledispatch"][1],
743+
)
744+
elif "singledispatchmethod" in decorators_map:
746745
self.add_message(
747746
"singledispatchmethod-function",
748747
node=decorators_map["singledispatchmethod"][0],
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
"""Tests for singledispatch-method"""
2+
# pylint: disable=missing-class-docstring, missing-function-docstring,too-few-public-methods
3+
4+
5+
from functools import singledispatch
6+
7+
8+
class Board1:
9+
@singledispatch # [singledispatch-method]
10+
def convert_position(self, position):
11+
pass
12+
13+
@convert_position.register # [singledispatch-method]
14+
def _(self, position: str) -> tuple:
15+
position_a, position_b = position.split(",")
16+
return (int(position_a), int(position_b))
17+
18+
@convert_position.register # [singledispatch-method]
19+
def _(self, position: tuple) -> str:
20+
return f"{position[0]},{position[1]}"
21+
22+
23+
class Board2:
24+
@singledispatch # [singledispatch-method]
25+
@classmethod
26+
def convert_position(cls, position):
27+
pass
28+
29+
@convert_position.register # [singledispatch-method]
30+
@classmethod
31+
def _(cls, position: str) -> tuple:
32+
position_a, position_b = position.split(",")
33+
return (int(position_a), int(position_b))
34+
35+
@convert_position.register # [singledispatch-method]
36+
@classmethod
37+
def _(cls, position: tuple) -> str:
38+
return f"{position[0]},{position[1]}"
39+
40+
41+
42+
class Board3:
43+
@singledispatch # [singledispatch-method]
44+
@staticmethod
45+
def convert_position(position):
46+
pass
47+
48+
@convert_position.register # [singledispatch-method]
49+
@staticmethod
50+
def _(position: str) -> tuple:
51+
position_a, position_b = position.split(",")
52+
return (int(position_a), int(position_b))
53+
54+
@convert_position.register # [singledispatch-method]
55+
@staticmethod
56+
def _(position: tuple) -> str:
57+
return f"{position[0]},{position[1]}"
58+
59+
60+
# Do not emit `singledispatch-method`:
61+
@singledispatch
62+
def convert_position(position):
63+
print(position)
64+
65+
@convert_position.register
66+
def _(position: str) -> tuple:
67+
position_a, position_b = position.split(",")
68+
return (int(position_a), int(position_b))
69+
70+
@convert_position.register
71+
def _(position: tuple) -> str:
72+
return f"{position[0]},{position[1]}"
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
1-
singledispatch-method:26:5:26:19:Board.convert_position:singledispatch decorator should not be used with methods, use singledispatchmethod instead.:HIGH
2-
singledispatch-method:31:5:31:30:Board._:singledispatch decorator should not be used with methods, use singledispatchmethod instead.:INFERENCE
3-
singledispatch-method:37:5:37:30:Board._:singledispatch decorator should not be used with methods, use singledispatchmethod instead.:INFERENCE
1+
singledispatch-method:9:5:9:19:Board1.convert_position:singledispatch decorator should not be used with methods, use singledispatchmethod instead.:HIGH
2+
singledispatch-method:13:5:13:30:Board1._:singledispatch decorator should not be used with methods, use singledispatchmethod instead.:INFERENCE
3+
singledispatch-method:18:5:18:30:Board1._:singledispatch decorator should not be used with methods, use singledispatchmethod instead.:INFERENCE
4+
singledispatch-method:24:5:24:19:Board2.convert_position:singledispatch decorator should not be used with methods, use singledispatchmethod instead.:HIGH
5+
singledispatch-method:29:5:29:30:Board2._:singledispatch decorator should not be used with methods, use singledispatchmethod instead.:INFERENCE
6+
singledispatch-method:35:5:35:30:Board2._:singledispatch decorator should not be used with methods, use singledispatchmethod instead.:INFERENCE
7+
singledispatch-method:43:5:43:19:Board3.convert_position:singledispatch decorator should not be used with methods, use singledispatchmethod instead.:HIGH
8+
singledispatch-method:48:5:48:30:Board3._:singledispatch decorator should not be used with methods, use singledispatchmethod instead.:INFERENCE
9+
singledispatch-method:54:5:54:30:Board3._:singledispatch decorator should not be used with methods, use singledispatchmethod instead.:INFERENCE

tests/functional/s/singledispatch/singledispatch_method_py37.py

-23
This file was deleted.

tests/functional/s/singledispatch/singledispatch_method_py37.rc

-2
This file was deleted.

tests/functional/s/singledispatch/singledispatch_method_py37.txt

-3
This file was deleted.

tests/functional/s/singledispatch/singledispatch_method_py38.py

-40
This file was deleted.

tests/functional/s/singledispatch/singledispatch_method_py38.txt

-3
This file was deleted.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
"""Tests for singledispatchmethod-function"""
2+
# pylint: disable=missing-class-docstring, missing-function-docstring,too-few-public-methods
3+
4+
5+
from functools import singledispatchmethod
6+
7+
8+
# Emit `singledispatchmethod-function` when functions are decorated with `singledispatchmethod`
9+
@singledispatchmethod # [singledispatchmethod-function]
10+
def convert_position2(position):
11+
print(position)
12+
13+
@convert_position2.register # [singledispatchmethod-function]
14+
def _(position: str) -> tuple:
15+
position_a, position_b = position.split(",")
16+
return (int(position_a), int(position_b))
17+
18+
@convert_position2.register # [singledispatchmethod-function]
19+
def _(position: tuple) -> str:
20+
return f"{position[0]},{position[1]}"
21+
22+
23+
class Board1:
24+
@singledispatchmethod
25+
def convert_position(self, position):
26+
pass
27+
28+
@convert_position.register
29+
def _(self, position: str) -> tuple:
30+
position_a, position_b = position.split(",")
31+
return (int(position_a), int(position_b))
32+
33+
@convert_position.register
34+
def _(self, position: tuple) -> str:
35+
return f"{position[0]},{position[1]}"
36+
37+
38+
class Board2:
39+
@singledispatchmethod
40+
@staticmethod
41+
def convert_position(position):
42+
pass
43+
44+
@convert_position.register
45+
@staticmethod
46+
def _(position: str) -> tuple:
47+
position_a, position_b = position.split(",")
48+
return (int(position_a), int(position_b))
49+
50+
@convert_position.register
51+
@staticmethod
52+
def _(position: tuple) -> str:
53+
return f"{position[0]},{position[1]}"
54+
55+
56+
class Board3:
57+
@singledispatchmethod
58+
@classmethod
59+
def convert_position(cls, position):
60+
pass
61+
62+
@convert_position.register
63+
@classmethod
64+
def _(cls, position: str) -> tuple:
65+
position_a, position_b = position.split(",")
66+
return (int(position_a), int(position_b))
67+
68+
@convert_position.register
69+
@classmethod
70+
def _(cls, position: tuple) -> str:
71+
return f"{position[0]},{position[1]}"
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
singledispatchmethod-function:9:1:9:21:convert_position2:singledispatchmethod decorator should not be used with functions, use singledispatch instead.:HIGH
2+
singledispatchmethod-function:13:1:13:27:_:singledispatchmethod decorator should not be used with functions, use singledispatch instead.:INFERENCE
3+
singledispatchmethod-function:18:1:18:27:_:singledispatchmethod decorator should not be used with functions, use singledispatch instead.:INFERENCE

0 commit comments

Comments
 (0)