Skip to content

Commit

Permalink
Avoid false unreachable and redundant-expr warnings in loops. (#1…
Browse files Browse the repository at this point in the history
…8433)

Fixes #18348
Fixes #13973
Fixes #11612
Fixes #8721
Fixes #8865
Fixes #7204

I manually checked all the listed issues. Some of them were already
partly fixed by #18180.
  • Loading branch information
tyralla authored Jan 12, 2025
1 parent 9274a07 commit 9685171
Show file tree
Hide file tree
Showing 2 changed files with 50 additions and 5 deletions.
29 changes: 24 additions & 5 deletions mypy/checker.py
Original file line number Diff line number Diff line change
Expand Up @@ -584,24 +584,43 @@ def accept_loop(
*,
exit_condition: Expression | None = None,
) -> None:
"""Repeatedly type check a loop body until the frame doesn't change.
If exit_condition is set, assume it must be False on exit from the loop.
"""Repeatedly type check a loop body until the frame doesn't change."""

Then check the else_body.
"""
# The outer frame accumulates the results of all iterations
# The outer frame accumulates the results of all iterations:
with self.binder.frame_context(can_skip=False, conditional_frame=True):

# Check for potential decreases in the number of partial types so as not to stop the
# iteration too early:
partials_old = sum(len(pts.map) for pts in self.partial_types)

# Disable error types that we cannot safely identify in intermediate iteration steps:
warn_unreachable = self.options.warn_unreachable
warn_redundant = codes.REDUNDANT_EXPR in self.options.enabled_error_codes
self.options.warn_unreachable = False
self.options.enabled_error_codes.discard(codes.REDUNDANT_EXPR)

while True:
with self.binder.frame_context(can_skip=True, break_frame=2, continue_frame=1):
self.accept(body)
partials_new = sum(len(pts.map) for pts in self.partial_types)
if (partials_new == partials_old) and not self.binder.last_pop_changed:
break
partials_old = partials_new

# If necessary, reset the modified options and make up for the postponed error checks:
self.options.warn_unreachable = warn_unreachable
if warn_redundant:
self.options.enabled_error_codes.add(codes.REDUNDANT_EXPR)
if warn_unreachable or warn_redundant:
with self.binder.frame_context(can_skip=True, break_frame=2, continue_frame=1):
self.accept(body)

# If exit_condition is set, assume it must be False on exit from the loop:
if exit_condition:
_, else_map = self.find_isinstance_check(exit_condition)
self.push_type_map(else_map)

# Check the else body:
if else_body:
self.accept(else_body)

Expand Down
26 changes: 26 additions & 0 deletions test-data/unit/check-narrowing.test
Original file line number Diff line number Diff line change
Expand Up @@ -2390,3 +2390,29 @@ class A:
z.append(1)

[builtins fixtures/primitives.pyi]

[case testAvoidFalseUnreachableInLoop]
# flags: --warn-unreachable --python-version 3.11

def f() -> int | None: ...
def b() -> bool: ...

x: int | None
x = 1
while x is not None or b():
x = f()

[builtins fixtures/bool.pyi]

[case testAvoidFalseRedundantExprInLoop]
# flags: --enable-error-code redundant-expr --python-version 3.11

def f() -> int | None: ...
def b() -> bool: ...

x: int | None
x = 1
while x is not None and b():
x = f()

[builtins fixtures/primitives.pyi]

0 comments on commit 9685171

Please sign in to comment.