Skip to content

Commit

Permalink
Add option to show links to error code docs (once per code) (#15449)
Browse files Browse the repository at this point in the history
Fixes #7186

We can probably add some kind of redirect from mypy-lang.org, but I
think RTD link is already OK. This PR will need to wait until next
release, unless we want to use `/latest` in the link.
  • Loading branch information
ilevkivskyi committed Jun 28, 2023
1 parent 310b914 commit fca4cae
Show file tree
Hide file tree
Showing 5 changed files with 101 additions and 0 deletions.
78 changes: 78 additions & 0 deletions mypy/errors.py
Expand Up @@ -20,8 +20,27 @@
# Show error codes for some note-level messages (these usually appear alone
# and not as a comment for a previous error-level message).
SHOW_NOTE_CODES: Final = {codes.ANNOTATION_UNCHECKED}

# Do not add notes with links to error code docs to errors with these codes.
# We can tweak this set as we get more experience about what is helpful and what is not.
HIDE_LINK_CODES: Final = {
# This is a generic error code, so it has no useful docs
codes.MISC,
# These are trivial and have some custom notes (e.g. for list being invariant)
codes.ASSIGNMENT,
codes.ARG_TYPE,
codes.RETURN_VALUE,
# Undefined name/attribute errors are self-explanatory
codes.ATTR_DEFINED,
codes.NAME_DEFINED,
# Overrides have a custom link to docs
codes.OVERRIDE,
}

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

BASE_RTD_URL: Final = "https://mypy.rtfd.io/en/stable/_refs.html#code"

# 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, codes.TYPE_ABSTRACT: codes.MISC}
Expand Down Expand Up @@ -107,6 +126,7 @@ def __init__(
allow_dups: bool,
origin: tuple[str, Iterable[int]] | None = None,
target: str | None = None,
priority: int = 0,
) -> None:
self.import_ctx = import_ctx
self.file = file
Expand All @@ -125,6 +145,7 @@ def __init__(
self.allow_dups = allow_dups
self.origin = origin or (file, [line])
self.target = target
self.priority = priority


# Type used internally to represent errors:
Expand Down Expand Up @@ -530,6 +551,35 @@ def add_error_info(self, info: ErrorInfo) -> None:
allow_dups=False,
)
self._add_error_info(file, note)
if (
self.options.show_error_code_links
and not self.options.hide_error_codes
and info.code is not None
and info.code not in HIDE_LINK_CODES
):
message = f"See {BASE_RTD_URL}-{info.code.code} for more info"
if message in self.only_once_messages:
return
self.only_once_messages.add(message)
info = ErrorInfo(
import_ctx=info.import_ctx,
file=info.file,
module=info.module,
typ=info.type,
function_or_member=info.function_or_member,
line=info.line,
column=info.column,
end_line=info.end_line,
end_column=info.end_column,
severity="note",
message=message,
code=info.code,
blocker=False,
only_once=True,
allow_dups=False,
priority=20,
)
self._add_error_info(file, info)

def has_many_errors(self) -> bool:
if self.options.many_errors_threshold < 0:
Expand Down Expand Up @@ -1041,6 +1091,34 @@ def sort_messages(self, errors: list[ErrorInfo]) -> list[ErrorInfo]:

# Sort the errors specific to a file according to line number and column.
a = sorted(errors[i0:i], key=lambda x: (x.line, x.column))
a = self.sort_within_context(a)
result.extend(a)
return result

def sort_within_context(self, errors: list[ErrorInfo]) -> list[ErrorInfo]:
"""For the same location decide which messages to show first/last.
Currently, we only compare within the same error code, to decide the
order of various additional notes.
"""
result = []
i = 0
while i < len(errors):
i0 = i
# Find neighbouring errors with the same position and error code.
while (
i + 1 < len(errors)
and errors[i + 1].line == errors[i].line
and errors[i + 1].column == errors[i].column
and errors[i + 1].end_line == errors[i].end_line
and errors[i + 1].end_column == errors[i].end_column
and errors[i + 1].code == errors[i].code
):
i += 1
i += 1

# Sort the messages specific to a given error by priority.
a = sorted(errors[i0:i], key=lambda x: x.priority)
result.extend(a)
return result

Expand Down
6 changes: 6 additions & 0 deletions mypy/main.py
Expand Up @@ -887,6 +887,12 @@ def add_invertible_flag(
help="Hide error codes in error messages",
group=error_group,
)
add_invertible_flag(
"--show-error-code-links",
default=False,
help="Show links to error code documentation",
group=error_group,
)
add_invertible_flag(
"--pretty",
default=False,
Expand Down
1 change: 1 addition & 0 deletions mypy/options.py
Expand Up @@ -313,6 +313,7 @@ def __init__(self) -> None:
self.show_column_numbers: bool = False
self.show_error_end: bool = False
self.hide_error_codes = False
self.show_error_code_links = False
# Use soft word wrap and show trimmed source snippets with error location markers.
self.pretty = False
self.dump_graph = False
Expand Down
1 change: 1 addition & 0 deletions mypy_self_check.ini
Expand Up @@ -9,6 +9,7 @@ plugins = misc/proper_plugin.py
python_version = 3.7
exclude = mypy/typeshed/|mypyc/test-data/|mypyc/lib-rt/
enable_error_code = ignore-without-code,redundant-expr
show_error_code_links = True

[mypy-mypy.visitor]
# See docstring for NodeVisitor for motivation.
Expand Down
15 changes: 15 additions & 0 deletions test-data/unit/check-flags.test
Expand Up @@ -2195,3 +2195,18 @@ cb(lambda x: a) # OK

fn = lambda x: a
cb(fn)

[case testShowErrorCodeLinks]
# flags: --show-error-codes --show-error-code-links

x: int = "" # E: Incompatible types in assignment (expression has type "str", variable has type "int") [assignment]
list(1) # E: No overload variant of "list" matches argument type "int" [call-overload] \
# N: Possible overload variants: \
# N: def [T] __init__(self) -> List[T] \
# N: def [T] __init__(self, x: Iterable[T]) -> List[T] \
# N: See https://mypy.rtfd.io/en/stable/_refs.html#code-call-overload for more info
list(2) # E: No overload variant of "list" matches argument type "int" [call-overload] \
# N: Possible overload variants: \
# N: def [T] __init__(self) -> List[T] \
# N: def [T] __init__(self, x: Iterable[T]) -> List[T]
[builtins fixtures/list.pyi]

0 comments on commit fca4cae

Please sign in to comment.