Skip to content
Open
Show file tree
Hide file tree
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# **Upcoming release**

- Preserve same-module class data assignments and reject unsafe dependent renames (@yangfan-yf-yf)
- #895 patchedast Starred and keyword now consumes their syntactically expected and ** (@lieryan)
- #896 patchedast cleanup and refactoring (@lieryan)

Expand Down
12 changes: 12 additions & 0 deletions docs/library.rst
Original file line number Diff line number Diff line change
Expand Up @@ -617,6 +617,18 @@ Examples
Rename
------

Rope follows a single unconditional assignment such as ``Holder.value = Item()``
when both classes are defined at module level in the same module and the value
has ordinary data lookup behavior. Imported names in a class body retain their
original source identity when the corresponding class attribute is replaced.

Repeated or conditional writes, early or deferred reads, class rebinding,
inheritance, and custom attribute lookup can leave the value uncertain. For
these modeled writes, a potentially affected rename raises
``rope.base.exceptions.RefactoringError`` before producing changes. This
analysis is conservative and may refuse an otherwise safe rename; it does not
infer arbitrary factories, descriptors, or dynamic cross-module assignments.

Using rename refactoring:

.. code-block:: python
Expand Down
16 changes: 16 additions & 0 deletions rope/base/evaluate.py
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,22 @@ def get_primary_and_pyname_at(
elif isinstance(pyobject, pyobjects.AbstractFunction):
parameter_name = rope.base.pynames.ParameterName()
return (None, parameter_name)
# A class data overlay must not replace the imported source symbol.
if holding_scope.get_kind() == "Class":
from rope.base import externaldata

imported = externaldata.import_source_name(
holding_scope,
lineno,
offset,
self.worder.get_word_at(offset),
self.worder.get_primary_at(offset),
self.worder.get_from_module(offset),
self.worder.is_from_aliased(offset),
self.worder.is_from_statement_module(offset),
)
if imported is not None:
return (None, imported)
# class body
if self._is_defined_in_class_body(holding_scope, offset, lineno):
class_scope = holding_scope
Expand Down
Loading
Loading