summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authororSolocate <38433858+orSolocate@users.noreply.github.com>2022-11-30 23:56:02 +0200
committerGitHub <noreply@github.com>2022-11-30 22:56:02 +0100
commit1a81b16f6113b10233279a0cdfde61c30decaac3 (patch)
treec94f46a10925e73160bf55a0b928df7885dc4b0e
parent8e2c9078922ca1676587a78caa95509a1f8397f5 (diff)
downloadpylint-git-1a81b16f6113b10233279a0cdfde61c30decaac3.tar.gz
Add ``redundant-union-assign-typehint`` checker for duplicated assign typehints (#7745)
Co-authored-by: Pierre Sassoulas <pierre.sassoulas@gmail.com>
-rw-r--r--doc/whatsnew/fragments/7636.new_check4
-rw-r--r--pylint/extensions/typing.py80
-rw-r--r--tests/functional/ext/typing/redundant_typehint_argument.py21
-rw-r--r--tests/functional/ext/typing/redundant_typehint_argument.rc8
-rw-r--r--tests/functional/ext/typing/redundant_typehint_argument.txt9
5 files changed, 121 insertions, 1 deletions
diff --git a/doc/whatsnew/fragments/7636.new_check b/doc/whatsnew/fragments/7636.new_check
new file mode 100644
index 000000000..b00028c31
--- /dev/null
+++ b/doc/whatsnew/fragments/7636.new_check
@@ -0,0 +1,4 @@
+Add ``redundant-typehint-argument`` message for `typing` plugin for duplicate assign typehints.
+Enable the plugin to enable the message with: ``--load-plugins=pylint.extensions.typing``.
+
+Closes #7636
diff --git a/pylint/extensions/typing.py b/pylint/extensions/typing.py
index 5f89cd561..1cf7ec10c 100644
--- a/pylint/extensions/typing.py
+++ b/pylint/extensions/typing.py
@@ -18,7 +18,7 @@ from pylint.checkers.utils import (
safe_infer,
)
from pylint.constants import TYPING_NORETURN
-from pylint.interfaces import INFERENCE
+from pylint.interfaces import HIGH, INFERENCE
if TYPE_CHECKING:
from pylint.lint import PyLinter
@@ -124,6 +124,11 @@ class TypingChecker(BaseChecker):
"Python 3.9.0 and 3.9.1. Use ``typing.Callable`` for these cases instead. "
"https://bugs.python.org/issue42965",
),
+ "R6006": (
+ "Type `%s` is used more than once in union type annotation. Remove redundant typehints.",
+ "redundant-typehint-argument",
+ "Duplicated type arguments will be skipped by `mypy` tool, therefore should be removed to avoid confusion.",
+ ),
}
options = (
(
@@ -219,6 +224,79 @@ class TypingChecker(BaseChecker):
if self._should_check_callable and node.attrname == "Callable":
self._check_broken_callable(node)
+ @only_required_for_messages("redundant-typehint-argument")
+ def visit_annassign(self, node: nodes.AnnAssign) -> None:
+ annotation = node.annotation
+ if self._is_deprecated_union_annotation(annotation, "Optional"):
+ if self._is_optional_none_annotation(annotation):
+ self.add_message(
+ "redundant-typehint-argument",
+ node=annotation,
+ args="None",
+ confidence=HIGH,
+ )
+ return
+ if self._is_deprecated_union_annotation(annotation, "Union") and isinstance(
+ annotation.slice, nodes.Tuple
+ ):
+ types = annotation.slice.elts
+ elif self._is_binop_union_annotation(annotation):
+ types = self._parse_binops_typehints(annotation)
+ else:
+ return
+
+ self._check_union_types(types, node)
+
+ @staticmethod
+ def _is_deprecated_union_annotation(
+ annotation: nodes.NodeNG, union_name: str
+ ) -> bool:
+ return (
+ isinstance(annotation, nodes.Subscript)
+ and isinstance(annotation.value, nodes.Name)
+ and annotation.value.name == union_name
+ )
+
+ def _is_binop_union_annotation(self, annotation: nodes.NodeNG) -> bool:
+ return self._should_check_alternative_union_syntax and isinstance(
+ annotation, nodes.BinOp
+ )
+
+ @staticmethod
+ def _is_optional_none_annotation(annotation: nodes.Subscript) -> bool:
+ return (
+ isinstance(annotation.slice, nodes.Const) and annotation.slice.value is None
+ )
+
+ def _parse_binops_typehints(
+ self, binop_node: nodes.BinOp, typehints_list: list[nodes.NodeNG] | None = None
+ ) -> list[nodes.NodeNG]:
+ typehints_list = typehints_list or []
+ if isinstance(binop_node.left, nodes.BinOp):
+ typehints_list.extend(
+ self._parse_binops_typehints(binop_node.left, typehints_list)
+ )
+ else:
+ typehints_list.append(binop_node.left)
+ typehints_list.append(binop_node.right)
+ return typehints_list
+
+ def _check_union_types(
+ self, types: list[nodes.NodeNG], annotation: nodes.NodeNG
+ ) -> None:
+ types_set = set()
+ for typehint in types:
+ typehint_str = typehint.as_string()
+ if typehint_str in types_set:
+ self.add_message(
+ "redundant-typehint-argument",
+ node=annotation,
+ args=(typehint_str),
+ confidence=HIGH,
+ )
+ else:
+ types_set.add(typehint_str)
+
def _check_for_alternative_union_syntax(
self,
node: nodes.Name | nodes.Attribute,
diff --git a/tests/functional/ext/typing/redundant_typehint_argument.py b/tests/functional/ext/typing/redundant_typehint_argument.py
new file mode 100644
index 000000000..2b423dc7a
--- /dev/null
+++ b/tests/functional/ext/typing/redundant_typehint_argument.py
@@ -0,0 +1,21 @@
+""""Checks for redundant Union typehints in assignments"""
+# pylint: disable=deprecated-typing-alias,consider-alternative-union-syntax,consider-using-alias
+
+from __future__ import annotations
+from typing import Union, Optional, Sequence
+
+# +1: [redundant-typehint-argument, redundant-typehint-argument]
+ANSWER_0: Union[int, int, str, bool, float, str] = 0
+ANSWER_1: Optional[int] = 1
+ANSWER_2: Sequence[int] = [2]
+ANSWER_3: Union[list[int], str, int, bool, list[int]] = 3 # [redundant-typehint-argument]
+ANSWER_4: Optional[None] = None # [redundant-typehint-argument]
+ANSWER_5: Optional[list[int]] = None
+ANSWER_6: Union[None, None] = None # [redundant-typehint-argument]
+# +1: [redundant-typehint-argument]
+ANSWER_7: Union[list[int], dict[int], dict[list[int]], list[str], list[str]] = [7]
+ANSWER_8: int | int = 8 # [redundant-typehint-argument]
+ANSWER_9: str | int | None | int | bool = 9 # [redundant-typehint-argument]
+ANSWER_10: dict | list[int] | float | str | int | bool = 10
+# +1: [redundant-typehint-argument]
+ANSWER_11: list[int] | dict[int] | dict[list[int]] | list[str] | list[str] = ['string']
diff --git a/tests/functional/ext/typing/redundant_typehint_argument.rc b/tests/functional/ext/typing/redundant_typehint_argument.rc
new file mode 100644
index 000000000..7ffc1704b
--- /dev/null
+++ b/tests/functional/ext/typing/redundant_typehint_argument.rc
@@ -0,0 +1,8 @@
+[main]
+load-plugins=pylint.extensions.typing
+
+[testoptions]
+min_pyver=3.7
+
+[TYPING]
+runtime-typing=no
diff --git a/tests/functional/ext/typing/redundant_typehint_argument.txt b/tests/functional/ext/typing/redundant_typehint_argument.txt
new file mode 100644
index 000000000..e76dc562d
--- /dev/null
+++ b/tests/functional/ext/typing/redundant_typehint_argument.txt
@@ -0,0 +1,9 @@
+redundant-typehint-argument:8:0:8:52::Type `int` is used more than once in union type annotation. Remove redundant typehints.:HIGH
+redundant-typehint-argument:8:0:8:52::Type `str` is used more than once in union type annotation. Remove redundant typehints.:HIGH
+redundant-typehint-argument:11:0:11:57::Type `list[int]` is used more than once in union type annotation. Remove redundant typehints.:HIGH
+redundant-typehint-argument:12:10:12:24::Type `None` is used more than once in union type annotation. Remove redundant typehints.:HIGH
+redundant-typehint-argument:14:0:14:34::Type `None` is used more than once in union type annotation. Remove redundant typehints.:HIGH
+redundant-typehint-argument:16:0:16:82::Type `list[str]` is used more than once in union type annotation. Remove redundant typehints.:HIGH
+redundant-typehint-argument:17:0:17:23::Type `int` is used more than once in union type annotation. Remove redundant typehints.:HIGH
+redundant-typehint-argument:18:0:18:43::Type `int` is used more than once in union type annotation. Remove redundant typehints.:HIGH
+redundant-typehint-argument:21:0:21:87::Type `list[str]` is used more than once in union type annotation. Remove redundant typehints.:HIGH