Skip to content

Commit 612d59a

Browse files
authored
Better message if method incompatible with base class (#10572)
Closes #3379.
1 parent a37c388 commit 612d59a

13 files changed

+553
-90
lines changed

mypy/checker.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1685,7 +1685,7 @@ def erase_override(t: Type) -> Type:
16851685
if not emitted_msg:
16861686
# Fall back to generic incompatibility message.
16871687
self.msg.signature_incompatible_with_supertype(
1688-
name, name_in_super, supertype, node)
1688+
name, name_in_super, supertype, node, original=original, override=override)
16891689
if op_method_wider_note:
16901690
self.note("Overloaded operator methods can't have wider argument types"
16911691
" in overrides", node, code=codes.OVERRIDE)

mypy/errors.py

Lines changed: 42 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,9 @@ class ErrorInfo:
5858
# Only report this particular messages once per program.
5959
only_once = False
6060

61+
# Do not remove duplicate copies of this message (ignored if only_once is True).
62+
allow_dups = False
63+
6164
# Actual origin of the error message as tuple (path, line number, end line number)
6265
# If end line number is unknown, use line number.
6366
origin: Tuple[str, int, int]
@@ -82,6 +85,7 @@ def __init__(self,
8285
code: Optional[ErrorCode],
8386
blocker: bool,
8487
only_once: bool,
88+
allow_dups: bool,
8589
origin: Optional[Tuple[str, int, int]] = None,
8690
target: Optional[str] = None) -> None:
8791
self.import_ctx = import_ctx
@@ -96,17 +100,19 @@ def __init__(self,
96100
self.code = code
97101
self.blocker = blocker
98102
self.only_once = only_once
103+
self.allow_dups = allow_dups
99104
self.origin = origin or (file, line, line)
100105
self.target = target
101106

102107

103108
# Type used internally to represent errors:
104-
# (path, line, column, severity, message, code)
109+
# (path, line, column, severity, message, allow_dups, code)
105110
ErrorTuple = Tuple[Optional[str],
106111
int,
107112
int,
108113
str,
109114
str,
115+
bool,
110116
Optional[ErrorCode]]
111117

112118

@@ -290,6 +296,7 @@ def report(self,
290296
severity: str = 'error',
291297
file: Optional[str] = None,
292298
only_once: bool = False,
299+
allow_dups: bool = False,
293300
origin_line: Optional[int] = None,
294301
offset: int = 0,
295302
end_line: Optional[int] = None) -> None:
@@ -304,6 +311,7 @@ def report(self,
304311
severity: 'error' or 'note'
305312
file: if non-None, override current file as context
306313
only_once: if True, only report this exact message once per build
314+
allow_dups: if True, allow duplicate copies of this message (ignored if only_once)
307315
origin_line: if non-None, override current context as origin
308316
end_line: if non-None, override current context as end
309317
"""
@@ -333,7 +341,7 @@ def report(self,
333341

334342
info = ErrorInfo(self.import_context(), file, self.current_module(), type,
335343
function, line, column, severity, message, code,
336-
blocker, only_once,
344+
blocker, only_once, allow_dups,
337345
origin=(self.file, origin_line, end_line),
338346
target=self.current_target())
339347
self.add_error_info(info)
@@ -410,6 +418,7 @@ def report_hidden_errors(self, info: ErrorInfo) -> None:
410418
code=None,
411419
blocker=False,
412420
only_once=True,
421+
allow_dups=False,
413422
origin=info.origin,
414423
target=info.target,
415424
)
@@ -456,7 +465,7 @@ def generate_unused_ignore_errors(self, file: str) -> None:
456465
# Don't use report since add_error_info will ignore the error!
457466
info = ErrorInfo(self.import_context(), file, self.current_module(), None,
458467
None, line, -1, 'error', 'unused "type: ignore" comment',
459-
None, False, False)
468+
None, False, False, False)
460469
self._add_error_info(file, info)
461470

462471
def num_messages(self) -> int:
@@ -515,7 +524,7 @@ def format_messages(self, error_info: List[ErrorInfo],
515524
error_info = [info for info in error_info if not info.hidden]
516525
errors = self.render_messages(self.sort_messages(error_info))
517526
errors = self.remove_duplicates(errors)
518-
for file, line, column, severity, message, code in errors:
527+
for file, line, column, severity, message, allow_dups, code in errors:
519528
s = ''
520529
if file is not None:
521530
if self.show_column_numbers and line >= 0 and column >= 0:
@@ -590,7 +599,7 @@ def render_messages(self,
590599
errors: List[ErrorInfo]) -> List[ErrorTuple]:
591600
"""Translate the messages into a sequence of tuples.
592601
593-
Each tuple is of form (path, line, col, severity, message, code).
602+
Each tuple is of form (path, line, col, severity, message, allow_dups, code).
594603
The rendered sequence includes information about error contexts.
595604
The path item may be None. If the line item is negative, the
596605
line number is not defined for the tuple.
@@ -619,7 +628,8 @@ def render_messages(self,
619628
# Remove prefix to ignore from path (if present) to
620629
# simplify path.
621630
path = remove_path_prefix(path, self.ignore_prefix)
622-
result.append((None, -1, -1, 'note', fmt.format(path, line), None))
631+
result.append((None, -1, -1, 'note',
632+
fmt.format(path, line), e.allow_dups, None))
623633
i -= 1
624634

625635
file = self.simplify_path(e.file)
@@ -631,27 +641,27 @@ def render_messages(self,
631641
e.type != prev_type):
632642
if e.function_or_member is None:
633643
if e.type is None:
634-
result.append((file, -1, -1, 'note', 'At top level:', None))
644+
result.append((file, -1, -1, 'note', 'At top level:', e.allow_dups, None))
635645
else:
636646
result.append((file, -1, -1, 'note', 'In class "{}":'.format(
637-
e.type), None))
647+
e.type), e.allow_dups, None))
638648
else:
639649
if e.type is None:
640650
result.append((file, -1, -1, 'note',
641651
'In function "{}":'.format(
642-
e.function_or_member), None))
652+
e.function_or_member), e.allow_dups, None))
643653
else:
644654
result.append((file, -1, -1, 'note',
645655
'In member "{}" of class "{}":'.format(
646-
e.function_or_member, e.type), None))
656+
e.function_or_member, e.type), e.allow_dups, None))
647657
elif e.type != prev_type:
648658
if e.type is None:
649-
result.append((file, -1, -1, 'note', 'At top level:', None))
659+
result.append((file, -1, -1, 'note', 'At top level:', e.allow_dups, None))
650660
else:
651661
result.append((file, -1, -1, 'note',
652-
'In class "{}":'.format(e.type), None))
662+
'In class "{}":'.format(e.type), e.allow_dups, None))
653663

654-
result.append((file, e.line, e.column, e.severity, e.message, e.code))
664+
result.append((file, e.line, e.column, e.severity, e.message, e.allow_dups, e.code))
655665

656666
prev_import_context = e.import_ctx
657667
prev_function_or_member = e.function_or_member
@@ -691,23 +701,25 @@ def remove_duplicates(self, errors: List[ErrorTuple]) -> List[ErrorTuple]:
691701
# Use slightly special formatting for member conflicts reporting.
692702
conflicts_notes = False
693703
j = i - 1
694-
while j >= 0 and errors[j][0] == errors[i][0]:
695-
if errors[j][4].strip() == 'Got:':
696-
conflicts_notes = True
697-
j -= 1
698-
j = i - 1
699-
while (j >= 0 and errors[j][0] == errors[i][0] and
700-
errors[j][1] == errors[i][1]):
701-
if (errors[j][3] == errors[i][3] and
702-
# Allow duplicate notes in overload conflicts reporting.
703-
not ((errors[i][3] == 'note' and
704-
errors[i][4].strip() in allowed_duplicates)
705-
or (errors[i][4].strip().startswith('def ') and
706-
conflicts_notes)) and
707-
errors[j][4] == errors[i][4]): # ignore column
708-
dup = True
709-
break
710-
j -= 1
704+
# Find duplicates, unless duplicates are allowed.
705+
if not errors[i][5]:
706+
while j >= 0 and errors[j][0] == errors[i][0]:
707+
if errors[j][4].strip() == 'Got:':
708+
conflicts_notes = True
709+
j -= 1
710+
j = i - 1
711+
while (j >= 0 and errors[j][0] == errors[i][0] and
712+
errors[j][1] == errors[i][1]):
713+
if (errors[j][3] == errors[i][3] and
714+
# Allow duplicate notes in overload conflicts reporting.
715+
not ((errors[i][3] == 'note' and
716+
errors[i][4].strip() in allowed_duplicates)
717+
or (errors[i][4].strip().startswith('def ') and
718+
conflicts_notes)) and
719+
errors[j][4] == errors[i][4]): # ignore column
720+
dup = True
721+
break
722+
j -= 1
711723
if not dup:
712724
res.append(errors[i])
713725
i += 1

0 commit comments

Comments
 (0)