From 461831f107ba2d4cca4d72cc76c3ad32b3c6b850 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 7 May 2024 13:46:33 +0200 Subject: [PATCH] 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 6df4e1d238cec9151fd687a060bb74a1ea681e33) Co-authored-by: Mark Byrne <31762852+mbyrnepr2@users.noreply.github.com> --- .../messages/s/singledispatch-method/bad.py | 9 +-- .../messages/s/singledispatch-method/good.py | 26 ++++--- .../s/singledispatchmethod-function/bad.py | 26 ++++--- doc/whatsnew/fragments/9531.false_positive | 3 + pylint/checkers/stdlib.py | 23 +++--- .../s/singledispatch/singledispatch_method.py | 72 +++++++++++++++++++ .../singledispatch/singledispatch_method.txt | 12 +++- .../singledispatch_method_py37.py | 23 ------ .../singledispatch_method_py37.rc | 2 - .../singledispatch_method_py37.txt | 3 - .../singledispatch_method_py38.py | 40 ----------- .../singledispatch_method_py38.txt | 3 - .../singledispatchmethod_function.py | 71 ++++++++++++++++++ .../singledispatchmethod_function.txt | 3 + .../singledispatchmethod_function_py38.py | 41 ----------- .../singledispatchmethod_function_py38.txt | 3 - 16 files changed, 196 insertions(+), 164 deletions(-) create mode 100644 doc/whatsnew/fragments/9531.false_positive create mode 100644 tests/functional/s/singledispatch/singledispatch_method.py delete mode 100644 tests/functional/s/singledispatch/singledispatch_method_py37.py delete mode 100644 tests/functional/s/singledispatch/singledispatch_method_py37.rc delete mode 100644 tests/functional/s/singledispatch/singledispatch_method_py37.txt delete mode 100644 tests/functional/s/singledispatch/singledispatch_method_py38.py delete mode 100644 tests/functional/s/singledispatch/singledispatch_method_py38.txt create mode 100644 tests/functional/s/singledispatch/singledispatchmethod_function.py create mode 100644 tests/functional/s/singledispatch/singledispatchmethod_function.txt delete mode 100644 tests/functional/s/singledispatch/singledispatchmethod_function_py38.py delete mode 100644 tests/functional/s/singledispatch/singledispatchmethod_function_py38.txt diff --git a/doc/data/messages/s/singledispatch-method/bad.py b/doc/data/messages/s/singledispatch-method/bad.py index 49e545b92d..27df8d2ba0 100644 --- a/doc/data/messages/s/singledispatch-method/bad.py +++ b/doc/data/messages/s/singledispatch-method/bad.py @@ -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]}" diff --git a/doc/data/messages/s/singledispatch-method/good.py b/doc/data/messages/s/singledispatch-method/good.py index f38047cd13..36e623d1e0 100644 --- a/doc/data/messages/s/singledispatch-method/good.py +++ b/doc/data/messages/s/singledispatch-method/good.py @@ -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]}" diff --git a/doc/data/messages/s/singledispatchmethod-function/bad.py b/doc/data/messages/s/singledispatchmethod-function/bad.py index d2255f8659..861d3a20e9 100644 --- a/doc/data/messages/s/singledispatchmethod-function/bad.py +++ b/doc/data/messages/s/singledispatchmethod-function/bad.py @@ -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]}" diff --git a/doc/whatsnew/fragments/9531.false_positive b/doc/whatsnew/fragments/9531.false_positive new file mode 100644 index 0000000000..b776628397 --- /dev/null +++ b/doc/whatsnew/fragments/9531.false_positive @@ -0,0 +1,3 @@ +Fix a false positive with ``singledispatchmethod-function`` when a method is decorated with both ``functools.singledispatchmethod`` and ``staticmethod``. + +Closes #9531 diff --git a/pylint/checkers/stdlib.py b/pylint/checkers/stdlib.py index df8b271bf7..10c1d54bfc 100644 --- a/pylint/checkers/stdlib.py +++ b/pylint/checkers/stdlib.py @@ -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: @@ -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], diff --git a/tests/functional/s/singledispatch/singledispatch_method.py b/tests/functional/s/singledispatch/singledispatch_method.py new file mode 100644 index 0000000000..789abc1f84 --- /dev/null +++ b/tests/functional/s/singledispatch/singledispatch_method.py @@ -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]}" diff --git a/tests/functional/s/singledispatch/singledispatch_method.txt b/tests/functional/s/singledispatch/singledispatch_method.txt index c747fb6a84..794355121f 100644 --- a/tests/functional/s/singledispatch/singledispatch_method.txt +++ b/tests/functional/s/singledispatch/singledispatch_method.txt @@ -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 diff --git a/tests/functional/s/singledispatch/singledispatch_method_py37.py b/tests/functional/s/singledispatch/singledispatch_method_py37.py deleted file mode 100644 index c9269f7bf1..0000000000 --- a/tests/functional/s/singledispatch/singledispatch_method_py37.py +++ /dev/null @@ -1,23 +0,0 @@ -"""Tests for singledispatch-method""" -# pylint: disable=missing-class-docstring, missing-function-docstring,too-few-public-methods - - -from functools import singledispatch - - -class Board: - @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]}" diff --git a/tests/functional/s/singledispatch/singledispatch_method_py37.rc b/tests/functional/s/singledispatch/singledispatch_method_py37.rc deleted file mode 100644 index 77eb3be645..0000000000 --- a/tests/functional/s/singledispatch/singledispatch_method_py37.rc +++ /dev/null @@ -1,2 +0,0 @@ -[main] -py-version=3.7 diff --git a/tests/functional/s/singledispatch/singledispatch_method_py37.txt b/tests/functional/s/singledispatch/singledispatch_method_py37.txt deleted file mode 100644 index 111bc47225..0000000000 --- a/tests/functional/s/singledispatch/singledispatch_method_py37.txt +++ /dev/null @@ -1,3 +0,0 @@ -singledispatch-method:9:5:9:19:Board.convert_position:singledispatch decorator should not be used with methods, use singledispatchmethod instead.:HIGH -singledispatch-method:14:5:14:30:Board._:singledispatch decorator should not be used with methods, use singledispatchmethod instead.:INFERENCE -singledispatch-method:20:5:20:30:Board._:singledispatch decorator should not be used with methods, use singledispatchmethod instead.:INFERENCE diff --git a/tests/functional/s/singledispatch/singledispatch_method_py38.py b/tests/functional/s/singledispatch/singledispatch_method_py38.py deleted file mode 100644 index ad8eea1dd8..0000000000 --- a/tests/functional/s/singledispatch/singledispatch_method_py38.py +++ /dev/null @@ -1,40 +0,0 @@ -"""Tests for singledispatch-method""" -# pylint: disable=missing-class-docstring, missing-function-docstring,too-few-public-methods - - -from functools import singledispatch, singledispatchmethod - - -class BoardRight: - @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 - def _(self, position: tuple) -> str: - return f"{position[0]},{position[1]}" - - -class Board: - @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]}" diff --git a/tests/functional/s/singledispatch/singledispatch_method_py38.txt b/tests/functional/s/singledispatch/singledispatch_method_py38.txt deleted file mode 100644 index c747fb6a84..0000000000 --- a/tests/functional/s/singledispatch/singledispatch_method_py38.txt +++ /dev/null @@ -1,3 +0,0 @@ -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 diff --git a/tests/functional/s/singledispatch/singledispatchmethod_function.py b/tests/functional/s/singledispatch/singledispatchmethod_function.py new file mode 100644 index 0000000000..1a3bf8db9b --- /dev/null +++ b/tests/functional/s/singledispatch/singledispatchmethod_function.py @@ -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]}" diff --git a/tests/functional/s/singledispatch/singledispatchmethod_function.txt b/tests/functional/s/singledispatch/singledispatchmethod_function.txt new file mode 100644 index 0000000000..c25f70cf53 --- /dev/null +++ b/tests/functional/s/singledispatch/singledispatchmethod_function.txt @@ -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 diff --git a/tests/functional/s/singledispatch/singledispatchmethod_function_py38.py b/tests/functional/s/singledispatch/singledispatchmethod_function_py38.py deleted file mode 100644 index ef44f71c15..0000000000 --- a/tests/functional/s/singledispatch/singledispatchmethod_function_py38.py +++ /dev/null @@ -1,41 +0,0 @@ -"""Tests for singledispatchmethod-function""" -# pylint: disable=missing-class-docstring, missing-function-docstring,too-few-public-methods - - -from functools import singledispatch, singledispatchmethod - - -class BoardRight: - @singledispatch - @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 Board: - @singledispatchmethod # [singledispatchmethod-function] - @staticmethod - def convert_position(position): - pass - - @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]}" diff --git a/tests/functional/s/singledispatch/singledispatchmethod_function_py38.txt b/tests/functional/s/singledispatch/singledispatchmethod_function_py38.txt deleted file mode 100644 index 4c236b3466..0000000000 --- a/tests/functional/s/singledispatch/singledispatchmethod_function_py38.txt +++ /dev/null @@ -1,3 +0,0 @@ -singledispatchmethod-function:27:5:27:25:Board.convert_position:singledispatchmethod decorator should not be used with functions, use singledispatch instead.:HIGH -singledispatchmethod-function:32:5:32:30:Board._:singledispatchmethod decorator should not be used with functions, use singledispatch instead.:INFERENCE -singledispatchmethod-function:38:5:38:30:Board._:singledispatchmethod decorator should not be used with functions, use singledispatch instead.:INFERENCE