Skip to content

Commit f946af4

Browse files
authored
Don't assume that for loop body index variable is always set (#18631)
Fixes #18629. Fixes #16321. Fixes #8637.
1 parent f74f818 commit f946af4

File tree

2 files changed

+33
-3
lines changed

2 files changed

+33
-3
lines changed

mypy/checker.py

+15-3
Original file line numberDiff line numberDiff line change
@@ -585,12 +585,12 @@ def accept_loop(
585585
else_body: Statement | None = None,
586586
*,
587587
exit_condition: Expression | None = None,
588+
on_enter_body: Callable[[], None] | None = None,
588589
) -> None:
589590
"""Repeatedly type check a loop body until the frame doesn't change."""
590591

591592
# The outer frame accumulates the results of all iterations:
592593
with self.binder.frame_context(can_skip=False, conditional_frame=True):
593-
594594
# Check for potential decreases in the number of partial types so as not to stop the
595595
# iteration too early:
596596
partials_old = sum(len(pts.map) for pts in self.partial_types)
@@ -603,6 +603,9 @@ def accept_loop(
603603

604604
while True:
605605
with self.binder.frame_context(can_skip=True, break_frame=2, continue_frame=1):
606+
if on_enter_body is not None:
607+
on_enter_body()
608+
606609
self.accept(body)
607610
partials_new = sum(len(pts.map) for pts in self.partial_types)
608611
if (partials_new == partials_old) and not self.binder.last_pop_changed:
@@ -615,6 +618,9 @@ def accept_loop(
615618
self.options.enabled_error_codes.add(codes.REDUNDANT_EXPR)
616619
if warn_unreachable or warn_redundant:
617620
with self.binder.frame_context(can_skip=True, break_frame=2, continue_frame=1):
621+
if on_enter_body is not None:
622+
on_enter_body()
623+
618624
self.accept(body)
619625

620626
# If exit_condition is set, assume it must be False on exit from the loop:
@@ -5126,8 +5132,14 @@ def visit_for_stmt(self, s: ForStmt) -> None:
51265132
iterator_type, item_type = self.analyze_iterable_item_type(s.expr)
51275133
s.inferred_item_type = item_type
51285134
s.inferred_iterator_type = iterator_type
5129-
self.analyze_index_variables(s.index, item_type, s.index_type is None, s)
5130-
self.accept_loop(s.body, s.else_body)
5135+
5136+
self.accept_loop(
5137+
s.body,
5138+
s.else_body,
5139+
on_enter_body=lambda: self.analyze_index_variables(
5140+
s.index, item_type, s.index_type is None, s
5141+
),
5142+
)
51315143

51325144
def analyze_async_iterable_item_type(self, expr: Expression) -> tuple[Type, Type]:
51335145
"""Analyse async iterable expression and return iterator and iterator item types."""

test-data/unit/check-inference.test

+18
Original file line numberDiff line numberDiff line change
@@ -3894,3 +3894,21 @@ foo = [
38943894
]
38953895
reveal_type(foo) # N: Revealed type is "builtins.list[Tuple[builtins.int, typing.Sequence[builtins.str]]]"
38963896
[builtins fixtures/tuple.pyi]
3897+
3898+
[case testForLoopIndexVaribaleNarrowing1]
3899+
# flags: --local-partial-types
3900+
from typing import Union
3901+
x: Union[int, str]
3902+
x = "abc"
3903+
for x in list[int]():
3904+
reveal_type(x) # N: Revealed type is "builtins.int"
3905+
reveal_type(x) # N: Revealed type is "Union[builtins.int, builtins.str]"
3906+
3907+
[case testForLoopIndexVaribaleNarrowing2]
3908+
# flags: --enable-error-code=redundant-expr
3909+
from typing import Union
3910+
x: Union[int, str]
3911+
x = "abc"
3912+
for x in list[int]():
3913+
reveal_type(x) # N: Revealed type is "builtins.int"
3914+
reveal_type(x) # N: Revealed type is "Union[builtins.int, builtins.str]"

0 commit comments

Comments
 (0)