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