From f4a5a5094e4501d830fcfdc93400fd2815c8cd39 Mon Sep 17 00:00:00 2001 From: brighten Date: Thu, 4 Apr 2024 11:31:54 -0700 Subject: [PATCH 1/4] fix: __all__ references are not ignored --- libcst/metadata/scope_provider.py | 47 +++++++++++++++++++- libcst/metadata/tests/test_scope_provider.py | 15 ++++++- 2 files changed, 59 insertions(+), 3 deletions(-) diff --git a/libcst/metadata/scope_provider.py b/libcst/metadata/scope_provider.py index 75f37a06e..11658478c 100644 --- a/libcst/metadata/scope_provider.py +++ b/libcst/metadata/scope_provider.py @@ -166,8 +166,8 @@ def __init__(self, name: str, scope: "Scope") -> None: self.scope = scope self.__accesses = set() - def record_access(self, access: Access) -> None: - if access.scope != self.scope or self._index < access._index: + def record_access(self, access: Access, bypass: bool | None = None) -> None: + if bypass or access.scope != self.scope or self._index < access._index: self.__accesses.add(access) def record_accesses(self, accesses: Set[Access]) -> None: @@ -846,6 +846,7 @@ def __init__(self, provider: "ScopeProvider") -> None: self.scope: Scope = GlobalScope() self.__deferred_accesses: List[DeferredAccess] = [] self.__top_level_attribute_stack: List[Optional[cst.Attribute]] = [None] + self.__in___all___stack: List[bool] = [False] self.__in_annotation_stack: List[bool] = [False] self.__in_type_hint_stack: List[bool] = [False] self.__in_ignored_subscript: Set[cst.Subscript] = set() @@ -950,6 +951,32 @@ def _handle_string_annotation( self, node: Union[cst.SimpleString, cst.ConcatenatedString] ) -> bool: """Returns whether it successfully handled the string annotation""" + if self.__in___all___stack[-1]: + name: str | bytes | None = None + + if isinstance(node, cst.SimpleString): + name = node.value + + if isinstance(node, cst.ConcatenatedString): + name = node.evaluated_value + + assert isinstance(name, str), f"Expected str, got {type(name)}" + + name = name.replace('"', "") + + access = Access( + cst.Name(name), + self.scope, + is_annotation=False, + is_type_hint=False, + ) + + self.scope.record_access("__all__", access) + assignment = next(iter(self.scope.assignments["__all__"])) + assignment.record_access(access, True) + + return True + if ( self.__in_type_hint_stack[-1] or self.__in_annotation_stack[-1] ) and not self.__in_ignored_subscript: @@ -1091,6 +1118,22 @@ def visit_Nonlocal(self, node: cst.Nonlocal) -> Optional[bool]: self.scope.record_nonlocal_overwrite(name_item.name.value) return False + def is__all___assignment(self, node: cst.Assign) -> bool: + target = next( + (t.target for t in node.targets if isinstance(t.target, cst.Name)), None + ) + if target is None: + return False + return target.value == "__all__" + + def visit_Assign(self, node: cst.Assign) -> Optional[bool]: + if self.is__all___assignment(node): + self.__in___all___stack.append(True) + + def leave_Assign(self, node: cst.Assign) -> None: + if self.is__all___assignment(node): + self.__in___all___stack.pop() + def visit_ListComp(self, node: cst.ListComp) -> Optional[bool]: return self._visit_comp_alike(node) diff --git a/libcst/metadata/tests/test_scope_provider.py b/libcst/metadata/tests/test_scope_provider.py index a2087645c..afbb67316 100644 --- a/libcst/metadata/tests/test_scope_provider.py +++ b/libcst/metadata/tests/test_scope_provider.py @@ -2171,7 +2171,7 @@ def test_annotation_refers_to_nested_class(self) -> None: class Outer: class Nested: pass - + type Alias = Nested def meth1[T: Nested](self): pass @@ -2248,3 +2248,16 @@ def f[T: Inner](self): Inner f_scope = scopes[inner_in_func_body] self.assertIn(inner_in_func_body.value, f_scope.accesses) self.assertEqual(list(f_scope.accesses)[0].referents, set()) + + def test___all___assignment(self) -> None: + m, scopes = get_scope_metadata_provider( + """ + import a + import b + + __all__ = ["a", "b"] + """ + ) + all_assignment = list(scopes[m]["__all__"])[0] + self.assertIsInstance(all_assignment, Assignment) + self.assertEqual(len(all_assignment.references), 2) From 3d7e9da438eaa02141c90380db6d369f67a7c820 Mon Sep 17 00:00:00 2001 From: brighten Date: Thu, 4 Apr 2024 11:50:46 -0700 Subject: [PATCH 2/4] fix: target import assignments --- libcst/metadata/scope_provider.py | 8 +++----- libcst/metadata/tests/test_scope_provider.py | 8 +++++--- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/libcst/metadata/scope_provider.py b/libcst/metadata/scope_provider.py index 11658478c..6d0030a08 100644 --- a/libcst/metadata/scope_provider.py +++ b/libcst/metadata/scope_provider.py @@ -166,8 +166,8 @@ def __init__(self, name: str, scope: "Scope") -> None: self.scope = scope self.__accesses = set() - def record_access(self, access: Access, bypass: bool | None = None) -> None: - if bypass or access.scope != self.scope or self._index < access._index: + def record_access(self, access: Access) -> None: + if access.scope != self.scope or self._index < access._index: self.__accesses.add(access) def record_accesses(self, accesses: Set[Access]) -> None: @@ -971,9 +971,7 @@ def _handle_string_annotation( is_type_hint=False, ) - self.scope.record_access("__all__", access) - assignment = next(iter(self.scope.assignments["__all__"])) - assignment.record_access(access, True) + list(self.scope.assignments[name])[0].record_access(access) return True diff --git a/libcst/metadata/tests/test_scope_provider.py b/libcst/metadata/tests/test_scope_provider.py index afbb67316..0ee13a3bd 100644 --- a/libcst/metadata/tests/test_scope_provider.py +++ b/libcst/metadata/tests/test_scope_provider.py @@ -2258,6 +2258,8 @@ def test___all___assignment(self) -> None: __all__ = ["a", "b"] """ ) - all_assignment = list(scopes[m]["__all__"])[0] - self.assertIsInstance(all_assignment, Assignment) - self.assertEqual(len(all_assignment.references), 2) + import_a_assignment = list(scopes[m]["a"])[0] + import_b_assignment = list(scopes[m]["b"])[0] + + self.assertEqual(len(import_a_assignment.references), 1) + self.assertEqual(len(import_b_assignment.references), 1) From e4caf8e66da058f3b5818b508d834596314c8d61 Mon Sep 17 00:00:00 2001 From: brighten Date: Thu, 4 Apr 2024 15:27:24 -0700 Subject: [PATCH 3/4] Update libcst/metadata/scope_provider.py Co-authored-by: Zsolt Dollenstein --- libcst/metadata/scope_provider.py | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/libcst/metadata/scope_provider.py b/libcst/metadata/scope_provider.py index 6d0030a08..5e6aacf6d 100644 --- a/libcst/metadata/scope_provider.py +++ b/libcst/metadata/scope_provider.py @@ -952,17 +952,10 @@ def _handle_string_annotation( ) -> bool: """Returns whether it successfully handled the string annotation""" if self.__in___all___stack[-1]: - name: str | bytes | None = None + name = node.evaluated_value - if isinstance(node, cst.SimpleString): - name = node.value - - if isinstance(node, cst.ConcatenatedString): - name = node.evaluated_value - - assert isinstance(name, str), f"Expected str, got {type(name)}" - - name = name.replace('"', "") + if isinstance(name, bytes): + name = name.decode("utf-8") access = Access( cst.Name(name), From eb43998d5c1ad7628a57975d6b126eea65d2acd6 Mon Sep 17 00:00:00 2001 From: brighten Date: Thu, 4 Apr 2024 15:27:33 -0700 Subject: [PATCH 4/4] Update libcst/metadata/scope_provider.py Co-authored-by: Zsolt Dollenstein --- libcst/metadata/scope_provider.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libcst/metadata/scope_provider.py b/libcst/metadata/scope_provider.py index 5e6aacf6d..230d6a98e 100644 --- a/libcst/metadata/scope_provider.py +++ b/libcst/metadata/scope_provider.py @@ -958,7 +958,7 @@ def _handle_string_annotation( name = name.decode("utf-8") access = Access( - cst.Name(name), + node, self.scope, is_annotation=False, is_type_hint=False,