Skip to content

Commit

Permalink
Add note about wrong error code in type: ignore (#12067)
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
JukkaL authored Jan 25, 2022
1 parent fb94f80 commit 99f4d5a
Show file tree
Hide file tree
Showing 2 changed files with 76 additions and 18 deletions.
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]

0 comments on commit 99f4d5a

Please sign in to comment.