Skip to content

Commit

Permalink
Add redundant-union-assign-typehint checker for duplicated assign…
Browse files Browse the repository at this point in the history
… typehints (#7745)


Co-authored-by: Pierre Sassoulas <pierre.sassoulas@gmail.com>
  • Loading branch information
orSolocate and Pierre-Sassoulas committed Nov 30, 2022
1 parent 8e2c907 commit 1a81b16
Show file tree
Hide file tree
Showing 5 changed files with 121 additions and 1 deletion.
4 changes: 4 additions & 0 deletions doc/whatsnew/fragments/7636.new_check
Original file line number Diff line number Diff line change
@@ -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
80 changes: 79 additions & 1 deletion pylint/extensions/typing.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
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
Expand Down Expand Up @@ -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 = (
(
Expand Down Expand Up @@ -219,6 +224,79 @@ def visit_attribute(self, node: nodes.Attribute) -> None:
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,
Expand Down
21 changes: 21 additions & 0 deletions tests/functional/ext/typing/redundant_typehint_argument.py
Original file line number Diff line number Diff line change
@@ -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']
8 changes: 8 additions & 0 deletions tests/functional/ext/typing/redundant_typehint_argument.rc
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
[main]
load-plugins=pylint.extensions.typing

[testoptions]
min_pyver=3.7

[TYPING]
runtime-typing=no
9 changes: 9 additions & 0 deletions tests/functional/ext/typing/redundant_typehint_argument.txt
Original file line number Diff line number Diff line change
@@ -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

0 comments on commit 1a81b16

Please sign in to comment.