Scopes and Name Analysis
Relevant source files
The following files were used as context for generating this wiki page:
Scopes and Name Analysis form the backbone of the Rope library's ability to understand Python code structures. This system is responsible for identifying where names (variables, functions, classes) are defined, how they are visible across different blocks of code, and how to resolve a specific identifier at a given offset to its underlying PyName or PyObject.
This module interacts closely with the AST (Abstract Syntax Tree) to map physical code locations to logical visibility blocks. It enables refactorings such as Extract Method and Rename by providing precise data on variable lifetime and unbound name detection.
Scope Hierarchy and Architecture
The project implements a hierarchical scope model where each scope (except the Global scope) has a parent. Scopes are responsible for managing names defined within them and providing lookup mechanisms that can propagate to parent scopes.
Core Scope Classes
| Class | Description |
|---|---|
Scope | The base class for all scope types, providing name lookup and region checking. |
GlobalScope | Represents the module-level scope, including built-in names. |
FunctionScope | Manages local variables, parameters, and return information for functions. |
ClassScope | Represents a class body; notably, it does not propagate its names to sub-scopes. |
ComprehensionScope | Used for list, set, and dict comprehensions and generator expressions. |
TemporaryScope | A transient scope used during specific analyses like list comprehensions. |
Sources: rope/base/pyscopes.py:7-240
Scope Relationship Diagram
The following diagram illustrates the relationship and lookup flow between different scope types.
Note: ClassScope names are not visible from within method scopes using standard propagation; they must be accessed via class attributes.
Sources: rope/base/pyscopes.py:35-65, rope/base/pyscopes.py:202-210
Name Resolution and Analysis
Name analysis involves determining which PyName corresponds to a string identifier at a specific point in the source code. This is handled by evaluating the scope and the identifier's context.
Lookup Logic
The lookup(name) method in the Scope class attempts to find a name locally. If it fails and a parent exists, it calls _propagated_lookup(name) on the parent.
- Global lookup: If a name is not found in the module, it defaults to
builtin_names. - Class lookup:
ClassScopeoverridesget_propagated_names()to return an empty dictionary, preventing class-level variables from being treated as local variables in nested methods.
Sources: rope/base/pyscopes.py:35-65, rope/base/pyscopes.py:112-117
Unbound Name Detection
The library uses visitors to identify "unbound" names—identifiers used in a block that are not defined within that block or its accessible parents. This is critical for refactorings like Move or Extract.
class _UnboundNameFinder(ast.RopeNodeVisitor):
def _Name(self, node):
if self._get_root()._is_node_interesting(node) and not self.is_bound(node.id):
self.add_unbound(node.id)
Sources: rope/refactor/importutils/module_imports.py:382-384
Logical Line and Block Analysis
To accurately identify scope boundaries, Rope must differentiate between physical lines and logical Python statements.
LogicalLineFinder
The LogicalLineFinder processes source code to group physical lines joined by backslashes or parentheses into single logical units.
logical_line_in(lineno): Returns a tuple of(start_line, end_line)for the logical statement containing the given line number.generate_starts(start, end): Yields the starting line numbers of all logical statements in a range.
Sources: rope/base/codeanalyze.py:10-85, ropetest/codeanalyzetest.py:465-490
Scope Finding and Boundaries
The _HoldingScopeFinder utility is used to locate the specific scope object that "holds" a particular line or offset in the source code.
Sources: rope/base/pyscopes.py:213-250
Variable Lifecycle in Refactoring
During the Extract Method refactoring, name analysis determines which variables must be passed as arguments and which must be returned.
Argument and Return Identification
The _FunctionInformationCollector visitor tracks variable usage across the extracted region:
- Read Before Write: Variables read in the region but defined outside become arguments.
- Written and Read Later: Variables written inside the region and read in subsequent code become return values.
- Global/Nonlocal: Identifies names that require
globalornonlocaldeclarations.
Sources: rope/refactor/extract.py:598-630, rope/refactor/extract.py:734-770
Analysis Constraints Table
| Feature | Implementation Detail | Source |
|---|---|---|
| One-liners | Handled specially to detect reads and writes within expressions. | rope/refactor/extract.py:720-732 |
| Conditionals | Variables written only in if blocks are tracked as maybe_written. | rope/refactor/extract.py:764-775 |
| List Comps | Scopes variables within the comprehension to avoid leakage to parent. | rope/refactor/extract.py:795-810 |
| Async/Await | Validates structural compatibility for async extractions. | rope/refactor/extract.py:910-925 |
Technical Summary
Scopes and Name Analysis in Rope provide a robust framework for static code understanding. By mapping AST nodes to a hierarchical scope tree and implementing logical line detection, the library can precisely track identifier visibility. This allows for complex refactoring operations to maintain semantic correctness by correctly managing variable passing, return values, and name resolution across module boundaries.
Sources: rope/base/pyscopes.py, rope/base/codeanalyze.py, rope/refactor/extract.py