4
4
import sys
5
5
import traceback
6
6
from collections import defaultdict
7
- from collections .abc import Iterable
7
+ from collections .abc import Iterable , Iterator
8
+ from itertools import chain
8
9
from typing import Callable , Final , NoReturn , Optional , TextIO , TypeVar
9
10
from typing_extensions import Literal , Self , TypeAlias as _TypeAlias
10
11
11
12
from mypy import errorcodes as codes
12
13
from mypy .error_formatter import ErrorFormatter
13
14
from mypy .errorcodes import IMPORT , IMPORT_NOT_FOUND , IMPORT_UNTYPED , ErrorCode , mypy_error_codes
15
+ from mypy .nodes import Context
14
16
from mypy .options import Options
15
17
from mypy .scope import Scope
16
18
from mypy .util import DEFAULT_SOURCE_OFFSET , is_typeshed_file
@@ -219,23 +221,43 @@ def filtered_errors(self) -> list[ErrorInfo]:
219
221
return self ._filtered
220
222
221
223
222
- class LoopErrorWatcher (ErrorWatcher ):
223
- """Error watcher that filters and separately collects `unreachable` errors,
224
- `redundant-expr` and `redundant-casts` errors, and revealed types when analysing
225
- loops iteratively to help avoid making too-hasty reports."""
224
+ class IterationDependentErrors :
225
+ """An `IterationDependentErrors` instance serves to collect the `unreachable`,
226
+ `redundant-expr`, and `redundant-casts` errors, as well as the revealed types,
227
+ handled by the individual `IterationErrorWatcher` instances sequentially applied to
228
+ the same code section."""
226
229
227
- # Meaning of the tuple items: ErrorCode, message, line, column, end_line, end_column:
228
- uselessness_errors : set [tuple [ErrorCode , str , int , int , int , int ]]
230
+ # One set of `unreachable`, `redundant-expr`, and `redundant-casts` errors per
231
+ # iteration step. Meaning of the tuple items: ErrorCode, message, line, column,
232
+ # end_line, end_column.
233
+ uselessness_errors : list [set [tuple [ErrorCode , str , int , int , int , int ]]]
229
234
230
- # Meaning of the tuple items: function_or_member, line, column, end_line, end_column:
235
+ # One set of unreachable line numbers per iteration step. Not only the lines where
236
+ # the error report occurs but really all unreachable lines.
237
+ unreachable_lines : list [set [int ]]
238
+
239
+ # One set of revealed types for each `reveal_type` statement. Each created set can
240
+ # grow during the iteration. Meaning of the tuple items: function_or_member, line,
241
+ # column, end_line, end_column:
231
242
revealed_types : dict [tuple [str | None , int , int , int , int ], set [str ]]
232
243
233
- # Not only the lines where the error report occurs but really all unreachable lines:
234
- unreachable_lines : set [int ]
244
+ def __init__ (self ) -> None :
245
+ self .uselessness_errors = []
246
+ self .unreachable_lines = []
247
+ self .revealed_types = defaultdict (set )
248
+
249
+
250
+ class IterationErrorWatcher (ErrorWatcher ):
251
+ """Error watcher that filters and separately collects `unreachable` errors,
252
+ `redundant-expr` and `redundant-casts` errors, and revealed types when analysing
253
+ code sections iteratively to help avoid making too-hasty reports."""
254
+
255
+ iteration_dependent_errors : IterationDependentErrors
235
256
236
257
def __init__ (
237
258
self ,
238
259
errors : Errors ,
260
+ iteration_dependent_errors : IterationDependentErrors ,
239
261
* ,
240
262
filter_errors : bool | Callable [[str , ErrorInfo ], bool ] = False ,
241
263
save_filtered_errors : bool = False ,
@@ -247,31 +269,71 @@ def __init__(
247
269
save_filtered_errors = save_filtered_errors ,
248
270
filter_deprecated = filter_deprecated ,
249
271
)
250
- self .uselessness_errors = set ()
251
- self . unreachable_lines = set ()
252
- self . revealed_types = defaultdict (set )
272
+ self .iteration_dependent_errors = iteration_dependent_errors
273
+ iteration_dependent_errors . uselessness_errors . append ( set () )
274
+ iteration_dependent_errors . unreachable_lines . append (set () )
253
275
254
276
def on_error (self , file : str , info : ErrorInfo ) -> bool :
277
+ """Filter out the "iteration-dependent" errors and notes and store their
278
+ information to handle them after iteration is completed."""
279
+
280
+ iter_errors = self .iteration_dependent_errors
255
281
256
282
if info .code in (codes .UNREACHABLE , codes .REDUNDANT_EXPR , codes .REDUNDANT_CAST ):
257
- self .uselessness_errors .add (
283
+ iter_errors .uselessness_errors [ - 1 ] .add (
258
284
(info .code , info .message , info .line , info .column , info .end_line , info .end_column )
259
285
)
260
286
if info .code == codes .UNREACHABLE :
261
- self .unreachable_lines .update (range (info .line , info .end_line + 1 ))
287
+ iter_errors .unreachable_lines [ - 1 ] .update (range (info .line , info .end_line + 1 ))
262
288
return True
263
289
264
290
if info .code == codes .MISC and info .message .startswith ("Revealed type is " ):
265
291
key = info .function_or_member , info .line , info .column , info .end_line , info .end_column
266
292
types = info .message .split ('"' )[1 ]
267
293
if types .startswith ("Union[" ):
268
- self .revealed_types [key ].update (types [6 :- 1 ].split (", " ))
294
+ iter_errors .revealed_types [key ].update (types [6 :- 1 ].split (", " ))
269
295
else :
270
- self .revealed_types [key ].add (types )
296
+ iter_errors .revealed_types [key ].add (types )
271
297
return True
272
298
273
299
return super ().on_error (file , info )
274
300
301
+ def yield_error_infos (self ) -> Iterator [tuple [str , Context , ErrorCode ]]:
302
+ """Report only those `unreachable`, `redundant-expr`, and `redundant-casts`
303
+ errors that could not be ruled out in any iteration step."""
304
+
305
+ persistent_uselessness_errors = set ()
306
+ iter_errors = self .iteration_dependent_errors
307
+ for candidate in set (chain (* iter_errors .uselessness_errors )):
308
+ if all (
309
+ (candidate in errors ) or (candidate [2 ] in lines )
310
+ for errors , lines in zip (
311
+ iter_errors .uselessness_errors , iter_errors .unreachable_lines
312
+ )
313
+ ):
314
+ persistent_uselessness_errors .add (candidate )
315
+ for error_info in persistent_uselessness_errors :
316
+ context = Context (line = error_info [2 ], column = error_info [3 ])
317
+ context .end_line = error_info [4 ]
318
+ context .end_column = error_info [5 ]
319
+ yield error_info [1 ], context , error_info [0 ]
320
+
321
+ def yield_note_infos (self , options : Options ) -> Iterator [tuple [str , Context ]]:
322
+ """Yield all types revealed in at least one iteration step."""
323
+
324
+ for note_info , types in self .iteration_dependent_errors .revealed_types .items ():
325
+ sorted_ = sorted (types , key = lambda typ : typ .lower ())
326
+ if len (types ) == 1 :
327
+ revealed = sorted_ [0 ]
328
+ elif options .use_or_syntax ():
329
+ revealed = " | " .join (sorted_ )
330
+ else :
331
+ revealed = f"Union[{ ', ' .join (sorted_ )} ]"
332
+ context = Context (line = note_info [1 ], column = note_info [2 ])
333
+ context .end_line = note_info [3 ]
334
+ context .end_column = note_info [4 ]
335
+ yield f'Revealed type is "{ revealed } "' , context
336
+
275
337
276
338
class Errors :
277
339
"""Container for compile errors.
0 commit comments