Skip to content

Commit de001c9

Browse files
committed
address comments
1 parent d36a8f3 commit de001c9

File tree

2 files changed

+65
-40
lines changed

2 files changed

+65
-40
lines changed

mypy/checkexpr.py

Lines changed: 23 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
)
99
from typing_extensions import ClassVar, Final, overload, TypeAlias as _TypeAlias
1010

11-
from mypy.errors import report_internal_error
11+
from mypy.errors import ErrorWatcher, report_internal_error
1212
from mypy.typeanal import (
1313
has_any_from_unimported_type, check_for_explicit_any, set_any_tvars, expand_type_alias,
1414
make_optional_type,
@@ -2293,26 +2293,28 @@ def visit_comparison_expr(self, e: ComparisonExpr) -> Type:
22932293
self.msg.add_errors(local_errors)
22942294
elif operator in operators.op_methods:
22952295
method = self.get_operator_method(operator)
2296-
err_count = self.msg.errors.total_errors()
2297-
sub_result, method_type = self.check_op(method, left_type, right, e,
2298-
allow_reverse=True)
2299-
# Only show dangerous overlap if there are no other errors. See
2300-
# testCustomEqCheckStrictEquality for an example.
2301-
if self.msg.errors.total_errors() == err_count and operator in ('==', '!='):
2302-
right_type = self.accept(right)
2303-
# We suppress the error if there is a custom __eq__() method on either
2304-
# side. User defined (or even standard library) classes can define this
2305-
# to return True for comparisons between non-overlapping types.
2306-
if (not custom_special_method(left_type, '__eq__') and
2307-
not custom_special_method(right_type, '__eq__')):
2308-
# Also flag non-overlapping literals in situations like:
2309-
# x: Literal['a', 'b']
2310-
# if x == 'c':
2311-
# ...
2312-
left_type = try_getting_literal(left_type)
2313-
right_type = try_getting_literal(right_type)
2314-
if self.dangerous_comparison(left_type, right_type):
2315-
self.msg.dangerous_comparison(left_type, right_type, 'equality', e)
2296+
2297+
with ErrorWatcher(self.msg.errors) as w:
2298+
sub_result, method_type = self.check_op(method, left_type, right, e,
2299+
allow_reverse=True)
2300+
2301+
# Only show dangerous overlap if there are no other errors. See
2302+
# testCustomEqCheckStrictEquality for an example.
2303+
if not w.has_new_errors() and operator in ('==', '!='):
2304+
right_type = self.accept(right)
2305+
# We suppress the error if there is a custom __eq__() method on either
2306+
# side. User defined (or even standard library) classes can define this
2307+
# to return True for comparisons between non-overlapping types.
2308+
if (not custom_special_method(left_type, '__eq__') and
2309+
not custom_special_method(right_type, '__eq__')):
2310+
# Also flag non-overlapping literals in situations like:
2311+
# x: Literal['a', 'b']
2312+
# if x == 'c':
2313+
# ...
2314+
left_type = try_getting_literal(left_type)
2315+
right_type = try_getting_literal(right_type)
2316+
if self.dangerous_comparison(left_type, right_type):
2317+
self.msg.dangerous_comparison(left_type, right_type, 'equality', e)
23162318

23172319
elif operator == 'is' or operator == 'is not':
23182320
right_type = self.accept(right) # validate the right operand

mypy/errors.py

Lines changed: 42 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@
44
from mypy.backports import OrderedDict
55
from collections import defaultdict
66

7-
from typing import Tuple, List, TypeVar, Set, Dict, Optional, TextIO, Callable
8-
from typing_extensions import Final
7+
from typing import Any, Tuple, List, TypeVar, Set, Dict, Optional, TextIO, Callable
8+
from typing_extensions import Final, Literal
99

1010
from mypy.scope import Scope
1111
from mypy.options import Options
@@ -122,6 +122,32 @@ def __init__(self,
122122
Optional[ErrorCode]]
123123

124124

125+
class ErrorWatcher:
126+
"""Context manager that can be used to keep track of new errors recorded
127+
around a given operation.
128+
"""
129+
def __init__(self, errors: 'Errors'):
130+
self.errors = errors
131+
self._has_new_errors = False
132+
133+
def __enter__(self) -> 'ErrorWatcher':
134+
self.errors._watchers.add(self)
135+
return self
136+
137+
def __exit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> Literal[False]:
138+
self.errors._watchers.remove(self)
139+
return False
140+
141+
def on_error(self, file: str, info: ErrorInfo) -> None:
142+
"""Handler called when a new error is recorded.
143+
144+
The default implementation just sets the has_new_errors flag"""
145+
self._has_new_errors = True
146+
147+
def has_new_errors(self) -> bool:
148+
return self._has_new_errors
149+
150+
125151
class Errors:
126152
"""Container for compile errors.
127153
@@ -135,9 +161,7 @@ class Errors:
135161
error_info_map: Dict[str, List[ErrorInfo]]
136162

137163
# optimization for legacy codebases with many files with errors
138-
has_real_errors: bool = False
139-
has_blockers: bool = False
140-
total_error_count: int = 0
164+
has_blockers: Set[str]
141165

142166
# Files that we have reported the errors for
143167
flushed_files: Set[str]
@@ -183,6 +207,8 @@ class Errors:
183207
# in some cases to avoid reporting huge numbers of errors.
184208
seen_import_error = False
185209

210+
_watchers: Set[ErrorWatcher] = set()
211+
186212
def __init__(self,
187213
show_error_context: bool = False,
188214
show_column_numbers: bool = False,
@@ -214,6 +240,7 @@ def initialize(self) -> None:
214240
self.used_ignored_lines = defaultdict(lambda: defaultdict(list))
215241
self.ignored_files = set()
216242
self.only_once_messages = set()
243+
self.has_blockers = set()
217244
self.scope = None
218245
self.target_module = None
219246
self.seen_import_error = False
@@ -239,9 +266,6 @@ def copy(self) -> 'Errors':
239266
new.seen_import_error = self.seen_import_error
240267
return new
241268

242-
def total_errors(self) -> int:
243-
return self.total_error_count
244-
245269
def set_ignore_prefix(self, prefix: str) -> None:
246270
"""Set path prefix that will be removed from all paths."""
247271
prefix = os.path.normpath(prefix)
@@ -362,11 +386,10 @@ def _add_error_info(self, file: str, info: ErrorInfo) -> None:
362386
if file not in self.error_info_map:
363387
self.error_info_map[file] = []
364388
self.error_info_map[file].append(info)
365-
self.total_error_count += 1
366-
if info.severity == 'error':
367-
self.has_real_errors = True
389+
for w in self._watchers:
390+
w.on_error(file, info)
368391
if info.blocker:
369-
self.has_blockers = True
392+
self.has_blockers.add(file)
370393
if info.code is IMPORT:
371394
self.seen_import_error = True
372395

@@ -486,12 +509,16 @@ def clear_errors_in_targets(self, path: str, targets: Set[str]) -> None:
486509
"""Remove errors in specific fine-grained targets within a file."""
487510
if path in self.error_info_map:
488511
new_errors = []
512+
has_blocker = False
489513
for info in self.error_info_map[path]:
490514
if info.target not in targets:
491515
new_errors.append(info)
516+
has_blocker |= info.blocker
492517
elif info.only_once:
493518
self.only_once_messages.remove(info.message)
494519
self.error_info_map[path] = new_errors
520+
if not has_blocker and path in self.has_blockers:
521+
self.has_blockers.remove(path)
495522

496523
def generate_unused_ignore_errors(self, file: str) -> None:
497524
ignored_lines = self.ignored_lines[file]
@@ -561,18 +588,14 @@ def is_errors(self) -> bool:
561588
"""Are there any generated messages?"""
562589
return bool(self.error_info_map)
563590

564-
def is_real_errors(self) -> bool:
565-
"""Are there any generated errors (not just notes, for example)?"""
566-
return self.has_real_errors
567-
568591
def is_blockers(self) -> bool:
569592
"""Are the any errors that are blockers?"""
570-
return self.has_blockers
593+
return bool(self.has_blockers)
571594

572595
def blocker_module(self) -> Optional[str]:
573596
"""Return the module with a blocking error, or None if not possible."""
574-
for errs in self.error_info_map.values():
575-
for err in errs:
597+
for path in self.has_blockers:
598+
for err in self.error_info_map[path]:
576599
if err.blocker:
577600
return err.module
578601
return None

0 commit comments

Comments
 (0)