Skip to content

Commit

Permalink
Fix a false positive with singledispatchmethod-function (#9599)
Browse files Browse the repository at this point in the history
* Fix a false positive with ``singledispatchmethod-function`` when a method is decorated with both ``functools.singledispatchmethod`` and ``staticmethod``.

Closes #9531
  • Loading branch information
mbyrnepr2 committed May 7, 2024
1 parent 1198113 commit 6df4e1d
Show file tree
Hide file tree
Showing 16 changed files with 196 additions and 164 deletions.
9 changes: 3 additions & 6 deletions doc/data/messages/s/singledispatch-method/bad.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,14 @@

class Board:
@singledispatch # [singledispatch-method]
@classmethod
def convert_position(cls, position):
def convert_position(self, position):
pass

@convert_position.register # [singledispatch-method]
@classmethod
def _(cls, position: str) -> tuple:
def _(self, position: str) -> tuple:
position_a, position_b = position.split(",")
return (int(position_a), int(position_b))

@convert_position.register # [singledispatch-method]
@classmethod
def _(cls, position: tuple) -> str:
def _(self, position: tuple) -> str:
return f"{position[0]},{position[1]}"
26 changes: 12 additions & 14 deletions doc/data/messages/s/singledispatch-method/good.py
Original file line number Diff line number Diff line change
@@ -1,19 +1,17 @@
from functools import singledispatch


class Board:
@singledispatch
@staticmethod
def convert_position(position):
pass
@singledispatch
def convert_position(position):
print(position)

@convert_position.register
@staticmethod
def _(position: str) -> tuple:
position_a, position_b = position.split(",")
return (int(position_a), int(position_b))

@convert_position.register
@staticmethod
def _(position: tuple) -> str:
return f"{position[0]},{position[1]}"
@convert_position.register
def _(position: str) -> tuple:
position_a, position_b = position.split(",")
return (int(position_a), int(position_b))


@convert_position.register
def _(position: tuple) -> str:
return f"{position[0]},{position[1]}"
26 changes: 12 additions & 14 deletions doc/data/messages/s/singledispatchmethod-function/bad.py
Original file line number Diff line number Diff line change
@@ -1,19 +1,17 @@
from functools import singledispatchmethod


class Board:
@singledispatchmethod # [singledispatchmethod-function]
@staticmethod
def convert_position(position):
pass
@singledispatchmethod # [singledispatchmethod-function]
def convert_position(position):
print(position)

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

@convert_position.register # [singledispatchmethod-function]
@staticmethod
def _(position: tuple) -> str:
return f"{position[0]},{position[1]}"
@convert_position.register # [singledispatchmethod-function]
def _(position: str) -> tuple:
position_a, position_b = position.split(",")
return (int(position_a), int(position_b))


@convert_position.register # [singledispatchmethod-function]
def _(position: tuple) -> str:
return f"{position[0]},{position[1]}"
3 changes: 3 additions & 0 deletions doc/whatsnew/fragments/9531.false_positive
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Fix a false positive with ``singledispatchmethod-function`` when a method is decorated with both ``functools.singledispatchmethod`` and ``staticmethod``.

Closes #9531
23 changes: 11 additions & 12 deletions pylint/checkers/stdlib.py
Original file line number Diff line number Diff line change
Expand Up @@ -673,8 +673,9 @@ def visit_boolop(self, node: nodes.BoolOp) -> None:
"singledispatchmethod-function",
)
def visit_functiondef(self, node: nodes.FunctionDef) -> None:
if node.decorators and isinstance(node.parent, nodes.ClassDef):
self._check_lru_cache_decorators(node)
if node.decorators:
if isinstance(node.parent, nodes.ClassDef):
self._check_lru_cache_decorators(node)
self._check_dispatch_decorators(node)

def _check_lru_cache_decorators(self, node: nodes.FunctionDef) -> None:
Expand Down Expand Up @@ -733,16 +734,14 @@ def _check_dispatch_decorators(self, node: nodes.FunctionDef) -> None:
interfaces.INFERENCE,
)

if "singledispatch" in decorators_map and "classmethod" in decorators_map:
self.add_message(
"singledispatch-method",
node=decorators_map["singledispatch"][0],
confidence=decorators_map["singledispatch"][1],
)
elif (
"singledispatchmethod" in decorators_map
and "staticmethod" in decorators_map
):
if node.is_method():
if "singledispatch" in decorators_map:
self.add_message(
"singledispatch-method",
node=decorators_map["singledispatch"][0],
confidence=decorators_map["singledispatch"][1],
)
elif "singledispatchmethod" in decorators_map:
self.add_message(
"singledispatchmethod-function",
node=decorators_map["singledispatchmethod"][0],
Expand Down
72 changes: 72 additions & 0 deletions tests/functional/s/singledispatch/singledispatch_method.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
"""Tests for singledispatch-method"""
# pylint: disable=missing-class-docstring, missing-function-docstring,too-few-public-methods


from functools import singledispatch


class Board1:
@singledispatch # [singledispatch-method]
def convert_position(self, position):
pass

@convert_position.register # [singledispatch-method]
def _(self, position: str) -> tuple:
position_a, position_b = position.split(",")
return (int(position_a), int(position_b))

@convert_position.register # [singledispatch-method]
def _(self, position: tuple) -> str:
return f"{position[0]},{position[1]}"


class Board2:
@singledispatch # [singledispatch-method]
@classmethod
def convert_position(cls, position):
pass

@convert_position.register # [singledispatch-method]
@classmethod
def _(cls, position: str) -> tuple:
position_a, position_b = position.split(",")
return (int(position_a), int(position_b))

@convert_position.register # [singledispatch-method]
@classmethod
def _(cls, position: tuple) -> str:
return f"{position[0]},{position[1]}"



class Board3:
@singledispatch # [singledispatch-method]
@staticmethod
def convert_position(position):
pass

@convert_position.register # [singledispatch-method]
@staticmethod
def _(position: str) -> tuple:
position_a, position_b = position.split(",")
return (int(position_a), int(position_b))

@convert_position.register # [singledispatch-method]
@staticmethod
def _(position: tuple) -> str:
return f"{position[0]},{position[1]}"


# Do not emit `singledispatch-method`:
@singledispatch
def convert_position(position):
print(position)

@convert_position.register
def _(position: str) -> tuple:
position_a, position_b = position.split(",")
return (int(position_a), int(position_b))

@convert_position.register
def _(position: tuple) -> str:
return f"{position[0]},{position[1]}"
12 changes: 9 additions & 3 deletions tests/functional/s/singledispatch/singledispatch_method.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
singledispatch-method:26:5:26:19:Board.convert_position:singledispatch decorator should not be used with methods, use singledispatchmethod instead.:HIGH
singledispatch-method:31:5:31:30:Board._:singledispatch decorator should not be used with methods, use singledispatchmethod instead.:INFERENCE
singledispatch-method:37:5:37:30:Board._:singledispatch decorator should not be used with methods, use singledispatchmethod instead.:INFERENCE
singledispatch-method:9:5:9:19:Board1.convert_position:singledispatch decorator should not be used with methods, use singledispatchmethod instead.:HIGH
singledispatch-method:13:5:13:30:Board1._:singledispatch decorator should not be used with methods, use singledispatchmethod instead.:INFERENCE
singledispatch-method:18:5:18:30:Board1._:singledispatch decorator should not be used with methods, use singledispatchmethod instead.:INFERENCE
singledispatch-method:24:5:24:19:Board2.convert_position:singledispatch decorator should not be used with methods, use singledispatchmethod instead.:HIGH
singledispatch-method:29:5:29:30:Board2._:singledispatch decorator should not be used with methods, use singledispatchmethod instead.:INFERENCE
singledispatch-method:35:5:35:30:Board2._:singledispatch decorator should not be used with methods, use singledispatchmethod instead.:INFERENCE
singledispatch-method:43:5:43:19:Board3.convert_position:singledispatch decorator should not be used with methods, use singledispatchmethod instead.:HIGH
singledispatch-method:48:5:48:30:Board3._:singledispatch decorator should not be used with methods, use singledispatchmethod instead.:INFERENCE
singledispatch-method:54:5:54:30:Board3._:singledispatch decorator should not be used with methods, use singledispatchmethod instead.:INFERENCE
23 changes: 0 additions & 23 deletions tests/functional/s/singledispatch/singledispatch_method_py37.py

This file was deleted.

This file was deleted.

This file was deleted.

40 changes: 0 additions & 40 deletions tests/functional/s/singledispatch/singledispatch_method_py38.py

This file was deleted.

This file was deleted.

71 changes: 71 additions & 0 deletions tests/functional/s/singledispatch/singledispatchmethod_function.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
"""Tests for singledispatchmethod-function"""
# pylint: disable=missing-class-docstring, missing-function-docstring,too-few-public-methods


from functools import singledispatchmethod


# Emit `singledispatchmethod-function` when functions are decorated with `singledispatchmethod`
@singledispatchmethod # [singledispatchmethod-function]
def convert_position2(position):
print(position)

@convert_position2.register # [singledispatchmethod-function]
def _(position: str) -> tuple:
position_a, position_b = position.split(",")
return (int(position_a), int(position_b))

@convert_position2.register # [singledispatchmethod-function]
def _(position: tuple) -> str:
return f"{position[0]},{position[1]}"


class Board1:
@singledispatchmethod
def convert_position(self, position):
pass

@convert_position.register
def _(self, position: str) -> tuple:
position_a, position_b = position.split(",")
return (int(position_a), int(position_b))

@convert_position.register
def _(self, position: tuple) -> str:
return f"{position[0]},{position[1]}"


class Board2:
@singledispatchmethod
@staticmethod
def convert_position(position):
pass

@convert_position.register
@staticmethod
def _(position: str) -> tuple:
position_a, position_b = position.split(",")
return (int(position_a), int(position_b))

@convert_position.register
@staticmethod
def _(position: tuple) -> str:
return f"{position[0]},{position[1]}"


class Board3:
@singledispatchmethod
@classmethod
def convert_position(cls, position):
pass

@convert_position.register
@classmethod
def _(cls, position: str) -> tuple:
position_a, position_b = position.split(",")
return (int(position_a), int(position_b))

@convert_position.register
@classmethod
def _(cls, position: tuple) -> str:
return f"{position[0]},{position[1]}"
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
singledispatchmethod-function:9:1:9:21:convert_position2:singledispatchmethod decorator should not be used with functions, use singledispatch instead.:HIGH
singledispatchmethod-function:13:1:13:27:_:singledispatchmethod decorator should not be used with functions, use singledispatch instead.:INFERENCE
singledispatchmethod-function:18:1:18:27:_:singledispatchmethod decorator should not be used with functions, use singledispatch instead.:INFERENCE

0 comments on commit 6df4e1d

Please sign in to comment.