Commits

Anonymous committed 523f341

Making module dependency graphs

Comments (0)

Files changed (11)

 Version Overview
 ================
 
-Some of the features added since 0.1 release:
+Features added in this release:
 
-* Auto-completing names
-* Auto-completing attributes of an object with known type
-* Go to definition
-* Renaming local variable
-* Quick outline
-* Proposing templates in code assists
-* reST highlighting for ``.txt`` files
+* Rename modules/packages
+* Reloading changed editors after refactorings
+* Rename class/function
+* Simple function returned object inference
+* Show PyDoc; F2
+* Object inference for chained assignments
 
 
 Getting Started
 Alt-/          Show code assists
 Ctrl-o         Show quick outline
 F3             Go to definition
-Alt-Shift-r    Rename local variable
+Alt-Shift-r    Rename Refactoring
 Alt-Q r        Show project tree
+F2             Show PyDoc
 =============  ======================
 
 You can complete names as you type by using code assist (``M-/``).
 ===========
 
 
+- Making module dependancy graph : August 19, 2006
+
+
 - Rename modules/packages : August 18, 2006
 
 
 for local variables or modules that does not import that name.
 
 
-Cann't We Start Static Type Inference?
-======================================
+Can't We Start Static Type Inference?
+=====================================
 
 I need to think about the problems that might arise for static TI.
 Now I think that static TI is not that hard and in dynamic TI some of
 That is if a name is defined in class body it is an attribute and if
 the name is used it must be looked up in parent scope.
 
-This problem was solved before ``0.3pre1`` release.
+This problem was solved for ``0.3pre1`` release.
 
 
 Renaming modules/packages
    file or folder.
 2. The only part of the problem is after ``from`` import statements.
 
+This was done for ``0.3pre1`` release.
+
 
 Undoing Refactorings
 ====================
 `rope.ui.editor` very large and full of duplication.  These modules
 need many refactorings.  Put more time on these modules.
 
+
+`PyName`\s and `PyObject`\s; Values Or References
+=================================================
+
+Finally I am in a situation in which I should answer this old question
+to go forward.  The problem with current way of doing things is when
+some changes are made to some modules, other modules might have
+imported some older objects from this changed module and problems
+start.  Because the imported objects are no longer valid and fail when
+finding occurances of some object.
+
+Was done for ``0.3pre1``.
+
+
+The Problem
+-----------
+
+Before proposing possible solution lets analyze the situation more
+deeply.  For making the discussion more accurate consider we have two
+modules `mod1` and `mod2` and the latter imports the other.
+
+``mod1.py``::
+  
+  class AClass(object):
+      pass
+
+``mod2.py``::
+
+  import mod1
+  
+  a = mod1.AClass()
+
+Consider after the creation of `PyModule` for `mod2` we change `mod1`:
+
+``mod1.py``::
+  
+  class AClass(object):
+      an_attr = 10
+
+The problems shows up here.  If we want to rename `mod1.AClass`, the
+third line in `mod2` will not be changed because it is not the object
+which should be changed.  Here we should have relied on old data.  But
+if we change `mod1` as:
+
+``mod1.py``::
+
+  AClass = str
+
+We should invalidate old data this time.  So the problem is not only
+using old data, but it is to invalidate the out of date data.
+
+
+Looser Coupling Between `PyModule`\s
+------------------------------------
+
+This is one of the approaches that does not seem applicable.  It says
+we don't import the main objects and we only point to the imported
+objects.  For example we can have something like `ImportedPyName` that
+only holds the module name and the imported name and looks for the real
+objects whenever needed.  Clearly this does not work; The `mod2.a`
+will contain an instance of `mod1.AClass`, but after change it should
+have been an empty string.  That is among module relations it is not
+the imported names only that are concerned.
+
+
+Making Module Dependency Graphs
+-------------------------------
+
+For each module we can hold the modules that depend on it, so that
+if this module is changed all dependant modules would be invalidated.
+One advantage of this approach is that it eliminates the possibility
+of touching invalid objects.
+

docs/workingon.txt

-Clean Up And Refactoring
-========================
+Clean Ups And Refactorings
+==========================
 
 * Updating editor buttons after file has been renamed
 * Showing the initial name in renaming dialogs
 * Goto definition for ``# comment.\na_var``
 * Better editor changing dialog; use uihelpers module
 * Slow `GraphicalEditor._get_offset`
-* PyObject and PyName equality checks; Value objects?
 * Do something for modules that can not be found
 * Dotted not found modules
 * GUI testing redux; make a functests directory?; rename ropetest to unittests?

rope/codeanalyze.py

             if self.source_code[current_offset] in ':,':
                 pass
             else:
-                current_offset = self._find_name_start(current_offset)
+                current_offset = self._find_primary_start(current_offset)
             current_offset = self._find_last_non_space_char(current_offset - 1)
         return current_offset
 
             return self._find_word_start(offset)
         return old_offset
 
-    def _find_name_start(self, offset):
+    def _find_primary_start(self, offset):
         current_offset = offset + 1
         if self.source_code[offset] != '.':
             current_offset = self._find_atom_start(offset)
                     break
         return current_offset
     
-    def get_statement_at(self, offset):
-        return self.source_code[self._find_name_start(offset - 1):
+    def get_primary_at(self, offset):
+        return self.source_code[self._find_primary_start(offset - 1):
                                 self._find_word_end(offset - 1) + 1].strip()
 
-    def get_splitted_statement_before(self, offset):
+    def get_splitted_primary_before(self, offset):
         """returns expression, starting, starting_offset
         
         This function is used in `rope.codeassist.assist` function.
         if offset == 0:
             return ('', '', 0)
         word_start = self._find_atom_start(offset - 1)
-        real_start = self._find_name_start(offset - 1)
+        real_start = self._find_primary_start(offset - 1)
         if self.source_code[word_start:offset].strip() == '':
             word_start = offset
         if self.source_code[real_start:offset].strip() == '':
         return prev_word in ['def', 'class']
     
     def is_from_statement_module(self, offset):
-        stmt_start = self._find_name_start(offset)
+        stmt_start = self._find_primary_start(offset)
         line_start = self._get_line_start(stmt_start)
         prev_word = self.source_code[line_start:stmt_start].strip()
         return prev_word == 'from'
             class_scope = holding_scope
             if lineno == holding_scope.get_start():
                 class_scope = holding_scope.parent
-            name = self.word_finder.get_statement_at(offset).strip()
+            name = self.word_finder.get_primary_at(offset).strip()
             return class_scope.pyobject.get_attributes().get(name, None)
         if self._is_function_name_in_function_header(holding_scope, offset, lineno):
-            name = self.word_finder.get_statement_at(offset).strip()
+            name = self.word_finder.get_primary_at(offset).strip()
             return holding_scope.parent.get_names()[name]
         if self.word_finder.is_from_statement_module(offset):
-            module = self.word_finder.get_statement_at(offset)
+            module = self.word_finder.get_primary_at(offset)
             module_pyobject = self.module_scope.pycore.get_module(module)
             module_pyname = rope.pycore.PyName(module_pyobject, False, 
                                                module=module_pyobject.get_module(),
                                                lineno=1)
             return module_pyname
-        name = self.word_finder.get_statement_at(offset)
+        name = self.word_finder.get_primary_at(offset)
         result = self.get_pyname_in_scope(holding_scope, name)
         return result
     

rope/codeassist.py

         if offset > len(source_code):
             return Proposals()
         word_finder = WordRangeFinder(source_code)
-        expression, starting, starting_offset = word_finder.get_splitted_statement_before(offset)
+        expression, starting, starting_offset = word_finder.get_splitted_primary_before(offset)
         completions = self._get_code_completions(source_code, offset, expression, starting)
         templates = []
         if expression.strip() == '' and starting.strip() != '':
 
     def _invalidate_resource_cache(self, resource):
         if resource in self.module_map:
+            module = self.module_map[resource]
             del self.module_map[resource]
             resource.remove_change_observer(self._invalidate_resource_cache)
+            for dependant in module.dependant_modules:
+                self._invalidate_resource_cache(dependant)
 
     def create_module(self, src_folder, new_module):
         """Creates a module and returns a `rope.project.File`"""

rope/pyobjects.py

         return rope.pyscopes.ClassScope(self.pycore, self)
 
 
-class PyModule(PyDefinedObject):
+class _PyModule(PyDefinedObject):
+    
+    def __init__(self, pycore, ast_node, resource=None):
+        super(_PyModule, self).__init__(PyObject.get_base_type('Module'),
+                                        pycore, ast_node, None)
+        self.dependant_modules = []
+        self.resource = resource
+
+    def _add_dependant(self, pymodule):
+        if pymodule.get_resource():
+            self.dependant_modules.append(pymodule.get_resource())
+
+    def get_resource(self):
+        return self.resource
+    
+
+class PyModule(_PyModule):
 
     def __init__(self, pycore, source_code, resource=None):
         self.source_code = source_code
         ast_node = compiler.parse(source_code)
-        super(PyModule, self).__init__(PyObject.get_base_type('Module'),
-                                       pycore, ast_node, None)
-        self.resource = resource
+        super(PyModule, self).__init__(pycore, ast_node, resource)
 
     def _update_attributes_from_ast(self, attributes):
         visitor = _GlobalVisitor(self.pycore, self)
     def _create_scope(self):
         return rope.pyscopes.GlobalScope(self.pycore, self)
 
-    def get_resource(self):
-        return self.resource
 
-
-class PyPackage(PyDefinedObject):
+class PyPackage(_PyModule):
 
     def __init__(self, pycore, resource=None):
         self.resource = resource
             ast_node = compiler.parse(resource.get_child('__init__.py').read())
         else:
             ast_node = compiler.parse('\n')
-        super(PyPackage, self).__init__(PyObject.get_base_type('Module'), pycore, ast_node, None)
+        super(PyPackage, self).__init__(pycore, ast_node, resource)
 
     def _update_attributes_from_ast(self, attributes):
         if self.resource is None:
                 attributes[name] = PyName(child_pyobject, False, 1,
                                                       child_pyobject)
 
-    def get_resource(self):
-        return self.resource
-    
     def _get_init_dot_py(self):
         if self.resource is not None and self.resource.has_child('__init__.py'):
             return self.resource.get_child('__init__.py')
                 imported = alias
             try:
                 module = self.pycore.get_module(name)
+                module._add_dependant(self.owner_object.get_module())
                 lineno = 1
             except ModuleNotFoundException:
                 self.names[imported] = PyName()
     def visitFrom(self, node):
         try:
             module = self.pycore.get_module(node.modname)
+            module._add_dependant(self.owner_object.get_module())
         except ModuleNotFoundException:
             module = None
 

rope/refactoring.py

         result = []
         module_scope = self.pycore.get_string_scope(source_code)
         word_finder = rope.codeanalyze.WordRangeFinder(source_code)
-        old_name = word_finder.get_statement_at(offset).split('.')[-1]
+        old_name = word_finder.get_primary_at(offset).split('.')[-1]
         pyname_finder = rope.codeanalyze.ScopeNameFinder(source_code, module_scope)
         old_pyname = pyname_finder.get_pyname_at(offset)
         if old_pyname is None:
         module_scope = self.pycore.resource_to_pyobject(resource).get_scope()
         source_code = resource.read()
         word_finder = rope.codeanalyze.WordRangeFinder(source_code)
-        old_name = word_finder.get_statement_at(offset).split('.')[-1]
+        old_name = word_finder.get_primary_at(offset).split('.')[-1]
         pyname_finder = rope.codeanalyze.ScopeNameFinder(source_code, module_scope)
         old_pyname = pyname_finder.get_pyname_at(offset)
         if old_pyname is None:

ropetest/codeanalyzetest.py

 
     def test_inside_parans(self):
         word_finder = WordRangeFinder('a_func(a_var)')
-        self.assertEquals('a_var', word_finder.get_statement_at(10))
+        self.assertEquals('a_var', word_finder.get_primary_at(10))
 
     def test_simple_names(self):
         word_finder = WordRangeFinder('a_var = 10')
-        self.assertEquals('a_var', word_finder.get_statement_at(3))
+        self.assertEquals('a_var', word_finder.get_primary_at(3))
 
     def test_function_calls(self):
         word_finder = WordRangeFinder('sample_function()')
     
     def test_attribute_accesses(self):
         word_finder = WordRangeFinder('a_var.an_attr')
-        self.assertEquals('a_var.an_attr', word_finder.get_statement_at(10))
+        self.assertEquals('a_var.an_attr', word_finder.get_primary_at(10))
     
     def test_strings(self):
         word_finder = WordRangeFinder('"a string".split()')
-        self.assertEquals('"a string".split', word_finder.get_statement_at(14))
+        self.assertEquals('"a string".split', word_finder.get_primary_at(14))
 
     def test_function_calls(self):
         word_finder = WordRangeFinder('file("afile.txt").read()')
         self.assertEquals('file("afile.txt").read',
-                          word_finder.get_statement_at(18))
+                          word_finder.get_primary_at(18))
 
     def test_parens(self):
         word_finder = WordRangeFinder('("afile.txt").split()')
         self.assertEquals('("afile.txt").split',
-                          word_finder.get_statement_at(18))
+                          word_finder.get_primary_at(18))
 
     def test_function_with_no_param(self):
         word_finder = WordRangeFinder('AClass().a_func()')
-        self.assertEquals('AClass().a_func', word_finder.get_statement_at(12))
+        self.assertEquals('AClass().a_func', word_finder.get_primary_at(12))
 
     def test_function_with_multiple_param(self):
         word_finder = WordRangeFinder('AClass(a_param, another_param, "a string").a_func()')
         self.assertEquals('AClass(a_param, another_param, "a string").a_func',
-                          word_finder.get_statement_at(44))
+                          word_finder.get_primary_at(44))
     
     def test_param_expressions(self):
         word_finder = WordRangeFinder('AClass(an_object.an_attr).a_func()')
         self.assertEquals('an_object.an_attr',
-                          word_finder.get_statement_at(20))
+                          word_finder.get_primary_at(20))
 
     def test_string_parens(self):
         word_finder = WordRangeFinder('a_func("(").an_attr')
         self.assertEquals('a_func("(").an_attr',
-                          word_finder.get_statement_at(16))
+                          word_finder.get_primary_at(16))
 
     def test_extra_spaces(self):
         word_finder = WordRangeFinder('a_func  (  "(" ) .   an_attr')
         self.assertEquals('a_func  (  "(" ) .   an_attr',
-                          word_finder.get_statement_at(26))
+                          word_finder.get_primary_at(26))
 
     def test_splitted_statement(self):
         word_finder = WordRangeFinder('an_object.an_attr')
         self.assertEquals(('an_object', 'an_at', 10),
-                          word_finder.get_splitted_statement_before(15))
+                          word_finder.get_splitted_primary_before(15))
 
     def test_empty_splitted_statement(self):
         word_finder = WordRangeFinder('an_attr')
         self.assertEquals(('', 'an_at', 0),
-                          word_finder.get_splitted_statement_before(5))
+                          word_finder.get_splitted_primary_before(5))
 
     def test_empty_splitted_statement2(self):
         word_finder = WordRangeFinder('an_object.')
         self.assertEquals(('an_object', '', 10),
-                          word_finder.get_splitted_statement_before(10))
+                          word_finder.get_splitted_primary_before(10))
 
     def test_empty_splitted_statement3(self):
         word_finder = WordRangeFinder('')
         self.assertEquals(('', '', 0),
-                          word_finder.get_splitted_statement_before(0))
+                          word_finder.get_splitted_primary_before(0))
 
     def test_empty_splitted_statement4(self):
         word_finder = WordRangeFinder('a_var = ')
         self.assertEquals(('', '', 8),
-                          word_finder.get_splitted_statement_before(8))
+                          word_finder.get_splitted_primary_before(8))
 
     def test_operators_inside_parens(self):
         word_finder = WordRangeFinder('(a_var + another_var).reverse()')
         self.assertEquals('(a_var + another_var).reverse',
-                          word_finder.get_statement_at(25))
+                          word_finder.get_primary_at(25))
 
     def test_dictionaries(self):
         word_finder = WordRangeFinder('print {1: "one", 2: "two"}.keys()')
         self.assertEquals('print {1: "one", 2: "two"}.keys',
-                          word_finder.get_statement_at(29))
+                          word_finder.get_primary_at(29))
 
     # TODO: eliminating comments
     def xxx_test_comments_for_finding_statements(self):
         word_finder = WordRangeFinder('# var2 . \n  var3')
         self.assertEquals('var3',
-                          word_finder.get_statement_at(14))
+                          word_finder.get_primary_at(14))
 
     def test_comments_for_finding_statements2(self):
         word_finder = WordRangeFinder('var1 + "# var2".\n  var3')
         self.assertEquals('"# var2".\n  var3',
-                          word_finder.get_statement_at(21))
+                          word_finder.get_primary_at(21))
 
 
 class ScopeNameFinderTest(unittest.TestCase):

ropetest/refactoringtest.py

         self.assertEquals('newpkg/mod1.py', mod1.get_path())
         self.assertEquals('from newpkg.mod1 import a_func\n', mod2.read())
 
-    def test_importing_special_case(self):
-        pkg = self.pycore.create_package(self.project.get_root_folder(), 'pkg')
-        mod1 = self.pycore.create_module(pkg, 'mod1')
-        mod1.write('def a_func():\n    pass\n')
-        mod = self.pycore.create_module(self.project.get_root_folder(), 'mod')
-        mod.write('import pkg.mod1\npkg.mod1.a_func()')
-        self.refactoring.rename(mod, len(mod.read()) - 3, 'new_func')
-        self.assertEquals('def new_func():\n    pass\n', mod1.read())
-        self.assertEquals('import pkg.mod1\npkg.mod1.new_func()', mod.read())
+    def test_module_dependencies(self):
+        mod1 = self.pycore.create_module(self.project.get_root_folder(), 'mod1')
+        mod1.write('class AClass(object):\n    pass\n')
+        mod2 = self.pycore.create_module(self.project.get_root_folder(), 'mod2')
+        mod2.write('import mod1\na_var = mod1.AClass()\n')
+        self.pycore.resource_to_pyobject(mod2).get_attributes()['mod1']
+        mod1.write('def AClass():\n    return 0\n')
+        
+        self.refactoring.rename(mod2, len(mod2.read()) - 3, 'a_func')
+        self.assertEquals('def a_func():\n    return 0\n', mod1.read())
+        self.assertEquals('import mod1\na_var = mod1.a_func()\n', mod2.read())
         
 
 if __name__ == '__main__':