Skip to content

Commit b8b50b1

Browse files
authored
Use standard formatting for note on unexpected keyword (#20808)
Closes #4773 Closes #10480 Closes #20640 Currently this is the only message that uses this non-standard formatting, while: * Some users find it confusing * We need ~400 lines of code to support this special case * It still has bugs that will require even more special-casing in various places to fix them. So here I implement a minimal change I propose in #4773, namely use this (standard) format: ``` main.py:5: error: Unexpected keyword argument "location" for "method" of "Class" main.py:5: note: "method" defined in "pkg.mod" ``` if the function is defined in a different module. If we want to, we can add extra info (like available argument names with same type), as well as start using this note in more places in separate PRs.
1 parent 41045dc commit b8b50b1

24 files changed

+93
-471
lines changed

mypy/build.py

Lines changed: 6 additions & 108 deletions
Original file line numberDiff line numberDiff line change
@@ -90,14 +90,7 @@
9090
WORKER_START_TIMEOUT,
9191
)
9292
from mypy.error_formatter import OUTPUT_CHOICES, ErrorFormatter
93-
from mypy.errors import (
94-
CompileError,
95-
ErrorInfo,
96-
Errors,
97-
ErrorTuple,
98-
ErrorTupleRaw,
99-
report_internal_error,
100-
)
93+
from mypy.errors import CompileError, ErrorInfo, Errors, ErrorTuple, report_internal_error
10194
from mypy.graph_utils import prepare_sccs, strongly_connected_components, topsort
10295
from mypy.indirection import TypeIndirectionVisitor
10396
from mypy.ipc import (
@@ -111,21 +104,11 @@
111104
send,
112105
)
113106
from mypy.messages import MessageBuilder
114-
from mypy.nodes import (
115-
ClassDef,
116-
Context,
117-
Import,
118-
ImportAll,
119-
ImportBase,
120-
ImportFrom,
121-
MypyFile,
122-
SymbolTable,
123-
)
107+
from mypy.nodes import Import, ImportAll, ImportBase, ImportFrom, MypyFile, SymbolTable
124108
from mypy.options import OPTIONS_AFFECTING_CACHE_NO_PLATFORM
125109
from mypy.partially_defined import PossiblyUndefinedVariableVisitor
126110
from mypy.semanal import SemanticAnalyzer
127111
from mypy.semanal_pass1 import SemanticAnalyzerPreAnalysis
128-
from mypy.traverser import find_definitions
129112
from mypy.util import (
130113
DecodeError,
131114
decode_python_encoding,
@@ -908,10 +891,6 @@ def __init__(
908891
self.queue_order: int = 0
909892
# Is this an instance used by a parallel worker?
910893
self.parallel_worker = parallel_worker
911-
# Cache for ASTs created during error message generation. Note these are
912-
# raw parsed trees not analyzed with mypy. We use these to find absolute
913-
# location of a symbol used as a location for an error message.
914-
self.extra_trees: dict[str, MypyFile] = {}
915894
# Snapshot of import-related options per module. We record these even for
916895
# suppressed imports, since they can affect errors in the callers. Bytes
917896
# value is opaque but can be compared to detect changes in options.
@@ -1080,60 +1059,6 @@ def report_file(
10801059
if self.reports is not None and self.source_set.is_source(file):
10811060
self.reports.file(file, self.modules, type_map, options)
10821061

1083-
def resolve_location(self, graph: dict[str, State], fullname: str) -> Context | None:
1084-
"""Resolve an absolute location of a symbol with given fullname."""
1085-
rest = []
1086-
head = fullname
1087-
while True:
1088-
# TODO: this mimics the logic in lookup.py but it is actually wrong.
1089-
# This is because we don't distinguish between submodule and a local symbol
1090-
# with the same name.
1091-
head, tail = head.rsplit(".", maxsplit=1)
1092-
rest.append(tail)
1093-
if head in graph:
1094-
state = graph[head]
1095-
break
1096-
if "." not in head:
1097-
return None
1098-
*prefix, name = reversed(rest)
1099-
# If this happens something is wrong, but it is better to give slightly
1100-
# less helpful error message than crash.
1101-
if state.path is None:
1102-
return None
1103-
if state.tree is not None and state.tree.defs:
1104-
# We usually free ASTs after processing, but reuse an existing AST if
1105-
# it is still available.
1106-
tree = state.tree
1107-
elif state.id in self.extra_trees:
1108-
tree = self.extra_trees[state.id]
1109-
else:
1110-
if state.source is not None:
1111-
# Sources are usually discarded after processing as well, check
1112-
# if we still have one just in case.
1113-
source = state.source
1114-
else:
1115-
path = state.manager.maybe_swap_for_shadow_path(state.path)
1116-
source = decode_python_encoding(state.manager.fscache.read(path))
1117-
tree = parse(source, state.path, state.id, state.manager.errors, state.options)
1118-
# TODO: run first pass of semantic analysis on freshly parsed trees,
1119-
# we need this to get correct reachability information.
1120-
self.extra_trees[state.id] = tree
1121-
statements = tree.defs
1122-
while prefix:
1123-
part = prefix.pop(0)
1124-
for statement in statements:
1125-
defs = find_definitions(statement, part)
1126-
if not defs or not isinstance((defn := defs[0]), ClassDef):
1127-
continue
1128-
statements = defn.defs.body
1129-
break
1130-
else:
1131-
return None
1132-
for statement in statements:
1133-
if defs := find_definitions(statement, name):
1134-
return defs[0]
1135-
return None
1136-
11371062
def verbosity(self) -> int:
11381063
return self.options.verbosity
11391064

@@ -3999,9 +3924,7 @@ def find_stale_sccs(
39993924
path = manager.errors.simplify_path(graph[id].xpath)
40003925
formatted = manager.errors.format_messages(
40013926
path,
4002-
transform_error_tuples(
4003-
manager, graph, deserialize_codes(graph[id].error_lines)
4004-
),
3927+
deserialize_codes(graph[id].error_lines),
40053928
formatter=manager.error_formatter,
40063929
)
40073930
manager.flush_errors(path, formatted, False)
@@ -4271,9 +4194,7 @@ def process_stale_scc(
42714194
if graph[id].xpath not in manager.errors.ignored_files:
42724195
errors = manager.errors.file_messages(graph[id].xpath)
42734196
formatted = manager.errors.format_messages(
4274-
graph[id].xpath,
4275-
transform_error_tuples(manager, graph, errors),
4276-
formatter=manager.error_formatter,
4197+
graph[id].xpath, errors, formatter=manager.error_formatter
42774198
)
42784199
manager.flush_errors(manager.errors.simplify_path(graph[id].xpath), formatted, False)
42794200
errors_by_id[id] = errors
@@ -4455,37 +4376,14 @@ def write_undocumented_ref_info(
44554376
metastore.write(ref_info_file, json_dumps(deps_json))
44564377

44574378

4458-
def transform_error_tuples(
4459-
manager: BuildManager, graph: dict[str, State], error_tuples_rel: list[ErrorTupleRaw]
4460-
) -> list[ErrorTuple]:
4461-
"""Transform raw error tuples by resolving relative error locations."""
4462-
error_tuples = []
4463-
for e in error_tuples_rel:
4464-
file, line_rel, column, end_line, end_column, severity, message, code = e
4465-
if isinstance(line_rel, int):
4466-
line = line_rel
4467-
else:
4468-
assert file is not None
4469-
loc = manager.resolve_location(graph, line_rel)
4470-
if loc is not None:
4471-
line = loc.line
4472-
column = loc.column
4473-
end_line = loc.end_line or -1
4474-
end_column = loc.end_column or -1
4475-
else:
4476-
line = -1
4477-
error_tuples.append((file, line, column, end_line, end_column, severity, message, code))
4478-
return error_tuples
4479-
4480-
4481-
def serialize_codes(errs: list[ErrorTupleRaw]) -> list[SerializedError]:
4379+
def serialize_codes(errs: list[ErrorTuple]) -> list[SerializedError]:
44824380
return [
44834381
(path, line, column, end_line, end_column, severity, message, code.code if code else None)
44844382
for path, line, column, end_line, end_column, severity, message, code in errs
44854383
]
44864384

44874385

4488-
def deserialize_codes(errs: list[SerializedError]) -> list[ErrorTupleRaw]:
4386+
def deserialize_codes(errs: list[SerializedError]) -> list[ErrorTuple]:
44894387
return [
44904388
(
44914389
path,

mypy/cache.py

Lines changed: 5 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -69,9 +69,9 @@
6969
from mypy_extensions import u8
7070

7171
# High-level cache layout format
72-
CACHE_VERSION: Final = 4
72+
CACHE_VERSION: Final = 5
7373

74-
SerializedError: _TypeAlias = tuple[str | None, int | str, int, int, int, str, str, str | None]
74+
SerializedError: _TypeAlias = tuple[str | None, int, int, int, int, str, str, str | None]
7575

7676

7777
class CacheMeta:
@@ -504,10 +504,7 @@ def write_errors(data: WriteBuffer, errs: list[SerializedError]) -> None:
504504
for path, line, column, end_line, end_column, severity, message, code in errs:
505505
write_tag(data, TUPLE_GEN)
506506
write_str_opt(data, path)
507-
if isinstance(line, str):
508-
write_str(data, line)
509-
else:
510-
write_int(data, line)
507+
write_int(data, line)
511508
write_int(data, column)
512509
write_int(data, end_line)
513510
write_int(data, end_column)
@@ -521,17 +518,10 @@ def read_errors(data: ReadBuffer) -> list[SerializedError]:
521518
result = []
522519
for _ in range(read_int_bare(data)):
523520
assert read_tag(data) == TUPLE_GEN
524-
path = read_str_opt(data)
525-
tag = read_tag(data)
526-
if tag == LITERAL_STR:
527-
line: str | int = read_str_bare(data)
528-
else:
529-
assert tag == LITERAL_INT
530-
line = read_int_bare(data)
531521
result.append(
532522
(
533-
path,
534-
line,
523+
read_str_opt(data),
524+
read_int(data),
535525
read_int(data),
536526
read_int(data),
537527
read_int(data),

mypy/errors.py

Lines changed: 9 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,6 @@ def __init__(
130130
target: str | None = None,
131131
priority: int = 0,
132132
parent_error: ErrorInfo | None = None,
133-
location_ref: str | None = None,
134133
) -> None:
135134
self.import_ctx = import_ctx
136135
self.file = file
@@ -152,18 +151,12 @@ def __init__(
152151
if parent_error is not None:
153152
assert severity == "note", "Only notes can specify parent errors"
154153
self.parent_error = parent_error
155-
self.location_ref = location_ref
156154

157155

158156
# Type used internally to represent errors:
159157
# (path, line, column, end_line, end_column, severity, message, code)
160158
ErrorTuple: _TypeAlias = tuple[str | None, int, int, int, int, str, str, ErrorCode | None]
161159

162-
# A raw version of the above that can refer to either absolute or relative location.
163-
# If the location is relative, the first item (line) is a string with a symbol fullname,
164-
# and three other values (column, end_line, end_column) are set to -1.
165-
ErrorTupleRaw: _TypeAlias = tuple[str | None, int | str, int, int, int, str, str, ErrorCode | None]
166-
167160

168161
class ErrorWatcher:
169162
"""Context manager that can be used to keep track of new errors recorded
@@ -569,14 +562,12 @@ def report(
569562
*,
570563
blocker: bool = False,
571564
severity: str = "error",
572-
file: str | None = None,
573565
only_once: bool = False,
574566
origin_span: Iterable[int] | None = None,
575567
offset: int = 0,
576568
end_line: int | None = None,
577569
end_column: int | None = None,
578570
parent_error: ErrorInfo | None = None,
579-
location_ref: str | None = None,
580571
) -> ErrorInfo:
581572
"""Report message at the given line using the current error context.
582573
@@ -587,7 +578,6 @@ def report(
587578
code: error code (defaults to 'misc'; not shown for notes)
588579
blocker: if True, don't continue analysis after this error
589580
severity: 'error' or 'note'
590-
file: if non-None, override current file as context
591581
only_once: if True, only report this exact message once per build
592582
origin_span: if non-None, override current context as origin
593583
(type: ignores have effect here)
@@ -611,8 +601,6 @@ def report(
611601
else:
612602
end_column = column + 1
613603

614-
if file is None:
615-
file = self.file
616604
if offset:
617605
message = " " * offset + message
618606

@@ -627,7 +615,7 @@ def report(
627615

628616
info = ErrorInfo(
629617
import_ctx=self.import_context(),
630-
file=file,
618+
file=self.file,
631619
module=self.current_module(),
632620
typ=type,
633621
function_or_member=function,
@@ -643,7 +631,6 @@ def report(
643631
origin=(self.file, origin_span),
644632
target=self.current_target(),
645633
parent_error=parent_error,
646-
location_ref=location_ref,
647634
)
648635
self.add_error_info(info)
649636
return info
@@ -1019,8 +1006,6 @@ def raise_error(self, use_stdout: bool = True) -> NoReturn:
10191006
"""
10201007
# self.new_messages() will format all messages that haven't already
10211008
# been returned from a file_messages() call.
1022-
# TODO: pass resolve_location callback here.
1023-
# This will be needed if we are going to use relative locations in blocker errors.
10241009
raise CompileError(
10251010
self.new_messages(), use_stdout=use_stdout, module_with_blocker=self.blocker_module()
10261011
)
@@ -1084,7 +1069,7 @@ def format_messages_default(
10841069
a.append(" " * (DEFAULT_SOURCE_OFFSET + column) + marker)
10851070
return a
10861071

1087-
def file_messages(self, path: str) -> list[ErrorTupleRaw]:
1072+
def file_messages(self, path: str) -> list[ErrorTuple]:
10881073
"""Return an error tuple list of new error messages from a given file."""
10891074
if path not in self.error_info_map:
10901075
return []
@@ -1127,9 +1112,7 @@ def find_shadow_file_mapping(self, path: str) -> str | None:
11271112
return i[1]
11281113
return None
11291114

1130-
def new_messages(
1131-
self, resolve_location: Callable[[str], Context | None] | None = None
1132-
) -> list[str]:
1115+
def new_messages(self) -> list[str]:
11331116
"""Return a string list of new error messages.
11341117
11351118
Use a form suitable for displaying to the user.
@@ -1139,29 +1122,7 @@ def new_messages(
11391122
msgs = []
11401123
for path in self.error_info_map.keys():
11411124
if path not in self.flushed_files:
1142-
error_tuples_rel = self.file_messages(path)
1143-
error_tuples = []
1144-
for e in error_tuples_rel:
1145-
# This has a bit of code duplication with build.py, but it is hard
1146-
# to avoid without either an import cycle or a performance penalty.
1147-
file, line_rel, column, end_line, end_column, severity, message, code = e
1148-
if isinstance(line_rel, int):
1149-
line = line_rel
1150-
elif resolve_location is not None:
1151-
assert file is not None
1152-
loc = resolve_location(line_rel)
1153-
if loc is not None:
1154-
line = loc.line
1155-
column = loc.column
1156-
end_line = loc.end_line or -1
1157-
end_column = loc.end_column or -1
1158-
else:
1159-
line = -1
1160-
else:
1161-
line = -1
1162-
error_tuples.append(
1163-
(file, line, column, end_line, end_column, severity, message, code)
1164-
)
1125+
error_tuples = self.file_messages(path)
11651126
msgs.extend(self.format_messages(path, error_tuples))
11661127
return msgs
11671128

@@ -1173,15 +1134,15 @@ def targets(self) -> set[str]:
11731134
info.target for errs in self.error_info_map.values() for info in errs if info.target
11741135
}
11751136

1176-
def render_messages(self, errors: list[ErrorInfo]) -> list[ErrorTupleRaw]:
1137+
def render_messages(self, errors: list[ErrorInfo]) -> list[ErrorTuple]:
11771138
"""Translate the messages into a sequence of tuples.
11781139
11791140
Each tuple is of form (path, line, col, severity, message, code).
11801141
The rendered sequence includes information about error contexts.
11811142
The path item may be None. If the line item is negative, the
11821143
line number is not defined for the tuple.
11831144
"""
1184-
result: list[ErrorTupleRaw] = []
1145+
result: list[ErrorTuple] = []
11851146
prev_import_context: list[tuple[str, int]] = []
11861147
prev_function_or_member: str | None = None
11871148
prev_type: str | None = None
@@ -1256,21 +1217,9 @@ def render_messages(self, errors: list[ErrorInfo]) -> list[ErrorTupleRaw]:
12561217
else:
12571218
result.append((file, -1, -1, -1, -1, "note", f'In class "{e.type}":', None))
12581219

1259-
if e.location_ref is not None:
1260-
result.append((file, e.location_ref, -1, -1, -1, e.severity, e.message, e.code))
1261-
else:
1262-
result.append(
1263-
(
1264-
file,
1265-
e.line,
1266-
e.column,
1267-
e.end_line,
1268-
e.end_column,
1269-
e.severity,
1270-
e.message,
1271-
e.code,
1272-
)
1273-
)
1220+
result.append(
1221+
(file, e.line, e.column, e.end_line, e.end_column, e.severity, e.message, e.code)
1222+
)
12741223

12751224
prev_import_context = e.import_ctx
12761225
prev_function_or_member = e.function_or_member

0 commit comments

Comments
 (0)