Skip to content

Commit

Permalink
scope_provider: Simplify parent resolution (#1013)
Browse files Browse the repository at this point in the history
This PR introduces `Scope._next_visible_parent` which deduplicates much of the logic between `_contains_in_self_or_parent`, `_find_assignment_target_parent`, and `_getitem_from_self_or_parent`.

This will be helpful when implementing scope resolution for the future `AnnotationScope`.

There should be no functionality change.
  • Loading branch information
zsol authored Sep 16, 2023
1 parent b509cc8 commit 9d869b6
Showing 1 changed file with 35 additions and 45 deletions.
80 changes: 35 additions & 45 deletions libcst/metadata/scope_provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -436,20 +436,34 @@ def record_import_assignment(
def _find_assignment_target(self, name: str) -> "Scope":
return self

def _find_assignment_target_parent(self, name: str) -> "Scope":
return self

def record_access(self, name: str, access: Access) -> None:
self._accesses_by_name[name].add(access)
self._accesses_by_node[access.node].add(access)

def _getitem_from_self_or_parent(self, name: str) -> Set[BaseAssignment]:
"""Overridden by ClassScope to hide it's assignments from child scopes."""
return self[name]
def _is_visible_from_children(self) -> bool:
"""Returns if the assignments in this scope can be accessed from children.
This is normally True, except for class scopes::
def outer_fn():
v = ... # outer_fn's declaration
class InnerCls:
v = ... # shadows outer_fn's declaration
class InnerInnerCls:
v = ... # shadows all previous declarations of v
def inner_fn():
nonlocal v
v = ... # this refers to outer_fn's declaration
# and not to any of the inner classes' as those are
# hidden from their children.
"""
return True

def _contains_in_self_or_parent(self, name: str) -> bool:
"""Overridden by ClassScope to hide it's assignments from child scopes."""
return name in self
def _next_visible_parent(self, first: Optional["Scope"] = None) -> "Scope":
parent = first if first is not None else self.parent
while not parent._is_visible_from_children():
parent = parent.parent
return parent

@abc.abstractmethod
def __contains__(self, name: str) -> bool:
Expand Down Expand Up @@ -630,13 +644,14 @@ def __init__(self) -> None:
def __contains__(self, name: str) -> bool:
if name in self._assignments:
return len(self._assignments[name]) > 0
return self.parent._contains_in_self_or_parent(name)
return name in self._next_visible_parent()

def __getitem__(self, name: str) -> Set[BaseAssignment]:
if name in self._assignments:
return self._assignments[name]
else:
return self.parent._getitem_from_self_or_parent(name)

parent = self._next_visible_parent()
return parent[name]

def record_global_overwrite(self, name: str) -> None:
pass
Expand Down Expand Up @@ -672,7 +687,8 @@ def record_nonlocal_overwrite(self, name: str) -> None:

def _find_assignment_target(self, name: str) -> "Scope":
if name in self._scope_overwrites:
return self._scope_overwrites[name]._find_assignment_target_parent(name)
scope = self._scope_overwrites[name]
return self._next_visible_parent(scope)._find_assignment_target(name)
else:
return super()._find_assignment_target(name)

Expand All @@ -681,15 +697,16 @@ def __contains__(self, name: str) -> bool:
return name in self._scope_overwrites[name]
if name in self._assignments:
return len(self._assignments[name]) > 0
return self.parent._contains_in_self_or_parent(name)
return name in self._next_visible_parent()

def __getitem__(self, name: str) -> Set[BaseAssignment]:
if name in self._scope_overwrites:
return self._scope_overwrites[name]._getitem_from_self_or_parent(name)
scope = self._scope_overwrites[name]
return self._next_visible_parent(scope)[name]
if name in self._assignments:
return self._assignments[name]
else:
return self.parent._getitem_from_self_or_parent(name)
return self._next_visible_parent()[name]

def _make_name_prefix(self) -> str:
# filter falsey strings out
Expand All @@ -711,35 +728,8 @@ class ClassScope(LocalScope):
When a class is defined, it creates a ClassScope.
"""

def _find_assignment_target_parent(self, name: str) -> "Scope":
"""
Forward the assignment to parent.
def outer_fn():
v = ... # outer_fn's declaration
class InnerCls:
v = ... # shadows outer_fn's declaration
def inner_fn():
nonlocal v
v = ... # this should actually refer to outer_fn's declaration
# and not to InnerCls's, because InnerCls's scope is
# hidden from its children.
"""
return self.parent._find_assignment_target_parent(name)

def _getitem_from_self_or_parent(self, name: str) -> Set[BaseAssignment]:
"""
Class variables are only accessible using ClassName.attribute, cls.attribute, or
self.attribute in child scopes. They cannot be accessed with their bare names.
"""
return self.parent._getitem_from_self_or_parent(name)

def _contains_in_self_or_parent(self, name: str) -> bool:
"""
See :meth:`_getitem_from_self_or_parent`
"""
return self.parent._contains_in_self_or_parent(name)
def _is_visible_from_children(self) -> bool:
return False

def _make_name_prefix(self) -> str:
# filter falsey strings out
Expand Down

0 comments on commit 9d869b6

Please sign in to comment.