Skip to content

Commit 5910e0d

Browse files
committed
Add redundant-annotation warning
Relates to #18540
1 parent 8e2ce96 commit 5910e0d

File tree

8 files changed

+97
-0
lines changed

8 files changed

+97
-0
lines changed

docs/source/command_line.rst

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -477,6 +477,11 @@ Configuring warnings
477477
The following flags enable warnings for code that is sound but is
478478
potentially problematic or redundant in some way.
479479

480+
.. option:: --warn-redundant-annotation
481+
482+
This flag will make mypy report an error whenever your code uses
483+
an unnecessary annotation in an assignment that can safely be removed.
484+
480485
.. option:: --warn-redundant-casts
481486

482487
This flag will make mypy report an error whenever your code uses

docs/source/error_code_list2.rst

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,23 @@ Example:
6666
def __init__(self) -> None:
6767
self.value = 0
6868
69+
.. _code-redundant-annotation:
70+
71+
Check that annotation is not redundant [redundant-annotation]
72+
-------------------------------------------------------------
73+
74+
If you use :option:`--warn-redundant-annotation <mypy --warn-redundant-annotation>`, mypy will generate an error if the
75+
annotation type is the same as the inferred type.
76+
77+
Example:
78+
79+
.. code-block:: python
80+
81+
# mypy: warn-redundant-annotation
82+
83+
# Error: Annotation "int" is redundant [redundant-annotation]
84+
count: int = 4
85+
6986
.. _code-redundant-cast:
7087

7188
Check that cast is not redundant [redundant-cast]

mypy/checker.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3206,6 +3206,8 @@ def visit_assignment_stmt(self, s: AssignmentStmt) -> None:
32063206
Handle all kinds of assignment statements (simple, indexed, multiple).
32073207
"""
32083208

3209+
self.check_redundant_annotation(s)
3210+
32093211
# Avoid type checking type aliases in stubs to avoid false
32103212
# positives about modern type syntax available in stubs such
32113213
# as X | Y.
@@ -3258,6 +3260,32 @@ def check_type_alias_rvalue(self, s: AssignmentStmt) -> None:
32583260
alias_type = self.expr_checker.accept(s.rvalue)
32593261
self.store_type(s.lvalues[-1], alias_type)
32603262

3263+
def check_redundant_annotation(self, s: AssignmentStmt) -> None:
3264+
if (
3265+
self.options.warn_redundant_annotation
3266+
and not s.is_final_def
3267+
and not s.is_alias_def
3268+
and s.unanalyzed_type is not None
3269+
and s.type is not None
3270+
and not is_same_type(s.type, AnyType(TypeOfAny.special_form))
3271+
and is_same_type(s.type, self.expr_checker.accept(s.rvalue))
3272+
):
3273+
# skip ClassVar
3274+
if any(
3275+
isinstance(lvalue, NameExpr)
3276+
and isinstance(lvalue.node, Var)
3277+
and lvalue.node.is_classvar
3278+
for lvalue in s.lvalues
3279+
):
3280+
return
3281+
3282+
# skip dataclass and NamedTuple
3283+
cls = self.scope.active_class()
3284+
if cls and (dataclasses_plugin.is_processed_dataclass(cls) or cls.is_named_tuple):
3285+
return
3286+
3287+
self.msg.redundant_annotation(s.type, s.rvalue)
3288+
32613289
def check_assignment(
32623290
self,
32633291
lvalue: Lvalue,

mypy/errorcodes.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,9 @@ def __hash__(self) -> int:
162162
"Disallow calling functions without type annotations from annotated functions",
163163
"General",
164164
)
165+
REDUNDANT_ANNOTATION: Final = ErrorCode(
166+
"redundant-annotation", "Check that the annotation is necessary or can be omitted", "General"
167+
)
165168
REDUNDANT_CAST: Final = ErrorCode(
166169
"redundant-cast", "Check that cast changes type of expression", "General"
167170
)

mypy/main.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -820,6 +820,13 @@ def add_invertible_flag(
820820
title="Configuring warnings",
821821
description="Detect code that is sound but redundant or problematic.",
822822
)
823+
add_invertible_flag(
824+
"--warn-redundant-annotation",
825+
default=False,
826+
strict_flag=False,
827+
help="Warn when an annotation is the same as its inferred type",
828+
group=lint_group,
829+
)
823830
add_invertible_flag(
824831
"--warn-redundant-casts",
825832
default=False,

mypy/messages.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1784,6 +1784,13 @@ def unsupported_type_type(self, item: Type, context: Context) -> None:
17841784
f'Cannot instantiate type "type[{format_type_bare(item, self.options)}]"', context
17851785
)
17861786

1787+
def redundant_annotation(self, typ: Type, context: Context) -> None:
1788+
self.fail(
1789+
f"Annotation {format_type(typ, self.options)} is redundant (inferred type is the same)",
1790+
context,
1791+
code=codes.REDUNDANT_ANNOTATION,
1792+
)
1793+
17871794
def redundant_cast(self, typ: Type, context: Context) -> None:
17881795
self.fail(
17891796
f"Redundant cast to {format_type(typ, self.options)}",

mypy/options.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,9 @@ def __init__(self) -> None:
175175
# Also check typeshed for missing annotations
176176
self.warn_incomplete_stub = False
177177

178+
# Warn when an annotation is the same as its inferred type
179+
self.warn_redundant_annotation = False
180+
178181
# Warn about casting an expression to its inferred type
179182
self.warn_redundant_casts = False
180183

test-data/unit/check-warnings.test

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,33 @@ z = Any
6868
def f(q: Union[x, y, z]) -> None:
6969
cast(Union[x, y], q)
7070

71+
-- Redundant annotation
72+
-- --------------------
73+
74+
[case testRedundantAnnotation]
75+
# flags: --warn-redundant-annotation
76+
a = 1
77+
b: int = a
78+
[out]
79+
main:3: error: Annotation "int" is redundant (inferred type is the same)
80+
81+
[case testRedundantAnnotationSkips]
82+
# flags: --warn-redundant-annotation
83+
from dataclasses import dataclass
84+
from typing import ClassVar, NamedTuple
85+
86+
class a:
87+
b: ClassVar[int] = 1
88+
c: ClassVar = 1
89+
90+
class d(NamedTuple):
91+
e: int = 1
92+
93+
@dataclass
94+
class f:
95+
g: int = 1
96+
[builtins fixtures/tuple.pyi]
97+
7198
-- Unused 'type: ignore' comments
7299
-- ------------------------------
73100

0 commit comments

Comments
 (0)