Skip to content

Commit 9685171

Browse files
authored
Avoid false unreachable and redundant-expr warnings in loops. (#18433)
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.
1 parent 9274a07 commit 9685171

File tree

2 files changed

+50
-5
lines changed

2 files changed

+50
-5
lines changed

mypy/checker.py

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -584,24 +584,43 @@ def accept_loop(
584584
*,
585585
exit_condition: Expression | None = None,
586586
) -> None:
587-
"""Repeatedly type check a loop body until the frame doesn't change.
588-
If exit_condition is set, assume it must be False on exit from the loop.
587+
"""Repeatedly type check a loop body until the frame doesn't change."""
589588

590-
Then check the else_body.
591-
"""
592-
# The outer frame accumulates the results of all iterations
589+
# The outer frame accumulates the results of all iterations:
593590
with self.binder.frame_context(can_skip=False, conditional_frame=True):
591+
592+
# Check for potential decreases in the number of partial types so as not to stop the
593+
# iteration too early:
594594
partials_old = sum(len(pts.map) for pts in self.partial_types)
595+
596+
# Disable error types that we cannot safely identify in intermediate iteration steps:
597+
warn_unreachable = self.options.warn_unreachable
598+
warn_redundant = codes.REDUNDANT_EXPR in self.options.enabled_error_codes
599+
self.options.warn_unreachable = False
600+
self.options.enabled_error_codes.discard(codes.REDUNDANT_EXPR)
601+
595602
while True:
596603
with self.binder.frame_context(can_skip=True, break_frame=2, continue_frame=1):
597604
self.accept(body)
598605
partials_new = sum(len(pts.map) for pts in self.partial_types)
599606
if (partials_new == partials_old) and not self.binder.last_pop_changed:
600607
break
601608
partials_old = partials_new
609+
610+
# If necessary, reset the modified options and make up for the postponed error checks:
611+
self.options.warn_unreachable = warn_unreachable
612+
if warn_redundant:
613+
self.options.enabled_error_codes.add(codes.REDUNDANT_EXPR)
614+
if warn_unreachable or warn_redundant:
615+
with self.binder.frame_context(can_skip=True, break_frame=2, continue_frame=1):
616+
self.accept(body)
617+
618+
# If exit_condition is set, assume it must be False on exit from the loop:
602619
if exit_condition:
603620
_, else_map = self.find_isinstance_check(exit_condition)
604621
self.push_type_map(else_map)
622+
623+
# Check the else body:
605624
if else_body:
606625
self.accept(else_body)
607626

test-data/unit/check-narrowing.test

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2390,3 +2390,29 @@ class A:
23902390
z.append(1)
23912391

23922392
[builtins fixtures/primitives.pyi]
2393+
2394+
[case testAvoidFalseUnreachableInLoop]
2395+
# flags: --warn-unreachable --python-version 3.11
2396+
2397+
def f() -> int | None: ...
2398+
def b() -> bool: ...
2399+
2400+
x: int | None
2401+
x = 1
2402+
while x is not None or b():
2403+
x = f()
2404+
2405+
[builtins fixtures/bool.pyi]
2406+
2407+
[case testAvoidFalseRedundantExprInLoop]
2408+
# flags: --enable-error-code redundant-expr --python-version 3.11
2409+
2410+
def f() -> int | None: ...
2411+
def b() -> bool: ...
2412+
2413+
x: int | None
2414+
x = 1
2415+
while x is not None and b():
2416+
x = f()
2417+
2418+
[builtins fixtures/primitives.pyi]

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy