44import sys
55import traceback
66from collections import defaultdict
7- from collections .abc import Iterable
7+ from collections .abc import Iterable , Iterator
8+ from itertools import chain
89from typing import Callable , Final , NoReturn , Optional , TextIO , TypeVar
910from typing_extensions import Literal , Self , TypeAlias as _TypeAlias
1011
1112from mypy import errorcodes as codes
1213from mypy .error_formatter import ErrorFormatter
1314from mypy .errorcodes import IMPORT , IMPORT_NOT_FOUND , IMPORT_UNTYPED , ErrorCode , mypy_error_codes
15+ from mypy .nodes import Context
1416from mypy .options import Options
1517from mypy .scope import Scope
1618from mypy .util import DEFAULT_SOURCE_OFFSET , is_typeshed_file
@@ -219,23 +221,43 @@ def filtered_errors(self) -> list[ErrorInfo]:
219221 return self ._filtered
220222
221223
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."""
226229
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 ]]]
229234
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:
231242 revealed_types : dict [tuple [str | None , int , int , int , int ], set [str ]]
232243
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
235256
236257 def __init__ (
237258 self ,
238259 errors : Errors ,
260+ iteration_dependent_errors : IterationDependentErrors ,
239261 * ,
240262 filter_errors : bool | Callable [[str , ErrorInfo ], bool ] = False ,
241263 save_filtered_errors : bool = False ,
@@ -247,31 +269,71 @@ def __init__(
247269 save_filtered_errors = save_filtered_errors ,
248270 filter_deprecated = filter_deprecated ,
249271 )
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 () )
253275
254276 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
255281
256282 if info .code in (codes .UNREACHABLE , codes .REDUNDANT_EXPR , codes .REDUNDANT_CAST ):
257- self .uselessness_errors .add (
283+ iter_errors .uselessness_errors [ - 1 ] .add (
258284 (info .code , info .message , info .line , info .column , info .end_line , info .end_column )
259285 )
260286 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 ))
262288 return True
263289
264290 if info .code == codes .MISC and info .message .startswith ("Revealed type is " ):
265291 key = info .function_or_member , info .line , info .column , info .end_line , info .end_column
266292 types = info .message .split ('"' )[1 ]
267293 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 (", " ))
269295 else :
270- self .revealed_types [key ].add (types )
296+ iter_errors .revealed_types [key ].add (types )
271297 return True
272298
273299 return super ().on_error (file , info )
274300
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+
275337
276338class Errors :
277339 """Container for compile errors.
0 commit comments