From 22ce0d642f439df6e0e6dbaea1fabb0c578523ec Mon Sep 17 00:00:00 2001 From: A5rocks Date: Fri, 13 Jun 2025 11:08:29 +0900 Subject: [PATCH 1/4] Mark code after for loops that never end as unreachable --- mypy/checker.py | 19 +++++++++++++++++++ test-data/unit/check-unreachable-code.test | 20 ++++++++++++++++++++ 2 files changed, 39 insertions(+) diff --git a/mypy/checker.py b/mypy/checker.py index fc9733117a0a..2ba3e566c3cb 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -5108,12 +5108,31 @@ def visit_for_stmt(self, s: ForStmt) -> None: s.inferred_item_type = item_type s.inferred_iterator_type = iterator_type + proper_iter_type = get_proper_type(iterator_type) + if isinstance(proper_iter_type, Instance) and proper_iter_type.type.fullname in ( + "typing.Generator", + "types.GeneratorType", + ): + supertype = self.named_type("typing.Generator").type + super_instance = map_instance_to_supertype(proper_iter_type, supertype) + if isinstance(get_proper_type(super_instance.args[2]), UninhabitedType): + exit_condition = NameExpr("True") + exit_condition.fullname = "builtins.True" + print("woohoo!") + else: + exit_condition = NameExpr("False") + exit_condition.fullname = "builtins.False" + else: + exit_condition = NameExpr("False") + exit_condition.fullname = "builtins.False" + self.accept_loop( s.body, s.else_body, on_enter_body=lambda: self.analyze_index_variables( s.index, item_type, s.index_type is None, s ), + exit_condition=exit_condition, ) def analyze_async_iterable_item_type(self, expr: Expression) -> tuple[Type, Type]: diff --git a/test-data/unit/check-unreachable-code.test b/test-data/unit/check-unreachable-code.test index 368431127b76..2c8417f2f494 100644 --- a/test-data/unit/check-unreachable-code.test +++ b/test-data/unit/check-unreachable-code.test @@ -1587,3 +1587,23 @@ x = 0 # not unreachable f2: Callable[[], NoReturn] = lambda: foo() x = 0 # not unreachable + +[case testUnendingForLoop] +# flags: --warn-unreachable +from typing import Generator, NoReturn + +def generator() -> Generator[int, None, NoReturn]: + while True: + yield 1 + +def foo() -> int: + for x in generator(): + return x + + y = 0 # E: Statement is unreachable + +def bar() -> int: # E: Missing return statement + for x in generator(): + break + + y = 0 From 59b49afdb0a78f5f3ece30ffa3faa412de9d54ae Mon Sep 17 00:00:00 2001 From: A5rocks Date: Fri, 13 Jun 2025 11:20:03 +0900 Subject: [PATCH 2/4] Support Generator subtypes, too --- mypy/checker.py | 5 ++-- test-data/unit/check-unreachable-code.test | 30 +++++++++++++++++++++- 2 files changed, 31 insertions(+), 4 deletions(-) diff --git a/mypy/checker.py b/mypy/checker.py index 2ba3e566c3cb..33827ae0ef6b 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -5109,9 +5109,8 @@ def visit_for_stmt(self, s: ForStmt) -> None: s.inferred_iterator_type = iterator_type proper_iter_type = get_proper_type(iterator_type) - if isinstance(proper_iter_type, Instance) and proper_iter_type.type.fullname in ( - "typing.Generator", - "types.GeneratorType", + if isinstance(proper_iter_type, Instance) and any( + base.fullname == "typing.Generator" for base in proper_iter_type.type.mro ): supertype = self.named_type("typing.Generator").type super_instance = map_instance_to_supertype(proper_iter_type, supertype) diff --git a/test-data/unit/check-unreachable-code.test b/test-data/unit/check-unreachable-code.test index 2c8417f2f494..8a7ee3866dcb 100644 --- a/test-data/unit/check-unreachable-code.test +++ b/test-data/unit/check-unreachable-code.test @@ -1590,7 +1590,7 @@ x = 0 # not unreachable [case testUnendingForLoop] # flags: --warn-unreachable -from typing import Generator, NoReturn +from typing import Generator, NoReturn, TypeVar def generator() -> Generator[int, None, NoReturn]: while True: @@ -1607,3 +1607,31 @@ def bar() -> int: # E: Missing return statement break y = 0 + +def baz() -> int: + for x in generator(): + pass + +A = TypeVar("A") +B = TypeVar("B") +C = TypeVar("C") + +class GeneratorSubtype(Generator[A, B, C]): + ... + +def foo2(gen: GeneratorSubtype[int, None, NoReturn]) -> int: + for x in gen: + return x + + y = 0 # E: Statement is unreachable + +def bar2(gen: GeneratorSubtype[int, None, NoReturn]) -> int: # E: Missing return statement + for x in gen: + break + + y = 0 + +def baz2(gen: GeneratorSubtype[int, None, NoReturn]) -> int: + for x in gen: + pass +[builtins fixtures/tuple.pyi] From 4e9559a6885b78a131c162dd70b1b7c7c985333b Mon Sep 17 00:00:00 2001 From: A5rocks Date: Fri, 13 Jun 2025 11:31:24 +0900 Subject: [PATCH 3/4] Remove debug statement Co-authored-by: Brian Schubert --- mypy/checker.py | 1 - 1 file changed, 1 deletion(-) diff --git a/mypy/checker.py b/mypy/checker.py index 33827ae0ef6b..8f407a57e798 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -5117,7 +5117,6 @@ def visit_for_stmt(self, s: ForStmt) -> None: if isinstance(get_proper_type(super_instance.args[2]), UninhabitedType): exit_condition = NameExpr("True") exit_condition.fullname = "builtins.True" - print("woohoo!") else: exit_condition = NameExpr("False") exit_condition.fullname = "builtins.False" From 966d52cbeab70b5d2e5ce2a07129f7be5e84f855 Mon Sep 17 00:00:00 2001 From: A5rocks Date: Fri, 13 Jun 2025 11:54:39 +0900 Subject: [PATCH 4/4] Simplify attempt at boolean literals to integer literals --- mypy/checker.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/mypy/checker.py b/mypy/checker.py index 8f407a57e798..ca55f5fe12d7 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -5115,14 +5115,11 @@ def visit_for_stmt(self, s: ForStmt) -> None: supertype = self.named_type("typing.Generator").type super_instance = map_instance_to_supertype(proper_iter_type, supertype) if isinstance(get_proper_type(super_instance.args[2]), UninhabitedType): - exit_condition = NameExpr("True") - exit_condition.fullname = "builtins.True" + exit_condition = IntExpr(1) else: - exit_condition = NameExpr("False") - exit_condition.fullname = "builtins.False" + exit_condition = IntExpr(0) else: - exit_condition = NameExpr("False") - exit_condition.fullname = "builtins.False" + exit_condition = IntExpr(0) self.accept_loop( s.body,