From 2b1bba258d3678b3152476e52bd01040337c4ca3 Mon Sep 17 00:00:00 2001 From: Bagou Ines Date: Thu, 9 Oct 2025 10:43:29 +0200 Subject: [PATCH 1/5] ajout du test unitaire issue_scope.test --- test-data/unit/issue_scope.test | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 test-data/unit/issue_scope.test diff --git a/test-data/unit/issue_scope.test b/test-data/unit/issue_scope.test new file mode 100644 index 000000000000..2ea013dd4e62 --- /dev/null +++ b/test-data/unit/issue_scope.test @@ -0,0 +1,17 @@ +[case testScopeOptionalIntResolution] +from typing import Optional + +x: Optional[int] = None +y: Optional[int] = None + +def f() -> None: + x = 1 + y = 1 + class C: + reveal_type(x) # should be int + reveal_type(y) # should be Optional[int] + x = 2 + +[out] +note: Revealed type is "builtins.int" +note: Revealed type is "Union[builtins.int, None]" From 4bbb618200bb511d9bd00ef6eaca2c16f3eddf71 Mon Sep 17 00:00:00 2001 From: Bagou Ines Date: Mon, 1 Dec 2025 10:44:35 +0100 Subject: [PATCH 2/5] ajout du correctif autour de la fonction visit_class_def --- mypy/semanal.py | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/mypy/semanal.py b/mypy/semanal.py index 17dc9bfadc1f..14283c530960 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -1763,7 +1763,6 @@ def check_decorated_function_is_method(self, decorator: str, context: Context) - # # Classes # - def visit_class_def(self, defn: ClassDef) -> None: self.statement = defn self.incomplete_type_stack.append(not defn.info) @@ -1773,10 +1772,31 @@ def visit_class_def(self, defn: ClassDef) -> None: self.mark_incomplete(defn.name, defn) return - self.analyze_class(defn) + # --- PATCH START --- + # If the class is defined inside a function, skip that function's locals + if self.scope.active_function() is not None: + with self.scope.without_function_locals(): + self.analyze_class(defn) + else: + self.analyze_class(defn) + # --- PATCH END --- + self.pop_type_args(defn.type_args) self.incomplete_type_stack.pop() + # def visit_class_def(self, defn: ClassDef) -> None: + # self.statement = defn + # self.incomplete_type_stack.append(not defn.info) + # namespace = self.qualified_name(defn.name) + # with self.tvar_scope_frame(self.tvar_scope.class_frame(namespace)): + # if self.push_type_args(defn.type_args, defn) is None: + # self.mark_incomplete(defn.name, defn) + # return + + # self.analyze_class(defn) + # self.pop_type_args(defn.type_args) + # self.incomplete_type_stack.pop() + def push_type_args( self, type_args: list[TypeParam] | None, context: Context ) -> list[tuple[str, TypeVarLikeExpr]] | None: From fdec44446d515847f8647a99542842b9537ed5d0 Mon Sep 17 00:00:00 2001 From: Bagou Ines Date: Mon, 1 Dec 2025 10:49:08 +0100 Subject: [PATCH 3/5] =?UTF-8?q?ajout=20de=20la=20m=C3=A9thode=20without=5F?= =?UTF-8?q?function=5Flocals=20dans=20scope.py?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- mypy/scope.py | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/mypy/scope.py b/mypy/scope.py index 766048c41180..b410ba0eebbd 100644 --- a/mypy/scope.py +++ b/mypy/scope.py @@ -124,3 +124,24 @@ def saved_scope(self, saved: SavedScope) -> Iterator[None]: with self.class_scope(info) if info else nullcontext(): with self.function_scope(function) if function else nullcontext(): yield + + @contextmanager + def without_function_locals(self): + """Temporarily disable the current function scope (used for nested class definitions).""" + # Save current function + saved_function = self.function + saved_ignored = self.ignored + saved_functions_stack = list(self.functions) + + # Temporarily remove the current function scope + self.function = None + self.functions = [] + self.ignored = 0 + + try: + yield + finally: + # Restore the previous function context + self.function = saved_function + self.functions = saved_functions_stack + self.ignored = saved_ignored \ No newline at end of file From edd0db4fe38f3066e6c196b8f3d9b4285104bb6c Mon Sep 17 00:00:00 2001 From: Bagou Ines Date: Mon, 1 Dec 2025 11:45:24 +0100 Subject: [PATCH 4/5] ajout du test nested-class-scope.test --- test-data/unit/nested-class-scope.test | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 test-data/unit/nested-class-scope.test diff --git a/test-data/unit/nested-class-scope.test b/test-data/unit/nested-class-scope.test new file mode 100644 index 000000000000..2072e5597949 --- /dev/null +++ b/test-data/unit/nested-class-scope.test @@ -0,0 +1,15 @@ +[case testClassScopeInFunction] +from typing import Optional + +x: Optional[int] = None +y: Optional[int] = None + +def f() -> None: + x = 1 + y = 1 + class C: + reveal_type(x) + reveal_type(y) +[out] +note: Revealed type is "builtins.int" +note: Revealed type is "Union[builtins.int, None]" From 59496d0571751f0100453c26619318bd30607bdc Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 8 Dec 2025 08:27:32 +0000 Subject: [PATCH 5/5] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- mypy/scope.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mypy/scope.py b/mypy/scope.py index b410ba0eebbd..3f0bc7d80367 100644 --- a/mypy/scope.py +++ b/mypy/scope.py @@ -144,4 +144,4 @@ def without_function_locals(self): # Restore the previous function context self.function = saved_function self.functions = saved_functions_stack - self.ignored = saved_ignored \ No newline at end of file + self.ignored = saved_ignored