Skip to content

Commit 0901689

Browse files
authored
Revisit the body of a loop if the number of partial types has changed. (#18180)
Fixes #5423
1 parent c859cb1 commit 0901689

File tree

4 files changed

+44
-3
lines changed

4 files changed

+44
-3
lines changed

mypy/checker.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -607,11 +607,14 @@ def accept_loop(
607607
"""
608608
# The outer frame accumulates the results of all iterations
609609
with self.binder.frame_context(can_skip=False, conditional_frame=True):
610+
partials_old = sum(len(pts.map) for pts in self.partial_types)
610611
while True:
611612
with self.binder.frame_context(can_skip=True, break_frame=2, continue_frame=1):
612613
self.accept(body)
613-
if not self.binder.last_pop_changed:
614+
partials_new = sum(len(pts.map) for pts in self.partial_types)
615+
if (partials_new == partials_old) and not self.binder.last_pop_changed:
614616
break
617+
partials_old = partials_new
615618
if exit_condition:
616619
_, else_map = self.find_isinstance_check(exit_condition)
617620
self.push_type_map(else_map)

mypyc/test-data/commandline.test

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -200,9 +200,9 @@ wtvr = next(i for i in range(10) if i == 5)
200200

201201
d1 = {1: 2}
202202

203-
# Make sure we can produce an error when we hit the awful None case
203+
# Since PR 18180, the following pattern should pose no problems anymore:
204204
def f(l: List[object]) -> None:
205-
x = None # E: Local variable "x" has inferred type None; add an annotation
205+
x = None
206206
for i in l:
207207
if x is None:
208208
x = i

test-data/unit/check-narrowing.test

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2352,3 +2352,40 @@ def fn_while(arg: T) -> None:
23522352
return None
23532353
return None
23542354
[builtins fixtures/primitives.pyi]
2355+
2356+
[case testRefinePartialTypeWithinLoop]
2357+
2358+
x = None
2359+
for _ in range(2):
2360+
if x is not None:
2361+
reveal_type(x) # N: Revealed type is "builtins.int"
2362+
x = 1
2363+
reveal_type(x) # N: Revealed type is "Union[builtins.int, None]"
2364+
2365+
def f() -> bool: ...
2366+
2367+
y = None
2368+
while f():
2369+
reveal_type(y) # N: Revealed type is "None" \
2370+
# N: Revealed type is "Union[builtins.int, None]"
2371+
y = 1
2372+
reveal_type(y) # N: Revealed type is "Union[builtins.int, None]"
2373+
2374+
z = [] # E: Need type annotation for "z" (hint: "z: List[<type>] = ...")
2375+
def g() -> None:
2376+
for i in range(2):
2377+
while f():
2378+
if z:
2379+
z[0] + "v" # E: Unsupported operand types for + ("int" and "str")
2380+
z.append(1)
2381+
2382+
class A:
2383+
def g(self) -> None:
2384+
z = [] # E: Need type annotation for "z" (hint: "z: List[<type>] = ...")
2385+
for i in range(2):
2386+
while f():
2387+
if z:
2388+
z[0] + "v" # E: Unsupported operand types for + ("int" and "str")
2389+
z.append(1)
2390+
2391+
[builtins fixtures/primitives.pyi]

test-data/unit/fixtures/primitives.pyi

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ class memoryview(Sequence[int]):
4848
class tuple(Generic[T]):
4949
def __contains__(self, other: object) -> bool: pass
5050
class list(Sequence[T]):
51+
def append(self, v: T) -> None: pass
5152
def __iter__(self) -> Iterator[T]: pass
5253
def __contains__(self, other: object) -> bool: pass
5354
def __getitem__(self, item: int) -> T: pass

0 commit comments

Comments
 (0)