Skip to content

Add note about wrong error code in type: ignore #12067

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Jan 25, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions mypy/errors.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,13 @@
from mypy.util import DEFAULT_SOURCE_OFFSET, is_typeshed_file

T = TypeVar("T")

allowed_duplicates: Final = ["@overload", "Got:", "Expected:"]

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


class ErrorInfo:
"""Representation of a single error message."""
Expand Down Expand Up @@ -388,6 +393,24 @@ def add_error_info(self, info: ErrorInfo) -> None:
info.hidden = True
self.report_hidden_errors(info)
self._add_error_info(file, info)
ignored_codes = self.ignored_lines.get(file, {}).get(info.line, [])
if ignored_codes and info.code:
# Something is ignored on the line, but not this error, so maybe the error
# code is incorrect.
msg = f'Error code "{info.code.code}" not covered by "type: ignore" comment'
if info.code in original_error_codes:
# If there seems to be a "type: ignore" with a stale error
# code, report a more specific note.
old_code = original_error_codes[info.code].code
if old_code in ignored_codes:
msg = (f'Error code changed to {info.code.code}; "type: ignore" comment ' +
'may be out of date')
note = ErrorInfo(
info.import_ctx, info.file, info.module, info.type, info.function_or_member,
info.line, info.column, 'note', msg,
code=None, blocker=False, only_once=False, allow_dups=False
)
self._add_error_info(file, note)

def has_many_errors(self) -> bool:
if self.many_errors_threshold < 0:
Expand Down
71 changes: 53 additions & 18 deletions test-data/unit/check-errorcodes.test
Original file line number Diff line number Diff line change
Expand Up @@ -75,41 +75,51 @@ for v in f(): # type: int, int # E: Syntax error in type annotation [syntax]

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

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

[case testErrorCodeIgnore1_python2]
'x'.foobar # type: ignore[attr-defined]
'x'.foobar # type: ignore[xyz] # E: "str" has no attribute "foobar" [attr-defined]
'x'.foobar # type: ignore[xyz] # E: "str" has no attribute "foobar" [attr-defined] \
# N: Error code "attr-defined" not covered by "type: ignore" comment
'x'.foobar # type: ignore

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

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

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

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

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

[case testErrorCodeWarnUnusedIgnores1]
# flags: --warn-unused-ignores
Expand Down Expand Up @@ -140,16 +150,22 @@ x # type: ignore [name-defined]
x2 # type: ignore [ name-defined ]
x3 # type: ignore [ xyz , name-defined ]
x4 # type: ignore[xyz,name-defined]
y # type: ignore [xyz] # E: Name "y" is not defined [name-defined]
y # type: ignore[ xyz ] # E: Name "y" is not defined [name-defined]
y # type: ignore[ xyz , foo ] # E: Name "y" is not defined [name-defined]
y # type: ignore [xyz] # E: Name "y" is not defined [name-defined] \
# N: Error code "name-defined" not covered by "type: ignore" comment
y # type: ignore[ xyz ] # E: Name "y" is not defined [name-defined] \
# N: Error code "name-defined" not covered by "type: ignore" comment
y # type: ignore[ xyz , foo ] # E: Name "y" is not defined [name-defined] \
# N: Error code "name-defined" not covered by "type: ignore" comment

a = z # type: int # type: ignore [name-defined]
b = z2 # type: int # type: ignore [ name-defined ]
c = z2 # type: int # type: ignore [ name-defined , xyz ]
d = zz # type: int # type: ignore [xyz] # E: Name "zz" is not defined [name-defined]
e = zz # type: int # type: ignore [ xyz ] # E: Name "zz" is not defined [name-defined]
f = zz # type: int # type: ignore [ xyz,foo ] # E: Name "zz" is not defined [name-defined]
d = zz # type: int # type: ignore [xyz] # E: Name "zz" is not defined [name-defined] \
# N: Error code "name-defined" not covered by "type: ignore" comment
e = zz # type: int # type: ignore [ xyz ] # E: Name "zz" is not defined [name-defined] \
# N: Error code "name-defined" not covered by "type: ignore" comment
f = zz # type: int # type: ignore [ xyz,foo ] # E: Name "zz" is not defined [name-defined] \
# N: Error code "name-defined" not covered by "type: ignore" comment

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

def h(x # type: xyz # type: ignore[foo] # E: Name "xyz" is not defined [name-defined]
def h(x # type: xyz # type: ignore[foo] # E: Name "xyz" is not defined [name-defined] \
# N: Error code "name-defined" not covered by "type: ignore" comment
):
# type () -> None
pass
Expand All @@ -178,7 +195,8 @@ def g(x # type: xyz # type: ignore # Comment
# type () -> None
pass

def h(x # type: xyz # type: ignore[foo] # E: Name "xyz" is not defined [name-defined]
def h(x # type: xyz # type: ignore[foo] # E: Name "xyz" is not defined [name-defined] \
# N: Error code "name-defined" not covered by "type: ignore" comment
):
# type () -> None
pass
Expand Down Expand Up @@ -944,3 +962,20 @@ class TensorType: ...
t: TensorType["batch":..., float] # type: ignore
reveal_type(t) # N: Revealed type is "__main__.TensorType"
[builtins fixtures/tuple.pyi]

[case testNoteAboutChangedTypedDictErrorCode]
from typing_extensions import TypedDict
class D(TypedDict):
x: int

def f(d: D, s: str) -> None:
d[s] # type: ignore[xyz] \
# E: TypedDict key must be a string literal; expected one of ("x") [literal-required] \
# N: Error code "literal-required" not covered by "type: ignore" comment
d[s] # E: TypedDict key must be a string literal; expected one of ("x") [literal-required]
d[s] # type: ignore[misc] \
# E: TypedDict key must be a string literal; expected one of ("x") [literal-required] \
# N: Error code changed to literal-required; "type: ignore" comment may be out of date
d[s] # type: ignore[literal-required]
[builtins fixtures/dict.pyi]
[typing fixtures/typing-typeddict.pyi]