2525from mypy .constraints import SUPERTYPE_OF
2626from mypy .erasetype import erase_type , erase_typevars , remove_instance_last_known_values
2727from mypy .errorcodes import TYPE_VAR , UNUSED_AWAITABLE , UNUSED_COROUTINE , ErrorCode
28- from mypy .errors import Errors , ErrorWatcher , report_internal_error
28+ from mypy .errors import Errors , ErrorWatcher , LoopErrorWatcher , report_internal_error
2929from mypy .expandtype import expand_type
3030from mypy .literals import Key , extract_var_from_literal_hash , literal , literal_hash
3131from mypy .maptype import map_instance_to_supertype
@@ -599,19 +599,27 @@ def accept_loop(
599599 # on without bound otherwise)
600600 widened_old = len (self .widened_vars )
601601
602- # Disable error types that we cannot safely identify in intermediate iteration steps:
603- warn_unreachable = self .options .warn_unreachable
604- warn_redundant = codes .REDUNDANT_EXPR in self .options .enabled_error_codes
605- self .options .warn_unreachable = False
606- self .options .enabled_error_codes .discard (codes .REDUNDANT_EXPR )
607-
602+ # one set of `unreachable`, `redundant-expr`, and `redundant-casts` errors
603+ # per iteration step:
604+ uselessness_errors = []
605+ # one set of unreachable line numbers per iteration step:
606+ unreachable_lines = []
607+ # one set of revealed types per line where `reveal_type` is used (each
608+ # created set can grow during the iteration):
609+ revealed_types = defaultdict (set )
608610 iter = 1
609611 while True :
610612 with self .binder .frame_context (can_skip = True , break_frame = 2 , continue_frame = 1 ):
611613 if on_enter_body is not None :
612614 on_enter_body ()
613615
614- self .accept (body )
616+ with LoopErrorWatcher (self .msg .errors ) as watcher :
617+ self .accept (body )
618+ uselessness_errors .append (watcher .uselessness_errors )
619+ unreachable_lines .append (watcher .unreachable_lines )
620+ for key , values in watcher .revealed_types .items ():
621+ revealed_types [key ].update (values )
622+
615623 partials_new = sum (len (pts .map ) for pts in self .partial_types )
616624 widened_new = len (self .widened_vars )
617625 # Perform multiple iterations if something changed that might affect
@@ -632,16 +640,29 @@ def accept_loop(
632640 if iter == 20 :
633641 raise RuntimeError ("Too many iterations when checking a loop" )
634642
635- # If necessary, reset the modified options and make up for the postponed error checks:
636- self .options .warn_unreachable = warn_unreachable
637- if warn_redundant :
638- self .options .enabled_error_codes .add (codes .REDUNDANT_EXPR )
639- if warn_unreachable or warn_redundant :
640- with self .binder .frame_context (can_skip = True , break_frame = 2 , continue_frame = 1 ):
641- if on_enter_body is not None :
642- on_enter_body ()
643-
644- self .accept (body )
643+ # Report only those `unreachable`, `redundant-expr`, and `redundant-casts`
644+ # errors that could not be ruled out in any iteration step:
645+ persistent_uselessness_errors = set ()
646+ for candidate in set (itertools .chain (* uselessness_errors )):
647+ if all (
648+ (candidate in errors ) or (candidate [2 ] in lines )
649+ for errors , lines in zip (uselessness_errors , unreachable_lines )
650+ ):
651+ persistent_uselessness_errors .add (candidate )
652+ for error_info in persistent_uselessness_errors :
653+ context = Context (line = error_info [2 ], column = error_info [3 ])
654+ context .end_line = error_info [4 ]
655+ context .end_column = error_info [5 ]
656+ self .msg .fail (error_info [1 ], context , code = error_info [0 ])
657+
658+ # Report all types revealed in at least one iteration step:
659+ for note_info , types in revealed_types .items ():
660+ sorted_ = sorted (types , key = lambda typ : typ .lower ())
661+ revealed = sorted_ [0 ] if len (types ) == 1 else f"Union[{ ', ' .join (sorted_ )} ]"
662+ context = Context (line = note_info [1 ], column = note_info [2 ])
663+ context .end_line = note_info [3 ]
664+ context .end_column = note_info [4 ]
665+ self .note (f'Revealed type is "{ revealed } "' , context )
645666
646667 # If exit_condition is set, assume it must be False on exit from the loop:
647668 if exit_condition :
0 commit comments