1
0
forked from VimPlug/jedi
Files
jedi-fork/jedi/evaluate/dynamic.py

187 lines
7.2 KiB
Python

"""
One of the really important features of |jedi| is to have an option to
understand code like this::
def foo(bar):
bar. # completion here
foo(1)
There's no doubt wheter bar is an ``int`` or not, but if there's also a call
like ``foo('str')``, what would happen? Well, we'll just show both. Because
that's what a human would expect.
It works as follows:
- |Jedi| sees a param
- search for function calls named ``foo``
- execute these calls and check the input. This work with a ``ParamListener``.
"""
from jedi._compatibility import unicode
from jedi.parser import tree as pr
from jedi import settings
from jedi import debug
from jedi.evaluate import helpers
from jedi.evaluate.cache import memoize_default
from jedi.evaluate import imports
# This is something like the sys.path, but only for searching params. It means
# that this is the order in which Jedi searches params.
search_param_modules = ['.']
class ParamListener(object):
"""
This listener is used to get the params for a function.
"""
def __init__(self):
self.param_possibilities = []
def execute(self, params):
self.param_possibilities.append(params)
@debug.increase_indent
@memoize_default([], evaluator_is_first_arg=True)
def search_params(evaluator, param):
"""
A dynamic search for param values. If you try to complete a type:
>>> def func(foo):
... foo
>>> func(1)
>>> func("")
It is not known what the type ``foo`` without analysing the whole code. You
have to look for all calls to ``func`` to find out what ``foo`` possibly
is.
"""
if not settings.dynamic_params:
return []
from jedi.evaluate import representation as er
debug.dbg('Dynamic param search for %s', param)
def get_params_for_module(module):
"""
Returns the values of a param, or an empty array.
"""
@memoize_default([], evaluator_is_first_arg=True)
def get_posibilities(evaluator, module, func_name):
try:
names = module.used_names[func_name]
except KeyError:
return []
for name in names:
stmt = name.get_definition()
if not isinstance(stmt, (pr.ExprStmt, pr.CompFor, pr.ReturnStmt)):
continue
parent = name.parent
if pr.is_node(parent, 'trailer'):
parent = parent.parent
trailer = None
if pr.is_node(parent, 'power'):
for t in parent.children[1:]:
if t == '**':
break
if t.start_pos > name.start_pos and t.children[0] == '(':
trailer = t
break
if trailer is not None:
types = evaluator.goto_definition(name)
# We have to remove decorators, because they are not the
# "original" functions, this way we can easily compare.
# At the same time we also have to remove InstanceElements.
undec = [escope.decorates or
(escope.var if isinstance(escope, er.InstanceElement) else escope)
for escope in types if escope.isinstance(er.Function, er.Instance)]
if er.wrap(evaluator, compare) in undec:
# Only if we have the correct function we execute
# it, otherwise just ignore it.
evaluator.eval_trailer(types, trailer)
# TODO REMOVE
continue
calls = helpers.scan_statement_for_calls(stmt, func_name)
for c in calls:
# no execution means that params cannot be set
call_path = list(c.generate_call_path())
pos = c.start_pos
scope = stmt.parent
# This whole stuff is just to not execute certain parts
# (speed improvement), basically we could just call
# ``eval_call_path`` on the call_path and it would also
# work.
def listRightIndex(lst, value):
return len(lst) - lst[-1::-1].index(value) - 1
# Need to take right index, because there could be a
# func usage before.
call_path_simple = [unicode(d) if isinstance(d, pr.Name)
else d for d in call_path]
i = listRightIndex(call_path_simple, func_name)
before, after = call_path[:i], call_path[i + 1:]
if not after and not call_path_simple.index(func_name) != i:
continue
scopes = [scope]
if before:
scopes = evaluator.eval_call_path(iter(before), c.parent, pos)
pos = None
for scope in scopes:
# Not resolving decorators is a speed hack:
# By ignoring them, we get the function that is
# probably called really fast. If it's not called, it
# doesn't matter. But this is a way to get potential
# candidates for calling that function really quick!
s = evaluator.find_types(scope, func_name, position=pos,
search_global=not before,
resolve_decorator=False)
c = [getattr(escope, 'base_func', None) or escope.base
for escope in s
if escope.isinstance(er.Function, er.Class)]
if compare in c:
# only if we have the correct function we execute
# it, otherwise just ignore it.
evaluator.follow_path(iter(after), s, scope)
return listener.param_possibilities
result = []
for params in get_posibilities(evaluator, module, func_name):
for p in params:
if str(p) == str(param.get_name()):
result += p.parent.eval(evaluator)
return result
func = param.get_parent_until(pr.Function)
current_module = param.get_parent_until()
func_name = unicode(func.name)
compare = func
if func_name == '__init__':
cls = func.get_parent_scope()
if isinstance(cls, pr.Class):
func_name = unicode(cls.name)
compare = cls
# add the listener
listener = ParamListener()
func.listeners.add(listener)
try:
result = []
# This is like backtracking: Get the first possible result.
for mod in imports.get_modules_containing_name([current_module], func_name):
result = get_params_for_module(mod)
if result:
break
finally:
# cleanup: remove the listener; important: should not stick.
func.listeners.remove(listener)
debug.dbg('Dynamic param result %s', result)
return result