Commits

Anonymous committed 35e7435

Basic holding per name information for lists

Comments (0)

Files changed (14)

docs/dev/issues.txt

 Holding Per Name Information
 ============================
 
-We can change our type DB to add this feature.::
+We can change our object DB to add this feature.::
 
   objectdb.add_per_object(scope, name, function_name, parameters)
 
-* Forcing `create_arguments` to pass the implicit object for
-  builtin functions.
 * Not ignoring the first argument for builtin types
 
+This feature has two aspects:
+
+When analyzing a module we should save per name information and when
+getting the object we should use those information.  The main reason
+for implementing this feature is supporting builtin container types.
+They should be untyped and ask `callinfo` about that.
+
+Consider analyzing ``a_list.append(x)``.  We know that we should hold
+information about this call; since `list.append()` function says so
+(We can indicate that by checking if it is an instance of
+`BuiltinFunction` and the method `BuiltinFunction.is_per_object()`
+return `True`).  So when we see this method call we add information
+to our `callinfo` DB the same way as described above.
+
+Now consider the time we need to know what object a container holds.
+We want to find out the type of `a_var` in ``a_var = a_list.pop()``.
+`list.pop()` knows that there might be useful information in our
+`callinfo` so it tries to retrieve these information.  It does so by
+calling `Arguments.get_instance_pyname()`.  Now that we have it, we
+can call `CallInfoManager.get_per_instance(scope, name)`.  This
+requires `list` types not to have explict holding types.
+
+* We do these only for containers with unknowns; not for ``[C()]``
+
 
 Enhancing `CallInfoManager`
 ===========================
 ------------------------
 
 When do we need to recalculate the information?  Currently rope does
-not save anything unless an exact object is calculated in that case
+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:
 
 --------------------
 
 We don't want small changes in source files to invalidate all
-information.  For example if a blank line is inserted at the
-top of a file we don't want the information held for all the
-methods contained in that module to be lost.
+information.  For example if a blank line is inserted at the top of a
+file we don't want the information held for all the methods contained
+in that module to be lost.
+
+
+Using Less Space
+----------------
+
+* Currently we hold absolute path for files inside our project
+* Using foreign keys instead of textual form for params and returned
 
 
 Using A Real Database
 ---------------------
 
 There are two main reasons for saving object information into disk.
-One is that We can use these information in future sessions.  The
+One is that we can use these information in future sessions.  The
 other is holding all type information in memory takes lots of space.
 
 We can take two approaches:
 
 * Manual DB:
 
-  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
+  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.
 information and testing.
 
 
+Generate Element
+================
+
+* Generate variable or field
+* Generate function or method
+* Generate class
+* Generate module
+* Generate package
+
+We should choose a good prefix:
+
+* generate; ``C-c g``
+* generate; ``C-c n``
+* generate; ``C-c 1``
+* new; ``C-c n``
+* make; ``C-c m``
+* set up; ``C-c s``
+* stub; ``C-c s``
+* fix; ``C-c f``
+* create; ``C-c c``
+* build; ``C-c b``
+* produce; ``C-c p``
+* dummy; ``C-c d``
+
+
 Better Concluded Data
 =====================
 
 The main problem is when using star imports, after a module has been
-invalidated its concluded data still exist.  This does not seem to
-be a major memory leak, since these `_ConcludedData`\s are invalidated
+invalidated its concluded data still exist.  This does not seem to be
+a major memory leak, since these `_ConcludedData`\s are invalidated
 and contain `None`.
 
 * Using weak references for holding concluded data
   `ImportedModule` and `ImportedName`\s
 
 The other suggestion here is that currently `ImportedName`\s store
-object information in the concluded data of the importing module
-while they can get concluded data from imported module.  The problem
-happens when the imported module gets deleted.
+object information in the concluded data of the importing module while
+they can get concluded data from imported module.  The problem happens
+when the imported module gets deleted.
 
 Note that concluded attributes are:
 
 =========================
 
 The current implementation for finding occurrences of a `PyName` is to
-test every textual occurrence of that name has the same `PyName`
-or not.  This approach does not work when a name is repeated many
-times in the source.  For example renaming `self`\s are very time
-consuming.
+test every textual occurrence of that name has the same `PyName` or
+not.  This approach does not work when a name is repeated many times
+in the source.  For example renaming `self`\s are very time consuming.
 
 Maybe we can use one of these solutions:
 
 * Limiting the places to search for a name
 
   For example for renaming a parameter we search the body of that
-  function and the keyword arguments passed to functions.  As
-  another example function parameters never appear as attributes
-  and class variables and methods never are referenced directly
-  except in class body.  The search locations include:
+  function and the keyword arguments passed to functions.  As another
+  example function parameters never appear as attributes and class
+  variables and methods never are referenced directly except in class
+  body.  The search locations include:
 
   * module bodies
   * class bodies
 
 * Excluding unimported modules
 
-  This does not seem to be a good solution.  One reason is that
-  the imports might be indirect.  So we have to put a long time
-  for creating import trees.
+  This does not seem to be a good solution.  One reason is that the
+  imports might be indirect.  So we have to put a long time for
+  creating import trees.
 
 Or maybe we can use a strategy object for searching.
 

docs/dev/stories.txt

 * Supporting templates in text modes
 
 
-* Line break algorithm for rest mode; ``M-q``
-
-
 * Supporting modules without source code
 
 

docs/dev/workingon.txt

-Filling
-=======
+Holding Per Name Information
+============================
 
-- End line dots
+- Handling `__getitem__()`
+- Fetching defining scope for fields
+- Handling `__setitem__()`
+- Handling `extend`
 
+* Lots of places that call a function with `get_returned_value()` with
+  no args; Should we remove args' default?
+* For loops call `__iter__` with no args
+* Remove duplication in `SOIVisitor`
+* Remove duplication between `Evaluator` and `SOIVisitor`
+* Using `builtins._AttributeConstructor`
+* `__setslice__`?
+* Removing `BuiltinFunction`; using `PerNameFunction` instead
+* `ObjectArguments` should extend `Arguments`?
+* Moving `Arguments`, `ObjectArguments` and `_CallContext`;
+  `rope.base.args` or `callcontext` module
+* What sins did we make in `staticoi` and `evaluate`
+
+* Handling slices?
+* Refactor `builtins`
+* Adding an option not to collect per name information
+* Better per object data holding in `callinfo`
+* Not overwriting useful per object data in `callinfo`
+* `list`\s
+* `set`\s
+* `dict`\s
+
+* Some `self`\s are unknown!
+
+* Registers for holding places in buffers; names or letters?
+* Handling lists in fill paragraph
 * Evaluate function parameter defaults in staticoi?
-* Enhanced templates
-
-  * Generate method; ``C-c w m``
-  * Assign field in constructor; ``C-c w f``
+* Adding 'do when unsure' to all refactorings?
+* Profiling rope
 
 
 Remaining Small Stories

rope/base/builtins.py

-"""This module trys to support some of the builtin types and
-functions.
-
-"""
+"""This module trys to support builtin types and functions."""
 import __builtin__
 import inspect
 
         return self.builtin.__name__
 
 
+class BuiltinFunction(pyobjects.AbstractFunction):
+
+    def __init__(self, returned=None, function=None, builtin=None):
+        super(BuiltinFunction, self).__init__()
+        self.returned = returned
+        self.function = function
+        self.builtin = builtin
+
+    def get_returned_object(self, args=None):
+        if self.function is not None:
+            return self.function(args)
+        return self.returned
+
+    def get_doc(self):
+        if self.builtin:
+            return self.builtin.__doc__
+
+    def get_name(self):
+        if self.builtin:
+            return self.builtin.__name__
+
+
+class PerNameFunction(BuiltinFunction):
+
+    def __init__(self, returned=None, function=None, builtin=None, argnames=[]):
+        super(PerNameFunction, self).__init__(
+            returned=returned, function=self, builtin=builtin)
+        self.called = function
+        self.argnames = argnames
+
+    def __call__(self, args):
+        if self.called is not None:
+            return self.called(_CallContext(self.argnames, args))
+        else:
+            return self.returned
+
+
+class _CallContext(object):
+
+    def __init__(self, argnames, args):
+        self.argnames = argnames
+        self.args = args
+
+    def _get_scope_and_pyname(self, pyname):
+        if pyname is not None and isinstance(pyname, pynames.AssignedName):
+            pymodule, lineno = pyname.get_definition_location()
+            if pymodule is None or lineno is None:
+                return None, None
+            scope = pymodule.get_scope().get_inner_scope_for_line(lineno)
+            name = None
+            while name is None and scope is not None:
+                for current in scope.get_names():
+                    if scope.get_name(current) is pyname:
+                        name = current
+                        break
+                else:
+                    scope = scope.parent
+            return scope, name
+        return None, None
+
+    def get_argument(self, name):
+        if self.args:
+            args = self.args.get_arguments(self.argnames)
+            return args[self.argnames.index(name)]
+
+    def get_pyname(self, name):
+        if self.args:
+            args = self.args.get_pynames(self.argnames)
+            return args[self.argnames.index(name)]
+
+    def get_arguments(self, argnames):
+        if self.args:
+            return self.args.get_arguments(argnames)
+
+    def get_per_name(self):
+        if self.args is None:
+            return None
+        pyname = self.args.get_instance_pyname()
+        scope, name = self._get_scope_and_pyname(pyname)
+        if name is not None:
+            pymodule = pyname.get_definition_location()[0]
+            return pymodule.pycore.call_info.get_per_name(scope, name)
+        return None
+
+    def save_per_name(self, value):
+        if self.args is None:
+            return None
+        pyname = self.args.get_instance_pyname()
+        scope, name = self._get_scope_and_pyname(pyname)
+        if name is not None:
+            pymodule = pyname.get_definition_location()[0]
+            pymodule.pycore.call_info.save_per_name(scope, name, value)
+
+
+class _AttributeCollector(object):
+
+    def __init__(self, type):
+        self.attributes = {}
+        self.type = type
+
+    def __call__(self, name, returned=None, function=None, argnames=['self']):
+        self.attributes[name] = BuiltinName(
+            PerNameFunction(returned=returned, function=function,
+                            argnames=argnames,
+                            builtin=getattr(self.type, name)))
+
+    def __setitem__(self, name, value):
+        self.attributes[name] = value
+
+
 class List(BuiltinClass):
 
     def __init__(self, holding=None):
         self.holding = holding
-        attributes = {}
-        def add(name, returned=None, function=None):
-            attributes[name] = BuiltinName(
-                BuiltinFunction(returned=returned, function=function,
-                                builtin=getattr(list, name)))
+        collector = _AttributeCollector(list)
 
-        add('__getitem__', self.holding)
-        add('__getslice__', pyobjects.PyObject(self))
-        add('pop', self.holding)
-        add('__iter__', Iterator(self.holding))
-        add('__new__', function=self._new_list)
-        for method in ['append', 'count', 'extend', 'index', 'insert',
-                       'remove', 'reverse', 'sort']:
-            add(method)
-        super(List, self).__init__(list, attributes)
+        collector('__iter__', function=self._iterator_get)
+        collector('__new__', function=self._new_list)
+        for method in ['count', 'index', 'remove', 'reverse', 'sort']:
+            collector(method)
+
+        # Adding methods
+        collector('append', function=self._list_add, argnames=['self', 'value'])
+        collector('__setitem__', function=self._list_add,
+                  argnames=['self', 'index', 'value'])
+        collector('insert', function=self._list_add,
+                  argnames=['self', 'index', 'value'])
+        collector('extend', function=self._self_set, 
+                  argnames=['self', 'iterable'])
+
+        # Getting methods
+        collector('__getitem__', function=self._list_get)
+        collector('pop', function=self._list_get)
+        collector('__getslice__', function=self._self_get)
+
+        super(List, self).__init__(list, collector.attributes)
 
     def _new_list(self, args):
         return _create_builtin(args, get_list)
 
+    def _list_add(self, context):
+        if self.holding is not None:
+            return
+        holding = context.get_argument('value')
+        if holding is not None and holding != pyobjects.get_unknown():
+            context.save_per_name(holding)
+
+    def _self_set(self, context):
+        if self.holding is not None:
+            return
+        iterable = context.get_pyname('iterable')
+        holding = _infer_sequence_for_pyname(iterable)
+        if holding is not None and holding != pyobjects.get_unknown():
+            context.save_per_name(holding)
+
+    def _list_get(self, context):
+        if self.holding is not None:
+            return self.holding
+        return context.get_per_name()
+
+    def _iterator_get(self, context):
+        holding = self.holding
+        if holding is None:
+            holding = context.get_per_name()
+        return Iterator(holding)
+
+    def _self_get(self, context):
+        holding = self.holding
+        if holding is None:
+            holding = context.get_per_name()
+        return get_list(holding)
+
 
 get_list = _create_builtin_getter(List)
 get_list_type = _create_builtin_type_getter(List)
     def get_definition_location(self):
         return (None, None)
 
-class BuiltinFunction(pyobjects.AbstractFunction):
-
-    def __init__(self, returned=None, function=None, builtin=None):
-        super(BuiltinFunction, self).__init__()
-        self.returned = returned
-        self.function = function
-        self.builtin = builtin
-
-    def get_returned_object(self, args=None):
-        if self.function is not None:
-            return self.function(args)
-        return self.returned
-
-    def get_doc(self):
-        if self.builtin:
-            return self.builtin.__doc__
-
-    def get_name(self):
-        if self.builtin:
-            return self.builtin.__name__
-
-
 class Iterator(pyobjects.AbstractClass):
 
     def __init__(self, holding=None):
                       get_returned_object()
             return holding
 
+def _infer_sequence_for_pyname(pyname):
+    if pyname is None:
+        return None
+    seq = pyname.get_object()
+    args = evaluate.ObjectArguments(pyname, [])
+    if '__iter__' in seq.get_attributes():
+        iter = seq.get_attribute('__iter__').get_object().\
+               get_returned_object(args)
+        if iter is not None and 'next' in iter.get_attributes():
+            holding = iter.get_attribute('next').get_object().\
+                      get_returned_object(args)
+            return holding
+
 
 def _create_builtin(args, creator):
     passed = args.get_arguments(['sequence'])[0]
     if passed_self is None:
         return passed_class
     else:
-        pyclass = passed_self.get_type()
+        #pyclass = passed_self.get_type()
+        pyclass = passed_class
         if isinstance(pyclass, pyobjects.AbstractClass):
             supers = pyclass.get_superclasses()
             if supers:

rope/base/codeanalyze.py

             ast = compiler.parse('(%s)' % name)
         except SyntaxError:
             raise BadIdentifierError('Not a python identifier selected.')
-        evaluator = evaluate.StatementEvaluator(holding_scope)
-        compiler.walk(ast, evaluator)
-        return evaluator.old_result, evaluator.result
+        return evaluate.get_primary_and_result(holding_scope, ast)
 
 
 class BadIdentifierError(rope.base.exceptions.RopeError):

rope/base/evaluate.py

                 self.result = None
 
     def visitCallFunc(self, node):
-        pyobject = self._get_object_for_node(node.node)
+        primary, pyobject = self._get_primary_and_object_for_node(node.node)
         if pyobject is None:
             return
         def _get_returned(pyobject):
-            args = create_arguments(pyobject, node, self.scope)
+            args = create_arguments(primary, pyobject, node, self.scope)
             return pyobject.get_returned_object(args)
         if isinstance(pyobject, rope.base.pyobjects.AbstractClass):
             result = None
             pyobject = pyname.get_object()
         return pyobject
 
+    def _get_primary_and_object_for_node(self, stmt):
+        primary, pyname = get_primary_and_result(self.scope, stmt)
+        pyobject = None
+        if pyname is not None:
+            pyobject = pyname.get_object()
+        return primary, pyobject
+
     def visitSubscript(self, node):
-        self._call_function(node.expr, '__getitem__')
+        self._call_function(node.expr, '__getitem__', node.subs)
 
-    def _call_function(self, node, function_name):
-        pyobject = self._get_object_for_node(node)
-        if pyobject is None:
+    def _call_function(self, node, function_name, other_args=None):
+        pyname = get_statement_result(self.scope, node)
+        if pyname is not None:
+            pyobject = pyname.get_object()
+        else:
             return
         if function_name in pyobject.get_attributes():
-            call_function = pyobject.get_attribute(function_name)
+            call_function = pyobject.get_attribute(function_name).get_object()
+            args = [node]
+            if other_args:
+                args += other_args
+            arguments = Arguments(args, self.scope)
             self.result = rope.base.pynames.UnboundName(
-                pyobject=call_function.get_object().get_returned_object())
+                pyobject=call_function.get_returned_object(arguments))
 
     def visitLambda(self, node):
         self.result = rope.base.pynames.UnboundName(
     Returns `None` if the expression cannot be evaluated.
 
     """
+    return get_primary_and_result(scope, node)[1]
+
+
+def get_primary_and_result(scope, node):
     evaluator = StatementEvaluator(scope)
     compiler.walk(node, evaluator)
-    return evaluator.result
+    return evaluator.old_result, evaluator.result
 
 
 def get_string_result(scope, string):
 class Arguments(object):
     """A class for evaluating parameters passed to a function
 
-    Always use the `create_argument` factory.  It handles when the
+    You can use the `create_argument` factory.  It handles when the
     first argument is implicit
 
     """
         self.scope = scope
 
     def get_arguments(self, parameters):
+        result = []
+        for pyname in self.get_pynames(parameters):
+            if pyname is None:
+                result.append(None)
+            else:
+                result.append(pyname.get_object())
+        return result
+
+    def get_pynames(self, parameters):
         result = [None] * max(len(parameters), len(self.args))
         for index, arg in enumerate(self.args):
             if isinstance(arg, compiler.ast.Keyword) and arg.name in parameters:
-                pyname = self._evaluate(arg.expr)
-                if pyname is not None:
-                    result[parameters.index(arg.name)] = pyname.get_object()
+                result[parameters.index(arg.name)] = self._evaluate(arg.expr)
             else:
-                pyname = self._evaluate(arg)
-                if pyname is not None:
-                    result[index] = pyname.get_object()
+                result[index] = self._evaluate(arg)
         return result
 
+    def get_instance_pyname(self):
+        if self.args:
+            return self._evaluate(self.args[0])
+
     def _evaluate(self, ast_node):
         return get_statement_result(self.scope, ast_node)
 
 
-def create_arguments(pyfunction, call_func_node, scope):
+class ObjectArguments(object):
+
+    def __init__(self, pyname, args):
+        self.pyname = pyname
+        self.args = args
+
+    def get_arguments(self, parameters):
+        result = list(self.args)
+        if self.pyname:
+            result.insert(0, self.pyname.get_object())
+        return result
+
+    def get_instance_pyname(self):
+        return self.pyname
+
+
+def create_arguments(primary, pyfunction, call_func_node, scope):
     """A factory for creating `Arguments`'"""
     args = call_func_node.args
     called = call_func_node.node
-    if isinstance(pyfunction, rope.base.pyobjects.PyFunction) and \
-       isinstance(pyfunction.parent, rope.base.pyobjects.PyClass) and \
-       isinstance(called, compiler.ast.Getattr):
+    # XXX: Handle constructors
+    if _is_method_call(primary, pyfunction):
         args = list(args)
         args.insert(0, called.expr)
     return Arguments(args, scope)
+
+
+def _is_method_call(primary, pyfunction):
+    if primary is None:
+        return False
+    pyobject = primary.get_object()
+    if isinstance(pyobject.get_type(), rope.base.pyobjects.PyClass) and \
+       isinstance(pyfunction, rope.base.pyobjects.PyFunction) and \
+       isinstance(pyfunction.parent, rope.base.pyobjects.PyClass):
+        return True
+    if isinstance(pyobject.get_type(), rope.base.pyobjects.AbstractClass) and \
+       isinstance(pyfunction, rope.base.builtins.BuiltinFunction):
+        return True
+    return False

rope/base/oi/callinfo.py

         self.files = {}
         self.to_textual = _PyObjectToTextual(pycore.project)
         self.to_pyobject = _TextualToPyObject(pycore.project)
+        self.per_object = {}
 
     def get_returned(self, pyobject, args):
         organizer = self.find_organizer(pyobject)
             returned_text = self.to_textual.transform(returned)
         self._save_data(function_text, params_text, returned_text)
 
+    def save_per_name(self, scope, name, data):
+        key = (self.to_textual.transform(scope.pyobject), name)
+        self.per_object[key] = self.to_textual.transform(data)
+
+    def get_per_name(self, scope, name):
+        key = (self.to_textual.transform(scope.pyobject), name)
+        data = self.per_object.get(key, ('unknown',))
+        return self.to_pyobject.transform(data)
+
     def _save_data(self, function, args, returned=('unknown',)):
         path = function[1]
         lineno = function[2]

rope/base/oi/objectinfer.py

         except pyobjects.IsBeingInferredError:
             pass
 
-    def evaluate_object(self, pyname):
+    def evaluate_object(self, evaluated):
         pyobject = self._infer_pyobject_for_assign_node(
-            pyname.assignment.ast_node, pyname.module, pyname.lineno)
-        tokens = pyname.evaluation.split('.')
+            evaluated.assignment.ast_node, evaluated.module, evaluated.lineno)
+        pyname = evaluated
+        tokens = evaluated.evaluation.split('.')
         for token in tokens:
             call = token.endswith('()')
             if call:
                 token = token[:-2]
             if token:
-                pyobject = self._get_attribute(pyobject, token)
+                pyname2 = self._get_attribute(pyobject, token)
+                if pyname2 is not None:
+                    pyobject = pyname2.get_object()
             if pyobject is not None and call:
-                pyobject = pyobject.get_returned_object()
+                args = evaluate.ObjectArguments(pyname, [])
+                pyobject = pyobject.get_returned_object(args)
+                pyname = None
             if pyobject is None:
                 break
-        if pyname is None:
+        if evaluated is None:
             return pyobject
-        return self._infer_assignment_object(pyname.assignment, pyobject)
+        return self._infer_assignment_object(evaluated.assignment, pyobject)
 
     def _get_attribute(self, pyobject, name):
         if pyobject is not None and name in pyobject.get_attributes():
-            return pyobject.get_attribute(name).get_object()
+            return pyobject.get_attribute(name)

rope/base/oi/staticoi.py

 import compiler.consts
 
 import rope.base
-from rope.base import pyobjects, evaluate, builtins
+from rope.base import pyobjects, pynames, evaluate, builtins
 
 
 class StaticObjectInference(object):
 
 
 class SOIVisitor(object):
-    
+
     def __init__(self, pycore, pymodule):
         self.pycore = pycore
         self.pymodule = pymodule
         for child in node.getChildNodes():
             compiler.walk(child, self)
         scope = self.scope.get_inner_scope_for_line(node.lineno)
-        pyname = evaluate.get_statement_result(scope, node.node)
+        primary, pyname = evaluate.get_primary_and_result(scope, node.node)
         if pyname is None:
             return
         pyfunction = pyname.get_object()
         if '__call__' in pyfunction.get_attributes():
             pyfunction = pyfunction.get_attribute('__call__')
-        if not isinstance(pyfunction, pyobjects.PyFunction):
+        if not isinstance(pyfunction, pyobjects.AbstractFunction):
             return
-        args = evaluate.create_arguments(pyfunction, node, scope)
-        self.pycore.call_info.function_called(
-            pyfunction, args.get_arguments(pyfunction.get_param_names()))
+        args = evaluate.create_arguments(primary, pyfunction, node, scope)
+        self._call(pyfunction, args)
+
+    def _call(self, pyfunction, args):
+        if isinstance(pyfunction, pyobjects.PyFunction):
+            self.pycore.call_info.function_called(
+                pyfunction, args.get_arguments(pyfunction.get_param_names()))
+        # XXX: Maybe we should not call every builtin function
+        if isinstance(pyfunction, builtins.BuiltinFunction):
+            pyfunction.get_returned_object(args)
+
+    def visitAssign(self, node):
+        visitor = _SOIAssignVisitor()
+        nodes = []
+        for child in node.nodes:
+            compiler.walk(child, visitor)
+            nodes.extend(visitor.nodes)
+        for assigned, levels in nodes:
+            scope = self.scope.get_inner_scope_for_line(node.lineno)
+            instance = evaluate.get_statement_result(scope, assigned.expr)
+            value = self.pycore.object_infer._infer_assignment(
+                pynames._Assigned(node.expr, levels), self.pymodule)
+            if instance is not None and value is not None:
+                pyobject = instance.get_object()
+                if '__setitem__' in pyobject.get_attributes():
+                    pyfunction = pyobject.get_attribute('__setitem__').get_object()
+                    args = evaluate.ObjectArguments(instance, [None, value])
+                    self._call(pyfunction, args)
+
+
+class _SOIAssignVisitor(pyobjects._NodeNameCollector):
+
+    def __init__(self):
+        super(_SOIAssignVisitor, self).__init__()
+        self.nodes = []
+
+    def _added(self, node, levels):
+        if isinstance(node, compiler.ast.Subscript):
+            self.nodes.append((node, levels))

rope/base/pynames.py

     
     def __init__(self, pyobject=None):
         self.pyobject = pyobject
+        if self.pyobject is None:
+            self.pyobject = rope.base.pyobjects.get_unknown()
 
     def get_object(self):
         return self.pyobject

rope/base/pyobjects.py

     def get_param_names(self, special_args=True):
         return []
 
-    def get_returned_object(self):
+    def get_returned_object(self, args=None):
         return get_unknown()
 
 
         self.levels = levels
         self.index = 0
 
-    def _add_name(self, name):
+    def _add_node(self, node):
         new_levels = []
         if self.levels is not None:
             new_levels = list(self.levels)
             new_levels.append(self.index)
         self.index += 1
-        if name is not None:
-            self.names.append((name, new_levels))
+        self._added(node, new_levels)
+
+    def _added(self, node, levels):
+        if hasattr(node, 'name'):
+            self.names.append((node.name, levels))
 
     def visitAssName(self, node):
-        self._add_name(node.name)
+        self._add_node(node)
 
     def visitName(self, node):
-        self._add_name(node.name)
+        self._add_node(node)
 
     def visitTuple(self, node):
         new_levels = []
         self.visitTuple(node)
 
     def visitAssAttr(self, node):
-        self._add_name(None)
+        self._add_node(node)
 
     def visitSubscript(self, node):
-        self._add_name(None)
+        self._add_node(node)
 
     def visitSlice(self, node):
-        self._add_name(None)
+        self._add_node(node)
 
     @staticmethod
     def get_assigned_names(node):

rope/ui/editactions.py

 actions.append(SimpleAction('swap_mark_and_insert', swap_mark_and_insert, 'C-x C-x',
                             None, ['all']))
 actions.append(SimpleAction('fill_paragraph', FillParagraph(), 'M-q',
-                            None, ['rest', 'other']))
+                            MenuAddress(['Edit', 'Fill Paragraph']), ['rest', 'other']))
 
 actions.append(SimpleAction('undo', undo_editing, 'C-x u',
                             MenuAddress(['Edit', 'Undo Editing'], 'u', 1), ['all']))
 
     def __init__(self, width=70):
         self.width = width
-        self.separators = re.compile('\\S+\\s*')
+        self.word_pattern = re.compile('\\S+\\s*')
 
     def fill(self, text):
         lines = text.splitlines()
         indents = self._find_indents(lines)
         builder = _TextBuilder(self.width, indents)
         for line in lines:
-            for match in self.separators.finditer(line):
+            for match in self.word_pattern.finditer(line):
                 matched = match.group()
                 word = matched.strip()
                 builder.add_word(word)

ropetest/objectinfertest.py

         a_var = pymod2.get_attribute('a_var').get_object()
         self.assertEquals(c2_class, a_var.get_type())
 
-    # TODO: Requires saving per object data
-    def xxx_test_static_oi_for_lists_depending_on_append_function(self):
-        code = 'class C(object):\n    pass\nl = list()\n' \
-               'l.append(C())\na_var = l[0]\n'
-        self.mod.write(code)
-        self.pycore.analyze_module(self.mod)
-        pymod = self.pycore.resource_to_pyobject(self.mod)
-        c_class = pymod.get_attribute('C').get_object()
-        a_var = pymod.get_attribute('a_var').get_object()
-        self.assertEquals(c_class, a_var.get_type())
-
     def test_handling_generator_functions_for_strs(self):
         self.mod.write('class C(object):\n    pass\ndef f(p):\n    yield p()\n'
                        'for c in f(C):\n    a_var = c\n')
         self.assertTrue(isinstance(a_var.get_type(),
                                    rope.base.builtins.Generator))
 
+    def test_static_oi_for_lists_depending_on_append_function(self):
+        code = 'class C(object):\n    pass\nl = list()\n' \
+               'l.append(C())\na_var = l.pop()\n'
+        self.mod.write(code)
+        self.pycore.analyze_module(self.mod)
+        pymod = self.pycore.resource_to_pyobject(self.mod)
+        c_class = pymod.get_attribute('C').get_object()
+        a_var = pymod.get_attribute('a_var').get_object()
+        self.assertEquals(c_class, a_var.get_type())
+
+    def test_static_oi_for_lists_per_object_for_get_item(self):
+        code = 'class C(object):\n    pass\nl = list()\n' \
+               'l.append(C())\na_var = l[0]\n'
+        self.mod.write(code)
+        self.pycore.analyze_module(self.mod)
+        pymod = self.pycore.resource_to_pyobject(self.mod)
+        c_class = pymod.get_attribute('C').get_object()
+        a_var = pymod.get_attribute('a_var').get_object()
+        self.assertEquals(c_class, a_var.get_type())
+
+    def test_static_oi_for_lists_per_object_for_fields(self):
+        code = 'class C(object):\n    pass\n' \
+               'class A(object):\n    def __init__(self):\n        self.l = []\n' \
+               '    def set(self):\n        self.l.append(C())\n' \
+               'a = A()\na.set()\na_var = a.l[0]\n'
+        self.mod.write(code)
+        self.pycore.analyze_module(self.mod)
+        pymod = self.pycore.resource_to_pyobject(self.mod)
+        c_class = pymod.get_attribute('C').get_object()
+        a_var = pymod.get_attribute('a_var').get_object()
+        self.assertEquals(c_class, a_var.get_type())
+
+    def test_static_oi_for_lists_per_object_for_set_item(self):
+        code = 'class C(object):\n    pass\nl = [None]\n' \
+               'l[0] = C()\na_var = l[0]\n'
+        self.mod.write(code)
+        self.pycore.analyze_module(self.mod)
+        pymod = self.pycore.resource_to_pyobject(self.mod)
+        c_class = pymod.get_attribute('C').get_object()
+        a_var = pymod.get_attribute('a_var').get_object()
+        self.assertEquals(c_class, a_var.get_type())
+
+    def test_static_oi_for_lists_per_object_for_extending_lists(self):
+        code = 'class C(object):\n    pass\nl = []\n' \
+               'l.append(C())\nl2 = []\nl2.extend(l)\na_var = l2[0]\n'
+        self.mod.write(code)
+        self.pycore.analyze_module(self.mod)
+        pymod = self.pycore.resource_to_pyobject(self.mod)
+        c_class = pymod.get_attribute('C').get_object()
+        a_var = pymod.get_attribute('a_var').get_object()
+        self.assertEquals(c_class, a_var.get_type())
+
+    # TODO: Handle this case
+    def xxx_test_static_oi_for_lists_per_object_for_iters(self):
+        code = 'class C(object):\n    pass\nl = []\n' \
+               'l.append(C())\nfor c in l:\n    a_var = c\n'
+        self.mod.write(code)
+        self.pycore.analyze_module(self.mod)
+        pymod = self.pycore.resource_to_pyobject(self.mod)
+        c_class = pymod.get_attribute('C').get_object()
+        a_var = pymod.get_attribute('a_var').get_object()
+        self.assertEquals(c_class, a_var.get_type())
+
 
 class DynamicOITest(unittest.TestCase):