4
4
from mypy .backports import OrderedDict
5
5
from collections import defaultdict
6
6
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
9
9
10
10
from mypy .scope import Scope
11
11
from mypy .options import Options
@@ -122,6 +122,32 @@ def __init__(self,
122
122
Optional [ErrorCode ]]
123
123
124
124
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
+
125
151
class Errors :
126
152
"""Container for compile errors.
127
153
@@ -135,9 +161,7 @@ class Errors:
135
161
error_info_map : Dict [str , List [ErrorInfo ]]
136
162
137
163
# 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 ]
141
165
142
166
# Files that we have reported the errors for
143
167
flushed_files : Set [str ]
@@ -183,6 +207,8 @@ class Errors:
183
207
# in some cases to avoid reporting huge numbers of errors.
184
208
seen_import_error = False
185
209
210
+ _watchers : Set [ErrorWatcher ] = set ()
211
+
186
212
def __init__ (self ,
187
213
show_error_context : bool = False ,
188
214
show_column_numbers : bool = False ,
@@ -214,6 +240,7 @@ def initialize(self) -> None:
214
240
self .used_ignored_lines = defaultdict (lambda : defaultdict (list ))
215
241
self .ignored_files = set ()
216
242
self .only_once_messages = set ()
243
+ self .has_blockers = set ()
217
244
self .scope = None
218
245
self .target_module = None
219
246
self .seen_import_error = False
@@ -239,9 +266,6 @@ def copy(self) -> 'Errors':
239
266
new .seen_import_error = self .seen_import_error
240
267
return new
241
268
242
- def total_errors (self ) -> int :
243
- return self .total_error_count
244
-
245
269
def set_ignore_prefix (self , prefix : str ) -> None :
246
270
"""Set path prefix that will be removed from all paths."""
247
271
prefix = os .path .normpath (prefix )
@@ -362,11 +386,10 @@ def _add_error_info(self, file: str, info: ErrorInfo) -> None:
362
386
if file not in self .error_info_map :
363
387
self .error_info_map [file ] = []
364
388
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 )
368
391
if info .blocker :
369
- self .has_blockers = True
392
+ self .has_blockers . add ( file )
370
393
if info .code is IMPORT :
371
394
self .seen_import_error = True
372
395
@@ -486,12 +509,16 @@ def clear_errors_in_targets(self, path: str, targets: Set[str]) -> None:
486
509
"""Remove errors in specific fine-grained targets within a file."""
487
510
if path in self .error_info_map :
488
511
new_errors = []
512
+ has_blocker = False
489
513
for info in self .error_info_map [path ]:
490
514
if info .target not in targets :
491
515
new_errors .append (info )
516
+ has_blocker |= info .blocker
492
517
elif info .only_once :
493
518
self .only_once_messages .remove (info .message )
494
519
self .error_info_map [path ] = new_errors
520
+ if not has_blocker and path in self .has_blockers :
521
+ self .has_blockers .remove (path )
495
522
496
523
def generate_unused_ignore_errors (self , file : str ) -> None :
497
524
ignored_lines = self .ignored_lines [file ]
@@ -561,18 +588,14 @@ def is_errors(self) -> bool:
561
588
"""Are there any generated messages?"""
562
589
return bool (self .error_info_map )
563
590
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
-
568
591
def is_blockers (self ) -> bool :
569
592
"""Are the any errors that are blockers?"""
570
- return self .has_blockers
593
+ return bool ( self .has_blockers )
571
594
572
595
def blocker_module (self ) -> Optional [str ]:
573
596
"""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 ] :
576
599
if err .blocker :
577
600
return err .module
578
601
return None
0 commit comments