From c8eee51ffcec3494475bd0516715b548e7423020 Mon Sep 17 00:00:00 2001 From: Christoph Tyralla Date: Sun, 24 Nov 2024 00:14:29 +0100 Subject: [PATCH 1/4] Revisit the body of a loop if the number of partial types has changed. Fixes #5423 --- mypy/checker.py | 5 ++++- test-data/unit/check-narrowing.test | 28 ++++++++++++++++++++++++++ test-data/unit/fixtures/primitives.pyi | 1 + 3 files changed, 33 insertions(+), 1 deletion(-) diff --git a/mypy/checker.py b/mypy/checker.py index ef3f7502d7ce..09377722c456 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -607,11 +607,14 @@ def accept_loop( """ # The outer frame accumulates the results of all iterations with self.binder.frame_context(can_skip=False, conditional_frame=True): + partials_old = len(self.partial_types[0].map) while True: with self.binder.frame_context(can_skip=True, break_frame=2, continue_frame=1): self.accept(body) - if not self.binder.last_pop_changed: + partials_new = len(self.partial_types[0].map) + if (partials_new == partials_old) and not self.binder.last_pop_changed: break + partials_old = partials_new if exit_condition: _, else_map = self.find_isinstance_check(exit_condition) self.push_type_map(else_map) diff --git a/test-data/unit/check-narrowing.test b/test-data/unit/check-narrowing.test index bc763095477e..4b8abfffe499 100644 --- a/test-data/unit/check-narrowing.test +++ b/test-data/unit/check-narrowing.test @@ -2333,3 +2333,31 @@ def f(x: C) -> None: f(C(5)) [builtins fixtures/primitives.pyi] + +[case testRefinePartialTypeWithinLoop] + +x = None +for _ in range(2): + if x is not None: + reveal_type(x) # N: Revealed type is "builtins.int" + x = 1 +reveal_type(x) # N: Revealed type is "Union[builtins.int, None]" + +def f() -> bool: ... + +y = None +while f(): + reveal_type(y) # N: Revealed type is "None" \ + # N: Revealed type is "Union[builtins.int, None]" + y = 1 +reveal_type(y) # N: Revealed type is "Union[builtins.int, None]" + +z = [] # E: Need type annotation for "z" (hint: "z: List[] = ...") +def g() -> None: + for i in range(2): + while f(): + if z: + z[0] + "v" # E: Unsupported operand types for + ("int" and "str") + z.append(1) + +[builtins fixtures/primitives.pyi] diff --git a/test-data/unit/fixtures/primitives.pyi b/test-data/unit/fixtures/primitives.pyi index 63128a8ae03d..e7d3e12bd5e6 100644 --- a/test-data/unit/fixtures/primitives.pyi +++ b/test-data/unit/fixtures/primitives.pyi @@ -48,6 +48,7 @@ class memoryview(Sequence[int]): class tuple(Generic[T]): def __contains__(self, other: object) -> bool: pass class list(Sequence[T]): + def append(self, v: T) -> None: pass def __iter__(self) -> Iterator[T]: pass def __contains__(self, other: object) -> bool: pass def __getitem__(self, item: int) -> T: pass From 82106db908502c33f82b80e50b1271db227a06dd Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sat, 23 Nov 2024 23:22:16 +0000 Subject: [PATCH 2/4] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- test-data/unit/check-narrowing.test | 1 - 1 file changed, 1 deletion(-) diff --git a/test-data/unit/check-narrowing.test b/test-data/unit/check-narrowing.test index 6e9302a2c33d..1bf1eca6e833 100644 --- a/test-data/unit/check-narrowing.test +++ b/test-data/unit/check-narrowing.test @@ -2380,4 +2380,3 @@ def g() -> None: z.append(1) [builtins fixtures/primitives.pyi] - From 9b0ec61fe705685c92fd0ac9c0d95259fcae4398 Mon Sep 17 00:00:00 2001 From: Christoph Tyralla Date: Sun, 24 Nov 2024 00:44:19 +0100 Subject: [PATCH 3/4] fix --- mypy/checker.py | 4 ++-- test-data/unit/check-narrowing.test | 9 +++++++++ 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/mypy/checker.py b/mypy/checker.py index e72bbb8aee0d..b966492315c7 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -607,11 +607,11 @@ def accept_loop( """ # The outer frame accumulates the results of all iterations with self.binder.frame_context(can_skip=False, conditional_frame=True): - partials_old = len(self.partial_types[0].map) + partials_old = sum(len(pts.map) for pts in self.partial_types) while True: with self.binder.frame_context(can_skip=True, break_frame=2, continue_frame=1): self.accept(body) - partials_new = len(self.partial_types[0].map) + 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 diff --git a/test-data/unit/check-narrowing.test b/test-data/unit/check-narrowing.test index 1bf1eca6e833..ad59af01010c 100644 --- a/test-data/unit/check-narrowing.test +++ b/test-data/unit/check-narrowing.test @@ -2379,4 +2379,13 @@ def g() -> None: z[0] + "v" # E: Unsupported operand types for + ("int" and "str") z.append(1) +class A: + def g(self) -> None: + z = [] # E: Need type annotation for "z" (hint: "z: List[] = ...") + for i in range(2): + while f(): + if z: + z[0] + "v" # E: Unsupported operand types for + ("int" and "str") + z.append(1) + [builtins fixtures/primitives.pyi] From 00efbe9f6b4b349597efc794c9798c9d34f35fef Mon Sep 17 00:00:00 2001 From: Christoph Tyralla Date: Sun, 24 Nov 2024 08:39:08 +0100 Subject: [PATCH 4/4] Remove error message from `testErrorOutput`. --- mypyc/test-data/commandline.test | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mypyc/test-data/commandline.test b/mypyc/test-data/commandline.test index 672e879fbe1e..7c8c2d9d92c9 100644 --- a/mypyc/test-data/commandline.test +++ b/mypyc/test-data/commandline.test @@ -200,9 +200,9 @@ wtvr = next(i for i in range(10) if i == 5) d1 = {1: 2} -# Make sure we can produce an error when we hit the awful None case +# Since PR 18180, the following pattern should pose no problems anymore: def f(l: List[object]) -> None: - x = None # E: Local variable "x" has inferred type None; add an annotation + x = None for i in l: if x is None: x = i