From a1ed9c3cbc288a85480c0b9bc5f13fdfa3054e6a Mon Sep 17 00:00:00 2001 From: Christoph Tyralla Date: Thu, 30 Oct 2025 23:39:59 +0100 Subject: [PATCH 1/3] Avoid false `possibly-undefined` errors due to omitted unrequired else statements. --- mypy/checker.py | 2 ++ mypy/nodes.py | 6 +++-- mypy/partially_defined.py | 10 ++++---- test-data/unit/check-possibly-undefined.test | 24 ++++++++++++++++++++ 4 files changed, 36 insertions(+), 6 deletions(-) diff --git a/mypy/checker.py b/mypy/checker.py index 63e128f78310..285a48d85dda 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -5036,6 +5036,8 @@ def visit_if_stmt(self, s: IfStmt) -> None: if_map, else_map = self.find_isinstance_check(e) + s.else_irrelevant_for_possibly_undefined = else_map is None + # XXX Issue a warning if condition is always False? with self.binder.frame_context(can_skip=True, fall_through=2): self.push_type_map(if_map, from_assignment=False) diff --git a/mypy/nodes.py b/mypy/nodes.py index 040f3fc28dce..2573563065e6 100644 --- a/mypy/nodes.py +++ b/mypy/nodes.py @@ -1792,19 +1792,21 @@ def accept(self, visitor: StatementVisitor[T]) -> T: class IfStmt(Statement): - __slots__ = ("expr", "body", "else_body") + __slots__ = ("expr", "body", "else_body", "else_irrelevant_for_possibly_undefined") - __match_args__ = ("expr", "body", "else_body") + __match_args__ = ("expr", "body", "else_body", "else_body_irrelevant_for_possibly_undefined") expr: list[Expression] body: list[Block] else_body: Block | None + else_irrelevant_for_possibly_undefined: bool def __init__(self, expr: list[Expression], body: list[Block], else_body: Block | None) -> None: super().__init__() self.expr = expr self.body = body self.else_body = else_body + self.else_irrelevant_for_possibly_undefined = False def accept(self, visitor: StatementVisitor[T]) -> T: return visitor.visit_if_stmt(self) diff --git a/mypy/partially_defined.py b/mypy/partially_defined.py index 38154cf697e1..bc1b61f700b9 100644 --- a/mypy/partially_defined.py +++ b/mypy/partially_defined.py @@ -395,11 +395,13 @@ def visit_if_stmt(self, o: IfStmt) -> None: continue b.accept(self) self.tracker.next_branch() - if o.else_body: - if not o.else_body.is_unreachable: - o.else_body.accept(self) - else: + if o.else_irrelevant_for_possibly_undefined: + self.tracker.skip_branch() + elif o.else_body: + if o.else_body.is_unreachable: self.tracker.skip_branch() + else: + o.else_body.accept(self) self.tracker.end_branch_statement() def visit_match_stmt(self, o: MatchStmt) -> None: diff --git a/test-data/unit/check-possibly-undefined.test b/test-data/unit/check-possibly-undefined.test index ae277949c049..fb2edc39aa15 100644 --- a/test-data/unit/check-possibly-undefined.test +++ b/test-data/unit/check-possibly-undefined.test @@ -1043,3 +1043,27 @@ def foo(x: Union[int, str]) -> None: assert_never(x) f # OK [builtins fixtures/tuple.pyi] + +[case testOmittedUnrequiredElse] +# flags: --enable-error-code possibly-undefined +from typing import Literal + +a: Literal[True] +if a: + w = 1 +w + 1 + +b: bool +if b: + x = 1 +elif not b: + x = 2 +x + 1 + +if b: + y = 1 +elif not b: + if a: + z = 2 + y = z +y + 1 From 7e9b592d0a15c0da26a8c073eb90f45e2c0ebeba Mon Sep 17 00:00:00 2001 From: Christoph Tyralla Date: Fri, 31 Oct 2025 15:59:03 +0100 Subject: [PATCH 2/3] else_irrelevant_for_possibly_undefined -> unreachable_else --- mypy/checker.py | 2 +- mypy/nodes.py | 6 +++--- mypy/partially_defined.py | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/mypy/checker.py b/mypy/checker.py index 285a48d85dda..163252829f94 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -5036,7 +5036,7 @@ def visit_if_stmt(self, s: IfStmt) -> None: if_map, else_map = self.find_isinstance_check(e) - s.else_irrelevant_for_possibly_undefined = else_map is None + s.unreachable_else = else_map is None # XXX Issue a warning if condition is always False? with self.binder.frame_context(can_skip=True, fall_through=2): diff --git a/mypy/nodes.py b/mypy/nodes.py index 2573563065e6..1dbcb1c997fe 100644 --- a/mypy/nodes.py +++ b/mypy/nodes.py @@ -1792,21 +1792,21 @@ def accept(self, visitor: StatementVisitor[T]) -> T: class IfStmt(Statement): - __slots__ = ("expr", "body", "else_body", "else_irrelevant_for_possibly_undefined") + __slots__ = ("expr", "body", "else_body", "unreachable_else") __match_args__ = ("expr", "body", "else_body", "else_body_irrelevant_for_possibly_undefined") expr: list[Expression] body: list[Block] else_body: Block | None - else_irrelevant_for_possibly_undefined: bool + unreachable_else: bool def __init__(self, expr: list[Expression], body: list[Block], else_body: Block | None) -> None: super().__init__() self.expr = expr self.body = body self.else_body = else_body - self.else_irrelevant_for_possibly_undefined = False + self.unreachable_else = False def accept(self, visitor: StatementVisitor[T]) -> T: return visitor.visit_if_stmt(self) diff --git a/mypy/partially_defined.py b/mypy/partially_defined.py index bc1b61f700b9..fad1f79493ad 100644 --- a/mypy/partially_defined.py +++ b/mypy/partially_defined.py @@ -395,7 +395,7 @@ def visit_if_stmt(self, o: IfStmt) -> None: continue b.accept(self) self.tracker.next_branch() - if o.else_irrelevant_for_possibly_undefined: + if o.unreachable_else: self.tracker.skip_branch() elif o.else_body: if o.else_body.is_unreachable: From b9c59be02109ae1c5325d7de2bf8d99b9dc1af7c Mon Sep 17 00:00:00 2001 From: Christoph Tyralla Date: Fri, 31 Oct 2025 23:15:23 +0100 Subject: [PATCH 3/3] else_irrelevant_for_possibly_undefined -> unreachable_else --- mypy/nodes.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mypy/nodes.py b/mypy/nodes.py index 1dbcb1c997fe..4cbd7fbb8fa2 100644 --- a/mypy/nodes.py +++ b/mypy/nodes.py @@ -1794,7 +1794,7 @@ def accept(self, visitor: StatementVisitor[T]) -> T: class IfStmt(Statement): __slots__ = ("expr", "body", "else_body", "unreachable_else") - __match_args__ = ("expr", "body", "else_body", "else_body_irrelevant_for_possibly_undefined") + __match_args__ = ("expr", "body", "else_body", "unreachable_else") expr: list[Expression] body: list[Block]