8000 Use a dedicated error code for assignment to method by ilevkivskyi · Pull Request #14570 · python/mypy · GitHub
[go: up one dir, main page]

Skip to content

Use a dedicated error code for assignment to method #14570

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 4 commits into from
Feb 5, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
Prev Previous commit
Next Next commit
Add support for sub-error codes, and use it
  • Loading branch information
ilevkivskyi committed Feb 3, 2023
commit e4c328988ee189ffe2e8e77d088a265c7f452908
4 changes: 4 additions & 0 deletions docs/source/error_code_list.rst
Original file line number Diff line number Diff line change
Expand Up @@ -347,6 +347,10 @@ To prevent the ambiguity, mypy will flag both assignments by default. If this
error code is disabled, mypy will treat all method assignments r.h.s. as unbound,
so the second assignment will still generate an error.

.. note::

This error code is a sub-error code of a wider ``[assignment]`` code.

Check type variable values [type-var]
-------------------------------------

Expand Down
11 changes: 11 additions & 0 deletions docs/source/error_codes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -113,3 +113,14 @@ still keep the other two error codes enabled. The overall logic is following:
So one can e.g. enable some code globally, disable it for all tests in
the corresponding config section, and then re-enable it with an inline
comment in some specific test.

Sub-error codes of other error codes
------------------------------------

In rare cases (mostly for backwards compatibility reasons), some error
code may be covered by another, wider error code. For example, an error with
code ``[method-assign]`` can be ignored by ``# type: ignore[assignment]``.
Similar logic works for disabling error codes globally. If a given error code
is a sub code of another one, it must mentioned in the docs for the narrower
code. This hierarchy is not nested, there cannot be sub-error codes of other
sub-error codes.
15 changes: 13 additions & 2 deletions mypy/errorcodes.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,20 @@

class ErrorCode:
def __init__(
self, code: str, description: str, category: str, default_enabled: bool = True
self,
code: str,
description: str,
category: str,
default_enabled: bool = True,
sub_code_of: ErrorCode | None = None,
) -> None:
self.code = code
self.description = description
self.category = category
self.default_enabled = default_enabled
self.sub_code_of = sub_code_of
if sub_code_of is not None:
assert sub_code_of.sub_code_of is None, "Nested subcategories are not supported"
error_codes[code] = self

def __str__(self) -> str:
Expand Down Expand Up @@ -52,7 +60,10 @@ def __str__(self) -> str:
"assignment", "Check that assigned value is compatible with target", "General"
)
METHOD_ASSIGN: Final[ErrorCode] = ErrorCode(
"method-assign", "Check that assignment target is not a method", "General"
"method-assign",
"Check that assignment target is not a method",
"General",
sub_code_of=ASSIGNMENT,
)
TYPE_ARG: Final = ErrorCode("type-arg", "Check that generic type arguments are present", "General")
TYPE_VAR: Final = ErrorCode("type-var", "Check that type variable values are valid", "General")
Expand Down
8 changes: 7 additions & 1 deletion mypy/errors.py
Original file line number Diff line number Diff line change
Expand Up @@ -586,7 +586,11 @@ def is_ignored_error(self, line: int, info: ErrorInfo, ignores: dict[int, list[s
# Empty list means that we ignore all errors
return True
if info.code and self.is_error_code_enabled(info.code):
return info.code.code in ignores[line]
return (
info.code.code in ignores[line]
C912 or info.code.sub_code_of is not None
and info.code.sub_code_of.code in ignores[line]
)
return False

def is_error_code_enabled(self, error_code: ErrorCode) -> bool:
Expand All @@ -601,6 +605,8 @@ def is_error_code_enabled(self, error_code: ErrorCode) -> bool:
return False
elif error_code in current_mod_enabled:
return True
elif error_code.sub_code_of is not None and error_code.sub_code_of in current_mod_disabled:
return False
else:
return error_code.default_enabled

Expand Down
13 changes: 13 additions & 0 deletions test-data/unit/check-errorcodes.test
Original file line number Diff line number Diff line change
Expand Up @@ -1018,3 +1018,16 @@ def h(self: A) -> None: pass
A.f = h
# This actually works at runtime, but there is no way to express this in current type system
A.f = A().g # E: Incompatible types in assignment (expression has type "Callable[[], None]", variable has type "Callable[[A], None]") [assignment]

[case testMethodAssignCoveredByAssignmentIgnore]
class A:
def f(self) -> None: pass
def h(self: A) -> None: pass
A.f = h # type: ignore[assignment]

[case testMethodAssignCoveredByAssignmentFlag]
# flags: --disable-error-code=assignment
class A:
def f(self) -> None: pass
def h(self: A) -> None: pass
A.f = h # OK
0