From 0c7adc64f4f7090577977d7e863cc5c2b9593876 Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Mon, 11 Mar 2024 08:28:12 -0700 Subject: [PATCH] Fix the issue --- docs/changelog.md | 2 ++ pyanalyze/name_check_visitor.py | 10 +++++++++- pyanalyze/test_stacked_scopes.py | 18 ++++++++++++++++++ 3 files changed, 29 insertions(+), 1 deletion(-) diff --git a/docs/changelog.md b/docs/changelog.md index bab76592..9875de02 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -2,6 +2,8 @@ ## Unreleased +- Fix incorrect `undefined_name` errors when a class is nested in a nested + function and uses a name from the outer function (#750) - Fix incorrect `possibly_undefined_name` error on certain uses of the walrus operator (#749) - Fix narrowing on `isinstance` calls with arguments that are not diff --git a/pyanalyze/name_check_visitor.py b/pyanalyze/name_check_visitor.py index dc8c6594..3ad7fc41 100644 --- a/pyanalyze/name_check_visitor.py +++ b/pyanalyze/name_check_visitor.py @@ -1822,7 +1822,15 @@ def _get_current_class_object(self, node: ast.ClassDef) -> Optional[type]: def _visit_class_and_get_value( self, node: ast.ClassDef, current_class: Optional[type] ) -> Value: - if self._is_checking(): + if self._is_collecting(): + # If this is a nested class, we need to run the collecting phase to get data + # about names accessed from the class. + if len(self.scopes.scopes) > 2: + with self.scopes.add_scope( + ScopeType.class_scope, scope_node=node, scope_object=current_class + ), self._set_current_class(current_class): + self._generic_visit_list(node.body) + else: with self.scopes.add_scope( ScopeType.class_scope, scope_node=None, scope_object=current_class ), self._set_current_class(current_class): diff --git a/pyanalyze/test_stacked_scopes.py b/pyanalyze/test_stacked_scopes.py index d703f659..28c3d675 100644 --- a/pyanalyze/test_stacked_scopes.py +++ b/pyanalyze/test_stacked_scopes.py @@ -1937,3 +1937,21 @@ class Nested: return Nested return inner + + @assert_passes() + def test_triple_function_nesting(self): + from typing_extensions import Literal, assert_type + + def outer(): + outer_var = "outer" + + def inner(): + inner_var = "inner" + + def innermost(): + assert_type(outer_var, Literal["outer"]) + assert_type(inner_var, Literal["inner"]) + + return innermost + + return inner