forked from VimPlug/jedi
161 lines
5.1 KiB
Python
161 lines
5.1 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 itertools import chain
|
|
|
|
from jedi._compatibility import unicode
|
|
from jedi.parser import tree
|
|
from jedi import settings
|
|
from jedi import debug
|
|
from jedi.evaluate.cache import memoize_default
|
|
from jedi.evaluate import imports
|
|
from jedi.evaluate.param import TreeArguments, create_default_param
|
|
from jedi.common import to_list, unite
|
|
from jedi.evaluate import context
|
|
|
|
|
|
MAX_PARAM_SEARCHES = 20
|
|
|
|
|
|
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 += params
|
|
|
|
|
|
class MergedExecutedParams(object):
|
|
"""
|
|
Simulates being a parameter while actually just being multiple params.
|
|
"""
|
|
def __init__(self, executed_params):
|
|
self._executed_params = executed_params
|
|
|
|
def infer(self):
|
|
return unite(p.infer() for p in self._executed_params)
|
|
|
|
|
|
@debug.increase_indent
|
|
def search_params(evaluator, parent_context, funcdef):
|
|
"""
|
|
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 set()
|
|
|
|
evaluator.dynamic_params_depth += 1
|
|
try:
|
|
debug.dbg('Dynamic param search in %s.', funcdef.name.value, color='MAGENTA')
|
|
module_context = parent_context.get_root_context()
|
|
function_executions = _search_function_executions(
|
|
evaluator,
|
|
module_context,
|
|
funcdef
|
|
)
|
|
if function_executions:
|
|
zipped_params = zip(*(
|
|
function_execution.get_params()
|
|
for function_execution in function_executions
|
|
))
|
|
params = [MergedExecutedParams(executed_params) for executed_params in zipped_params]
|
|
# Evaluate the ExecutedParams to types.
|
|
else:
|
|
params = [create_default_param(parent_context, p) for p in funcdef.params]
|
|
debug.dbg('Dynamic param result finished', color='MAGENTA')
|
|
return params
|
|
finally:
|
|
evaluator.dynamic_params_depth -= 1
|
|
|
|
|
|
@memoize_default([], evaluator_is_first_arg=True)
|
|
@to_list
|
|
def _search_function_executions(evaluator, module_context, funcdef):
|
|
"""
|
|
Returns a list of param names.
|
|
"""
|
|
from jedi.evaluate import representation as er
|
|
|
|
def get_possible_nodes(module_context, func_name):
|
|
if not isinstance(module_context, er.ModuleContext):
|
|
return
|
|
try:
|
|
names = module_context.module_node.used_names[func_name]
|
|
except KeyError:
|
|
return
|
|
|
|
for name in names:
|
|
bracket = name.get_next_leaf()
|
|
trailer = bracket.parent
|
|
if trailer.type == 'trailer' and bracket == '(':
|
|
yield name, trailer
|
|
|
|
func_name = unicode(funcdef.name)
|
|
compare_node = funcdef
|
|
if func_name == '__init__':
|
|
cls = funcdef.get_parent_scope()
|
|
if isinstance(cls, tree.Class):
|
|
func_name = unicode(cls.name)
|
|
compare_node = cls
|
|
|
|
found_executions = False
|
|
i = 0
|
|
for for_mod_context in imports.get_modules_containing_name(
|
|
evaluator, [module_context], func_name):
|
|
for name, trailer in get_possible_nodes(for_mod_context, func_name):
|
|
i += 1
|
|
|
|
# This is a simple way to stop Jedi's dynamic param recursion
|
|
# from going wild: The deeper Jedi's in the recursion, the less
|
|
# code should be evaluated.
|
|
if i * evaluator.dynamic_params_depth > MAX_PARAM_SEARCHES:
|
|
return
|
|
|
|
random_context = evaluator.create_context(module_context, name)
|
|
for value in evaluator.goto_definitions(random_context, name):
|
|
value_node = value.get_node()
|
|
if compare_node == value_node:
|
|
arglist = trailer.children[1]
|
|
if arglist == ')':
|
|
arglist = ()
|
|
args = TreeArguments(evaluator, random_context, arglist, trailer)
|
|
yield er.FunctionExecutionContext(
|
|
evaluator,
|
|
value.parent_context,
|
|
value_node,
|
|
args
|
|
)
|
|
found_executions = True
|
|
|
|
# If there are results after processing a module, we're probably
|
|
# good to process. This is a speed optimization.
|
|
if found_executions:
|
|
return
|