Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Backport maintenance/3.1.x] Fix a false positive with singledispatchmethod-function #9605

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading
Loading