Commits

Anonymous committed da9d25b

Change signature for constructors

Comments (0)

Files changed (18)

docs/dev/done.txt

 ===========
 
 
+- Change method signature for constructors : March 11, 2007
+
+
 - Supporting generator functions : March 9, 2007
 
 

docs/dev/stories.txt

 * Supporting templates in text modes
 
 
-* Line break algorithm for rest mode; ``C-q``
+* Line break algorithm for rest mode; ``M-q``
 
 
 * Supporting modules without source code
 
 
 * Holding per name information for builtin containers
-
-
-* Handling change method signature for constructors

docs/dev/workingon.txt

-Change Method Signature For Constructors
-========================================
+Small Stories And Refactorings
+==============================
 
-- Transforming more `PyObject`\s to textuals
-- Organize import problems in builtins module
+* Not saving return value in callinfo when we're approximating?
+* Separating 'none' from 'unknown'
+* Should every unknown be calculated everytime?
+* When to forget old results?
+* Adding `None` builtin
+* Returning a `Generator` for functions with unknown return type;
 
-* Returning a `Generator` for functions with unknown return type
-
-* Binding keys in show pydoc
+* Binding next/prev page keys in show pydoc
 * Changing `callinfo` to match not exactly the same but similar args
 * Is throwing `IsBeingInferredException` a good thing?  Can't we
   return `get_unknown()` instead?

rope/base/builtins.py

     if passed_self is None:
         return passed_class
     else:
+        pyclass = passed_self.get_type()
+        if isinstance(pyclass, pyobjects.AbstractClass):
+            supers = pyclass.get_superclasses()
+            if supers:
+                return pyobjects.PyObject(supers[0])
         return passed_self
 
 def _zip_function(args):

rope/base/codeanalyze.py

         return False
 
     def get_pyname_at(self, offset):
+        return self.get_primary_and_pyname_at(offset)[1]
+
+    def get_primary_and_pyname_at(self, offset):
         lineno = self.lines.get_line_number(offset)
         holding_scope = self.module_scope.get_inner_scope_for_line(lineno)
         # function keyword parameter
             keyword_name = self.word_finder.get_word_at(offset)
             pyobject = self.get_enclosing_function(offset)
             if isinstance(pyobject, pyobjects.PyFunction):
-                return pyobject.get_parameters().get(keyword_name, None)
+                return (None, pyobject.get_parameters().get(keyword_name, None))
 
         # class body
         if self._is_defined_in_class_body(holding_scope, offset, lineno):
                 class_scope = holding_scope.parent
             name = self.word_finder.get_primary_at(offset).strip()
             try:
-                return class_scope.pyobject.get_attribute(name)
+                return (None, class_scope.pyobject.get_attribute(name))
             except rope.base.exceptions.AttributeNotFoundError:
-                return None
+                return (None, None)
         # function header
         if self._is_function_name_in_function_header(holding_scope, offset, lineno):
             name = self.word_finder.get_primary_at(offset).strip()
-            return holding_scope.parent.get_name(name)
+            return (None, holding_scope.parent.get_name(name))
         # from statement module
         if self.word_finder.is_from_statement_module(offset):
             module = self.word_finder.get_primary_at(offset)
             module_pyname = self._find_module(module)
-            return module_pyname
+            return (None, module_pyname)
         name = self.word_finder.get_primary_at(offset)
-        result = self.get_pyname_in_scope(holding_scope, name)
-        return result
+        return self.get_primary_and_pyname_in_scope(holding_scope, name)
 
     def get_enclosing_function(self, offset):
         function_parens = self.word_finder.find_parens_start_from_inside(offset)
 
     @staticmethod
     def get_pyname_in_scope(holding_scope, name):
+        return ScopeNameFinder.get_primary_and_pyname_in_scope(
+            holding_scope, name)[1]
+
+    # XXX: This might belong to `rope.base.evaluate` module
+    @staticmethod
+    def get_primary_and_pyname_in_scope(holding_scope, name):
         #ast = compiler.parse(name)
-        # parenthesizing for handling cases like 'a_var.\nattr'
         try:
+            # parenthesizing for handling cases like 'a_var.\nattr'
             ast = compiler.parse('(%s)' % name)
         except SyntaxError:
             raise BadIdentifierError('Not a python identifier selected.')
-        result = evaluate.get_statement_result(holding_scope, ast)
-        return result
+        evaluator = evaluate.StatementEvaluator(holding_scope)
+        compiler.walk(ast, evaluator)
+        return evaluator.old_result, evaluator.result
 
 
 class BadIdentifierError(rope.base.exceptions.RopeError):

rope/base/evaluate.py

     def __init__(self, scope):
         self.scope = scope
         self.result = None
+        self.old_result = None
 
     def visitName(self, node):
         self.result = self.scope.lookup(node.name)
 
     def visitGetattr(self, node):
         pyname = get_statement_result(self.scope, node.expr)
+        self.old_result = pyname
         if pyname is not None and pyname.get_object() is not None:
             try:
                 self.result = pyname.get_object().get_attribute(node.attrname)

rope/base/oi/objectinfer.py

         except pyobjects.IsBeingInferredError:
             pass
 
-
     def evaluate_object(self, pyname):
         pyobject = self._infer_pyobject_for_assign_node(
             pyname.assignment.ast_node, pyname.module, pyname.lineno)

rope/base/oi/staticoi.py

         if args:
             # HACK: Setting parameter objects manually
             # This is not thread safe and might cause problems if `args`
-            # is not a good call example
+            # is not a good call site
             pyobject.get_scope().invalidate_data()
             pyobject._set_parameter_pyobjects(
                 args.get_arguments(pyobject.get_param_names(special_args=False)))
                                                                  returned_node)
                 if resulting_pyname is None:
                     return None
+                pyobject = resulting_pyname.get_object()
+                if pyobject == pyobjects.get_unknown():
+                    return
                 if not scope._is_generator():
                     return resulting_pyname.get_object()
                 else:

rope/base/pyobjects.py

 
     def __hash__(self):
         """See docs for `__eq__()` method"""
-        if type(self) == PyObject:
+        if type(self) == PyObject and self != self.type:
             return hash(self.type) + 1
         else:
             return super(PyObject, self).__hash__()

rope/refactor/change_signature.py

 import copy
 
 import rope.base.exceptions
-import rope.refactor.occurrences
 from rope.base import pyobjects, codeanalyze
 from rope.base.change import ChangeContents, ChangeSet
-from rope.refactor import sourceutils, functionutils, rename
+from rope.refactor import occurrences, sourceutils, functionutils, rename
 
 
 class ChangeSignature(object):
         self.name = codeanalyze.get_name_at(self.resource, self.offset)
         self.pyname = codeanalyze.get_pyname_at(self.pycore, self.resource,
                                                 self.offset)
-#        if self.pyname is None:
-#            return
-#        pyobject = self.pyname.get_object()
-#        if isinstance(pyobject, pyobjects.PyClass) and \
-#           '__init__' in pyobject.get_attributes():
-#            self.name = '__init__'
-#        if self.name == '__init__' and \
-#           isinstance(pyobject, pyobjects.PyFunction) and \
-#           isinstance(pyobject.parent, pyobjects.PyClass):
-#            print 'init'
+        if self.pyname is None:
+            return
+        pyobject = self.pyname.get_object()
+        if isinstance(pyobject, pyobjects.PyClass) and \
+           '__init__' in pyobject.get_attributes():
+            self.pyname = pyobject.get_attribute('__init__')
+            self.name = '__init__'
+        pyobject = self.pyname.get_object()
+        self.others = None
+        if self.name == '__init__' and \
+           isinstance(pyobject, pyobjects.PyFunction) and \
+           isinstance(pyobject.parent, pyobjects.PyClass):
+            pyclass = pyobject.parent
+            self.others = (pyclass.get_name(),
+                           pyclass.parent.get_attribute(pyclass.get_name()))
 
     def _change_calls(self, call_changer, in_hierarchy=False):
         changes = ChangeSet('Changing signature of <%s>' % self.name)
             pynames = rename.get_all_methods_in_hierarchy(pyclass, self.name)
         else:
             pynames = [self.pyname]
-        finder = rope.refactor.occurrences.FilteredFinder(
-            self.pycore, self.name, pynames)
+        finder = occurrences.FilteredFinder(self.pycore, self.name, pynames)
+        if self.others:
+            name, pyname = self.others
+            constructor_finder = occurrences.FilteredFinder(
+                self.pycore, name, [pyname], only_calls=True)
+            finder = occurrences.MultipleFinders(
+                [finder, constructor_finder])
         for file in self.pycore.get_python_files():
             change_calls = _ChangeCallsInModule(
                 self.pycore, finder, file, call_changer)
     def change_definition(self, call):
         return self.changed_definition_infos[-1].to_string()
 
-    def change_call(self, call):
-        call_info = functionutils.CallInfo.read(self.definition_info, call)
+    def change_call(self, primary, pyname, call):
+        call_info = functionutils.CallInfo.read(
+            primary, pyname, self.definition_info, call)
         mapping = functionutils.ArgumentMapping(self.definition_info, call_info)
 
         for definition_info, changer in zip(self.changed_definition_infos, self.changers):
         definition_info.args_with_defaults = new_args
 
 
-# XXX: This cannot be used because we need some way of changing
-# occurances inside function bodies.  By the way renaming
-# parameters is already supported by normal rename refactoring.
-# Supproting renaming parameters in change method signature
-# increases the compexity considerably.  So we leave that for now.
-class _XXXArgumentRenamer(_ArgumentChanger):
-
-    def __init__(self, index, new_name):
-        self.index = index
-        self.new_name = new_name
-
-    def change_definition_info(self, call_info):
-        if self.index < len(call_info.args_with_defaults):
-            call_info.args_with_defaults[self.index] = (
-                self.new_name, call_info.args_with_defaults[self.index][1])
-        elif self.index == len(call_info.args_with_defaults) and \
-           call_info.args_arg is not None:
-            call_info.args_arg = self.new_name
-        elif (self.index == len(call_info.args_with_defaults) and
-            call_info.args_arg is None and call_info.keywords_arg is not None) or \
-           (self.index == len(call_info.args_with_defaults) + 1 and
-            call_info.args_arg is not None and call_info.keywords_arg is not None):
-            call_info.keywords_arg = self.new_name
-
-    def change_argument_mapping(self, definition_info, mapping):
-        if self.index < len(definition_info.args_with_defaults):
-            old_name = definition_info.args_with_defaults[self.index][0]
-            if old_name != self.new_name and old_name in mapping.param_dict:
-                mapping.param_dict[self.new_name] = mapping.param_dict[old_name]
-                del mapping.param_dict[old_name]
-
-
 class _ChangeCallsInModule(object):
 
     def __init__(self, pycore, occurrence_finder, resource, call_changer):
             begin_parens =  self.source.index('(', end)
             end_parens = self._find_end_parens(self.source, begin_parens)
             if occurrence.is_called():
+                primary, pyname = occurrence.get_primary_and_pyname()
                 changed_call = self.call_changer.change_call(
-                    self.source[start:end_parens])
+                    primary, pyname, self.source[start:end_parens])
             else:
                 changed_call = self.call_changer.change_definition(
                     self.source[start:end_parens])

rope/refactor/functionutils.py

 class CallInfo(object):
 
     def __init__(self, function_name, args, keywords, args_arg,
-                 keywords_arg, is_method_call):
+                 keywords_arg, is_method_call, is_constructor):
         self.function_name = function_name
         self.args = args
         self.keywords = keywords
         self.args_arg = args_arg
         self.keywords_arg = keywords_arg
         self.is_method_call = is_method_call
+        self.is_constructor = is_constructor
 
     def to_string(self):
         function = self.function_name
             function = self.args[0] + '.' + self.function_name
         params = []
         start = 0
-        if self.is_method_call:
+        if self.is_method_call or self.is_constructor:
             start = 1
         if self.args[start:]:
             params.extend(self.args[start:])
         return '%s(%s)' % (function, ', '.join(params))
 
     @staticmethod
-    def read(definition_info, code):
-        info = _FunctionParser(code, definition_info.is_method)
+    def read(primary, pyname, definition_info, code):
+        is_method_call = False
+        is_constructor = False
+        if CallInfo._is_instance(primary) and CallInfo._is_method(pyname):
+            is_method_call = True
+        if CallInfo._is_class(pyname):
+            is_constructor = True
+        info = _FunctionParser(code, is_method_call)
         args, keywords = info.get_parameters()
         args_arg = None
         keywords_arg = None
         if args and args[-1].startswith('*'):
             args_arg = args[-1][1:]
             del args[-1]
+        if is_constructor:
+            args.insert(0, definition_info.args_with_defaults[0][0])
         return CallInfo(info.get_function_name(), args, keywords, args_arg,
-                         keywords_arg, info.is_called_as_a_method())
+                        keywords_arg, is_method_call, is_constructor)
+
+    @staticmethod
+    def _is_instance(pyname):
+        return pyname is not None and \
+               isinstance(pyname.get_object().get_type(),
+                          rope.base.pyobjects.PyClass)
+
+    @staticmethod
+    def _is_method(pyname):
+        return pyname is not None and \
+               isinstance(pyname.get_object(),
+                          rope.base.pyobjects.PyFunction) and \
+               isinstance(pyname.get_object().parent,
+                          rope.base.pyobjects.PyClass)
+
+    @staticmethod
+    def _is_class(pyname):
+        return pyname is not None and \
+               isinstance(pyname.get_object(),
+                          rope.base.pyobjects.PyClass)
+
 
 class ArgumentMapping(object):
 
         args.extend(self.args_arg)
         keywords.extend(self.keyword_args)
         return CallInfo(self.call_info.function_name, args, keywords,
-                         self.call_info.args_arg, self.call_info.keywords_arg,
-                         self.call_info.is_method_call)
+                        self.call_info.args_arg, self.call_info.keywords_arg,
+                        self.call_info.is_method_call, self.call_info.is_constructor)

rope/refactor/inline.py

             returns = self.source[line_start:start].strip() != '' or \
                       self.source[end_parens:line_end].strip() != ''
             indents = sourceutils.get_indents(self.lines, start_line)
+            primary, pyname = occurrence.get_primary_and_pyname()
             definition = self.generator.get_definition(
-                self.source[start:end_parens], returns=returns)
+                primary, pyname, self.source[start:end_parens], returns=returns)
             end = min(line_end + 1, len(self.source))
             change_collector.add_change(
                 line_start, end, sourceutils.fix_indentation(definition, indents))
     def get_function_name(self):
         return self.pyfunction.get_name()
 
-    def get_definition(self, call, returns=False):
+    def get_definition(self, primary, pyname, call, returns=False):
         # caching already calculated definitions
         key = (call, returns)
         if key not in self._calculated_definitions:
-            self._calculated_definitions[key] = self._calculate_definition(call, returns)
+            self._calculated_definitions[key] = self._calculate_definition(
+                primary, pyname, call, returns)
         return self._calculated_definitions[key]
 
-    def _calculate_definition(self, call, returns):
+    def _calculate_definition(self, primary, pyname, call, returns):
         call_info = rope.refactor.functionutils.CallInfo.read(
-            self.definition_info, call)
+            primary, pyname, self.definition_info, call)
         paramdict = self.definition_params
         mapping = rope.refactor.functionutils.ArgumentMapping(self.definition_info,
                                                                call_info)

rope/refactor/occurrences.py

     def get_pyname(self):
         return self.tools.name_finder.get_pyname_at(self.offset)
 
+    def get_primary_and_pyname(self):
+        return self.tools.name_finder.get_primary_and_pyname_at(self.offset)
+
     def is_in_import_statement(self):
         return (self.tools.word_finder.is_from_statement(self.offset) or
                 self.tools.word_finder.is_import_statement(self.offset))
                pyname1.get_object() == pyname2.get_object()
 
 
+class MultipleFinders(object):
+
+    def __init__(self, finders):
+        self.finders = finders
+
+    def find_occurrences(self, resource=None, pymodule=None):
+        all_occurrences = []
+        for finder in self.finders:
+            all_occurrences.extend(finder.find_occurrences(resource, pymodule))
+        all_occurrences.sort(self._cmp_occurrences)
+        return all_occurrences
+
+    def _cmp_occurrences(self, o1, o2):
+        return cmp(o1.get_primary_range(), o2.get_primary_range())
+
+
 class _OccurrenceToolsCreator(object):
 
     def __init__(self, pycore, resource=None, pymodule=None):

rope/ui/editactions.py

 
 
 def execute_command(context):
-    uihelpers.find_item_dialog(FindCommandHandle(context.core),
-                               'Execute Command', 'Matched Commands')
+    uihelpers.find_item_dialog(
+        FindCommandHandle(context.core), 'Execute Command',
+        'Matched Commands', height=10, width=25)
 
 
 core = rope.ui.core.Core.get_core()

rope/ui/refactor.py

             self.param_list.add_entry(
                 _Parameter('**' + self.definition_info.keywords_arg))
 
-        move_up = Tkinter.Button(frame, text='Move Up', width=20,
+        move_up = Tkinter.Button(frame, text='Move Up', width=20, underline=6,
                                  command=lambda: self.param_list.move_up())
-        move_down = Tkinter.Button(frame, text='Move Down', width=20,
+        move_down = Tkinter.Button(frame, text='Move Down', width=20, underline=8,
                                    command=lambda: self.param_list.move_down())
-        remove = Tkinter.Button(frame, text='Remove', width=20,
+        remove = Tkinter.Button(frame, text='Remove', width=20, underline=0,
                                 command=lambda: self._remove())
         add = Tkinter.Button(frame, text='Add New Parameter', width=20,
-                             command=lambda: self._add())
+                             underline=0, command=lambda: self._add())
         self.toplevel.bind('<Alt-r>', lambda event: self._remove())
         self.toplevel.bind('<Alt-a>', lambda event: self._add())
         param_frame.grid(row=0, column=0, rowspan=5)

rope/ui/uihelpers.py

         pass
 
 
-def find_item_dialog(handle, title='Find', matches='Matches'):
+def find_item_dialog(handle, title='Find', matches='Matches',
+                     height=14, width=50):
     toplevel = Tkinter.Toplevel()
     toplevel.title(title)
     find_dialog = Tkinter.Frame(toplevel)
     name_label = Tkinter.Label(toplevel, text='Name')
     name = Tkinter.Entry(toplevel)
     list_handle = _FindListViewAdapter(toplevel, handle)
-    found = EnhancedList(find_dialog, list_handle, matches, get_focus=False)
+    found = EnhancedList(find_dialog, list_handle, matches, get_focus=False,
+                         height=height, width=width)
     def name_changed(event):
         if name.get() == '':
             result = []

ropetest/objectinfertest.py

         a_var = pymod.get_attribute('a_var').get_object()
         self.assertEquals(c_class, a_var.get_type())
 
+    # TODO: Returning a generator for functions that yield unknowns
+    def xxx_test_handling_generator_functions_when_unknown_type_is_yielded(self):
+        self.mod.write('class C(object):\n    pass\ndef f():\n    yield eval("C()")\n'
+                       'a_var = f()\n')
+        pymod = self.pycore.resource_to_pyobject(self.mod)
+        a_var = pymod.get_attribute('a_var').get_object()
+        self.assertTrue(isinstance(a_var.get_type(),
+                                   rope.base.builtins.Generator))
+
 
 class DynamicOITest(unittest.TestCase):
 

ropetest/refactor/change_signature_test.py

             'class B(A):\n    def a_method(self, p1):\n        pass\n',
             self.mod.read())
 
-    # TODO: changing signature for constructors
-    def xxx_test_changing_signature_for_constructors(self):
+    def test_differentiating_class_accesses_from_instance_accesses(self):
+        self.mod.write(
+            'class A(object):\n    def a_func(self, param):\n        pass\n'
+            'a_var = A()\nA.a_func(a_var, param=1)')
+        signature = ChangeSignature(self.project, self.mod,
+                                    self.mod.read().index('a_func') + 1)
+        signature.remove(1).do()
+        self.assertEquals(
+            'class A(object):\n    def a_func(self):\n        pass\n'
+            'a_var = A()\nA.a_func(a_var)', self.mod.read())
+
+    def test_changing_signature_for_constructors(self):
         self.mod.write(
             'class C(object):\n    def __init__(self, p):\n        pass\n'
             'c = C(1)\n')
             'c = C()\n',
             self.mod.read())
 
-    # TODO: changing signature for constructors
-    def xxx_test_changing_signature_for_constructors2(self):
+    def test_changing_signature_for_constructors2(self):
         self.mod.write(
             'class C(object):\n    def __init__(self, p):\n        pass\n'
             'c = C(1)\n')