55import traceback
66from collections import defaultdict
77from collections .abc import Iterable
8- from typing import Callable , Final , NoReturn , Optional , TextIO , TypeVar
9- from typing_extensions import Literal , Self , TypeAlias as _TypeAlias
8+ from itertools import chain
9+ from typing import Callable , Final , Iterator , NoReturn , Optional , TextIO , TypeVar
10+ from typing_extensions import Literal , NamedTuple , 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
@@ -220,23 +222,44 @@ def filtered_errors(self) -> list[ErrorInfo]:
220222 return self ._filtered
221223
222224
223- class LoopErrorWatcher (ErrorWatcher ):
224- """Error watcher that filters and separately collects `unreachable` errors,
225- `redundant-expr` and `redundant-casts` errors, and revealed types when analysing
226- loops iteratively to help avoid making too-hasty reports."""
227225
228- # Meaning of the tuple items: ErrorCode, message, line, column, end_line, end_column:
229- uselessness_errors : set [tuple [ErrorCode , str , int , int , int , int ]]
226+ class ÎterationDependentErrors :
227+ """An `ÎterationDependentErrors` instance serves to collect the `unreachable`,
228+ `redundant-expr`, and `redundant-casts` errors, as well as the revealed types,
229+ handled by the individual `IterationErrorWatcher` instances sequentially applied to
230+ the same code section."""
231+
232+ # One set of `unreachable`, `redundant-expr`, and `redundant-casts` errors per
233+ # iteration step. Meaning of the tuple items: ErrorCode, message, line, column,
234+ # end_line, end_column.
235+ uselessness_errors : list [set [tuple [ErrorCode , str , int , int , int , int ]]]
236+
237+ # One set of unreachable line numbers per iteration step. Not only the lines where
238+ # the error report occurs but really all unreachable lines.
239+ unreachable_lines : list [set [int ]]
230240
231- # Meaning of the tuple items: function_or_member, line, column, end_line, end_column:
241+ # One set of revealed types for each `reveal_type` statement. Each created set can
242+ # grow during the iteration. Meaning of the tuple items: function_or_member, line,
243+ # column, end_line, end_column:
232244 revealed_types : dict [tuple [str | None , int , int , int , int ], set [str ]]
233245
234- # Not only the lines where the error report occurs but really all unreachable lines:
235- unreachable_lines : set [int ]
246+ def __init__ (self ) -> None :
247+ self .uselessness_errors = []
248+ self .unreachable_lines = []
249+ self .revealed_types = defaultdict (set )
250+
251+
252+ class IterationErrorWatcher (ErrorWatcher ):
253+ """Error watcher that filters and separately collects `unreachable` errors,
254+ `redundant-expr` and `redundant-casts` errors, and revealed types when analysing
255+ code sections iteratively to help avoid making too-hasty reports."""
256+
257+ iteration_dependent_errors : ÎterationDependentErrors
236258
237259 def __init__ (
238260 self ,
239261 errors : Errors ,
262+ iteration_dependent_errors : ÎterationDependentErrors ,
240263 * ,
241264 filter_errors : bool | Callable [[str , ErrorInfo ], bool ] = False ,
242265 save_filtered_errors : bool = False ,
@@ -248,31 +271,66 @@ def __init__(
248271 save_filtered_errors = save_filtered_errors ,
249272 filter_deprecated = filter_deprecated ,
250273 )
251- self .uselessness_errors = set ()
252- self . unreachable_lines = set ()
253- self . revealed_types = defaultdict (set )
274+ self .iteration_dependent_errors = iteration_dependent_errors
275+ iteration_dependent_errors . uselessness_errors . append ( set () )
276+ iteration_dependent_errors . unreachable_lines . append (set () )
254277
255278 def on_error (self , file : str , info : ErrorInfo ) -> bool :
279+ """Filter out the "iteration-dependent" errors and notes and store their
280+ information to handle them after iteration is completed."""
281+
282+ iter_errors = self .iteration_dependent_errors
256283
257284 if info .code in (codes .UNREACHABLE , codes .REDUNDANT_EXPR , codes .REDUNDANT_CAST ):
258- self .uselessness_errors .add (
285+ iter_errors .uselessness_errors [ - 1 ] .add (
259286 (info .code , info .message , info .line , info .column , info .end_line , info .end_column )
260287 )
261288 if info .code == codes .UNREACHABLE :
262- self .unreachable_lines .update (range (info .line , info .end_line + 1 ))
289+ iter_errors .unreachable_lines [ - 1 ] .update (range (info .line , info .end_line + 1 ))
263290 return True
264291
265292 if info .code == codes .MISC and info .message .startswith ("Revealed type is " ):
266293 key = info .function_or_member , info .line , info .column , info .end_line , info .end_column
267294 types = info .message .split ('"' )[1 ]
268295 if types .startswith ("Union[" ):
269- self .revealed_types [key ].update (types [6 :- 1 ].split (", " ))
296+ iter_errors .revealed_types [key ].update (types [6 :- 1 ].split (", " ))
270297 else :
271- self .revealed_types [key ].add (types )
298+ iter_errors .revealed_types [key ].add (types )
272299 return True
273300
274301 return super ().on_error (file , info )
275302
303+ def yield_error_infos (self ) -> Iterator [tuple [str , Context , ErrorCode ]]:
304+ """Report only those `unreachable`, `redundant-expr`, and `redundant-casts`
305+ errors that could not be ruled out in any iteration step."""
306+
307+ persistent_uselessness_errors = set ()
308+ iter_errors = self .iteration_dependent_errors
309+ for candidate in set (chain (* iter_errors .uselessness_errors )):
310+ if all (
311+ (candidate in errors ) or (candidate [2 ] in lines )
312+ for errors , lines in zip (
313+ iter_errors .uselessness_errors , iter_errors .unreachable_lines
314+ )
315+ ):
316+ persistent_uselessness_errors .add (candidate )
317+ for error_info in persistent_uselessness_errors :
318+ context = Context (line = error_info [2 ], column = error_info [3 ])
319+ context .end_line = error_info [4 ]
320+ context .end_column = error_info [5 ]
321+ yield error_info [1 ], context , error_info [0 ]
322+
323+ def yield_note_infos (self ) -> Iterator [tuple [str , Context ]]:
324+ """Yield all types revealed in at least one iteration step."""
325+
326+ for note_info , types in self .iteration_dependent_errors .revealed_types .items ():
327+ sorted_ = sorted (types , key = lambda typ : typ .lower ())
328+ revealed = sorted_ [0 ] if len (types ) == 1 else f"Union[{ ', ' .join (sorted_ )} ]"
329+ context = Context (line = note_info [1 ], column = note_info [2 ])
330+ context .end_line = note_info [3 ]
331+ context .end_column = note_info [4 ]
332+ yield f'Revealed type is "{ revealed } "' , context
333+
276334
277335class Errors :
278336 """Container for compile errors.
0 commit comments