8000 Add note about wrong error code in type: ignore (#12067) · python/mypy@99f4d5a · GitHub
[go: up one dir, main page]

Skip to content

Commit 99f4d5a

Browse files
authored
Add note about wrong error code in type: ignore (#12067)
If we change the error code of a message, it can result in existing "type: ignore" comments being ignored. If this seems to be the case, add a note that hints about changing the ignored error code. Give a more specific message for a set of special cased error codes where we know the original error code. In other cases give a more vague message.
1 parent fb94f80 commit 99f4d5a

File tree

2 files changed

+76
-18
lines changed

2 files changed

+76
-18
lines changed

mypy/errors.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,13 @@
1616
from mypy.util import DEFAULT_SOURCE_OFFSET, is_typeshed_file
1717

1818
T = TypeVar("T")
19+
1920
allowed_duplicates: Final = ["@overload", "Got:", "Expected:"]
2021

22+
# Keep track of the original error code when the error code of a message is changed.
23+
# This is used to give notes about out-of-date "type: ignore" comments.
24+
original_error_codes: Final = {codes.LITERAL_REQ: codes.MISC}
25+
2126

2227
class ErrorInfo:
2328
"""Representation of a single error message."""
@@ -388,6 +393,24 @@ def add_error_info(self, info: ErrorInfo) -> None:
388393
info.hidden = True
389394
self.report_hidden_errors(info)
390395
self._add_error_info(file, info)
396+
ignored_codes = self.ignored_lines.get(file, {}).get(info.line, [])
397+
if ignored_codes and info.code:
398+
# Something is ignored on the line, but not this error, so maybe the error
399+
# code is incorrect.
400+
msg = f'Error code "{info.code.code}" not covered by "type: ignore" comment'
401+
if info.code in original_error_codes:
402+
# If there seems to be a "type: ignore" with a stale error
403+
# code, report a more specific note.
404+
old_code = original_error_codes[info.code].code
405+
if old_code in ignored_codes:
406+
msg = (f'Error code changed to {info.code.code}; "type: ignore" comment ' +
407+
'may be out of date')
408+
note = ErrorInfo(
409+
info.import_ctx, info.file, info.module, info.type, info.function_or_member,
410+
info.line, info.column, 'note', msg,
411+
code=None, blocker=False, only_once=False, allow_dups=False
412+
)
413+
self._add_error_info(file, note)
391414

392415
def has_many_errors(self) -> bool:
393416
if self.many_errors_threshold < 0:

test-data/unit/check-errorcodes.test

Lines changed: 53 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -75,41 +75,51 @@ for v in f(): # type: int, int # E: Syntax error in type annotation [syntax]
7575

7676
[case testErrorCodeIgnore1]
7777
'x'.foobar # type: ignore[attr-defined]
78-
'x'.foobar # type: ignore[xyz] # E: "str" has no attribute "foobar" [attr-defined]
78+
'x'.foobar # type: ignore[xyz] # E: "str" has no attribute "foobar" [attr-defined] \
79+
# N: Error code "attr-defined" not covered by "type: ignore" comment
7980
'x'.foobar # type: ignore
8081

8182
[case testErrorCodeIgnore2]
8283
a = 'x'.foobar # type: int # type: ignore[attr-defined]
83-
b = 'x'.foobar # type: int # type: ignore[xyz] # E: "str" has no attribute "foobar" [attr-defined]
84+
b = 'x'.foobar # type: int # type: ignore[xyz] # E: "str" has no attribute "foobar" [attr-defined] \
85+
# N: Error code "attr-defined" not covered by "type: ignore" comment
8486
c = 'x'.foobar # type: int # type: ignore
8587

8688
[case testErrorCodeIgnore1_python2]
8789
'x'.foobar # type: ignore[attr-defined]
88-
'x'.foobar # type: ignore[xyz] # E: "str" has no attribute "foobar" [attr-defined]
90+
'x'.foobar # type: ignore[xyz] # E: "str" has no attribute "foobar" [attr-defined] \
91+
# N: Error code "attr-defined" not covered by "type: ignore" comment
8992
'x'.foobar # type: ignore
9093

9194
[case testErrorCodeIgnore2_python2]
9295
a = 'x'.foobar # type: int # type: ignore[attr-defined]
93-
b = 'x'.foobar # type: int # type: ignore[xyz] # E: "str" has no attribute "foobar" [attr-defined]
96+
b = 'x'.foobar # type: int # type: ignore[xyz] # E: "str" has no attribute "foobar" [attr-defined] \
97+
# N: Error code "attr-defined" not covered by "type: ignore" comment
9498
c = 'x'.foobar # type: int # type: ignore
9599

96100
[case testErrorCodeIgnoreMultiple1]
97101
a = 'x'.foobar(b) # type: ignore[name-defined, attr-defined]
98-
a = 'x'.foobar(b) # type: ignore[name-defined, xyz] # E: "str" has no attribute "foobar" [attr-defined]
99-
a = 'x'.foobar(b) # type: ignore[xyz, w, attr-defined] # E: Name "b" is not defined [name-defined]
102+
a = 'x'.foobar(b) # type: ignore[name-defined, xyz] # E: "str" has no attribute "foobar" [attr-defined] \
103+
# N: Error code "attr-defined" not covered by "type: ignore" comment
104+
a = 'x'.foobar(b) # type: ignore[xyz, w, attr-defined] # E: Name "b" is not defined [name-defined] \
105+
# N: Error code "name-defined" not covered by "type: ignore" comment
100106

101107
[case testErrorCodeIgnoreMultiple2]
102108
a = 'x'.foobar(b) # type: int # type: ignore[name-defined, attr-defined]
103-
b = 'x'.foobar(b) # type: int # type: ignore[name-defined, xyz] # E: "str" has no attribute "foobar" [attr-defined]
109+
b = 'x'.foobar(b) # type: int # type: ignore[name-defined, xyz] # E: "str" has no attribute "foobar" [attr-defined] \
110+
# N: Error code "attr-defined" not covered by "type: ignore" comment
104111

105112
[case testErrorCodeIgnoreMultiple1_python2]
106113
a = 'x'.foobar(b) # type: ignore[name-defined, attr-defined]
107-
a = 'x'.foobar(b) # type: ignore[name-defined, xyz] # E: "str" has no attribute "foobar" [attr-defined]
108-
a = 'x'.foobar(b) # type: ignore[xyz, w, attr-defined] # E: Name "b" is not defined [name-defined]
114+
a = 'x'.foobar(b) # type: ignore[name-defined, xyz] # E: "str" has no attribute "foobar" [attr-defined] \
115+
# N: Error code "attr-defined" not covered by "type: ignore" comment
116+
a = 'x'.foobar(b) # type: ignore[xyz, w, attr-defined] # E: Name "b" is not defined [name-defined] \
117+
# N: Error code "name-defined" not covered by "type: ignore" comment
109118

110119
[case testErrorCodeIgnoreMultiple2_python2]
111120
a = 'x'.foobar(b) # type: int # type: ignore[name-defined, attr-defined]
112-
b = 'x'.foobar(b) # type: int # type: ignore[name-defined, xyz] # E: "str" has no attribute "foobar" [attr-defined]
121+
b = 'x'.foobar(b) # type: int # type: ignore[name-defined, xyz] # E: "str" has no attribute "foobar" [attr-defined] \
122+
# N: Error code "attr-defined" not covered by "type: ignore" comment
113123

114124
[case testErrorCodeWarnUnusedIgnores1]
115125
# flags: --warn-unused-ignores
@@ -140,16 +150,22 @@ x # type: ignore [name-defined]
140150
x2 # type: ignore [ name-defined ]
141151
x3 # type: ignore [ xyz , name-defined ]
142152
x4 # type: ignore[xyz,name-defined]
143-
y # type: ignore [xyz] # E: Name "y" is not defined [name-defined]
144-
y # type: ignore[ xyz ] # E: Name "y" is not defined [name-defined]
145-
y # type: ignore[ xyz , foo ] # E: Name "y" is not defined [name-defined]
153+
y # type: ignore [xyz] # E: Name "y" is not defined [name-defined] \
154+
# N: Error code "name-defined" not covered by "type: ignore" comment
155+
y # type: ignore[ xyz ] # E: Name "y" is not defined [name-defined] \
156+
# N: Error code "name-defined" not covered by "type: ignore" comment
157+
y # type: ignore[ xyz , foo ] # E: Name "y" is not defined [name-defined] \
158+
# N: Error code "name-defined" not covered by "type: ignore" comment
146159

147160
a = z # type: int # type: ignore [name-defined]
148161
b = z2 # type: int # type: ignore [ name-defined ]
149162
c = z2 # type: int # type: ignore [ name-defined , xyz ]
150-
d = zz # type: int # type: ignore [xyz] # E: Name "zz" is not defined [name-defined]
151-
e = zz # type: int # type: ignore [ xyz ] # E: Name "zz" is not defined [name-defined]
152-
f = zz # type: int # type: ignore [ xyz,foo ] # E: Name "zz" is not defined [name-defined]
163+
d = zz # type: int # type: ignore [xyz] # E: Name "zz" is not defined [name-defined] \
164+
# N: Error code "name-defined" not covered by "type: ignore" comment
165+
e = zz # type: int # type: ignore [ xyz ] # E: Name "zz" is not defined [name-defined] \
166+
# N: Error code "name-defined" not covered by "type: ignore" comment
167+
f = zz # type: int # type: ignore [ xyz,foo ] # E: Name "zz" is not defined [name-defined] \
168+
# N: Error code "name-defined" not covered by "type: ignore" comment
153169

154170
[case testErrorCodeIgnoreAfterArgComment]
155171
def f(x # type: xyz # type: ignore[name-defined] # Comment
@@ -162,7 +178,8 @@ def g(x # type: xyz # type: ignore # Comment
162178
# type () -> None
163179
pass
164180

165-
def h(x # type: xyz # type: ignore[foo] # E: Name "xyz" is not defined [name-defined]
181+
def h(x # type: xyz # type: ignore[foo] # E: Name "xyz" is not defined [name-defined] \
182+
# N: Error code "name-defined" not covered by "type: ignore" comment
166183
):
167184
# type () -> None
168185
pass
@@ -178,7 +195,8 @@ def g(x # type: xyz # type: ignore # Comment
178195
# type () -> None
179196
pass
180197

181-
def h(x # type: xyz # type: ignore[foo] # E: Name "xyz" is not defined [name-defined]
198+
def h(x # type: xyz # type: ignore[foo] # E: Name "xyz" is not defined [name-defined] \
199+
# N: Error code "name-defined" not covered by "type: ignore" comment
182200
):
183201
# type () -> None
184202
pass
@@ -944,3 +962,20 @@ class TensorType: ...
944962
t: TensorType["batch":..., float] # type: ignore
945963
reveal_type(t) # N: Revealed type is "__main__.TensorType"
946964
[builtins fixtures/tuple.pyi]
965+
966+
[case testNoteAboutChangedTypedDictErrorCode]
967+
from typing_extensions import TypedDict
968+
class D(TypedDict):
969+
x: int
970+
971+
def f(d: D, s: str) -> None:
972+
d[s] # type: ignore[xyz] \
973+
# E: TypedDict key must be a string literal; expected one of ("x") [literal-required] \
974+
# N: Error code "literal-required" not covered by "type: ignore" comment
975+
d[s] # E: TypedDict key must be a string literal; expected one of ("x") [literal-required]
976+
d[s] # type: ignore[misc] \
977+
# E: TypedDict key must be a string literal; expected one of ("x") [literal-required] \
978+
# N: Error code changed to literal-required; "type: ignore" comment may be out of date
979+
d[s] # type: ignore[literal-required]
980+
[builtins fixtures/dict.pyi]
981+
[typing fixtures/typing-typeddict.pyi]

0 commit comments

Comments
 (0)
0