Skip to content

Commit fca4cae

Browse files
authored
Add option to show links to error code docs (once per code) (#15449)
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.
1 parent 310b914 commit fca4cae

File tree

5 files changed

+101
-0
lines changed

5 files changed

+101
-0
lines changed

mypy/errors.py

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,27 @@
2020
# Show error codes for some note-level messages (these usually appear alone
2121
# and not as a comment for a previous error-level message).
2222
SHOW_NOTE_CODES: Final = {codes.ANNOTATION_UNCHECKED}
23+
24+
# Do not add notes with links to error code docs to errors with these codes.
25+
# We can tweak this set as we get more experience about what is helpful and what is not.
26+
HIDE_LINK_CODES: Final = {
27+
# This is a generic error code, so it has no useful docs
28+
codes.MISC,
29+
# These are trivial and have some custom notes (e.g. for list being invariant)
30+
codes.ASSIGNMENT,
31+
codes.ARG_TYPE,
32+
codes.RETURN_VALUE,
33+
# Undefined name/attribute errors are self-explanatory
34+
codes.ATTR_DEFINED,
35+
codes.NAME_DEFINED,
36+
# Overrides have a custom link to docs
37+
codes.OVERRIDE,
38+
}
39+
2340
allowed_duplicates: Final = ["@overload", "Got:", "Expected:"]
2441

42+
BASE_RTD_URL: Final = "https://mypy.rtfd.io/en/stable/_refs.html#code"
43+
2544
# Keep track of the original error code when the error code of a message is changed.
2645
# This is used to give notes about out-of-date "type: ignore" comments.
2746
original_error_codes: Final = {codes.LITERAL_REQ: codes.MISC, codes.TYPE_ABSTRACT: codes.MISC}
@@ -107,6 +126,7 @@ def __init__(
107126
allow_dups: bool,
108127
origin: tuple[str, Iterable[int]] | None = None,
109128
target: str | None = None,
129+
priority: int = 0,
110130
) -> None:
111131
self.import_ctx = import_ctx
112132
self.file = file
@@ -125,6 +145,7 @@ def __init__(
125145
self.allow_dups = allow_dups
126146
self.origin = origin or (file, [line])
127147
self.target = target
148+
self.priority = priority
128149

129150

130151
# Type used internally to represent errors:
@@ -530,6 +551,35 @@ def add_error_info(self, info: ErrorInfo) -> None:
530551
allow_dups=False,
531552
)
532553
self._add_error_info(file, note)
554+
if (
555+
self.options.show_error_code_links
556+
and not self.options.hide_error_codes
557+
and info.code is not None
558+
and info.code not in HIDE_LINK_CODES
559+
):
560+
message = f"See {BASE_RTD_URL}-{info.code.code} for more info"
561+
if message in self.only_once_messages:
562+
return
563+
self.only_once_messages.add(message)
564+
info = ErrorInfo(
565+
import_ctx=info.import_ctx,
566+
file=info.file,
567+
module=info.module,
568+
typ=info.type,
569+
function_or_member=info.function_or_member,
570+
line=info.line,
571+
column=info.column,
572+
end_line=info.end_line,
573+
end_column=info.end_column,
574+
severity="note",
575+
message=message,
576+
code=info.code,
577+
blocker=False,
578+
only_once=True,
579+
allow_dups=False,
580+
priority=20,
581+
)
582+
self._add_error_info(file, info)
533583

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

10421092
# Sort the errors specific to a file according to line number and column.
10431093
a = sorted(errors[i0:i], key=lambda x: (x.line, x.column))
1094+
a = self.sort_within_context(a)
1095+
result.extend(a)
1096+
return result
1097+
1098+
def sort_within_context(self, errors: list[ErrorInfo]) -> list[ErrorInfo]:
1099+
"""For the same location decide which messages to show first/last.
1100+
1101+
Currently, we only compare within the same error code, to decide the
1102+
order of various additional notes.
1103+
"""
1104+
result = []
1105+
i = 0
1106+
while i < len(errors):
1107+
i0 = i
1108+
# Find neighbouring errors with the same position and error code.
1109+
while (
1110+
i + 1 < len(errors)
1111+
and errors[i + 1].line == errors[i].line
1112+
and errors[i + 1].column == errors[i].column
1113+
and errors[i + 1].end_line == errors[i].end_line
1114+
and errors[i + 1].end_column == errors[i].end_column
1115+
and errors[i + 1].code == errors[i].code
1116+
):
1117+
i += 1
1118+
i += 1
1119+
1120+
# Sort the messages specific to a given error by priority.
1121+
a = sorted(errors[i0:i], key=lambda x: x.priority)
10441122
result.extend(a)
10451123
return result
10461124

mypy/main.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -887,6 +887,12 @@ def add_invertible_flag(
887887
help="Hide error codes in error messages",
888888
group=error_group,
889889
)
890+
add_invertible_flag(
891+
"--show-error-code-links",
892+
default=False,
893+
help="Show links to error code documentation",
894+
group=error_group,
895+
)
890896
add_invertible_flag(
891897
"--pretty",
892898
default=False,

mypy/options.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -313,6 +313,7 @@ def __init__(self) -> None:
313313
self.show_column_numbers: bool = False
314314
self.show_error_end: bool = False
315315
self.hide_error_codes = False
316+
self.show_error_code_links = False
316317
# Use soft word wrap and show trimmed source snippets with error location markers.
317318
self.pretty = False
318319
self.dump_graph = False

mypy_self_check.ini

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ plugins = misc/proper_plugin.py
99
python_version = 3.7
1010
exclude = mypy/typeshed/|mypyc/test-data/|mypyc/lib-rt/
1111
enable_error_code = ignore-without-code,redundant-expr
12+
show_error_code_links = True
1213

1314
[mypy-mypy.visitor]
1415
# See docstring for NodeVisitor for motivation.

test-data/unit/check-flags.test

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2195,3 +2195,18 @@ cb(lambda x: a) # OK
21952195

21962196
fn = lambda x: a
21972197
cb(fn)
2198+
2199+
[case testShowErrorCodeLinks]
2200+
# flags: --show-error-codes --show-error-code-links
2201+
2202+
x: int = "" # E: Incompatible types in assignment (expression has type "str", variable has type "int") [assignment]
2203+
list(1) # E: No overload variant of "list" matches argument type "int" [call-overload] \
2204+
# N: Possible overload variants: \
2205+
# N: def [T] __init__(self) -> List[T] \
2206+
# N: def [T] __init__(self, x: Iterable[T]) -> List[T] \
2207+
# N: See https://mypy.rtfd.io/en/stable/_refs.html#code-call-overload for more info
2208+
list(2) # E: No overload variant of "list" matches argument type "int" [call-overload] \
2209+
# N: Possible overload variants: \
2210+
# N: def [T] __init__(self) -> List[T] \
2211+
# N: def [T] __init__(self, x: Iterable[T]) -> List[T]
2212+
[builtins fixtures/list.pyi]

0 commit comments

Comments
 (0)