Skip to content

Commit

Permalink
Fix #18. Identify AbstractFunction parameters as ParameterName
Browse files Browse the repository at this point in the history
When the function is a builtin function, the call parameter's name was
sometimes incorrectly identified as an AssignedName. This led to
rename refactoring incorrectly renaming these parameters.
  • Loading branch information
lieryan committed Oct 6, 2022
1 parent fd27086 commit 29f22e2
Show file tree
Hide file tree
Showing 3 changed files with 40 additions and 7 deletions.
28 changes: 23 additions & 5 deletions rope/base/evaluate.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,18 @@
from operator import itemgetter
from typing import Optional

import rope.base.builtins
import rope.base.pynames
import rope.base.pyobjects
from rope.base import ast, astutils, exceptions, pyobjects, arguments, worder
from rope.base.utils import pycompat
from rope.base import (
ast,
astutils,
exceptions,
pyobjects,
pyobjectsdef,
arguments,
worder,
)


BadIdentifierError = exceptions.BadIdentifierError
Expand Down Expand Up @@ -82,15 +90,25 @@ def _is_function_name_in_function_header(self, scope, offset, lineno):
def get_pyname_at(self, offset):
return self.get_primary_and_pyname_at(offset)[1]

def get_primary_and_pyname_at(self, offset):
def get_primary_and_pyname_at(
self,
offset: int,
) -> tuple[
Optional[rope.base.pynames.PyName],
Optional[rope.base.pynames.PyName],
]:
lineno = self.lines.get_line_number(offset)
holding_scope = self.module_scope.get_inner_scope_for_offset(offset)
# function keyword parameter
if self.worder.is_function_keyword_parameter(offset):
keyword_name = self.worder.get_word_at(offset)
pyobject = self.get_enclosing_function(offset)
if isinstance(pyobject, pyobjects.PyFunction):
return (None, pyobject.get_parameters().get(keyword_name, None))
if isinstance(pyobject, pyobjectsdef.PyFunction):
parameter_name = pyobject.get_parameters().get(keyword_name, None)
return (None, parameter_name)
elif isinstance(pyobject, pyobjects.AbstractFunction):
parameter_name = rope.base.pynames.ParameterName()
return (None, parameter_name)
# class body
if self._is_defined_in_class_body(holding_scope, offset, lineno):
class_scope = holding_scope
Expand Down
4 changes: 2 additions & 2 deletions rope/base/oi/type_hinting/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
except ImportError:
pass
import rope.base.utils as base_utils
from rope.base.evaluate import ScopeNameFinder
from rope.base import evaluate
from rope.base.exceptions import AttributeNotFoundError
from rope.base.pyobjects import PyClass, PyDefinedObject, PyFunction, PyObject
from rope.base.utils import pycompat
Expand Down Expand Up @@ -95,7 +95,7 @@ def resolve_type(type_name, pyobject):
else:
mod_name, attr_name = type_name.rsplit(".", 1)
try:
mod_finder = ScopeNameFinder(pyobject.get_module())
mod_finder = evaluate.ScopeNameFinder(pyobject.get_module())
mod = mod_finder._find_module(mod_name).get_object()
ret_type = mod.get_attribute(attr_name).get_object()
except AttributeNotFoundError:
Expand Down
15 changes: 15 additions & 0 deletions ropetest/refactor/renametest.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,21 @@ def _rename(self, resource, offset, new_name, **kwds):
changes = Rename(self.project, resource, offset).get_changes(new_name, **kwds)
self.project.do(changes)

def test_local_variable_but_not_parameter(self):
code = dedent("""\
a = 10
foo = dict(a=a)
""")

refactored = self._local_rename(code, 1, "new_a")
self.assertEqual(
dedent("""\
new_a = 10
foo = dict(a=new_a)
"""),
refactored,
)

def test_simple_global_variable_renaming(self):
refactored = self._local_rename("a_var = 20\n", 2, "new_var")
self.assertEqual("new_var = 20\n", refactored)
Expand Down

0 comments on commit 29f22e2

Please sign in to comment.