Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

scope_provider: Simplify parent resolution #1013

Merged
merged 1 commit into from
Sep 16, 2023
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading