Commits

Henning Schröder committed f06be73

added alternative inference engine

Comments (0)

Files changed (6)

vimftpplugin/README

+From https://github.com/tarmack/vim-python-ftplugin
+
+This software is licensed under the MIT license.
+(c) 2011 Peter Odding <peter@peterodding.com> and Bart Kroon <bart@tarmack.eu>.

vimftpplugin/example.py

+global_var = 42
+var1, var2 = 1, 2
+some_string = ''
+shadowed = ''
+
+class ExampleClass:
+
+  def __init__(self, init_arg, kw=[], kw_indirect=some_string):
+    shadowed = []
+    print global_var, shadowed, kw_indirect, kw
+    if init_arg:
+      def fun_inside_if(): pass
+    elif 1:
+      def fun_inside_elif_1(): pass
+    elif 2:
+      def fun_inside_elif_2(): pass
+    else:
+      def fun_inside_else(): pass
+
+  def a(self, example_argument):
+    for i in xrange(5):
+      print example_argument
+    while True:
+      def inside_while(): pass
+
+  def b(self):
+    def nested():
+      print 5
+    nested()
+
+ExampleClass.b(normal_arg, kw_arg=5, *(1, 2), **{'key': 42})
+ExampleClass.a('')
+
+def newlist():
+  return []
+
+l = newlist()
+
+def newmaps():
+  return set(), dict()
+
+s, d = newmaps()
+
+if True:
+  variant = []
+else:
+  variant = ''
+print variant
+
+def func(blaat):
+  print dir(s2)
+
+func(set())
+func(list())

vimftpplugin/inference.py

+# Type inference engine for the Python file type plug-in for Vim.
+# Authors:
+#  - Peter Odding <peter@peterodding.com>
+#  - Bart Kroon <bart@tarmack.eu>
+# Last Change: October 9, 2011
+# URL: https://github.com/tarmack/vim-python-ftplugin
+
+# TODO Nested yields don't work in Python, are there any nice alternatives? (I miss Lua's coroutines)
+# http://groups.google.com/group/comp.lang.python/browse_frm/thread/fcd2709952d23e34?hl=en&lr=&ie=UTF-8&rnum=9&prev=/&frame=on
+
+import ast
+import collections
+
+DEBUG = False
+LOGFILE = '/tmp/inference.log'
+
+# Mapping of built-ins to result types.
+BUILTINS = {
+    'bool': bool,
+    'int': int,
+    'long': long,
+    'float': float,
+    'str': str,
+    'unicode': unicode,
+    'list': list,
+    'dict': dict,
+    'set': set,
+    'frozenset': frozenset,
+    'object': object,
+    'tuple': tuple,
+    'file': file,
+    'open': file,
+    'len': int,
+    'locals': dict,
+    'max': int,
+    'min': int,
+}
+
+# Mapping of AST types to constructors.
+AST_TYPES = {
+    id(ast.Num): int,
+    id(ast.Str): str,
+    id(ast.List): list,
+    id(ast.ListComp): list,
+    id(ast.Dict): dict,
+}
+
+def log(msg, *args):
+  if DEBUG:
+    with open(LOGFILE, 'a') as handle:
+      handle.write(msg % args + '\n')
+
+def complete_inferred_types():
+  import vim
+  engine = TypeInferenceEngine(vim.eval('source'))
+  line = int(vim.eval('line'))
+  column = int(vim.eval('column'))
+  for name, types in engine.complete(line, column).iteritems():
+    fields = [name]
+    for t in types:
+      fields.append(t.__name__)
+    print '|'.join(fields)
+
+class TypeInferenceEngine:
+
+  def __init__(self, source):
+    self.tree = ast.parse(source)
+    self.link_parents(self.tree)
+
+  def complete(self, line, column):
+    node = self.find_node(line, column)
+    if node:
+      candidates = collections.defaultdict(list)
+      for possible_type in self.evaluate(node):
+        for name in dir(possible_type):
+          candidates[name].append(possible_type)
+      return candidates
+
+  def link_parents(self, node):
+    ''' Decorate the AST with child -> parent references. '''
+    for child in self.get_children(node):
+      self.link_parents(child)
+      child.parent = node
+
+  def get_parents(self, node):
+    ''' Yield all parent nodes of a given AST node. '''
+    while hasattr(node, 'parent'):
+      node = node.parent
+      yield node
+
+  def get_children(self, node):
+    ''' Get the nodes directly contained in a given AST node. '''
+    nodes = []
+    if isinstance(node, ast.If):
+      # Might be nice to exclude "if False:" blocks here? :-)
+      # TODO Take isinstance() into account as a type hint.
+      nodes.append(node.test)
+      nodes.extend(node.body)
+      nodes.extend(node.orelse)
+    elif isinstance(node, ast.Print):
+      nodes.extend(node.values)
+    elif isinstance(node, (ast.Expr, ast.Return)):
+      nodes.append(node.value)
+    elif isinstance(node, (ast.Module, ast.ClassDef)):
+      nodes.extend(node.body)
+    elif isinstance(node, ast.FunctionDef):
+      nodes.extend(node.args.args)
+      nodes.extend(node.args.defaults)
+      if node.args.vararg:
+        nodes.append(node.args.vararg)
+      if node.args.kwarg:
+        nodes.append(node.args.kwarg)
+      nodes.extend(node.body)
+    elif isinstance(node, ast.While):
+      nodes.append(node.test)
+      nodes.extend(node.body)
+      nodes.extend(node.orelse)
+    elif isinstance(node, ast.For):
+      nodes.append(node.target)
+      nodes.append(node.iter)
+      nodes.extend(node.body)
+      nodes.extend(node.orelse)
+    elif isinstance(node, ast.Call):
+      nodes.append(node.func)
+      nodes.extend(node.args)
+      nodes.extend(k.value for k in node.keywords)
+      if node.starargs:
+        nodes.append(node.starargs)
+      if node.kwargs:
+        nodes.append(node.kwargs)
+    elif isinstance(node, (ast.Tuple, ast.List)):
+      nodes.extend(node.elts)
+    elif isinstance(node, ast.Dict):
+      for i in xrange(len(node.values)):
+        nodes.append(node.keys[i])
+        nodes.append(node.values[i])
+    elif isinstance(node, ast.Assign):
+      nodes.extend(node.targets)
+      nodes.append(node.value)
+    elif isinstance(node, ast.Attribute):
+      nodes.append(node.value)
+      #nodes.append(node.attr)
+    elif isinstance(node, ast.Import):
+      #for imp in node.names:
+      #  nodes.append(imp.asname or imp.name)
+      pass
+    elif isinstance(node, ast.ImportFrom):
+      log("ImportFrom: %s", dir(node))
+      node
+    elif isinstance(node, (ast.Num, ast.Str, ast.Name, ast.Pass)):
+      # terminals
+      pass
+    else:
+      assert False, 'Node %s (%s) unsupported' % (node, type(node))
+    return nodes
+
+  def find_node(self, lnum, column):
+    ''' Find the node at the given (line, column) in the AST. '''
+    for node in ast.walk(self.tree):
+      node_id = getattr(node, 'id', '')
+      node_lnum = getattr(node, 'lineno', 0)
+      node_col = getattr(node, 'col_offset', 0)
+      if node_lnum == lnum and node_col <= column <= node_col + len(node_id):
+        return node
+
+  def evaluate(self, node):
+    ''' Resolve an AST expression node to its primitive value(s). '''
+    key = id(type(node))
+    if key in AST_TYPES:
+      # Constructor with special syntax (0, '', [], {}).
+      yield AST_TYPES[key]
+    elif type(node) == type:
+      yield node
+    elif isinstance(node, ast.Call):
+      # Function call.
+      # FIXME node.func can be an ast.Attribute!
+      name = getattr(node.func, 'id', None)
+      if name in BUILTINS:
+        # Call to built-in (int(), str(), len(), max(), etc).
+        yield BUILTINS[name]
+      # Search return type(s) of user defined function(s).
+      for func in self.find_function_definitions(node):
+        for n in ast.walk(func):
+          if isinstance(n, ast.Return):
+            for result in self.evaluate(n.value):
+              yield result
+    elif isinstance(node, (ast.Tuple, ast.List)):
+      if node.elts:
+        for value in node.elts:
+          yield value
+      elif isinstance(node, ast.Tuple):
+        yield tuple
+      elif isinstance(node, ast.List):
+        yield list
+    elif isinstance(node, ast.Name):
+      # Evaluate variable reference.
+      for parent, kind, location in self.resolve(node):
+        if isinstance(parent, ast.Assign):
+          # Variable has been assigned.
+          if len(parent.targets) > 1:
+            # FIXME Prone to breaking on nested structures.. Use a path expression instead!
+            values = self.flatten(parent.value, [])
+          else:
+            values = [parent.value]
+          # Evaluate stand alone function call before unpacking?
+          if len(values) == 1 and isinstance(values[0], ast.Call):
+            values = list(self.evaluate(values[0]))
+          for result in self.evaluate(values[location]):
+            yield result
+        elif kind == 'pos':
+          # Evaluate function argument default value?
+          num_required = len(parent.args.args) - len(parent.args.defaults)
+          if location >= num_required:
+            for result in self.evaluate(parent.args.defaults[location - num_required]):
+              yield result
+          # Check function arguments at potential call sites.
+          for call in self.find_function_calls(parent):
+            for result in self.evaluate(call.args[location]):
+              yield result
+        elif kind == 'var':
+          yield tuple
+        elif kind == 'kw':
+          yield dict
+        else:
+          assert False
+
+  def find_function_calls(self, node):
+    ''' Yield the function/method calls that might be related to a node. '''
+    assert isinstance(node, ast.FunctionDef)
+    for n in ast.walk(self.tree):
+      if isinstance(n, ast.Call) and self.call_to_name(n) == node.name:
+        yield n
+
+  def find_function_definitions(self, node):
+    ''' Yield the function definitions that might be related to a node. '''
+    assert isinstance(node, ast.Call)
+    name = self.call_to_name(node)
+    for n in ast.walk(self.tree):
+      if isinstance(n, ast.FunctionDef) and n.name == name:
+        yield n
+
+  def call_to_name(self, node):
+    ''' Translate a call to a function/method name. '''
+    assert isinstance(node, ast.Call)
+    if isinstance(node.func, ast.Name):
+      return node.func.id
+    elif isinstance(node.func, ast.Attribute):
+      return node.func.attr
+    else:
+      assert False
+
+  def resolve(self, node):
+    ''' Resolve an ast.Name node to its definition(s). '''
+    # TODO Class and function definitions are assignments as well!
+    # TODO Import statements are assignments as well!
+    sources = set()
+    assert isinstance(node, ast.Name)
+    for parent in self.get_parents(node):
+      # Search for variable assignments in the current scope?
+      if isinstance(parent, (ast.Module, ast.FunctionDef)):
+        for n in ast.walk(parent):
+          if isinstance(n, ast.Assign):
+            for i, target in enumerate(self.flatten(n.targets, [])):
+              if target.id == node.id:
+                sources.add((n, 'asn', i))
+      # Search function arguments?
+      if isinstance(parent, ast.FunctionDef):
+        # Check the named positional arguments.
+        for i, argument in enumerate(parent.args.args):
+          if argument.id == node.id:
+            sources.add((parent, 'pos', i))
+        # Check the tuple with other positional arguments.
+        if parent.args.vararg and parent.args.vararg.id == node.id:
+          sources.add((parent, 'var', parent.args.vararg))
+        # Check the dictionary with other keyword arguments.
+        if parent.args.kwarg and parent.args.kwarg.id == node.id:
+          sources.add(parent, 'kw', parent.args.kwarg)
+    return sources
+
+  def flatten(self, nested, flat):
+    ''' Squash a nested sequence into a flat list of nodes. '''
+    if isinstance(nested, (ast.Tuple, ast.List)):
+      for node in nested.elts:
+        self.flatten(node, flat)
+    elif isinstance(nested, (tuple, list)):
+      for node in nested:
+        self.flatten(node, flat)
+    else:
+      flat.append(nested)
+    return flat
+
+  ########################
+  ## Debugging helpers. ##
+  ########################
+
+  def dump(self, node, level=0):
+    ''' Print an AST subtree as a multi line indented string. '''
+    if isinstance(node, list):
+        for n in node:
+            self.dump(n, level)
+        return
+    if not isinstance(node, ast.Expr):
+      print '  ' * level + '(' + type(node).__name__ + ') ' + self.format(node)
+      level += 1
+    for child in self.get_children(node):
+      self.dump(child, level)
+
+  def format(self, node):
+    ''' Format an AST node as a string. '''
+    # Blocks.
+    if isinstance(node, ast.Module):
+      return 'module'
+    elif isinstance(node, ast.ClassDef):
+      return 'class ' + node.name
+    elif isinstance(node, ast.FunctionDef):
+      return 'function ' + node.name
+    elif isinstance(node, ast.For):
+      return 'for'
+    elif isinstance(node, ast.While):
+      return 'while'
+    elif isinstance(node, ast.If):
+      return 'if'
+    elif isinstance(node, ast.Pass):
+      return 'pass'
+    elif isinstance(node, ast.Print):
+      return 'print'
+    elif isinstance(node, ast.Assign):
+      return '%s=%s' % (', '.join(self.format(t) for t in node.targets), self.format(node.value))
+    # Expressions.
+    elif isinstance(node, ast.Call):
+      args = [self.format(a) for a in node.args]
+      for keyword in node.keywords:
+        args.append(keyword.arg + '=' + self.format(keyword.value))
+      if node.starargs:
+        args.append('*' + self.format(node.starargs))
+      if node.kwargs:
+        args.append('**' + self.format(node.kwargs))
+      name = self.format(node.func)
+      return 'call %s(%s)' % (name, ', '.join(args))
+    elif isinstance(node, ast.Tuple):
+      elts = ', '.join(self.format(e) for e in node.elts)
+      return '(' + elts + ')'
+    elif isinstance(node, ast.List):
+      elts = ', '.join(self.format(e) for e in node.elts)
+      return '[' + elts + ']'
+    elif isinstance(node, ast.Dict):
+      pairs = []
+      for i in xrange(len(node.values)):
+        key = self.format(node.keys[i])
+        value = self.format(node.values[i])
+        pairs.append(key + ': ' + value)
+      return '{' + ', '.join(pairs) + '}'
+    elif isinstance(node, ast.Name):
+      return node.id
+    elif isinstance(node, ast.Attribute):
+      return self.format(node.value) + '.' + self.format(node.attr)
+    elif isinstance(node, ast.Num):
+      return str(node.n)
+    elif isinstance(node, ast.Str):
+      return "'%s'" % node.s
+    elif isinstance(node, ast.Expr):
+      return self.format(node.value)
+    elif isinstance(node, (str, unicode)):
+      return node
+    elif isinstance(node, collections.Iterable):
+      values = ', '.join(self.format(v) for v in node)
+      if isinstance(node, tuple): values = '(%s)' % values
+      if isinstance(node, list): values = '[%s]' % values
+      return values
+    else:
+      return str(node)

vimftpplugin/other.py

+# from https://github.com/vim-scripts/pythoncomplete/tree/master/ftplugin
+import sys, tokenize, cStringIO, types
+from token import NAME, DEDENT, NEWLINE, STRING
+
+debugstmts=[]
+def dbg(s): debugstmts.append(s)
+def showdbg():
+    for d in debugstmts: print "DBG: %s " % d
+
+def vimcomplete(context,match):
+    global debugstmts
+    debugstmts = []
+    try:
+        import vim
+        def complsort(x,y):
+            try:
+                xa = x['abbr']
+                ya = y['abbr']
+                if xa[0] == '_':
+                    if xa[1] == '_' and ya[0:2] == '__':
+                        return xa > ya
+                    elif ya[0:2] == '__':
+                        return -1
+                    elif y[0] == '_':
+                        return xa > ya
+                    else:
+                        return 1
+                elif ya[0] == '_':
+                    return -1
+                else:
+                   return xa > ya
+            except:
+                return 0
+        cmpl = Completer()
+        cmpl.evalsource('\n'.join(vim.current.buffer),vim.eval("line('.')"))
+        all = cmpl.get_completions(context,match)
+        all.sort(complsort)
+        dictstr = '['
+        # have to do this for double quoting
+        for cmpl in all:
+            dictstr += '{'
+            for x in cmpl: dictstr += '"%s":"%s",' % (x,cmpl[x])
+            dictstr += '"icase":0},'
+        if dictstr[-1] == ',': dictstr = dictstr[:-1]
+        dictstr += ']'
+        #dbg("dict: %s" % dictstr)
+        vim.command("silent let g:pythoncomplete_completions = %s" % dictstr)
+        #dbg("Completion dict:\n%s" % all)
+    except vim.error:
+        dbg("VIM Error: %s" % vim.error)
+
+class Completer(object):
+    def __init__(self):
+       self.compldict = {}
+       self.parser = PyParser()
+
+    def evalsource(self,text,line=0):
+        sc = self.parser.parse(text,line)
+        src = sc.get_code()
+        dbg("source: %s" % src)
+        try: exec(src) in self.compldict
+        except: dbg("parser: %s, %s" % (sys.exc_info()[0],sys.exc_info()[1]))
+        for l in sc.locals:
+            try: exec(l) in self.compldict
+            except: dbg("locals: %s, %s [%s]" % (sys.exc_info()[0],sys.exc_info()[1],l))
+
+    def _cleanstr(self,doc):
+        return doc.replace('"',' ').replace("'",' ')
+
+    def get_arguments(self,func_obj):
+        def _ctor(obj):
+            try: return class_ob.__init__.im_func
+            except AttributeError:
+                for base in class_ob.__bases__:
+                    rc = _find_constructor(base)
+                    if rc is not None: return rc
+            return None
+
+        arg_offset = 1
+        if type(func_obj) == types.ClassType: func_obj = _ctor(func_obj)
+        elif type(func_obj) == types.MethodType: func_obj = func_obj.im_func
+        else: arg_offset = 0
+        
+        arg_text=''
+        if type(func_obj) in [types.FunctionType, types.LambdaType]:
+            try:
+                cd = func_obj.func_code
+                real_args = cd.co_varnames[arg_offset:cd.co_argcount]
+                defaults = func_obj.func_defaults or ''
+                defaults = map(lambda name: "=%s" % name, defaults)
+                defaults = [""] * (len(real_args)-len(defaults)) + defaults
+                items = map(lambda a,d: a+d, real_args, defaults)
+                if func_obj.func_code.co_flags & 0x4:
+                    items.append("...")
+                if func_obj.func_code.co_flags & 0x8:
+                    items.append("***")
+                arg_text = (','.join(items)) + ')'
+
+            except:
+                dbg("arg completion: %s: %s" % (sys.exc_info()[0],sys.exc_info()[1]))
+                pass
+        if len(arg_text) == 0:
+            # The doc string sometimes contains the function signature
+            #  this works for alot of C modules that are part of the
+            #  standard library
+            doc = func_obj.__doc__
+            if doc:
+                doc = doc.lstrip()
+                pos = doc.find('\n')
+                if pos > 0:
+                    sigline = doc[:pos]
+                    lidx = sigline.find('(')
+                    ridx = sigline.find(')')
+                    if lidx > 0 and ridx > 0:
+                        arg_text = sigline[lidx+1:ridx] + ')'
+        if len(arg_text) == 0: arg_text = ')'
+        return arg_text
+
+    def get_completions(self,context,match):
+        dbg("get_completions('%s','%s')" % (context,match))
+        stmt = ''
+        if context: stmt += str(context)
+        if match: stmt += str(match)
+        try:
+            result = None
+            all = {}
+            ridx = stmt.rfind('.')
+            if len(stmt) > 0 and stmt[-1] == '(':
+                result = eval(_sanitize(stmt[:-1]), self.compldict)
+                doc = result.__doc__
+                if doc is None: doc = ''
+                args = self.get_arguments(result)
+                return [{'word':self._cleanstr(args),'info':self._cleanstr(doc)}]
+            elif ridx == -1:
+                match = stmt
+                all = self.compldict
+            else:
+                match = stmt[ridx+1:]
+                stmt = _sanitize(stmt[:ridx])
+                result = eval(stmt, self.compldict)
+                all = dir(result)
+
+            dbg("completing: stmt:%s" % stmt)
+            completions = []
+
+            try: maindoc = result.__doc__
+            except: maindoc = ' '
+            if maindoc is None: maindoc = ' '
+            for m in all:
+                if m == "_PyCmplNoType": continue #this is internal
+                try:
+                    dbg('possible completion: %s' % m)
+                    if m.find(match) == 0:
+                        if result is None: inst = all[m]
+                        else: inst = getattr(result,m)
+                        try: doc = inst.__doc__
+                        except: doc = maindoc
+                        typestr = str(inst)
+                        if doc is None or doc == '': doc = maindoc
+
+                        wrd = m[len(match):]
+                        c = {'word':wrd, 'abbr':m,  'info':self._cleanstr(doc)}
+                        if "function" in typestr:
+                            c['word'] += '('
+                            c['abbr'] += '(' + self._cleanstr(self.get_arguments(inst))
+                        elif "method" in typestr:
+                            c['word'] += '('
+                            c['abbr'] += '(' + self._cleanstr(self.get_arguments(inst))
+                        elif "module" in typestr:
+                            c['word'] += '.'
+                        elif "class" in typestr:
+                            c['word'] += '('
+                            c['abbr'] += '('
+                        completions.append(c)
+                except:
+                    i = sys.exc_info()
+                    dbg("inner completion: %s,%s [stmt='%s']" % (i[0],i[1],stmt))
+            return completions
+        except:
+            i = sys.exc_info()
+            dbg("completion: %s,%s [stmt='%s']" % (i[0],i[1],stmt))
+            return []
+
+class Scope(object):
+    def __init__(self,name,indent,docstr=''):
+        self.subscopes = []
+        self.docstr = docstr
+        self.locals = []
+        self.parent = None
+        self.name = name
+        self.indent = indent
+
+    def add(self,sub):
+        #print 'push scope: [%s@%s]' % (sub.name,sub.indent)
+        sub.parent = self
+        self.subscopes.append(sub)
+        return sub
+
+    def doc(self,str):
+        """ Clean up a docstring """
+        d = str.replace('\n',' ')
+        d = d.replace('\t',' ')
+        while d.find('  ') > -1: d = d.replace('  ',' ')
+        while d[0] in '"\'\t ': d = d[1:]
+        while d[-1] in '"\'\t ': d = d[:-1]
+        dbg("Scope(%s)::docstr = %s" % (self,d))
+        self.docstr = d
+
+    def local(self,loc):
+        self._checkexisting(loc)
+        self.locals.append(loc)
+
+    def copy_decl(self,indent=0):
+        """ Copy a scope's declaration only, at the specified indent level - not local variables """
+        return Scope(self.name,indent,self.docstr)
+
+    def _checkexisting(self,test):
+        "Convienance function... keep out duplicates"
+        if test.find('=') > -1:
+            var = test.split('=')[0].strip()
+            for l in self.locals:
+                if l.find('=') > -1 and var == l.split('=')[0].strip():
+                    self.locals.remove(l)
+
+    def get_code(self):
+        str = ""
+        if len(self.docstr) > 0: str += '"""'+self.docstr+'"""\n'
+        for l in self.locals:
+            if l.startswith('import'): str += l+'\n'
+        str += 'class _PyCmplNoType:\n    def __getattr__(self,name):\n        return None\n'
+        for sub in self.subscopes:
+            str += sub.get_code()
+        for l in self.locals:
+            if not l.startswith('import'): str += l+'\n'
+
+        return str
+
+    def pop(self,indent):
+        #print 'pop scope: [%s] to [%s]' % (self.indent,indent)
+        outer = self
+        while outer.parent != None and outer.indent >= indent:
+            outer = outer.parent
+        return outer
+
+    def currentindent(self):
+        #print 'parse current indent: %s' % self.indent
+        return '    '*self.indent
+
+    def childindent(self):
+        #print 'parse child indent: [%s]' % (self.indent+1)
+        return '    '*(self.indent+1)
+
+class Class(Scope):
+    def __init__(self, name, supers, indent, docstr=''):
+        Scope.__init__(self,name,indent, docstr)
+        self.supers = supers
+    def copy_decl(self,indent=0):
+        c = Class(self.name,self.supers,indent, self.docstr)
+        for s in self.subscopes:
+            c.add(s.copy_decl(indent+1))
+        return c
+    def get_code(self):
+        str = '%sclass %s' % (self.currentindent(),self.name)
+        if len(self.supers) > 0: str += '(%s)' % ','.join(self.supers)
+        str += ':\n'
+        if len(self.docstr) > 0: str += self.childindent()+'"""'+self.docstr+'"""\n'
+        if len(self.subscopes) > 0:
+            for s in self.subscopes: str += s.get_code()
+        else:
+            str += '%spass\n' % self.childindent()
+        return str
+
+
+class Function(Scope):
+    def __init__(self, name, params, indent, docstr=''):
+        Scope.__init__(self,name,indent, docstr)
+        self.params = params
+    def copy_decl(self,indent=0):
+        return Function(self.name,self.params,indent, self.docstr)
+    def get_code(self):
+        str = "%sdef %s(%s):\n" % \
+            (self.currentindent(),self.name,','.join(self.params))
+        if len(self.docstr) > 0: str += self.childindent()+'"""'+self.docstr+'"""\n'
+        str += "%spass\n" % self.childindent()
+        return str
+
+class PyParser:
+    def __init__(self):
+        self.top = Scope('global',0)
+        self.scope = self.top
+
+    def _parsedotname(self,pre=None):
+        #returns (dottedname, nexttoken)
+        name = []
+        if pre is None:
+            tokentype, token, indent = self.next()
+            if tokentype != NAME and token != '*':
+                return ('', token)
+        else: token = pre
+        name.append(token)
+        while True:
+            tokentype, token, indent = self.next()
+            if token != '.': break
+            tokentype, token, indent = self.next()
+            if tokentype != NAME: break
+            name.append(token)
+        return (".".join(name), token)
+
+    def _parseimportlist(self):
+        imports = []
+        while True:
+            name, token = self._parsedotname()
+            if not name: break
+            name2 = ''
+            if token == 'as': name2, token = self._parsedotname()
+            imports.append((name, name2))
+            while token != "," and "\n" not in token:
+                tokentype, token, indent = self.next()
+            if token != ",": break
+        return imports
+
+    def _parenparse(self):
+        name = ''
+        names = []
+        level = 1
+        while True:
+            tokentype, token, indent = self.next()
+            if token in (')', ',') and level == 1:
+                if '=' not in name: name = name.replace(' ', '')
+                names.append(name.strip())
+                name = ''
+            if token == '(':
+                level += 1
+                name += "("
+            elif token == ')':
+                level -= 1
+                if level == 0: break
+                else: name += ")"
+            elif token == ',' and level == 1:
+                pass
+            else:
+                name += "%s " % str(token)
+        return names
+
+    def _parsefunction(self,indent):
+        self.scope=self.scope.pop(indent)
+        tokentype, fname, ind = self.next()
+        if tokentype != NAME: return None
+
+        tokentype, open, ind = self.next()
+        if open != '(': return None
+        params=self._parenparse()
+
+        tokentype, colon, ind = self.next()
+        if colon != ':': return None
+
+        return Function(fname,params,indent)
+
+    def _parseclass(self,indent):
+        self.scope=self.scope.pop(indent)
+        tokentype, cname, ind = self.next()
+        if tokentype != NAME: return None
+
+        super = []
+        tokentype, next, ind = self.next()
+        if next == '(':
+            super=self._parenparse()
+        elif next != ':': return None
+
+        return Class(cname,super,indent)
+
+    def _parseassignment(self):
+        assign=''
+        tokentype, token, indent = self.next()
+        if tokentype == tokenize.STRING or token == 'str':  
+            return '""'
+        elif token == '(' or token == 'tuple':
+            return '()'
+        elif token == '[' or token == 'list':
+            return '[]'
+        elif token == '{' or token == 'dict':
+            return '{}'
+        elif tokentype == tokenize.NUMBER:
+            return '0'
+        elif token == 'open' or token == 'file':
+            return 'file'
+        elif token == 'None':
+            return '_PyCmplNoType()'
+        elif token == 'type':
+            return 'type(_PyCmplNoType)' #only for method resolution
+        else:
+            assign += token
+            level = 0
+            while True:
+                tokentype, token, indent = self.next()
+                if token in ('(','{','['):
+                    level += 1
+                elif token in (']','}',')'):
+                    level -= 1
+                    if level == 0: break
+                elif level == 0:
+                    if token in (';','\n'): break
+                    assign += token
+        return "%s" % assign
+
+    def next(self):
+        type, token, (lineno, indent), end, self.parserline = self.gen.next()
+        if lineno == self.curline:
+            #print 'line found [%s] scope=%s' % (line.replace('\n',''),self.scope.name)
+            self.currentscope = self.scope
+        return (type, token, indent)
+
+    def _adjustvisibility(self):
+        newscope = Scope('result',0)
+        scp = self.currentscope
+        while scp != None:
+            if type(scp) == Function:
+                slice = 0
+                #Handle 'self' params
+                if scp.parent != None and type(scp.parent) == Class:
+                    slice = 1
+                    newscope.local('%s = %s' % (scp.params[0],scp.parent.name))
+                for p in scp.params[slice:]:
+                    i = p.find('=')
+                    if len(p) == 0: continue
+                    pvar = ''
+                    ptype = ''
+                    if i == -1:
+                        pvar = p
+                        ptype = '_PyCmplNoType()'
+                    else:
+                        pvar = p[:i]
+                        ptype = _sanitize(p[i+1:])
+                    if pvar.startswith('**'):
+                        pvar = pvar[2:]
+                        ptype = '{}'
+                    elif pvar.startswith('*'):
+                        pvar = pvar[1:]
+                        ptype = '[]'
+
+                    newscope.local('%s = %s' % (pvar,ptype))
+
+            for s in scp.subscopes:
+                ns = s.copy_decl(0)
+                newscope.add(ns)
+            for l in scp.locals: newscope.local(l)
+            scp = scp.parent
+
+        self.currentscope = newscope
+        return self.currentscope
+
+    #p.parse(vim.current.buffer[:],vim.eval("line('.')"))
+    def parse(self,text,curline=0):
+        self.curline = int(curline)
+        buf = cStringIO.StringIO(''.join(text) + '\n')
+        self.gen = tokenize.generate_tokens(buf.readline)
+        self.currentscope = self.scope
+
+        try:
+            freshscope=True
+            while True:
+                tokentype, token, indent = self.next()
+                #dbg( 'main: token=[%s] indent=[%s]' % (token,indent))
+
+                if tokentype == DEDENT or token == "pass":
+                    self.scope = self.scope.pop(indent)
+                elif token == 'def':
+                    func = self._parsefunction(indent)
+                    if func is None:
+                        print "function: syntax error..."
+                        continue
+                    dbg("new scope: function")
+                    freshscope = True
+                    self.scope = self.scope.add(func)
+                elif token == 'class':
+                    cls = self._parseclass(indent)
+                    if cls is None:
+                        print "class: syntax error..."
+                        continue
+                    freshscope = True
+                    dbg("new scope: class")
+                    self.scope = self.scope.add(cls)
+                    
+                elif token == 'import':
+                    imports = self._parseimportlist()
+                    for mod, alias in imports:
+                        loc = "import %s" % mod
+                        if len(alias) > 0: loc += " as %s" % alias
+                        self.scope.local(loc)
+                    freshscope = False
+                elif token == 'from':
+                    mod, token = self._parsedotname()
+                    if not mod or token != "import":
+                        print "from: syntax error..."
+                        continue
+                    names = self._parseimportlist()
+                    for name, alias in names:
+                        loc = "from %s import %s" % (mod,name)
+                        if len(alias) > 0: loc += " as %s" % alias
+                        self.scope.local(loc)
+                    freshscope = False
+                elif tokentype == STRING:
+                    if freshscope: self.scope.doc(token)
+                elif tokentype == NAME:
+                    name,token = self._parsedotname(token) 
+                    if token == '=':
+                        stmt = self._parseassignment()
+                        dbg("parseassignment: %s = %s" % (name, stmt))
+                        if stmt != None:
+                            self.scope.local("%s = %s" % (name,stmt))
+                    freshscope = False
+        except StopIteration: #thrown on EOF
+            pass
+        except:
+            dbg("parse error: %s, %s @ %s" %
+                (sys.exc_info()[0], sys.exc_info()[1], self.parserline))
+        return self._adjustvisibility()
+
+def _sanitize(str):
+    val = ''
+    level = 0
+    for c in str:
+        if c in ('(','{','['):
+            level += 1
+        elif c in (']','}',')'):
+            level -= 1
+        elif level == 0:
+            val += c
+    return val

vimftpplugin/support.py

+# Supporting functions for the Python file type plug-in for Vim.
+# Authors:
+#  - Peter Odding <peter@peterodding.com>
+#  - Bart Kroon <bart@tarmack.eu>
+# Last Change: October 9, 2011
+# URL: https://github.com/tarmack/vim-python-ftplugin
+
+import __builtin__
+import os
+import platform
+import re
+import sys
+
+def complete_modules(base):
+  '''
+  Find the names of the built-in, binary and source modules available on the
+  user's system without executing any Python code except for this function (in
+  other words, module name completion is completely safe).
+  '''
+  # Start with the names of the built-in modules.
+  if base:
+    modulenames = set()
+  else:
+    modulenames = set(sys.builtin_module_names)
+  # Find the installed modules.
+  for root in sys.path:
+    scan_modules(root, [x for x in base.split('.') if x], modulenames)
+  # Sort the module list ignoring case and underscores.
+  if base:
+    base += '.'
+  print '\n'.join(list(modulenames))
+  #return [base + modname for modname in friendly_sort(list(modulenames))]
+
+def scan_modules(directory, base, modulenames):
+  sharedext = platform.system() == 'Windows' and '\.dll' or '\.so'
+  if not os.path.isdir(directory):
+    return
+  for name in os.listdir(directory):
+    pathname = '%s/%s' % (directory, name)
+    if os.path.isdir(pathname):
+      listing = os.listdir(pathname)
+      if '__init__' in [os.path.splitext(x)[0] for x in listing]:
+        if base:
+          if name == base[0]:
+            scan_modules(pathname, base[1:], modulenames)
+        else:
+          modulenames.add(name)
+    elif (not base) and re.match(r'^[A-Za-z0-9_]+(\.py[co]?|%s)$' % sharedext, name):
+      name = os.path.splitext(name)[0]
+      if name != '__init__':
+        modulenames.add(name)
+
+def complete_variables(expr):
+  '''
+  Use __import__() and dir() to get the functions and/or variables available in
+  the given module or submodule.
+  '''
+  todo = [x for x in expr.split('.') if x]
+  done = []
+  attributes = []
+  module = load_module(todo, done)
+  subject = module
+  while todo:
+    if len(todo) == 1:
+      expr = ('.'.join(done) + '.') if done else ''
+      attributes = [expr + attr for attr in dir(subject) if attr.startswith(todo[0])]
+      print '\n'.join(attributes)
+    try:
+      subject = getattr(subject, todo[0])
+      done.append(todo.pop(0))
+    except AttributeError:
+      break
+  if subject:
+    expr = ('.'.join(done) + '.') if done else ''
+    subject = [expr + entry for entry in dir(subject)]
+    print '\n'.join(subject)
+
+def load_module(todo, done):
+  '''
+  Find the most specific valid Python module given a tokenized identifier
+  expression (e.g. `os.path' for `os.path.islink').
+  '''
+  path = []
+  module = __builtin__
+  while todo:
+    path.append(todo[0])
+    try:
+      temp = __import__('.'.join(path))
+      if temp.__name__ == '.'.join(path):
+        module = temp
+      else:
+        break
+    except ImportError:
+      break
+    done.append(todo.pop(0))
+  return module
+
+def find_module_path(name):
+  '''
+  Look for a Python module on the module search path (used for "gf" and
+  searching in imported modules).
+  '''
+  fname = name.replace('.', '/')
+  for directory in sys.path:
+    scriptfile = directory + '/' + fname + '.py'
+    if os.path.isfile(scriptfile):
+      print scriptfile
+      break
+
+# vim: ts=2 sw=2 sts=2 et

vimftpplugin/test_inference.py

+from inference import TypeInferenceEngine
+
+def resolve_simplified(tie, node):
+  nodes = []
+  for parent, kind, context in tie.resolve(node):
+    nodes.append(tie.format(parent))
+  return nodes
+
+source = open('example.py').read()
+tie = TypeInferenceEngine(source)
+shadowed_var = tie.find_node(9, 5)
+global_var = tie.find_node(10, 12)
+kw_indirect = tie.find_node(10, 33)
+kw = tie.find_node(10, 46)
+init_arg = tie.find_node(11, 9)
+
+def test_resolving():
+  assert global_var.id == 'global_var'
+  assert kw_indirect.id == 'kw_indirect'
+  assert init_arg.id == 'init_arg'
+  assert resolve_simplified(tie, global_var) == ['global_var=42']
+  assert resolve_simplified(tie, init_arg) == ['function __init__']
+  assert resolve_simplified(tie, kw_indirect) == ['function __init__']
+  assert sorted(resolve_simplified(tie, shadowed_var)) == sorted(["shadowed=''", "shadowed=[]"])
+
+def test_evaluation():
+  assert list(tie.evaluate(global_var)) == [int]
+  assert list(tie.evaluate(kw)) == [list]
+  assert list(tie.evaluate(kw_indirect)) == [str]
+  assert list(tie.evaluate(tie.find_node(37, 1))) == [list]
+  assert list(tie.evaluate(tie.find_node(37, 1))) == [list]
+  assert list(tie.evaluate(tie.find_node(42, 1))) == [set]
+  assert list(tie.evaluate(tie.find_node(42, 4))) == [dict]
+
+def test_completion():
+  assert 'conjugate' in tie.complete(1, 1)
+  assert 'keys' in tie.complete(42, 4)
+  assert 'append' in tie.complete(48, 7) and 'capitalize' in tie.complete(47, 7)
+  assert 'difference' in tie.complete(51, 13)
+  assert 'test' in tie.complete(22, 13)
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.