Commits

Anonymous committed 20721d1

Better formatting after move and inline
Better keys for error and template dialogs

Comments (0)

Files changed (14)

docs/dev/done.txt

 ===========
 
 
+- Saving object data to disk : March 28, 2207
+
+
+- Adding `.ropeproject` folder : Mark 26, 2007
+
+
 - Inlining `staticmethod`\s : March 23, 2007
 
 

docs/dev/issues.txt

 Invalidating Information
 ------------------------
 
-When do we need to recalculate the information?  Currently rope does
+When do we need to recalculate the information? Currently rope does
 not save anything unless an exact object is calculated; in that case
-it will be used forever(That is till project is closed).  The
-problems with this approach includes:
+it will be used until they remain valid.  The problems with this
+approach includes:
 
 * The method might be changed in future.
 
 file we don't want the information held for all the methods contained
 in that module to be lost.
 
+* Adding name to function textual form: ``(function, lineno, name)``
+
 
 Saving Data On Disk
 -------------------
 One is that we can use these information in future sessions.  The
 other is holding all type information in memory takes lots of space.
 
-* Manual DB:
+* ``shelvedb``:
 
-  We can use a simple saving to files approach.  This won't solve the
-  memory problem but it will save the data for future use.  Since
-  files are written once when closing a project and loaded once when
-  saving a project we can use a compression for our data not to take
-  much space.
+  This has been implemented in ``0.5m4``.
 
-  But this approach an be enhanced much.  By holding object
-  information in different files we can save or update files only
-  when needed.  Also we can use `shelve` module.
-
-* Using `sqlite3`:
+* ``sqlite3db``:
 
   This will solve both problems but requires having `sqlite3`.
   Fortunately it is included in standard library since ``2.5``.
 Saving object data requires having a good mechanisms for invalidating
 information and testing.
 
-* More than one rope on a project
-
-
-Better Data Management
-----------------------
-
-Invalidate when:
-
-* When calculating
-
-  * The return value is invalid
-  * Per name value is invalid
-
-* When cleaning up
-
-  * A file is invalid
-  * A scope is invalid
-  * A parameter is invalid
-  * The number of parameters are invalid
-
-* Overwriting rules
-
-  * Overwrite less accurate values
-
-* Matching similar parameters
-
-
-A Common DB Interface
----------------------
-
-::
-
-  class ObjectDB(object):
-
-      def __init__(self, validator):
-          pass
-
-      def get_scope_info(self, file, key):
-          pass
-
-      def validate_file(self, file):
-          pass
-
-      def sync(self):
-          pass
-
-
-  class ScopeInfo(object):
-
-      def get_per_name(self, name):
-          pass
-
-      def save_per_name(self, name, value):
-          pass
-
-      def get_returned(self, parameters):
-          pass
-
-      def get_call_infos(self):
-          pass
-
-      def add_call(self, parameters, returned):
-          pass
-
-
-  class CallInfo(object):
-
-      def get_parameters(self):
-          pass
-
-      def get_returned(self):
-          pass
-
-
-  class Validator(object):
-
-      def is_file_valid(self, file):
-          pass
-
-      def is_scope_valid(self, file, key):
-          pass
-
-      def is_value_valid(self, value):
-          pass
-
-      def get_invalid_value(self):
-          pass
-
-      def is_more_valid(self, new, old):
-          pass
-
 
 Using `sqlite3`
 ---------------
 
 Scope:
 
-=========  ========
-path       key
-=========  ========
-=========  ========
+* path
+* key
 
 
 Call info:
 
-==========  ======  ==========
-scope fk    args    returned
-==========  ======  ==========
-==========  ======  ==========
+* scope fk
+* args
+* returned
 
 
 Per name:
 
-==========  ======  ==========
-scope fk    name    value
-==========  ======  ==========
-==========  ======  ==========
+* scope fk
+* name
+* value
 
 
 Better Concluded Data

docs/dev/stories.txt

 * Open Type; ``C-T``
 
 
-* Saving history across sessions
-
-
 * Handling the return type of ``yield`` keyword
 
 
 * Moving initialization to constructor in local variable to field
 
 
-* Inlining a single occurrence
-
-
 * Performing import actions only on one import statement
 
 
 
 
 * Moving `staticmethod`\s
+
+
+* Saving history across sessions
+
+
+* Inlining a single occurrence

docs/dev/workingon.txt

-Moving CallInfo To Disk
-=======================
+Moving Object Info To Disk
+==========================
 
-- Using a new objectdb interface
-- Updating `shelvedb` to use the new interface
-- Testing dbs
-- Adding `Validator`\s
+- Change method signature changes other files with no changes
+- Better key bindings for template assists
+- Using `Core._report_error()` for no project is open
+- Removing blank lines after inlined
+- Removing blank lines after moved
 
+* Inline fails when there is an arg mismatch
 * `sqlite3db`
-* Renaming `rope.base.oi.callinfo` to `objectinfo`
 * Adding clear objectdb command
-* Multiple ropes on one project; problems for objectdb
-
-* Change method signature changes other files with no changes
-* ``some_path + '/' + file`` might cause extra leading slash
-* Better key bindings for template assists
-* Formatting problems in move refactoring
+* Multiple ropes on one project; problems for objectdb and history
 * Changing method to static method refactoring
 * Recursive SOI; Go where the calls go
-* Adding an option not to collect per name information
 * Evaluate function parameter defaults in staticoi?
 * Adding 'do when unsure' to all refactorings?
 * Profiling before ``0.5rc1`` for occurrence finding and SOI
 * Faster find file in large projects
 * A completing text widget for dialogs; `Completing(name, list, handle)`
 * Goto definition for ``"# comment.\na_var"``
-* Showing properties in quick outline
-* Changing ``C-a C-a`` to move to the first character in the line
-* ``M-a`` and ``M-e``
-* ``C-l`` and ``M-r``
 * Comments should be indented
-* Decide when to use `difflib` in `Editor.set_text` based on the
+* Decide when to use `difflib` in `Editor.set_text()` based on the
   number of changes
 
 Docs:

rope/base/change.py

         self.old_location = resource.path
         self.operations = resource.project.operations
         self.old_resource = resource
-        self.new_resource = None
+        if resource.is_folder():
+            self.new_resource = self.project.get_folder(self.new_location)
+        else:
+            self.new_resource = self.project.get_file(self.new_location)
 
     def do(self):
         self.operations.move(self.old_resource, self.new_location)
-        self.new_resource = self.project.get_resource(self.new_location)
 
     def undo(self):
         self.operations.move(self.new_resource, self.old_location)
     def get_changed_resources(self):
         return [self.resource]
 
+    def _get_child_path(self, parent, name):
+        if parent.path == '':
+            return name
+        else:
+            return parent.path + '/' + name
+
 
 class CreateFolder(CreateResource):
 
     def __init__(self, parent, name):
-        resource = parent.project.get_folder(parent.path + '/' + name)
+        resource = parent.project.get_folder(self._get_child_path(parent, name))
         super(CreateFolder, self).__init__(resource)
 
 
 class CreateFile(CreateResource):
 
     def __init__(self, parent, name):
-        resource = parent.project.get_file(parent.path + '/' + name)
+        resource = parent.project.get_file(self._get_child_path(parent, name))
         super(CreateFile, self).__init__(resource)
 
 

rope/base/evaluate.py

 import compiler
 
 import rope.base.exceptions
+import rope.base.pyobjects
 import rope.base.pynames
-import rope.base.pyobjects
 
 
 class StatementEvaluator(object):

rope/ide/codeassist.py

         templates['main'] = Template("if __name__ == '__main__':\n    ${cursor}\n")
         test_case_template = \
             ('import unittest\n\n\n'
-             'class ${class}(unittest.TestCase):\n\n'
-             '    def setUp(self):\n        super(${class}, self).setUp()\n\n'
-             '    def tearDown(self):\n        super(${class}, self).tearDown()\n\n'
-             '    def test_${aspect1}(self):\n        pass${cursor}\n\n\n'
+             'class ${TestClass}(unittest.TestCase):\n\n'
+             '    def setUp(self):\n        super(${TestClass}, self).setUp()\n\n'
+             '    def tearDown(self):\n        super(${TestClass}, self).tearDown()\n\n'
+             '    def test_trivial_case${cursor}(self):\n        pass\n\n\n'
              'if __name__ == \'__main__\':\n'
              '    unittest.main()\n')
         templates['testcase'] = Template(test_case_template)

rope/refactor/inline.py

         self._change_other_files(changes)
         return changes
 
+    def _get_removed_range(self):
+        scope = self.pyfunction.get_scope()
+        lines = self.pymodule.lines
+        start_line = scope.get_start()
+        start, end = self._get_scope_range()
+        end_line = scope.get_end()
+        for i in range(end_line + 1, lines.length()):
+            if lines.get_line(i).strip() == '':
+                end_line = i
+            else:
+                break
+        end = min(lines.get_line_end(end_line) + 1,
+                  len(self.pymodule.source_code))
+        return (start, end)
+
     def _change_defining_file(self, changes):
-        start_offset, end_offset = self._get_scope_range()
-        result = _InlineFunctionCallsForModule(
-            self.occurrence_finder, self.resource,
-            self.definition_generator,
-            start_offset, end_offset,
-            self._get_method_replacement()).get_changed_module()
+        start_offset, end_offset = self._get_removed_range()
+        handle = _InlineFunctionCallsForModuleHandle(
+            self.pycore, self.resource, self.definition_generator)
+        result = ModuleSkipRenamer(
+            self.occurrence_finder, self.resource, handle, start_offset,
+            end_offset, self._get_method_replacement()).get_changed_module()
         changes.add_change(ChangeContents(self.resource, result))
 
     def _get_method_replacement(self):
         for file in self.pycore.get_python_files():
             if file == self.resource:
                 continue
-            result = _InlineFunctionCallsForModule(
-                self.occurrence_finder, file,
-                self.definition_generator).get_changed_module()
+            handle = _InlineFunctionCallsForModuleHandle(
+                self.pycore, file, self.definition_generator)
+            result = ModuleSkipRenamer(
+                self.occurrence_finder, file, handle).get_changed_module()
             if result is not None:
                 changes.add_change(ChangeContents(file, result))
 
     return joined
 
 
-class _InlineFunctionCallsForModule(object):
-
-    def __init__(self, occurrence_finder, resource, definition_generator,
-                 skip_start=0, skip_end=0, replacement=''):
-        self.pycore = occurrence_finder.pycore
-        self.occurrence_finder = occurrence_finder
-        self.generator = definition_generator
-        self.resource = resource
-        self._pymodule = None
-        self._lines = None
-        self._source = None
-        self.skip_start = skip_start
-        self.skip_end = skip_end
-        self.replacement = replacement
-
-    def get_changed_module(self):
-        change_collector = sourceutils.ChangeCollector(self.source)
-        change_collector.add_change(self.skip_start, self.skip_end,
-                                    self.replacement)
-        for occurrence in self.occurrence_finder.find_occurrences(self.resource):
-            start, end = occurrence.get_primary_range()
-            if self.skip_start <= start < self.skip_end:
-                if occurrence.is_defined():
-                    continue
-                else:
-                    raise rope.base.exceptions.RefactoringError(
-                        'Cannot inline functions that reference themselves')
-            if not occurrence.is_called():
-                raise rope.base.exceptions.RefactoringError(
-                    'Reference to inlining function other than function call'
-                    ' in <file: %s, offset: %d>' % (self.resource.path, start))
-            end_parens = self._find_end_parens(self.source,
-                                               self.source.index('(', end))
-            lineno = self.lines.get_line_number(start)
-            start_line, end_line = codeanalyze.LogicalLineFinder(self.lines).\
-                                   get_logical_line_in(lineno)
-            line_start = self.lines.get_line_start(start_line)
-            line_end = self.lines.get_line_end(end_line)
-            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(
-                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))
-            if returns:
-                name = self.generator.get_returned()
-                if name is None:
-                    name = 'None'
-                change_collector.add_change(
-                    line_end, end, self.source[line_start:start] + name +
-                    self.source[end_parens:end])
-        result = change_collector.get_changed()
-        if result is not None and result != self.source:
-            return result
-
-    def _get_pymodule(self):
-        if self._pymodule is None:
-            self._pymodule = self.pycore.resource_to_pyobject(self.resource)
-        return self._pymodule
-
-    def _get_source(self):
-        if self._source is None:
-            if self.resource is not None:
-                self._source = self.resource.read()
-            else:
-                self._source = self.pymodule.source_code
-        return self._source
-
-    def _get_lines(self):
-        if self._lines is None:
-            self._lines = self.pymodule.lines
-        return self._lines
-
-    def _find_end_parens(self, source, start):
-        index = start
-        open_count = 0
-        while index < len(source):
-            if source[index] == '(':
-                open_count += 1
-            if source[index] == ')':
-                open_count -= 1
-            if open_count == 0:
-                return index + 1
-            index += 1
-        return index
-
-    source = property(_get_source)
-    lines = property(_get_lines)
-    pymodule = property(_get_pymodule)
-
-
 class _DefinitionGenerator(object):
 
     def __init__(self, pycore, pyfunction):
                                              string_pattern + "|" +
                                              return_pattern)
         return cls._return_pattern
+
+
+class _InlineFunctionCallsForModuleHandle(object):
+
+    def __init__(self, pycore, resource, definition_generator):
+        self.pycore = pycore
+        self.generator = definition_generator
+        self.resource = resource
+        self._pymodule = None
+        self._lines = None
+        self._source = None
+
+    def occurred_inside_skip(self, change_collector, occurrence):
+        if not occurrence.is_defined():
+            raise rope.base.exceptions.RefactoringError(
+                'Cannot inline functions that reference themselves')
+
+    def occurred_outside_skip(self, change_collector, occurrence):
+        start, end = occurrence.get_primary_range()
+        if not occurrence.is_called():
+            raise rope.base.exceptions.RefactoringError(
+                'Reference to inlining function other than function call'
+                ' in <file: %s, offset: %d>' % (self.resource.path, start))
+        end_parens = self._find_end_parens(self.source, end - 1)
+        lineno = self.lines.get_line_number(start)
+        start_line, end_line = codeanalyze.LogicalLineFinder(self.lines).\
+                               get_logical_line_in(lineno)
+        line_start = self.lines.get_line_start(start_line)
+        line_end = self.lines.get_line_end(end_line)
+        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(
+            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))
+        if returns:
+            name = self.generator.get_returned()
+            if name is None:
+                name = 'None'
+            change_collector.add_change(
+                line_end, end, self.source[line_start:start] + name +
+                self.source[end_parens:end])
+
+    def _find_end_parens(self, source, offset):
+        finder = codeanalyze.WordRangeFinder(source)
+        return finder.get_word_parens_range(offset)[1]
+
+    def _get_pymodule(self):
+        if self._pymodule is None:
+            self._pymodule = self.pycore.resource_to_pyobject(self.resource)
+        return self._pymodule
+
+    def _get_source(self):
+        if self._source is None:
+            if self.resource is not None:
+                self._source = self.resource.read()
+            else:
+                self._source = self.pymodule.source_code
+        return self._source
+
+    def _get_lines(self):
+        if self._lines is None:
+            self._lines = self.pymodule.lines
+        return self._lines
+
+    source = property(_get_source)
+    lines = property(_get_lines)
+    pymodule = property(_get_pymodule)
+
+
+class ModuleSkipRenamerHandle(object):
+
+    def occurred_outside_skip(self, change_collector, occurrence):
+        pass
+
+    def occurred_inside_skip(self, change_collector, occurrence):
+        pass
+
+
+class ModuleSkipRenamer(object):
+
+    def __init__(self, occurrence_finder, resource, handle=None,
+                 skip_start=0, skip_end=0, replacement=''):
+        self.occurrence_finder = occurrence_finder
+        self.resource = resource
+        self.skip_start = skip_start
+        self.skip_end = skip_end
+        self.replacement = replacement
+        self.handle = handle
+        if self.handle is None:
+            self.handle = ModuleSkipHandle()
+
+    def get_changed_module(self):
+        source = self.resource.read()
+        change_collector = sourceutils.ChangeCollector(source)
+        change_collector.add_change(self.skip_start, self.skip_end,
+                                    self.replacement)
+        for occurrence in self.occurrence_finder.find_occurrences(self.resource):
+            start, end = occurrence.get_primary_range()
+            if self.skip_start <= start < self.skip_end:
+                self.handle.occurred_inside_skip(change_collector, occurrence)
+            else:
+                self.handle.occurred_outside_skip(change_collector, occurrence)
+        result = change_collector.get_changed()
+        if result is not None and result != source:
+            return result

rope/refactor/move.py

 from rope.base import pyobjects, codeanalyze, exceptions, pynames
 from rope.base.change import ChangeSet, ChangeContents, MoveResource
 from rope.refactor import (importutils, rename, occurrences,
-                           sourceutils, functionutils)
+                           sourceutils, functionutils, inline)
 
 
 def create_move(project, resource, offset=None):
                 result = _add_imports_to_module(import_tools, goal_pymodule,
                                                 new_imports)
             changes.add_change(ChangeContents(resource2, result))
-                
+
         changes.add_change(ChangeContents(resource1,
                                           collector1.get_changed()))
         return changes
         finder = occurrences.FilteredFinder(
             self.pycore, self_name, [pymodule.get_attribute(self_name)])
         result = rename.rename_in_module(finder, host, pymodule=pymodule)
+        if result is None:
+            result = body
         return result[result.index('\n') + 1:]
 
     def _get_self_name(self):
                                                      pymodule.get_resource())
         return pymodule, can_select.changed
 
-    def _rename_in_module(self, pymodule, new_name, imports=False):
+    def _rename_in_module(self, pymodule, new_name, imports=False,
+                          skipstart=None, skipend=None, skiptext=''):
         occurrence_finder = occurrences.FilteredFinder(
             self.pycore, self.old_name, [self.old_pyname], imports=imports)
         source = rename.rename_in_module(occurrence_finder, new_name,
             self.pycore.resource_to_pyobject(dest))
 
     def _change_source_module(self, changes, dest):
-        uses_moving = False
-        # Changing occurrences
-        pymodule = self.pycore.resource_to_pyobject(self.source)
-        pymodule, has_changed = self._rename_in_module(
-            pymodule, self._new_name(dest))
-        if has_changed:
-            uses_moving = True
-        source = self._get_moved_moving_source(pymodule)
-        if uses_moving:
+        handle = _ChangeMoveOccurrencesHandle(self._new_name(dest))
+        occurrence_finder = occurrences.FilteredFinder(
+            self.pycore, self.old_name, [self.old_pyname])
+        start, end = self._get_moving_region()
+        renamer = inline.ModuleSkipRenamer(occurrence_finder, self.source,
+                                           handle, start, end)
+        source = renamer.get_changed_module()
+        if handle.occurred:
             pymodule = self.pycore.get_string_module(source, self.source)
             # Adding new import
             source = _add_imports_to_module(self.import_tools, pymodule,
                                             [self._new_import(dest)])
         changes.add_change(ChangeContents(self.source, source))
 
-    def _get_moved_moving_source(self, pymodule):
-        source = pymodule.source_code
-        lines = pymodule.lines
-        scope = self.old_pyname.get_object().get_scope()
-        start = lines.get_line_start(scope.get_start())
-        end = lines.get_line_end(scope.get_end())
-        source = source[:start] + source[end + 1:]
-        return source
-
     def _change_destination_module(self, changes, dest):
         # Changing occurrences
         pymodule = self.pycore.resource_to_pyobject(dest)
         else:
             result = ''
             start = -1
-        result += moving + '\n' + source[start + 1:]
+        result += moving + source[start + 1:]
 
         # Organizing imports
         source = result
         return self.import_tools.get_module_imports(pymodule)
 
     def _get_moving_element(self):
-        lines = self.pycore.resource_to_pyobject(self.source).lines
+        start, end = self._get_moving_region()
+        moving = self.source.read()[start:end]
+        return moving.rstrip() + '\n'
+
+    def _get_moving_region(self):
+        pymodule = self.pycore.resource_to_pyobject(self.source)
+        lines = pymodule.lines
         scope = self.old_pyname.get_object().get_scope()
         start = lines.get_line_start(scope.get_start())
-        end = lines.get_line_end(scope.get_end())
-        moving = self.source.read()[start:end]
-        return moving
+        end_line = scope.get_end()
+        for i in range(end_line + 1, lines.length()):
+            if lines.get_line(i).strip() == '':
+                end_line = i
+            else:
+                break
+        end = min(lines.get_line_end(end_line) + 1, len(pymodule.source_code))
+        return start, end
 
     def _get_used_imports_by_the_moving_element(self):
         pymodule = self.pycore.resource_to_pyobject(self.source)
     for new_import in new_imports:
         module_with_imports.add_import(new_import)
     return module_with_imports.get_changed_source()
+
+
+class _ChangeMoveOccurrencesHandle(object):
+
+    def __init__(self, new_name):
+        self.new_name = new_name
+        self.occurred = False
+
+    def occurred_inside_skip(self, change_collector, occurrence):
+        pass
+
+    def occurred_outside_skip(self, change_collector, occurrence):
+        start, end = occurrence.get_primary_range()
+        change_collector.add_change(start, end, self.new_name)
+        self.occurred = True

rope/refactor/sourceutils.py

         if not self.changes:
             return None
         self.changes.sort()
-        result = []
+        pieces = []
         last_changed = 0
         for change in self.changes:
             start, end, text = change
-            result.append(self.text[last_changed:start] + text)
+            pieces.append(self.text[last_changed:start] + text)
             last_changed = end
         if last_changed < len(self.text):
-            result.append(self.text[last_changed:])
-        return ''.join(result)
+            pieces.append(self.text[last_changed:])
+        result = ''.join(pieces)
+        if result != self.text:
+            return result
 
 
 def get_indents(lines, lineno):

rope/ui/actionhelpers.py

 import Tkinter
-import tkMessageBox
 
 import rope.base.project
 
 
 def check_project(core):
     if core.project is rope.base.project.get_no_project():
-        tkMessageBox.showerror(parent=core.root, title='No Open Project',
-                               message='No project is open')
+        core._report_error(message='Open a project first!',
+                           title='No Open Project')
         return False
     return True
 

rope/ui/codeactions.py

     cancel_button = Tkinter.Button(frame, text='Cancel', command=cancel)
     ok_button.grid(row=len(template.variables()) + 1, column=0)
     cancel_button.grid(row=len(template.variables()) + 1, column=1)
+    toplevel.bind('<Escape>', lambda event: cancel())
+    toplevel.bind('<Control-g>', lambda event: cancel())
+    toplevel.bind('<Return>', lambda event: ok())
     frame.grid()
 
 
             return
         toplevel = Toplevel()
         toplevel.title('Closing Project')
-        label = Label(toplevel, text='Which modified editors to save')
+        label = Label(toplevel, text='Which modified editors to save?')
         label.grid(row=0, columnspan=2)
         int_vars = []
         for i, editor in enumerate(modified_editors):
         done_button = Button(toplevel, text='Done', command=done)
         done_button.grid(row=len(int_vars) + 1, column=0)
         cancel_button = Button(toplevel, text='Cancel', command=cancel)
+        toplevel.bind('<Escape>', lambda event: cancel())
+        toplevel.bind('<Control-g>', lambda event: cancel())
+        toplevel.bind('<Return>', lambda event: done())
         cancel_button.grid(row=len(int_vars) + 1, column=1)
 
     def close_project(self):
         if self.last_action is not None:
             self.perform_action(self.last_action)
 
-    def _report_error(self, e):
+    def _report_error(self, message, title='RopeError Was Raised'):
         toplevel = Toplevel()
-        toplevel.title('RopeError Was Raised')
-        label = Label(toplevel, text=str(e))
+        toplevel.title(title)
+        label = Label(toplevel, text=str(message))
         def ok(event=None):
             toplevel.destroy()
             return 'break'

ropetest/objectinfertest.py

         a_var = pymod.get_attribute('a_var').get_object()
         self.assertEquals(c1_class, a_var.get_type())
 
+    def test_not_saving_unknown_function_returns(self):
+        mod2 = self.pycore.create_module(self.project.root, 'mod2')
+        self.mod.write('class C(object):\n    pass\nl = []\nl.append(C())\n')
+        mod2.write('import mod\ndef f():\n    return mod.l.pop()\na_var = f()\n')
+        pymod = self.pycore.resource_to_pyobject(self.mod)
+        pymod2 = self.pycore.resource_to_pyobject(mod2)
+        c_class = pymod.get_attribute('C').get_object()
+        a_var = pymod2.get_attribute('a_var')
+
+        self.pycore.analyze_module(mod2)
+        self.assertNotEquals(c_class, a_var.get_object().get_type())
+
+        self.pycore.analyze_module(self.mod)
+        self.assertEquals(c_class, a_var.get_object().get_type())
+
 
 class DynamicOITest(unittest.TestCase):