diff --git a/mypy/checker.py b/mypy/checker.py index b1c15d20c329..33ed5387554d 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -619,6 +619,7 @@ def check_first_pass(self, recurse_into_functions: bool = True) -> None: and not self.is_noop_for_reachability(d) ): self.msg.unreachable_statement(d) + self.binder.suppress_unreachable_warnings() finish = True reported_unreachable = True @@ -3331,6 +3332,7 @@ def visit_block(self, b: Block) -> None: and not self.is_noop_for_reachability(s) ): self.msg.unreachable_statement(s) + self.binder.suppress_unreachable_warnings() finish = True reported_unreachable = True @@ -5517,6 +5519,7 @@ def visit_try_stmt(self, s: TryStmt) -> None: self.accept(s.finally_body) if s.finally_body: + previously_suppressed = self.binder.is_unreachable_warning_suppressed() # Then we try again for the more restricted set of options # that can fall through. (Why do we need to check the # finally clause twice? Depending on whether the finally @@ -5534,6 +5537,11 @@ def visit_try_stmt(self, s: TryStmt) -> None: self.accept(s.finally_body) self.msg.iteration_dependent_errors(iter_errors) + if not previously_suppressed: + # The finally body might have warned about unreachability, + # but we still want anything afterwards to warn too. + self.binder.frames[-1].suppress_unreachable_warnings = False + def visit_try_without_finally(self, s: TryStmt, try_frame: bool) -> None: """Type check a try statement, ignoring the finally block. diff --git a/test-data/unit/check-unreachable-code.test b/test-data/unit/check-unreachable-code.test index 8238fd77e229..330983dab620 100644 --- a/test-data/unit/check-unreachable-code.test +++ b/test-data/unit/check-unreachable-code.test @@ -1751,3 +1751,30 @@ assert sys.platform == "win32" if False: reveal_type(5) # E: Statement is unreachable \ # N: Revealed type is "Literal[5]?" + +[case testCheckUnreachableOnlyFlagsOnce] +# flags: --check-unreachable --warn-unreachable +if False: + if False: # E: Statement is unreachable + "not flagged!" + +[case testCheckUnreachableHandlesFinallyRight] +# flags: --check-unreachable --warn-unreachable +def f1() -> None: + # it doesn't reset suppression + assert False + try: # E: Statement is unreachable + pass + finally: + reveal_type(5) # N: Revealed type is "Literal[5]?" + "something" + +def f2() -> None: + # but it does still mark things after as unreachable + try: + pass + finally: + assert False + reveal_type(5) # E: Statement is unreachable \ + # N: Revealed type is "Literal[5]?" + "something" # E: Statement is unreachable